From 81a2ae994820893fe53b23e0ce5303249cb5c540 Mon Sep 17 00:00:00 2001 From: Arun Mahapatra Date: Mon, 1 Aug 2016 06:59:08 +0530 Subject: [PATCH] Initial commit. :tada: Snapshot 67fcbc6f. --- .gitattributes | 67 + .gitignore | 108 ++ Nuget.config | 20 + TestPlatform.sln | 245 +++ build.cmd | 6 + ...Studio.TestPlatform.MSTest.TestAdapter.dll | Bin 0 -> 87552 bytes ...TestAdapter.PlatformServices.Interface.dll | Bin 0 -> 8192 bytes ...latform.MSTestAdapter.PlatformServices.dll | Bin 0 -> 10240 bytes ....TestPlatform.TestFramework.Extensions.dll | Bin 0 -> 6656 bytes ...isualStudio.TestPlatform.TestFramework.dll | Bin 0 -> 50176 bytes .../Microsoft.TestPlatform.E2ETest.xproj | 19 + .../Microsoft.TestPlatform.E2ETest/Program.cs | 48 + .../Properties/AssemblyInfo.cs | 19 + .../project.json | 42 + .../testhost.exe.config | 60 + .../testhost.x86.exe.config | 56 + ...Studio.TestPlatform.MSTest.TestAdapter.dll | Bin 0 -> 87552 bytes ...TestAdapter.PlatformServices.Interface.dll | Bin 0 -> 8192 bytes ...latform.MSTestAdapter.PlatformServices.dll | Bin 0 -> 10240 bytes ....TestPlatform.TestFramework.Extensions.dll | Bin 0 -> 6656 bytes ...isualStudio.TestPlatform.TestFramework.dll | Bin 0 -> 50176 bytes ...estPlatform.TranslationLayer.E2ETest.xproj | 19 + .../Program.cs | 178 +++ .../Properties/AssemblyInfo.cs | 19 + .../project.json | 38 + .../testhost.exe.config | 60 + .../testhost.x86.exe.config | 56 + .../Properties/AssemblyInfo.cs | 36 + dogfood/UnitTestProject/UnitTest.cs | 15 + dogfood/UnitTestProject/UnitTestProject.xproj | 21 + dogfood/UnitTestProject/packages.config | 5 + dogfood/UnitTestProject/project.json | 27 + global.json | 4 + scripts/DisableUnitTesting.bat | 22 + scripts/EnableUnitTesting.bat | 22 + scripts/build.ps1 | 208 +++ scripts/key.snk | Bin 0 -> 160 bytes .../DesignMode/DesignModeClient.cs | 238 +++ .../DesignModeTestEventsRegistrar.cs | 63 + .../DesignMode/DesignModeTestHostLauncher.cs | 43 + .../DesignModeTestHostLauncherFactory.cs | 36 + .../DesignMode/IDesignModeClient.cs | 32 + .../Discovery/DiscoveryRequest.cs | 402 +++++ .../Execution/TestRunRequest.cs | 448 ++++++ src/Microsoft.TestPlatform.Client/Friends.cs | 13 + .../Microsoft.TestPlatform.Client.xproj | 22 + .../Properties/AssemblyInfo.cs | 19 + .../RequestHelper/DiscoveryRequestPayload.cs | 28 + .../RequestHelper/ITestRequestManager.cs | 53 + .../RequestHelper/TestRunRequestPayload.cs | 52 + .../Resources.Designer.cs | 80 + .../Resources.resx | 126 ++ .../TestPlatform.cs | 124 ++ .../TestPlatformFactory.cs | 23 + .../project.json | 28 + .../Constants.cs | 40 + .../BeforeTestRunStartResult.cs | 51 + .../TestDiscoveryExtensionManager.cs | 202 +++ .../TestExecutorExtensionManager.cs | 180 +++ .../TestExtensionManager.cs | 198 +++ .../ExtensionFramework/TestPluginCache.cs | 566 +++++++ .../TestPluginDiscoverer.cs | 284 ++++ .../ExtensionFramework/TestPluginManager.cs | 249 +++ .../Utilities/LazyExtension.cs | 190 +++ .../TestDiscovererPluginInformation.cs | 108 ++ .../TestExecutorPluginInformation.cs | 21 + .../TestExtensionPluginInformation.cs | 84 + .../Utilities/TestExtensions.cs | 152 ++ .../Utilities/TestLoggerPluginInformation.cs | 70 + .../Utilities/TestPluginInformation.cs | 53 + .../TestSettingsProviderPluginInformation.cs | 84 + .../Filtering/Condition.cs | 262 ++++ .../Filtering/FilterExpression.cs | 333 ++++ .../Filtering/FilterExpressionWrapper.cs | 83 + .../Filtering/TestCaseFilterExpression.cs | 86 ++ src/Microsoft.TestPlatform.Common/Friends.cs | 15 + .../IParallelOperationManager.cs | 16 + .../IParallelProxyExecutionManager.cs | 31 + .../ClientProtocol/IProxyDiscoveryManager.cs | 20 + .../ClientProtocol/IProxyExecutionManager.cs | 25 + .../ClientProtocol/IProxyOperationManager.cs | 26 + .../Engine/ClientProtocol/ITestEngine.cs | 38 + .../ClientProtocol/ITestExtensionManager.cs | 19 + .../Engine/ClientProtocol/ITestHostManager.cs | 48 + .../ClientProtocol/TestExecutionContext.cs | 192 +++ .../Engine/ITestCaseEventsHandler.cs | 31 + .../TesthostProtocol/IDiscoveryManager.cs | 32 + .../TesthostProtocol/IExecutionManager.cs | 52 + .../ITestHostManagerFactory.cs | 22 + .../Interfaces/IPathUtilities.cs | 19 + .../Interfaces/IRunSettingsProvider.cs | 21 + .../ISettingsProviderCapabilities.cs | 18 + .../Interfaces/ITestDiscovererCapabilities.cs | 23 + .../ITestDiscoveryEventsRegistrar.cs | 26 + .../Interfaces/ITestExecutorCapabilities.cs | 11 + .../Interfaces/ITestExtensionCapabilities.cs | 17 + .../Interfaces/ITestLoggerCapabilities.cs | 15 + .../Interfaces/ITestRunEventsRegistrar.cs | 26 + .../Logging/InternalTestLoggerEvents.cs | 349 +++++ .../Logging/TestLoggerExtensionManager.cs | 63 + .../Logging/TestLoggerManager.cs | 430 ++++++ .../Logging/TestLoggerMetadata.cs | 45 + .../Logging/TestSessionMessageLogger.cs | 82 + .../Microsoft.TestPlatform.Common.xproj | 19 + .../Properties/AssemblyInfo.cs | 19 + .../Resources.Designer.cs | 251 +++ .../Resources.resx | 183 +++ .../RunSettings.cs | 277 ++++ .../RunSettingsManager.cs | 82 + .../SettingsProviderExtensionManager.cs | 245 +++ .../Utilities/AssemblyResolver.cs | 260 ++++ .../Utilities/ExceptionUtilities.cs | 35 + .../Utilities/PathUtilities.cs | 48 + .../Utilities/RunSettingsUtilities.cs | 121 ++ .../project.json | 32 + .../DataCollectionRequestHandler.cs | 96 ++ .../DataCollectionRequestSender.cs | 111 ++ .../EventHandlers/TestCaseEventsHandler.cs | 32 + .../TestDiscoveryEventHandler.cs | 86 ++ .../EventHandlers/TestRunEventsHandler.cs | 99 ++ .../Friends.cs | 10 + .../Interfaces/ICommunicationManager.cs | 103 ++ .../IDataCollectionRequestHandler.cs | 33 + .../IDataCollectionRequestSender.cs | 46 + .../Interfaces/IDataSerializer.cs | 40 + .../Interfaces/ITestRequestHandler.cs | 85 ++ .../Interfaces/ITestRequestSender.cs | 85 ++ .../JsonDataSerializer.cs | 78 + .../Messages/DiscoveryCompletePayload.cs | 28 + .../Messages/Message.cs | 29 + .../Messages/MessageType.cs | 165 ++ .../Messages/TestMessagePayload.cs | 22 + .../Messages/TestRunCompletePayload.cs | 35 + .../Messages/TestRunStatsPayload.cs | 24 + ....TestPlatform.CommunicationUtilities.xproj | 19 + .../ObjectModel/TestRunCriteriaWithSources.cs | 46 + .../ObjectModel/TestRunCriteriaWithTests.cs | 48 + .../Properties/AssemblyInfo.cs | 19 + .../SocketCommunicationManager.cs | 257 ++++ .../TestRequestHandler.cs | 301 ++++ .../TestRequestSender.cs | 227 +++ .../project.json | 29 + .../Friends.cs | 6 + .../Helpers/FileHelper.cs | 24 + .../Helpers/Interfaces/IFileHelper.cs | 17 + ...Microsoft.TestPlatform.CoreUtilities.xproj | 22 + .../Output/ConsoleColorHelper.cs | 65 + .../Output/ConsoleOutput.cs | 103 ++ .../Output/IOutput.cs | 31 + .../Output/OutputLevel.cs | 32 + .../Output/OutputUtilities.cs | 115 ++ .../Properties/AssemblyInfo.cs | 19 + .../Resources.Designer.cs | 125 ++ .../Resources.resx | 141 ++ .../Tracing/EqtTrace.cs | 772 ++++++++++ .../Tracing/RemoteEqtTrace.cs | 40 + .../Tracing/RollingFileTraceListener.cs | 9 + .../Utilities/Job.cs | 94 ++ .../Utilities/JobQueue.cs | 377 +++++ .../Utilities/MulticastDelegateUtilities.cs | 60 + .../ValidateArg.cs | 316 ++++ .../project.json | 25 + .../Adapter/FrameworkHandle.cs | 130 ++ .../Adapter/RunContext.cs | 91 ++ .../Adapter/TestCaseFilterExpression.cs | 85 ++ .../Adapter/TestExecutionRecorder.cs | 103 ++ .../Parallel/ParallelOperationManager.cs | 155 ++ .../Parallel/ParallelProxyExecutionManager.cs | 294 ++++ .../Parallel/ParallelRunDataAggregator.cs | 123 ++ .../Parallel/ParallelRunEventsHandler.cs | 156 ++ .../Client/ProxyDiscoveryManager.cs | 91 ++ .../Client/ProxyExecutionManager.cs | 132 ++ ...ProxyExecutionManagerWithDataCollection.cs | 176 +++ .../Client/ProxyOperationManager.cs | 142 ++ .../Constants.cs | 22 + .../DataCollection/DataCollectionLauncher.cs | 100 ++ .../DataCollectionParameters.cs | 71 + .../DataCollectionTestRunEventsHandler.cs | 152 ++ .../Interfaces/IDataCollectionLauncher.cs | 36 + .../IDataCollectorsSettingsProvider.cs | 18 + .../Interfaces/IProxyDataCollectionManager.cs | 51 + .../ProxyDataCollectionManager.cs | 192 +++ .../Discovery/DiscovererEnumerator.cs | 250 +++ .../Discovery/DiscoveryContext.cs | 17 + .../Discovery/DiscoveryManager.cs | 216 +++ .../Discovery/DiscoveryResultCache.cs | 139 ++ .../Discovery/TestCaseDiscoverySink.cs | 36 + .../EventHandlers/TestCaseEventsHandler.cs | 74 + .../Execution/BaseRunTests.cs | 421 ++++++ .../Execution/ExecutionManager.cs | 217 +++ .../InProcDataCollectionExtensionManager.cs | 220 +++ .../Execution/RunTestsWithSources.cs | 166 ++ .../Execution/RunTestsWithTests.cs | 96 ++ .../Execution/TestRunCache.cs | 402 +++++ .../Execution/TestRunStatistics.cs | 71 + .../Friends.cs | 9 + .../Helpers/Interfaces/IProcessHelper.cs | 27 + .../Helpers/ProcessHelper.cs | 61 + .../Hosting/DefaultTestHostManager.cs | 164 ++ .../Interfaces/ITestRunCache.cs | 39 + ...crosoft.TestPlatform.CrossPlatEngine.xproj | 19 + .../Properties/AssemblyInfo.cs | 19 + .../Resources.Designer.cs | 197 +++ .../Resources.resx | 167 ++ .../TestEngine.cs | 188 +++ .../TestExtensionManager.cs | 23 + .../TestHostManagerFactory.cs | 45 + .../project.json | 30 + .../Friends.cs | 9 + .../Interfaces/IDataAttachment.cs | 16 + .../Interfaces/IXmlTestStore.cs | 20 + .../Interfaces/IXmlTestStoreCustom.cs | 23 + .../Interfaces/XmlTestStoreParameters.cs | 48 + ...ft.TestPlatform.Extensions.TrxLogger.xproj | 21 + .../ObjectModel/CollectorDataEntry.cs | 239 +++ .../ObjectModel/RunInfo.cs | 84 + .../ObjectModel/TestCategoryItems.cs | 267 ++++ .../ObjectModel/TestEntry.cs | 134 ++ .../ObjectModel/TestExecId.cs | 97 ++ .../ObjectModel/TestId.cs | 275 ++++ .../ObjectModel/TestListCategory.cs | 200 +++ .../ObjectModel/TestListCategoryId.cs | 133 ++ .../ObjectModel/TestMethod.cs | 120 ++ .../ObjectModel/TestOutcome.cs | 105 ++ .../ObjectModel/TestResult.cs | 192 +++ .../ObjectModel/TestRun.cs | 205 +++ .../ObjectModel/TestRunConfiguration.cs | 156 ++ .../ObjectModel/TestRunConfigurationId.cs | 30 + .../ObjectModel/TestRunSummary.cs | 191 +++ .../ObjectModel/TestType.cs | 68 + .../ObjectModel/UnitTestElement.cs | 366 +++++ .../ObjectModel/UnitTestResult.cs | 455 ++++++ .../ObjectModel/UriDataAttachment.cs | 149 ++ .../Properties/AssemblyInfo.cs | 19 + .../TrxLogger.cs | 432 ++++++ .../TrxResource.Designer.cs | 333 ++++ .../TrxResource.resx | 211 +++ .../Utility/Collection.cs | 205 +++ .../Utility/Converter.cs | 566 +++++++ .../Utility/EqtAssert.cs | 75 + .../Utility/FilterHelper.cs | 357 +++++ .../Utility/TestRunDirectories.cs | 24 + .../XML/Attributes.cs | 141 ++ .../XML/XmlFilePersistence.cs | 22 + .../XML/XmlPersistence.cs | 892 +++++++++++ .../project.json | 31 + .../Adapter/Interfaces/IDiscoveryContext.cs | 15 + .../Adapter/Interfaces/IFrameworkHandle.cs | 30 + .../Adapter/Interfaces/IRunContext.cs | 50 + .../Adapter/Interfaces/IRunSettings.cs | 22 + .../Adapter/Interfaces/ISettingsProvider.cs | 20 + .../Interfaces/ITestCaseDiscoverySink.cs | 18 + .../Interfaces/ITestCaseFilterExpression.cs | 22 + .../Adapter/Interfaces/ITestDiscoverer.cs | 25 + .../Adapter/Interfaces/ITestExecutor.cs | 37 + .../Adapter/Interfaces/ITestLog.cs | 45 + .../Adapter/TestPlatformFormatException.cs | 104 ++ .../Adapter/TestsCanceledException.cs | 60 + .../Architecture.cs | 13 + .../AttachmentSet.cs | 75 + .../Client/DiscoveryCriteria.cs | 123 ++ .../Client/Events/DiscoveredTestsEventArgs.cs | 22 + .../Events/DiscoveryCompleteEventArgs.cs | 35 + .../Client/Events/TestRunChangedEventArgs.cs | 42 + .../Client/Events/TestRunCompleteEventArgs.cs | 67 + .../Client/Interfaces/IDiscoveryRequest.cs | 50 + .../Client/Interfaces/IRequest.cs | 32 + .../Interfaces/ITestDiscoveryEventsHandler.cs | 24 + .../Client/Interfaces/ITestHostLauncher.cs | 28 + .../Client/Interfaces/ITestPlatform.cs | 44 + .../Interfaces/ITestPlatformCapabilities.cs | 31 + .../Interfaces/ITestRunConfiguration.cs | 39 + .../Interfaces/ITestRunEventsHandler.cs | 56 + .../Client/Interfaces/ITestRunRequest.cs | 63 + .../Client/Interfaces/ITestRunStatistics.cs | 30 + .../Client/TestRunCriteria.cs | 362 +++++ .../Client/TestRunState.cs | 40 + .../CommonResources.Designer.cs | 89 ++ .../CommonResources.resx | 129 ++ .../CommonResources.tt | 1 + .../Constants.cs | 115 ++ .../Common/DataCollectorMessageLevel.cs | 15 + .../DataCollector/Common/FileHelper.cs | 84 + .../DataCollector/Common/RequestId.cs | 206 +++ .../DataCollector/Common/Session.cs | 61 + .../Common/TestCaseFailureType.cs | 17 + .../DataCollector/Common/TestExecId.cs | 62 + .../DataCollector/DataCollectionContext.cs | 154 ++ .../DataCollectionEnvironmentContext.cs | 83 + .../DataCollector/DataCollectionLogger.cs | 108 ++ .../DataCollectionRunSettings.cs | 229 +++ .../DataCollector/DataCollectionSink.cs | 114 ++ .../DataCollector/DataCollector.cs | 61 + .../DataCollector/DataCollectorSettings.cs | 219 +++ .../Events/CustomNotificationEventArgs.cs | 84 + .../Events/DataCollectionEventArgs.cs | 79 + .../Events/DataCollectionEvents.cs | 104 ++ .../Events/DataRequestEventArgs.cs | 160 ++ .../DataCollector/Events/SessionEvents.cs | 97 ++ .../DataCollector/Events/TestCaseEvents.cs | 554 +++++++ .../InProcDataCollectionArgs.cs | 11 + .../InProcDataCollector.cs | 46 + .../InProcDataCollector/TestCaseEndArgs.cs | 35 + .../InProcDataCollector/TestCaseStartArgs.cs | 26 + .../InProcDataCollector/TestSessionEndArgs.cs | 11 + .../TestSessionStartArgs.cs | 36 + .../BaseTransferInformation.cs | 102 ++ .../FileTransferInformation.cs | 66 + .../StreamTransferInformation.cs | 89 ++ .../DefaultExecutorUriAttribute.cs | 41 + .../ExceptionConverter.cs | 107 ++ .../ExtensionUriAttribute.cs | 44 + .../FileExtensionAttribute.cs | 44 + .../FrameworkVersion.cs | 15 + .../FriendlyNameAttribute.cs | 43 + .../Friends.cs | 5 + .../LazyPropertyValue.cs | 60 + .../Logger/ITestLogger.cs | 21 + .../Logger/ITestLoggerWithParams.cs | 21 + .../Logger/TestLoggerEvents.cs | 43 + .../Events/DataCollectionMessageEventArgs.cs | 60 + .../Logging/Events/TestResultEventArgs.cs | 39 + .../Logging/Events/TestRunMessageEventArgs.cs | 55 + .../Logging/Events/TestRunStartedEventArgs.cs | 26 + .../Logging/Interfaces/IMessageLogger.cs | 20 + .../Logging/TestMessageLevel.cs | 27 + .../Microsoft.TestPlatform.ObjectModel.xproj | 22 + .../Navigation/DiaNavigationData.cs | 31 + .../Navigation/DiaSession.cs | 458 ++++++ .../Navigation/INavigationData.cs | 25 + .../Navigation/INavigationSession.cs | 20 + .../Properties/AssemblyInfo.cs | 29 + .../Resources.Designer.cs | 623 ++++++++ .../Resources.resx | 332 ++++ .../RunSettings/RunConfiguration.cs | 488 ++++++ .../RunSettings/SettingsException.cs | 57 + .../RunSettings/SettingsNameAttribute.cs | 41 + .../RunSettings/TestRunParameters.cs | 75 + .../RunSettings/TestRunSettings.cs | 49 + .../TestCase.cs | 286 ++++ .../TestObject.cs | 375 +++++ .../TestOutcome.cs | 37 + .../TestOutcomeHelper.cs | 45 + .../TestProcessStartInfo.cs | 38 + .../TestProperty/TestProperty.cs | 430 ++++++ .../TestProperty/TestPropertyAttributes.cs | 16 + .../TestResult.cs | 337 +++++ .../Trait.cs | 33 + .../TraitCollection.cs | 105 ++ .../Utilities/AssemblyHelper.cs | 356 +++++ .../Utilities/AssemblyLoadWorker.cs | 386 +++++ .../Utilities/EqtHash.cs | 47 + .../InProcDataCollectionUtilities.cs | 59 + .../Utilities/StringUtilities.cs | 46 + .../Utilities/SuspendCodeCoverage.cs | 74 + .../Utilities/XmlReaderUtilities.cs | 73 + .../Utilities/XmlRunSettingsUtilities.cs | 343 +++++ .../project.json | 40 + .../ClientUtilities.cs | 65 + .../CodeCoverageDataAdapterUtilities.cs | 172 +++ .../Friends.cs | 5 + .../InferRunSettingsHelper.cs | 224 +++ .../MSTestSettingsUtilities.cs | 102 ++ .../Microsoft.TestPlatform.Utilities.xproj | 21 + .../ParallelRunSettingsUtilities.cs | 57 + .../Properties/AssemblyInfo.cs | 19 + .../Resource.Designer.cs | 125 ++ .../Resource.resx | 141 ++ .../XmlUtilities.cs | 101 ++ .../project.json | 34 + .../Microsoft.TestPlatform.VSIXCreator.xproj | 21 + .../Program.cs | 39 + .../Properties/AssemblyInfo.cs | 19 + .../VSIXCreator.cmd | 27 + .../VSIXDelete.cmd | 19 + .../[Content_Types].xml | 1 + .../extension.vsixmanifest | 21 + .../project.json | 47 + .../testhost.exe.config | 60 + .../testhost.x86.exe.config | 56 + .../Friends.cs | 13 + .../Interfaces/IProcessManager.cs | 31 + .../ITranslationLayerRequestSender.cs | 97 ++ .../Interfaces/IVsTestConsoleWrapper.cs | 83 + ...tform.VsTestConsole.TranslationLayer.xproj | 19 + .../Payloads/DiscoveryRequestPayload.cs | 28 + .../Payloads/TestRunRequestPayload.cs | 52 + .../Properties/AssemblyInfo.cs | 19 + .../TransationLayerException.cs | 22 + .../VsTestConsoleProcessManager.cs | 92 ++ .../VsTestConsoleRequestSender.cs | 321 ++++ .../VsTestConsoleWrapper.cs | 144 ++ .../project.json | 40 + src/datacollector.x86/Program.cs | 89 ++ .../Properties/AssemblyInfo.cs | 19 + src/datacollector.x86/app.config | 56 + src/datacollector.x86/datacollector.x86.xproj | 19 + src/datacollector.x86/project.json | 39 + src/datacollector/Properties/AssemblyInfo.cs | 19 + src/datacollector/app.config | 56 + src/datacollector/datacollector.xproj | 19 + src/datacollector/project.json | 40 + src/package/[Content_Types].xml | 1 + src/package/extension.vsixmanifest | 21 + src/package/project.json | 40 + src/package/sign/project.json | 11 + src/package/sign/sign.proj | 95 ++ src/package/testhost.exe.config | 60 + src/package/testhost.x86.exe.config | 56 + src/testhost.x86/Program.cs | 96 ++ src/testhost.x86/Properties/AssemblyInfo.cs | 19 + src/testhost.x86/app.config | 56 + src/testhost.x86/project.json | 38 + src/testhost.x86/testhost.x86.xproj | 21 + src/testhost/Properties/AssemblyInfo.cs | 19 + src/testhost/app.config | 56 + src/testhost/project.json | 40 + src/testhost/testhost.xproj | 21 + .../CommandLine/CommandArgumentPair.cs | 106 ++ .../CommandLine/CommandLineErrorException.cs | 43 + .../CommandLine/CommandLineOptions.cs | 267 ++++ src/vstest.console/CommandLine/Executor.cs | 290 ++++ .../CommandLine/TestRunResultAggregator.cs | 134 ++ src/vstest.console/Friends.cs | 13 + src/vstest.console/Internal/ConsoleLogger.cs | 345 +++++ .../BuildBasePathArgumentProcessor.cs | 150 ++ .../ConfigurationArgumentProcessor.cs | 148 ++ .../EnableLoggerArgumentProcessor.cs | 237 +++ .../EnableStaticLoggersArgumentProcessor.cs | 203 +++ .../Processors/HelpArgumentProcessor.cs | 185 +++ .../Processors/Interfaces/IArgumentExector.cs | 25 + .../Interfaces/IArgumentProcessor.cs | 23 + .../IArgumentProcessorCapabilities.cs | 57 + .../Processors/ListTestsArgumentProcessor.cs | 264 ++++ .../Processors/OutputArgumentProcessor.cs | 150 ++ .../Processors/ParallelArgumentProcessor.cs | 138 ++ .../Processors/PlatformArgumentProcessor.cs | 160 ++ .../Processors/PortArgumentProcessor.cs | 184 +++ .../RunSettingsArgumentProcessor.cs | 205 +++ .../RunSpecificTestsArgumentProcessor.cs | 335 ++++ .../Processors/RunTestsArgumentProcessor.cs | 243 +++ .../TestAdapterPathArgumentProcessor.cs | 201 +++ .../TestCaseFilterArgumentProcessor.cs | 137 ++ .../Processors/TestSourceArgumentProcessor.cs | 132 ++ .../Utilities/ArgumentProcessorFactory.cs | 306 ++++ .../Utilities/ArgumentProcessorPriority.cs | 64 + .../Utilities/ArgumentProcessorResult.cs | 25 + .../BaseArgumentProcessorCapabilities.cs | 55 + .../Utilities/HelpContentPriority.cs | 130 ++ .../Processors/Utilities/JsonUtilities.cs | 140 ++ .../Processors/Utilities/LoggerUtilities.cs | 82 + .../Utilities/RunSettingsUtilities.cs | 77 + src/vstest.console/Program.cs | 22 + src/vstest.console/Properties/AssemblyInfo.cs | 19 + src/vstest.console/Resources.Designer.cs | 1346 +++++++++++++++++ src/vstest.console/Resources.resx | 596 ++++++++ .../TestPlatformHelpers/TestRequestManager.cs | 248 +++ src/vstest.console/project.json | 42 + src/vstest.console/vstest.console.xproj | 21 + .../DesignMode/DesignModeClientTests.cs | 224 +++ .../DesignModeTestHostLauncherFactoryTests.cs | 44 + .../DesignModeTestHostLauncherTests.cs | 46 + .../Discovery/DiscoveryRequestTests.cs | 101 ++ .../Execution/TestRunRequestTests.cs | 243 +++ ...rosoft.TestPlatform.Client.UnitTests.xproj | 22 + .../Properties/AssemblyInfo.cs | 19 + .../TestPlatformTests.cs | 117 ++ .../project.json | 35 + .../ExceptionUtilities.cs | 33 + .../TestDiscoveryExtensionManagerTests.cs | 129 ++ .../TestExecutorExtensionManagerTests.cs | 86 ++ .../TestExtensionManagerTests.cs | 102 ++ .../TestPluginCacheTests.cs | 645 ++++++++ .../TestPluginDiscovererTests.cs | 279 ++++ .../TestPluginManagerTests.cs | 161 ++ .../Utilities/LazyExtensionTests.cs | 137 ++ .../TestDiscovererPluginInformationTests.cs | 102 ++ .../TestExtensionPluginInformationTests.cs | 76 + .../Utilities/TestExtensionsTests.cs | 249 +++ .../TestLoggerPluginInformationTests.cs | 72 + .../Utilities/TestPluginInformationTests.cs | 50 + ...tSettingsProviderPluginInformationTests.cs | 68 + .../Logging/InternalTestLoggerEventsTests.cs | 266 ++++ .../TestLoggerExtensionManagerTests.cs | 45 + .../Logging/TestLoggerManagerTests.cs | 361 +++++ .../Logging/TestSessionMessageLoggerTests.cs | 64 + ...rosoft.TestPlatform.Common.UnitTests.xproj | 22 + .../Properties/AssemblyInfo.cs | 19 + .../RunSettingsManagerTests.cs | 66 + .../RunSettingsTests.cs | 342 +++++ .../SettingsProviderExtensionManagerTests.cs | 241 +++ .../Utilities/ExceptionUtilitiesTests.cs | 37 + .../Utilities/PathUtilitiesTests.cs | 88 ++ .../Utilities/RunSettingsUtilitiesTests.cs | 110 ++ .../project.json | 35 + ...orm.CommunicationUtilities.UnitTests.xproj | 22 + .../TestDiscoveryEventHandlerTests.cs | 53 + .../ObjectModel/TestRunEventsHandlerTests.cs | 47 + .../Properties/AssemblyInfo.cs | 19 + .../TestRequestSenderTests.cs | 405 +++++ .../project.json | 35 + ...TestPlatform.CoreUtilities.UnitTests.xproj | 22 + .../Properties/AssemblyInfo.cs | 19 + .../Utilities/JobQueueTests.cs | 541 +++++++ .../project.json | 35 + .../Adapter/FrameworkHandleTests.cs | 120 ++ .../Adapter/RunContextTests.cs | 81 + .../Adapter/TestExecutionRecorderTests.cs | 152 ++ .../Parallel/ParallelOperationManagerTests.cs | 108 ++ .../ParallelProxyExecutionManagerTests.cs | 542 +++++++ .../ParallelRunDataAggregatorTests.cs | 241 +++ .../Parallel/ParallelRunEventsHandlerTests.cs | 178 +++ .../Client/ProxyDiscoveryManagerTests.cs | 155 ++ .../Client/ProxyExecutionManagerTests.cs | 224 +++ ...ExecutionManagerWithDataCollectionTests.cs | 79 + .../Client/ProxyOperationManagerTests.cs | 76 + ...DataCollectionTestRunEventsHandlerTests.cs | 100 ++ .../ProxyDataCollectionManagerTests.cs | 181 +++ .../Discovery/DiscovererEnumeratorTests.cs | 443 ++++++ .../Discovery/DiscoveryManagerTests.cs | 149 ++ .../Discovery/DiscoveryResultCacheTests.cs | 123 ++ .../Discovery/TestCaseDiscoverySinkTests.cs | 43 + .../Execution/BaseRunTestsTests.cs | 602 ++++++++ .../Execution/ExecutionManagerTests.cs | 178 +++ ...ProcDataCollectionExtensionManagerTests.cs | 61 + .../Execution/RunTestsWithSourcesTests.cs | 265 ++++ .../Execution/RunTestsWithTestsTests.cs | 176 +++ .../Execution/TestRunCacheTests.cs | 409 +++++ .../Hosting/DefaultTestHostManagerTests.cs | 304 ++++ ...stPlatform.CrossPlatEngine.UnitTests.xproj | 22 + .../Properties/AssemblyInfo.cs | 19 + .../TestEngineTests.cs | 97 ++ .../TestExtensionManagerTests.cs | 44 + .../TestHostManagerFactoryTests.cs | 43 + .../TestableTestRunCache.cs | 59 + .../project.json | 38 + .../End2EndTests.cs | 138 ++ .../Microsoft.TestPlatform.End2EndTests.xproj | 22 + .../Properties/AssemblyInfo.cs | 19 + .../project.json | 35 + ...tform.Extensions.TrxLogger.UnitTests.xproj | 22 + .../Properties/AssemblyInfo.cs | 19 + .../TrxLoggerTests.cs | 418 +++++ .../project.json | 37 + .../Client/BaseTestRunCriteriaTests.cs | 66 + .../Client/TestRunCriteriaTests.cs | 178 +++ ...t.TestPlatform.ObjectModel.UnitTests.xproj | 22 + .../Properties/AssemblyInfo.cs | 19 + .../InProcDataCollectionRunSettingsTests.cs | 159 ++ .../Utilities/XmlRunSettingsUtilitiesTests.cs | 359 +++++ .../project.json | 35 + .../ClientUtilitiesTests.cs | 162 ++ .../CodeCoverageDataAdapterUtilitiesTests.cs | 59 + .../ExceptionUtilities.cs | 34 + .../InferRunSettingsHelperTests.cs | 209 +++ .../MSTestSettingsUtilitiesTests.cs | 117 ++ ...oft.TestPlatform.Utilities.UnitTests.xproj | 22 + .../Properties/AssemblyInfo.cs | 19 + .../XmlUtilitiesTests.cs | 176 +++ .../project.json | 33 + ...stConsole.TranslationLayer.UnitTests.xproj | 22 + .../Properties/AssemblyInfo.cs | 19 + .../VsTestConsoleRequestSenderTests.cs | 1004 ++++++++++++ .../VsTestConsoleWrapperTests.cs | 401 +++++ .../project.json | 37 + .../Properties/AssemblyInfo.cs | 36 + .../SampleUnitTestProject.csproj | 103 ++ .../SampleUnitTestProject/UnitTest1.cs | 40 + .../SampleUnitTestProject/packages.config | 5 + .../TestImpactListener.Tests/Class1.cs | 77 + .../Properties/AssemblyInfo.cs | 36 + .../TestImpactListener.Tests/TITestDllKey.snk | Bin 0 -> 596 bytes .../TestImpactListener.Tests.csproj | 67 + .../TestImpactListener.Tests/app.config | 19 + .../TestAdapterPathArgumentProcessorTests.cs | 218 +++ .../TestHostManagerFactoryTests.cs | 31 + .../ExecutionManager.cs | 99 ++ .../Properties/AssemblyInfo.cs | 19 + .../TestPlatform.TestUtilities.xproj | 21 + .../VSTestConsoleTestBase.cs | 217 +++ test/TestPlatform.TestUtilities/project.json | 27 + .../Properties/AssemblyInfo.cs | 19 + test/testhost.UnitTests/project.json | 34 + .../testhost.UnitTests.xproj | 22 + .../CommandLine/CommandLineOptionsTests.cs | 139 ++ .../TestRunResultAggregatorTests.cs | 121 ++ .../ExceptionUtilities.cs | 34 + .../ExecutorUnitTests.cs | 85 ++ .../Implementations/MockFileHelper.cs | 22 + .../Internal/ConsoleLoggerTests.cs | 303 ++++ .../BuildPathArgumentProcessorTests.cs | 109 ++ .../ConfigurationArgumentProcessorTests.cs | 98 ++ .../EnableLoggersArgumentProcessorTests.cs | 117 ++ ...ableStaticLoggersArgumentProcessorTests.cs | 44 + .../Processors/HelpArgumentProcessorTests.cs | 98 ++ .../ListTestsArgumentProcessorTests.cs | 243 +++ .../OutputArgumentProcessorTests.cs | 109 ++ .../ParallelArgumentProcessorTests.cs | 86 ++ .../PlatformArgumentProcessorTests.cs | 127 ++ .../Processors/PortArgumentProcessorTests.cs | 130 ++ .../RunSettingsArgumentProcessortTests.cs | 260 ++++ .../RunSpecificTestsArgumentProcessorTests.cs | 271 ++++ .../RunTestsArgumentProcessorTests.cs | 314 ++++ .../TestAdapterPathArgumentProcessorTests.cs | 220 +++ .../TestCaseFilterArgumentProcessorTests.cs | 98 ++ .../TestSourceArgumentProcessorTests.cs | 121 ++ .../ArgumentProcessorFactoryTests.cs | 56 + .../Utilities/JsonUtilitiesTests.cs | 35 + .../Utilities/RunSettingsUtilitiesTests.cs | 161 ++ .../Properties/AssemblyInfo.cs | 19 + test/vstest.console.UnitTests/project.json | 36 + .../vstest.console.UnitTests.xproj | 22 + 612 files changed, 68405 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 Nuget.config create mode 100644 TestPlatform.sln create mode 100644 build.cmd create mode 100644 dogfood/Microsoft.TestPlatform.E2ETest/Adapter/Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.dll create mode 100644 dogfood/Microsoft.TestPlatform.E2ETest/Adapter/Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface.dll create mode 100644 dogfood/Microsoft.TestPlatform.E2ETest/Adapter/Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.dll create mode 100644 dogfood/Microsoft.TestPlatform.E2ETest/Adapter/Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll create mode 100644 dogfood/Microsoft.TestPlatform.E2ETest/Adapter/Microsoft.VisualStudio.TestPlatform.TestFramework.dll create mode 100644 dogfood/Microsoft.TestPlatform.E2ETest/Microsoft.TestPlatform.E2ETest.xproj create mode 100644 dogfood/Microsoft.TestPlatform.E2ETest/Program.cs create mode 100644 dogfood/Microsoft.TestPlatform.E2ETest/Properties/AssemblyInfo.cs create mode 100644 dogfood/Microsoft.TestPlatform.E2ETest/project.json create mode 100644 dogfood/Microsoft.TestPlatform.E2ETest/testhost.exe.config create mode 100644 dogfood/Microsoft.TestPlatform.E2ETest/testhost.x86.exe.config create mode 100644 dogfood/Microsoft.TestPlatform.TranslationLayer.E2ETest/Adapter/Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.dll create mode 100644 dogfood/Microsoft.TestPlatform.TranslationLayer.E2ETest/Adapter/Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface.dll create mode 100644 dogfood/Microsoft.TestPlatform.TranslationLayer.E2ETest/Adapter/Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.dll create mode 100644 dogfood/Microsoft.TestPlatform.TranslationLayer.E2ETest/Adapter/Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll create mode 100644 dogfood/Microsoft.TestPlatform.TranslationLayer.E2ETest/Adapter/Microsoft.VisualStudio.TestPlatform.TestFramework.dll create mode 100644 dogfood/Microsoft.TestPlatform.TranslationLayer.E2ETest/Microsoft.TestPlatform.TranslationLayer.E2ETest.xproj create mode 100644 dogfood/Microsoft.TestPlatform.TranslationLayer.E2ETest/Program.cs create mode 100644 dogfood/Microsoft.TestPlatform.TranslationLayer.E2ETest/Properties/AssemblyInfo.cs create mode 100644 dogfood/Microsoft.TestPlatform.TranslationLayer.E2ETest/project.json create mode 100644 dogfood/Microsoft.TestPlatform.TranslationLayer.E2ETest/testhost.exe.config create mode 100644 dogfood/Microsoft.TestPlatform.TranslationLayer.E2ETest/testhost.x86.exe.config create mode 100644 dogfood/UnitTestProject/Properties/AssemblyInfo.cs create mode 100644 dogfood/UnitTestProject/UnitTest.cs create mode 100644 dogfood/UnitTestProject/UnitTestProject.xproj create mode 100644 dogfood/UnitTestProject/packages.config create mode 100644 dogfood/UnitTestProject/project.json create mode 100644 global.json create mode 100644 scripts/DisableUnitTesting.bat create mode 100644 scripts/EnableUnitTesting.bat create mode 100644 scripts/build.ps1 create mode 100644 scripts/key.snk create mode 100644 src/Microsoft.TestPlatform.Client/DesignMode/DesignModeClient.cs create mode 100644 src/Microsoft.TestPlatform.Client/DesignMode/DesignModeTestEventsRegistrar.cs create mode 100644 src/Microsoft.TestPlatform.Client/DesignMode/DesignModeTestHostLauncher.cs create mode 100644 src/Microsoft.TestPlatform.Client/DesignMode/DesignModeTestHostLauncherFactory.cs create mode 100644 src/Microsoft.TestPlatform.Client/DesignMode/IDesignModeClient.cs create mode 100644 src/Microsoft.TestPlatform.Client/Discovery/DiscoveryRequest.cs create mode 100644 src/Microsoft.TestPlatform.Client/Execution/TestRunRequest.cs create mode 100644 src/Microsoft.TestPlatform.Client/Friends.cs create mode 100644 src/Microsoft.TestPlatform.Client/Microsoft.TestPlatform.Client.xproj create mode 100644 src/Microsoft.TestPlatform.Client/Properties/AssemblyInfo.cs create mode 100644 src/Microsoft.TestPlatform.Client/RequestHelper/DiscoveryRequestPayload.cs create mode 100644 src/Microsoft.TestPlatform.Client/RequestHelper/ITestRequestManager.cs create mode 100644 src/Microsoft.TestPlatform.Client/RequestHelper/TestRunRequestPayload.cs create mode 100644 src/Microsoft.TestPlatform.Client/Resources.Designer.cs create mode 100644 src/Microsoft.TestPlatform.Client/Resources.resx create mode 100644 src/Microsoft.TestPlatform.Client/TestPlatform.cs create mode 100644 src/Microsoft.TestPlatform.Client/TestPlatformFactory.cs create mode 100644 src/Microsoft.TestPlatform.Client/project.json create mode 100644 src/Microsoft.TestPlatform.Common/Constants.cs create mode 100644 src/Microsoft.TestPlatform.Common/DataCollection/BeforeTestRunStartResult.cs create mode 100644 src/Microsoft.TestPlatform.Common/ExtensionFramework/TestDiscoveryExtensionManager.cs create mode 100644 src/Microsoft.TestPlatform.Common/ExtensionFramework/TestExecutorExtensionManager.cs create mode 100644 src/Microsoft.TestPlatform.Common/ExtensionFramework/TestExtensionManager.cs create mode 100644 src/Microsoft.TestPlatform.Common/ExtensionFramework/TestPluginCache.cs create mode 100644 src/Microsoft.TestPlatform.Common/ExtensionFramework/TestPluginDiscoverer.cs create mode 100644 src/Microsoft.TestPlatform.Common/ExtensionFramework/TestPluginManager.cs create mode 100644 src/Microsoft.TestPlatform.Common/ExtensionFramework/Utilities/LazyExtension.cs create mode 100644 src/Microsoft.TestPlatform.Common/ExtensionFramework/Utilities/TestDiscovererPluginInformation.cs create mode 100644 src/Microsoft.TestPlatform.Common/ExtensionFramework/Utilities/TestExecutorPluginInformation.cs create mode 100644 src/Microsoft.TestPlatform.Common/ExtensionFramework/Utilities/TestExtensionPluginInformation.cs create mode 100644 src/Microsoft.TestPlatform.Common/ExtensionFramework/Utilities/TestExtensions.cs create mode 100644 src/Microsoft.TestPlatform.Common/ExtensionFramework/Utilities/TestLoggerPluginInformation.cs create mode 100644 src/Microsoft.TestPlatform.Common/ExtensionFramework/Utilities/TestPluginInformation.cs create mode 100644 src/Microsoft.TestPlatform.Common/ExtensionFramework/Utilities/TestSettingsProviderPluginInformation.cs create mode 100644 src/Microsoft.TestPlatform.Common/Filtering/Condition.cs create mode 100644 src/Microsoft.TestPlatform.Common/Filtering/FilterExpression.cs create mode 100644 src/Microsoft.TestPlatform.Common/Filtering/FilterExpressionWrapper.cs create mode 100644 src/Microsoft.TestPlatform.Common/Filtering/TestCaseFilterExpression.cs create mode 100644 src/Microsoft.TestPlatform.Common/Friends.cs create mode 100644 src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/IParallelOperationManager.cs create mode 100644 src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/IParallelProxyExecutionManager.cs create mode 100644 src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/IProxyDiscoveryManager.cs create mode 100644 src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/IProxyExecutionManager.cs create mode 100644 src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/IProxyOperationManager.cs create mode 100644 src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/ITestEngine.cs create mode 100644 src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/ITestExtensionManager.cs create mode 100644 src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/ITestHostManager.cs create mode 100644 src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/TestExecutionContext.cs create mode 100644 src/Microsoft.TestPlatform.Common/Interfaces/Engine/ITestCaseEventsHandler.cs create mode 100644 src/Microsoft.TestPlatform.Common/Interfaces/Engine/TesthostProtocol/IDiscoveryManager.cs create mode 100644 src/Microsoft.TestPlatform.Common/Interfaces/Engine/TesthostProtocol/IExecutionManager.cs create mode 100644 src/Microsoft.TestPlatform.Common/Interfaces/Engine/TesthostProtocol/ITestHostManagerFactory.cs create mode 100644 src/Microsoft.TestPlatform.Common/Interfaces/IPathUtilities.cs create mode 100644 src/Microsoft.TestPlatform.Common/Interfaces/IRunSettingsProvider.cs create mode 100644 src/Microsoft.TestPlatform.Common/Interfaces/ISettingsProviderCapabilities.cs create mode 100644 src/Microsoft.TestPlatform.Common/Interfaces/ITestDiscovererCapabilities.cs create mode 100644 src/Microsoft.TestPlatform.Common/Interfaces/ITestDiscoveryEventsRegistrar.cs create mode 100644 src/Microsoft.TestPlatform.Common/Interfaces/ITestExecutorCapabilities.cs create mode 100644 src/Microsoft.TestPlatform.Common/Interfaces/ITestExtensionCapabilities.cs create mode 100644 src/Microsoft.TestPlatform.Common/Interfaces/ITestLoggerCapabilities.cs create mode 100644 src/Microsoft.TestPlatform.Common/Interfaces/ITestRunEventsRegistrar.cs create mode 100644 src/Microsoft.TestPlatform.Common/Logging/InternalTestLoggerEvents.cs create mode 100644 src/Microsoft.TestPlatform.Common/Logging/TestLoggerExtensionManager.cs create mode 100644 src/Microsoft.TestPlatform.Common/Logging/TestLoggerManager.cs create mode 100644 src/Microsoft.TestPlatform.Common/Logging/TestLoggerMetadata.cs create mode 100644 src/Microsoft.TestPlatform.Common/Logging/TestSessionMessageLogger.cs create mode 100644 src/Microsoft.TestPlatform.Common/Microsoft.TestPlatform.Common.xproj create mode 100644 src/Microsoft.TestPlatform.Common/Properties/AssemblyInfo.cs create mode 100644 src/Microsoft.TestPlatform.Common/Resources.Designer.cs create mode 100644 src/Microsoft.TestPlatform.Common/Resources.resx create mode 100644 src/Microsoft.TestPlatform.Common/RunSettings.cs create mode 100644 src/Microsoft.TestPlatform.Common/RunSettingsManager.cs create mode 100644 src/Microsoft.TestPlatform.Common/SettingsProvider/SettingsProviderExtensionManager.cs create mode 100644 src/Microsoft.TestPlatform.Common/Utilities/AssemblyResolver.cs create mode 100644 src/Microsoft.TestPlatform.Common/Utilities/ExceptionUtilities.cs create mode 100644 src/Microsoft.TestPlatform.Common/Utilities/PathUtilities.cs create mode 100644 src/Microsoft.TestPlatform.Common/Utilities/RunSettingsUtilities.cs create mode 100644 src/Microsoft.TestPlatform.Common/project.json create mode 100644 src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionRequestHandler.cs create mode 100644 src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionRequestSender.cs create mode 100644 src/Microsoft.TestPlatform.CommunicationUtilities/EventHandlers/TestCaseEventsHandler.cs create mode 100644 src/Microsoft.TestPlatform.CommunicationUtilities/EventHandlers/TestDiscoveryEventHandler.cs create mode 100644 src/Microsoft.TestPlatform.CommunicationUtilities/EventHandlers/TestRunEventsHandler.cs create mode 100644 src/Microsoft.TestPlatform.CommunicationUtilities/Friends.cs create mode 100644 src/Microsoft.TestPlatform.CommunicationUtilities/Interfaces/ICommunicationManager.cs create mode 100644 src/Microsoft.TestPlatform.CommunicationUtilities/Interfaces/IDataCollectionRequestHandler.cs create mode 100644 src/Microsoft.TestPlatform.CommunicationUtilities/Interfaces/IDataCollectionRequestSender.cs create mode 100644 src/Microsoft.TestPlatform.CommunicationUtilities/Interfaces/IDataSerializer.cs create mode 100644 src/Microsoft.TestPlatform.CommunicationUtilities/Interfaces/ITestRequestHandler.cs create mode 100644 src/Microsoft.TestPlatform.CommunicationUtilities/Interfaces/ITestRequestSender.cs create mode 100644 src/Microsoft.TestPlatform.CommunicationUtilities/JsonDataSerializer.cs create mode 100644 src/Microsoft.TestPlatform.CommunicationUtilities/Messages/DiscoveryCompletePayload.cs create mode 100644 src/Microsoft.TestPlatform.CommunicationUtilities/Messages/Message.cs create mode 100644 src/Microsoft.TestPlatform.CommunicationUtilities/Messages/MessageType.cs create mode 100644 src/Microsoft.TestPlatform.CommunicationUtilities/Messages/TestMessagePayload.cs create mode 100644 src/Microsoft.TestPlatform.CommunicationUtilities/Messages/TestRunCompletePayload.cs create mode 100644 src/Microsoft.TestPlatform.CommunicationUtilities/Messages/TestRunStatsPayload.cs create mode 100644 src/Microsoft.TestPlatform.CommunicationUtilities/Microsoft.TestPlatform.CommunicationUtilities.xproj create mode 100644 src/Microsoft.TestPlatform.CommunicationUtilities/ObjectModel/TestRunCriteriaWithSources.cs create mode 100644 src/Microsoft.TestPlatform.CommunicationUtilities/ObjectModel/TestRunCriteriaWithTests.cs create mode 100644 src/Microsoft.TestPlatform.CommunicationUtilities/Properties/AssemblyInfo.cs create mode 100644 src/Microsoft.TestPlatform.CommunicationUtilities/SocketCommunicationManager.cs create mode 100644 src/Microsoft.TestPlatform.CommunicationUtilities/TestRequestHandler.cs create mode 100644 src/Microsoft.TestPlatform.CommunicationUtilities/TestRequestSender.cs create mode 100644 src/Microsoft.TestPlatform.CommunicationUtilities/project.json create mode 100644 src/Microsoft.TestPlatform.CoreUtilities/Friends.cs create mode 100644 src/Microsoft.TestPlatform.CoreUtilities/Helpers/FileHelper.cs create mode 100644 src/Microsoft.TestPlatform.CoreUtilities/Helpers/Interfaces/IFileHelper.cs create mode 100644 src/Microsoft.TestPlatform.CoreUtilities/Microsoft.TestPlatform.CoreUtilities.xproj create mode 100644 src/Microsoft.TestPlatform.CoreUtilities/Output/ConsoleColorHelper.cs create mode 100644 src/Microsoft.TestPlatform.CoreUtilities/Output/ConsoleOutput.cs create mode 100644 src/Microsoft.TestPlatform.CoreUtilities/Output/IOutput.cs create mode 100644 src/Microsoft.TestPlatform.CoreUtilities/Output/OutputLevel.cs create mode 100644 src/Microsoft.TestPlatform.CoreUtilities/Output/OutputUtilities.cs create mode 100644 src/Microsoft.TestPlatform.CoreUtilities/Properties/AssemblyInfo.cs create mode 100644 src/Microsoft.TestPlatform.CoreUtilities/Resources.Designer.cs create mode 100644 src/Microsoft.TestPlatform.CoreUtilities/Resources.resx create mode 100644 src/Microsoft.TestPlatform.CoreUtilities/Tracing/EqtTrace.cs create mode 100644 src/Microsoft.TestPlatform.CoreUtilities/Tracing/RemoteEqtTrace.cs create mode 100644 src/Microsoft.TestPlatform.CoreUtilities/Tracing/RollingFileTraceListener.cs create mode 100644 src/Microsoft.TestPlatform.CoreUtilities/Utilities/Job.cs create mode 100644 src/Microsoft.TestPlatform.CoreUtilities/Utilities/JobQueue.cs create mode 100644 src/Microsoft.TestPlatform.CoreUtilities/Utilities/MulticastDelegateUtilities.cs create mode 100644 src/Microsoft.TestPlatform.CoreUtilities/ValidateArg.cs create mode 100644 src/Microsoft.TestPlatform.CoreUtilities/project.json create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/Adapter/FrameworkHandle.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/Adapter/RunContext.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/Adapter/TestCaseFilterExpression.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/Adapter/TestExecutionRecorder.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelOperationManager.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelProxyExecutionManager.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelRunDataAggregator.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelRunEventsHandler.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyDiscoveryManager.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyExecutionManager.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyExecutionManagerWithDataCollection.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyOperationManager.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/Constants.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/DataCollection/DataCollectionLauncher.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/DataCollection/DataCollectionParameters.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/DataCollection/DataCollectionTestRunEventsHandler.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/DataCollection/Interfaces/IDataCollectionLauncher.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/DataCollection/Interfaces/IDataCollectorsSettingsProvider.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/DataCollection/Interfaces/IProxyDataCollectionManager.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/DataCollection/ProxyDataCollectionManager.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/Discovery/DiscovererEnumerator.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/Discovery/DiscoveryContext.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/Discovery/DiscoveryManager.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/Discovery/DiscoveryResultCache.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/Discovery/TestCaseDiscoverySink.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/EventHandlers/TestCaseEventsHandler.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/Execution/BaseRunTests.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/Execution/ExecutionManager.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/Execution/InProcDataCollectionExtensionManager.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/Execution/RunTestsWithSources.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/Execution/RunTestsWithTests.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/Execution/TestRunCache.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/Execution/TestRunStatistics.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/Friends.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/Helpers/Interfaces/IProcessHelper.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/Helpers/ProcessHelper.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/Hosting/DefaultTestHostManager.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/Interfaces/ITestRunCache.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/Microsoft.TestPlatform.CrossPlatEngine.xproj create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/Properties/AssemblyInfo.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/Resources.Designer.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/Resources.resx create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/TestEngine.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/TestExtensionManager.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/TestHostManagerFactory.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/project.json create mode 100644 src/Microsoft.TestPlatform.Extensions.TrxLogger/Friends.cs create mode 100644 src/Microsoft.TestPlatform.Extensions.TrxLogger/Interfaces/IDataAttachment.cs create mode 100644 src/Microsoft.TestPlatform.Extensions.TrxLogger/Interfaces/IXmlTestStore.cs create mode 100644 src/Microsoft.TestPlatform.Extensions.TrxLogger/Interfaces/IXmlTestStoreCustom.cs create mode 100644 src/Microsoft.TestPlatform.Extensions.TrxLogger/Interfaces/XmlTestStoreParameters.cs create mode 100644 src/Microsoft.TestPlatform.Extensions.TrxLogger/Microsoft.TestPlatform.Extensions.TrxLogger.xproj create mode 100644 src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/CollectorDataEntry.cs create mode 100644 src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/RunInfo.cs create mode 100644 src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestCategoryItems.cs create mode 100644 src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestEntry.cs create mode 100644 src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestExecId.cs create mode 100644 src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestId.cs create mode 100644 src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestListCategory.cs create mode 100644 src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestListCategoryId.cs create mode 100644 src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestMethod.cs create mode 100644 src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestOutcome.cs create mode 100644 src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestResult.cs create mode 100644 src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestRun.cs create mode 100644 src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestRunConfiguration.cs create mode 100644 src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestRunConfigurationId.cs create mode 100644 src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestRunSummary.cs create mode 100644 src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestType.cs create mode 100644 src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/UnitTestElement.cs create mode 100644 src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/UnitTestResult.cs create mode 100644 src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/UriDataAttachment.cs create mode 100644 src/Microsoft.TestPlatform.Extensions.TrxLogger/Properties/AssemblyInfo.cs create mode 100644 src/Microsoft.TestPlatform.Extensions.TrxLogger/TrxLogger.cs create mode 100644 src/Microsoft.TestPlatform.Extensions.TrxLogger/TrxResource.Designer.cs create mode 100644 src/Microsoft.TestPlatform.Extensions.TrxLogger/TrxResource.resx create mode 100644 src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/Collection.cs create mode 100644 src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/Converter.cs create mode 100644 src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/EqtAssert.cs create mode 100644 src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/FilterHelper.cs create mode 100644 src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/TestRunDirectories.cs create mode 100644 src/Microsoft.TestPlatform.Extensions.TrxLogger/XML/Attributes.cs create mode 100644 src/Microsoft.TestPlatform.Extensions.TrxLogger/XML/XmlFilePersistence.cs create mode 100644 src/Microsoft.TestPlatform.Extensions.TrxLogger/XML/XmlPersistence.cs create mode 100644 src/Microsoft.TestPlatform.Extensions.TrxLogger/project.json create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Adapter/Interfaces/IDiscoveryContext.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Adapter/Interfaces/IFrameworkHandle.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Adapter/Interfaces/IRunContext.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Adapter/Interfaces/IRunSettings.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Adapter/Interfaces/ISettingsProvider.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Adapter/Interfaces/ITestCaseDiscoverySink.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Adapter/Interfaces/ITestCaseFilterExpression.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Adapter/Interfaces/ITestDiscoverer.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Adapter/Interfaces/ITestExecutor.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Adapter/Interfaces/ITestLog.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Adapter/TestPlatformFormatException.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Adapter/TestsCanceledException.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Architecture.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/AttachmentSet.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Client/DiscoveryCriteria.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Client/Events/DiscoveredTestsEventArgs.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Client/Events/DiscoveryCompleteEventArgs.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Client/Events/TestRunChangedEventArgs.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Client/Events/TestRunCompleteEventArgs.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/IDiscoveryRequest.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/IRequest.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestDiscoveryEventsHandler.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestHostLauncher.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestPlatform.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestPlatformCapabilities.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestRunConfiguration.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestRunEventsHandler.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestRunRequest.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestRunStatistics.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Client/TestRunCriteria.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Client/TestRunState.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/CommonResources.Designer.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/CommonResources.resx create mode 100644 src/Microsoft.TestPlatform.ObjectModel/CommonResources.tt create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Constants.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/DataCollector/Common/DataCollectorMessageLevel.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/DataCollector/Common/FileHelper.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/DataCollector/Common/RequestId.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/DataCollector/Common/Session.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/DataCollector/Common/TestCaseFailureType.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/DataCollector/Common/TestExecId.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/DataCollector/DataCollectionContext.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/DataCollector/DataCollectionEnvironmentContext.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/DataCollector/DataCollectionLogger.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/DataCollector/DataCollectionRunSettings.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/DataCollector/DataCollectionSink.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/DataCollector/DataCollector.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/DataCollector/DataCollectorSettings.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/DataCollector/Events/CustomNotificationEventArgs.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/DataCollector/Events/DataCollectionEventArgs.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/DataCollector/Events/DataCollectionEvents.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/DataCollector/Events/DataRequestEventArgs.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/DataCollector/Events/SessionEvents.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/DataCollector/Events/TestCaseEvents.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/DataCollector/InProcDataCollector/InProcDataCollectionArgs.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/DataCollector/InProcDataCollector/InProcDataCollector.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/DataCollector/InProcDataCollector/TestCaseEndArgs.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/DataCollector/InProcDataCollector/TestCaseStartArgs.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/DataCollector/InProcDataCollector/TestSessionEndArgs.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/DataCollector/InProcDataCollector/TestSessionStartArgs.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/DataCollector/TransferInformation/BaseTransferInformation.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/DataCollector/TransferInformation/FileTransferInformation.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/DataCollector/TransferInformation/StreamTransferInformation.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/DefaultExecutorUriAttribute.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/ExceptionConverter.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/ExtensionUriAttribute.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/FileExtensionAttribute.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/FrameworkVersion.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/FriendlyNameAttribute.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Friends.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/LazyPropertyValue.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Logger/ITestLogger.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Logger/ITestLoggerWithParams.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Logger/TestLoggerEvents.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Logging/Events/DataCollectionMessageEventArgs.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Logging/Events/TestResultEventArgs.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Logging/Events/TestRunMessageEventArgs.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Logging/Events/TestRunStartedEventArgs.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Logging/Interfaces/IMessageLogger.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Logging/TestMessageLevel.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Microsoft.TestPlatform.ObjectModel.xproj create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Navigation/DiaNavigationData.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Navigation/DiaSession.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Navigation/INavigationData.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Navigation/INavigationSession.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Properties/AssemblyInfo.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Resources.Designer.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Resources.resx create mode 100644 src/Microsoft.TestPlatform.ObjectModel/RunSettings/RunConfiguration.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/RunSettings/SettingsException.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/RunSettings/SettingsNameAttribute.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/RunSettings/TestRunParameters.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/RunSettings/TestRunSettings.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/TestCase.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/TestObject.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/TestOutcome.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/TestOutcomeHelper.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/TestProcessStartInfo.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/TestProperty/TestProperty.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/TestProperty/TestPropertyAttributes.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/TestResult.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Trait.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/TraitCollection.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Utilities/AssemblyHelper.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Utilities/AssemblyLoadWorker.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Utilities/EqtHash.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Utilities/InProcDataCollectionUtilities.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Utilities/StringUtilities.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Utilities/SuspendCodeCoverage.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Utilities/XmlReaderUtilities.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Utilities/XmlRunSettingsUtilities.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/project.json create mode 100644 src/Microsoft.TestPlatform.Utilities/ClientUtilities.cs create mode 100644 src/Microsoft.TestPlatform.Utilities/CodeCoverageDataAdapterUtilities.cs create mode 100644 src/Microsoft.TestPlatform.Utilities/Friends.cs create mode 100644 src/Microsoft.TestPlatform.Utilities/InferRunSettingsHelper.cs create mode 100644 src/Microsoft.TestPlatform.Utilities/MSTestSettingsUtilities.cs create mode 100644 src/Microsoft.TestPlatform.Utilities/Microsoft.TestPlatform.Utilities.xproj create mode 100644 src/Microsoft.TestPlatform.Utilities/ParallelRunSettingsUtilities.cs create mode 100644 src/Microsoft.TestPlatform.Utilities/Properties/AssemblyInfo.cs create mode 100644 src/Microsoft.TestPlatform.Utilities/Resource.Designer.cs create mode 100644 src/Microsoft.TestPlatform.Utilities/Resource.resx create mode 100644 src/Microsoft.TestPlatform.Utilities/XmlUtilities.cs create mode 100644 src/Microsoft.TestPlatform.Utilities/project.json create mode 100644 src/Microsoft.TestPlatform.VSIXCreator/Microsoft.TestPlatform.VSIXCreator.xproj create mode 100644 src/Microsoft.TestPlatform.VSIXCreator/Program.cs create mode 100644 src/Microsoft.TestPlatform.VSIXCreator/Properties/AssemblyInfo.cs create mode 100644 src/Microsoft.TestPlatform.VSIXCreator/VSIXCreator.cmd create mode 100644 src/Microsoft.TestPlatform.VSIXCreator/VSIXDelete.cmd create mode 100644 src/Microsoft.TestPlatform.VSIXCreator/[Content_Types].xml create mode 100644 src/Microsoft.TestPlatform.VSIXCreator/extension.vsixmanifest create mode 100644 src/Microsoft.TestPlatform.VSIXCreator/project.json create mode 100644 src/Microsoft.TestPlatform.VSIXCreator/testhost.exe.config create mode 100644 src/Microsoft.TestPlatform.VSIXCreator/testhost.x86.exe.config create mode 100644 src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Friends.cs create mode 100644 src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/IProcessManager.cs create mode 100644 src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/ITranslationLayerRequestSender.cs create mode 100644 src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/IVsTestConsoleWrapper.cs create mode 100644 src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.xproj create mode 100644 src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Payloads/DiscoveryRequestPayload.cs create mode 100644 src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Payloads/TestRunRequestPayload.cs create mode 100644 src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Properties/AssemblyInfo.cs create mode 100644 src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/TransationLayerException.cs create mode 100644 src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/VsTestConsoleProcessManager.cs create mode 100644 src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/VsTestConsoleRequestSender.cs create mode 100644 src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/VsTestConsoleWrapper.cs create mode 100644 src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/project.json create mode 100644 src/datacollector.x86/Program.cs create mode 100644 src/datacollector.x86/Properties/AssemblyInfo.cs create mode 100644 src/datacollector.x86/app.config create mode 100644 src/datacollector.x86/datacollector.x86.xproj create mode 100644 src/datacollector.x86/project.json create mode 100644 src/datacollector/Properties/AssemblyInfo.cs create mode 100644 src/datacollector/app.config create mode 100644 src/datacollector/datacollector.xproj create mode 100644 src/datacollector/project.json create mode 100644 src/package/[Content_Types].xml create mode 100644 src/package/extension.vsixmanifest create mode 100644 src/package/project.json create mode 100644 src/package/sign/project.json create mode 100644 src/package/sign/sign.proj create mode 100644 src/package/testhost.exe.config create mode 100644 src/package/testhost.x86.exe.config create mode 100644 src/testhost.x86/Program.cs create mode 100644 src/testhost.x86/Properties/AssemblyInfo.cs create mode 100644 src/testhost.x86/app.config create mode 100644 src/testhost.x86/project.json create mode 100644 src/testhost.x86/testhost.x86.xproj create mode 100644 src/testhost/Properties/AssemblyInfo.cs create mode 100644 src/testhost/app.config create mode 100644 src/testhost/project.json create mode 100644 src/testhost/testhost.xproj create mode 100644 src/vstest.console/CommandLine/CommandArgumentPair.cs create mode 100644 src/vstest.console/CommandLine/CommandLineErrorException.cs create mode 100644 src/vstest.console/CommandLine/CommandLineOptions.cs create mode 100644 src/vstest.console/CommandLine/Executor.cs create mode 100644 src/vstest.console/CommandLine/TestRunResultAggregator.cs create mode 100644 src/vstest.console/Friends.cs create mode 100644 src/vstest.console/Internal/ConsoleLogger.cs create mode 100644 src/vstest.console/Processors/BuildBasePathArgumentProcessor.cs create mode 100644 src/vstest.console/Processors/ConfigurationArgumentProcessor.cs create mode 100644 src/vstest.console/Processors/EnableLoggerArgumentProcessor.cs create mode 100644 src/vstest.console/Processors/EnableStaticLoggersArgumentProcessor.cs create mode 100644 src/vstest.console/Processors/HelpArgumentProcessor.cs create mode 100644 src/vstest.console/Processors/Interfaces/IArgumentExector.cs create mode 100644 src/vstest.console/Processors/Interfaces/IArgumentProcessor.cs create mode 100644 src/vstest.console/Processors/Interfaces/IArgumentProcessorCapabilities.cs create mode 100644 src/vstest.console/Processors/ListTestsArgumentProcessor.cs create mode 100644 src/vstest.console/Processors/OutputArgumentProcessor.cs create mode 100644 src/vstest.console/Processors/ParallelArgumentProcessor.cs create mode 100644 src/vstest.console/Processors/PlatformArgumentProcessor.cs create mode 100644 src/vstest.console/Processors/PortArgumentProcessor.cs create mode 100644 src/vstest.console/Processors/RunSettingsArgumentProcessor.cs create mode 100644 src/vstest.console/Processors/RunSpecificTestsArgumentProcessor.cs create mode 100644 src/vstest.console/Processors/RunTestsArgumentProcessor.cs create mode 100644 src/vstest.console/Processors/TestAdapterPathArgumentProcessor.cs create mode 100644 src/vstest.console/Processors/TestCaseFilterArgumentProcessor.cs create mode 100644 src/vstest.console/Processors/TestSourceArgumentProcessor.cs create mode 100644 src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs create mode 100644 src/vstest.console/Processors/Utilities/ArgumentProcessorPriority.cs create mode 100644 src/vstest.console/Processors/Utilities/ArgumentProcessorResult.cs create mode 100644 src/vstest.console/Processors/Utilities/BaseArgumentProcessorCapabilities.cs create mode 100644 src/vstest.console/Processors/Utilities/HelpContentPriority.cs create mode 100644 src/vstest.console/Processors/Utilities/JsonUtilities.cs create mode 100644 src/vstest.console/Processors/Utilities/LoggerUtilities.cs create mode 100644 src/vstest.console/Processors/Utilities/RunSettingsUtilities.cs create mode 100644 src/vstest.console/Program.cs create mode 100644 src/vstest.console/Properties/AssemblyInfo.cs create mode 100644 src/vstest.console/Resources.Designer.cs create mode 100644 src/vstest.console/Resources.resx create mode 100644 src/vstest.console/TestPlatformHelpers/TestRequestManager.cs create mode 100644 src/vstest.console/project.json create mode 100644 src/vstest.console/vstest.console.xproj create mode 100644 test/Microsoft.TestPlatform.Client.UnitTests/DesignMode/DesignModeClientTests.cs create mode 100644 test/Microsoft.TestPlatform.Client.UnitTests/DesignMode/DesignModeTestHostLauncherFactoryTests.cs create mode 100644 test/Microsoft.TestPlatform.Client.UnitTests/DesignMode/DesignModeTestHostLauncherTests.cs create mode 100644 test/Microsoft.TestPlatform.Client.UnitTests/Discovery/DiscoveryRequestTests.cs create mode 100644 test/Microsoft.TestPlatform.Client.UnitTests/Execution/TestRunRequestTests.cs create mode 100644 test/Microsoft.TestPlatform.Client.UnitTests/Microsoft.TestPlatform.Client.UnitTests.xproj create mode 100644 test/Microsoft.TestPlatform.Client.UnitTests/Properties/AssemblyInfo.cs create mode 100644 test/Microsoft.TestPlatform.Client.UnitTests/TestPlatformTests.cs create mode 100644 test/Microsoft.TestPlatform.Client.UnitTests/project.json create mode 100644 test/Microsoft.TestPlatform.Common.UnitTests/ExceptionUtilities.cs create mode 100644 test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/TestDiscoveryExtensionManagerTests.cs create mode 100644 test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/TestExecutorExtensionManagerTests.cs create mode 100644 test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/TestExtensionManagerTests.cs create mode 100644 test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/TestPluginCacheTests.cs create mode 100644 test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/TestPluginDiscovererTests.cs create mode 100644 test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/TestPluginManagerTests.cs create mode 100644 test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/Utilities/LazyExtensionTests.cs create mode 100644 test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/Utilities/TestDiscovererPluginInformationTests.cs create mode 100644 test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/Utilities/TestExtensionPluginInformationTests.cs create mode 100644 test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/Utilities/TestExtensionsTests.cs create mode 100644 test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/Utilities/TestLoggerPluginInformationTests.cs create mode 100644 test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/Utilities/TestPluginInformationTests.cs create mode 100644 test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/Utilities/TestSettingsProviderPluginInformationTests.cs create mode 100644 test/Microsoft.TestPlatform.Common.UnitTests/Logging/InternalTestLoggerEventsTests.cs create mode 100644 test/Microsoft.TestPlatform.Common.UnitTests/Logging/TestLoggerExtensionManagerTests.cs create mode 100644 test/Microsoft.TestPlatform.Common.UnitTests/Logging/TestLoggerManagerTests.cs create mode 100644 test/Microsoft.TestPlatform.Common.UnitTests/Logging/TestSessionMessageLoggerTests.cs create mode 100644 test/Microsoft.TestPlatform.Common.UnitTests/Microsoft.TestPlatform.Common.UnitTests.xproj create mode 100644 test/Microsoft.TestPlatform.Common.UnitTests/Properties/AssemblyInfo.cs create mode 100644 test/Microsoft.TestPlatform.Common.UnitTests/RunSettingsManagerTests.cs create mode 100644 test/Microsoft.TestPlatform.Common.UnitTests/RunSettingsTests.cs create mode 100644 test/Microsoft.TestPlatform.Common.UnitTests/SettingsProvider/SettingsProviderExtensionManagerTests.cs create mode 100644 test/Microsoft.TestPlatform.Common.UnitTests/Utilities/ExceptionUtilitiesTests.cs create mode 100644 test/Microsoft.TestPlatform.Common.UnitTests/Utilities/PathUtilitiesTests.cs create mode 100644 test/Microsoft.TestPlatform.Common.UnitTests/Utilities/RunSettingsUtilitiesTests.cs create mode 100644 test/Microsoft.TestPlatform.Common.UnitTests/project.json create mode 100644 test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/Microsoft.TestPlatform.CommunicationUtilities.UnitTests.xproj create mode 100644 test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/ObjectModel/TestDiscoveryEventHandlerTests.cs create mode 100644 test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/ObjectModel/TestRunEventsHandlerTests.cs create mode 100644 test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/Properties/AssemblyInfo.cs create mode 100644 test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/TestRequestSenderTests.cs create mode 100644 test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/project.json create mode 100644 test/Microsoft.TestPlatform.CoreUtilities.UnitTests/Microsoft.TestPlatform.CoreUtilities.UnitTests.xproj create mode 100644 test/Microsoft.TestPlatform.CoreUtilities.UnitTests/Properties/AssemblyInfo.cs create mode 100644 test/Microsoft.TestPlatform.CoreUtilities.UnitTests/Utilities/JobQueueTests.cs create mode 100644 test/Microsoft.TestPlatform.CoreUtilities.UnitTests/project.json create mode 100644 test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Adapter/FrameworkHandleTests.cs create mode 100644 test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Adapter/RunContextTests.cs create mode 100644 test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Adapter/TestExecutionRecorderTests.cs create mode 100644 test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/ParallelOperationManagerTests.cs create mode 100644 test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/ParallelProxyExecutionManagerTests.cs create mode 100644 test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/ParallelRunDataAggregatorTests.cs create mode 100644 test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/ParallelRunEventsHandlerTests.cs create mode 100644 test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyDiscoveryManagerTests.cs create mode 100644 test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyExecutionManagerTests.cs create mode 100644 test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyExecutionManagerWithDataCollectionTests.cs create mode 100644 test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyOperationManagerTests.cs create mode 100644 test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/DataCollection/DataCollectionTestRunEventsHandlerTests.cs create mode 100644 test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/DataCollection/ProxyDataCollectionManagerTests.cs create mode 100644 test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Discovery/DiscovererEnumeratorTests.cs create mode 100644 test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Discovery/DiscoveryManagerTests.cs create mode 100644 test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Discovery/DiscoveryResultCacheTests.cs create mode 100644 test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Discovery/TestCaseDiscoverySinkTests.cs create mode 100644 test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Execution/BaseRunTestsTests.cs create mode 100644 test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Execution/ExecutionManagerTests.cs create mode 100644 test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Execution/InProcDataCollectionExtensionManagerTests.cs create mode 100644 test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Execution/RunTestsWithSourcesTests.cs create mode 100644 test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Execution/RunTestsWithTestsTests.cs create mode 100644 test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Execution/TestRunCacheTests.cs create mode 100644 test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Hosting/DefaultTestHostManagerTests.cs create mode 100644 test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Microsoft.TestPlatform.CrossPlatEngine.UnitTests.xproj create mode 100644 test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Properties/AssemblyInfo.cs create mode 100644 test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/TestEngineTests.cs create mode 100644 test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/TestExtensionManagerTests.cs create mode 100644 test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/TestHostManagerFactoryTests.cs create mode 100644 test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/TestableImplementations/TestableTestRunCache.cs create mode 100644 test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/project.json create mode 100644 test/Microsoft.TestPlatform.End2EndTests/End2EndTests.cs create mode 100644 test/Microsoft.TestPlatform.End2EndTests/Microsoft.TestPlatform.End2EndTests.xproj create mode 100644 test/Microsoft.TestPlatform.End2EndTests/Properties/AssemblyInfo.cs create mode 100644 test/Microsoft.TestPlatform.End2EndTests/project.json create mode 100644 test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests.xproj create mode 100644 test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/Properties/AssemblyInfo.cs create mode 100644 test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/TrxLoggerTests.cs create mode 100644 test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/project.json create mode 100644 test/Microsoft.TestPlatform.ObjectModel.UnitTests/Client/BaseTestRunCriteriaTests.cs create mode 100644 test/Microsoft.TestPlatform.ObjectModel.UnitTests/Client/TestRunCriteriaTests.cs create mode 100644 test/Microsoft.TestPlatform.ObjectModel.UnitTests/Microsoft.TestPlatform.ObjectModel.UnitTests.xproj create mode 100644 test/Microsoft.TestPlatform.ObjectModel.UnitTests/Properties/AssemblyInfo.cs create mode 100644 test/Microsoft.TestPlatform.ObjectModel.UnitTests/RunSettings/InProcDataCollectionRunSettingsTests.cs create mode 100644 test/Microsoft.TestPlatform.ObjectModel.UnitTests/Utilities/XmlRunSettingsUtilitiesTests.cs create mode 100644 test/Microsoft.TestPlatform.ObjectModel.UnitTests/project.json create mode 100644 test/Microsoft.TestPlatform.Utilities.UnitTests/ClientUtilitiesTests.cs create mode 100644 test/Microsoft.TestPlatform.Utilities.UnitTests/CodeCoverageDataAdapterUtilitiesTests.cs create mode 100644 test/Microsoft.TestPlatform.Utilities.UnitTests/ExceptionUtilities.cs create mode 100644 test/Microsoft.TestPlatform.Utilities.UnitTests/InferRunSettingsHelperTests.cs create mode 100644 test/Microsoft.TestPlatform.Utilities.UnitTests/MSTestSettingsUtilitiesTests.cs create mode 100644 test/Microsoft.TestPlatform.Utilities.UnitTests/Microsoft.TestPlatform.Utilities.UnitTests.xproj create mode 100644 test/Microsoft.TestPlatform.Utilities.UnitTests/Properties/AssemblyInfo.cs create mode 100644 test/Microsoft.TestPlatform.Utilities.UnitTests/XmlUtilitiesTests.cs create mode 100644 test/Microsoft.TestPlatform.Utilities.UnitTests/project.json create mode 100644 test/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.UnitTests/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.UnitTests.xproj create mode 100644 test/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.UnitTests/Properties/AssemblyInfo.cs create mode 100644 test/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.UnitTests/VsTestConsoleRequestSenderTests.cs create mode 100644 test/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.UnitTests/VsTestConsoleWrapperTests.cs create mode 100644 test/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.UnitTests/project.json create mode 100644 test/Samples/SampleUnitTestProject/Properties/AssemblyInfo.cs create mode 100644 test/Samples/SampleUnitTestProject/SampleUnitTestProject.csproj create mode 100644 test/Samples/SampleUnitTestProject/UnitTest1.cs create mode 100644 test/Samples/SampleUnitTestProject/packages.config create mode 100644 test/Samples/TestImpactListener.Tests/Class1.cs create mode 100644 test/Samples/TestImpactListener.Tests/Properties/AssemblyInfo.cs create mode 100644 test/Samples/TestImpactListener.Tests/TITestDllKey.snk create mode 100644 test/Samples/TestImpactListener.Tests/TestImpactListener.Tests.csproj create mode 100644 test/Samples/TestImpactListener.Tests/app.config create mode 100644 test/TestPlatform.CommandLine.UnitTests/Processors/TestAdapterPathArgumentProcessorTests.cs create mode 100644 test/TestPlatform.CrossPlatEngine.UnitTests/TestHostManagerFactoryTests.cs create mode 100644 test/TestPlatform.TestUtilities/ExecutionManager.cs create mode 100644 test/TestPlatform.TestUtilities/Properties/AssemblyInfo.cs create mode 100644 test/TestPlatform.TestUtilities/TestPlatform.TestUtilities.xproj create mode 100644 test/TestPlatform.TestUtilities/VSTestConsoleTestBase.cs create mode 100644 test/TestPlatform.TestUtilities/project.json create mode 100644 test/testhost.UnitTests/Properties/AssemblyInfo.cs create mode 100644 test/testhost.UnitTests/project.json create mode 100644 test/testhost.UnitTests/testhost.UnitTests.xproj create mode 100644 test/vstest.console.UnitTests/CommandLine/CommandLineOptionsTests.cs create mode 100644 test/vstest.console.UnitTests/CommandLine/TestRunResultAggregatorTests.cs create mode 100644 test/vstest.console.UnitTests/ExceptionUtilities.cs create mode 100644 test/vstest.console.UnitTests/ExecutorUnitTests.cs create mode 100644 test/vstest.console.UnitTests/Implementations/MockFileHelper.cs create mode 100644 test/vstest.console.UnitTests/Internal/ConsoleLoggerTests.cs create mode 100644 test/vstest.console.UnitTests/Processors/BuildPathArgumentProcessorTests.cs create mode 100644 test/vstest.console.UnitTests/Processors/ConfigurationArgumentProcessorTests.cs create mode 100644 test/vstest.console.UnitTests/Processors/EnableLoggersArgumentProcessorTests.cs create mode 100644 test/vstest.console.UnitTests/Processors/EnableStaticLoggersArgumentProcessorTests.cs create mode 100644 test/vstest.console.UnitTests/Processors/HelpArgumentProcessorTests.cs create mode 100644 test/vstest.console.UnitTests/Processors/ListTestsArgumentProcessorTests.cs create mode 100644 test/vstest.console.UnitTests/Processors/OutputArgumentProcessorTests.cs create mode 100644 test/vstest.console.UnitTests/Processors/ParallelArgumentProcessorTests.cs create mode 100644 test/vstest.console.UnitTests/Processors/PlatformArgumentProcessorTests.cs create mode 100644 test/vstest.console.UnitTests/Processors/PortArgumentProcessorTests.cs create mode 100644 test/vstest.console.UnitTests/Processors/RunSettingsArgumentProcessortTests.cs create mode 100644 test/vstest.console.UnitTests/Processors/RunSpecificTestsArgumentProcessorTests.cs create mode 100644 test/vstest.console.UnitTests/Processors/RunTestsArgumentProcessorTests.cs create mode 100644 test/vstest.console.UnitTests/Processors/TestAdapterPathArgumentProcessorTests.cs create mode 100644 test/vstest.console.UnitTests/Processors/TestCaseFilterArgumentProcessorTests.cs create mode 100644 test/vstest.console.UnitTests/Processors/TestSourceArgumentProcessorTests.cs create mode 100644 test/vstest.console.UnitTests/Processors/Utilities/ArgumentProcessorFactoryTests.cs create mode 100644 test/vstest.console.UnitTests/Processors/Utilities/JsonUtilitiesTests.cs create mode 100644 test/vstest.console.UnitTests/Processors/Utilities/RunSettingsUtilitiesTests.cs create mode 100644 test/vstest.console.UnitTests/Properties/AssemblyInfo.cs create mode 100644 test/vstest.console.UnitTests/project.json create mode 100644 test/vstest.console.UnitTests/vstest.console.UnitTests.xproj diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..87a3690c01 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,67 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain + +# Force bash scripts to always use lf line endings so that if a repo is accessed +# in Unix via a file share from Windows, the scripts will work. +*.sh text eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..79098b528b --- /dev/null +++ b/.gitignore @@ -0,0 +1,108 @@ +############################################################################ +# For ignoring files that appear under a specific folder, you should +# usually add a .gitignore file to that specific folder. +# This file is reserved for common file patterns that should be +# ignored everywhere. +############################################################################ + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates +*.sln.metaproj +*.sln.metaproj.tmp +.vs/ +*.cache + +# Build results +[Bb]in/ +[Oo]bj/ +!**/LocProjects/[Bb]in/ + +*.tlog +msbuild*.log +msbuild*.wrn +msbuild*.err +build*.log +build*.wrn +build*.err + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +*.ncrunch* +.*crunch*.local.xml + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +#GlobalAssemblyInfo produced for Microbuild +GlobalAssemblyInfo.cs + +#lock files for UWP projects +project.lock.json +project.fragment.lock.json + +# ========================= +# Windows detritus +# ========================= + +# Windows image file caches +Thumbs.db +ehthumbs.db + +# =========================== +# Fakes outputs +# =========================== +*.Fakes.dll +*.fakesconfig +*.Fakes.xml + +# =========================== +# Custom ignores +# =========================== +packages +artifacts +Tools + +*.nupkg +*.zip diff --git a/Nuget.config b/Nuget.config new file mode 100644 index 0000000000..aa7bc03ce4 --- /dev/null +++ b/Nuget.config @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/TestPlatform.sln b/TestPlatform.sln new file mode 100644 index 0000000000..16600ead40 --- /dev/null +++ b/TestPlatform.sln @@ -0,0 +1,245 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{D8EF073C-279A-4279-912D-E9D4B0635E17}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{D938FFE7-5879-4A80-8301-35ACCE3B63D1}" + ProjectSection(SolutionItems) = preProject + global.json = global.json + Nuget.config = Nuget.config + EndProjectSection +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "vstest.console", "src\vstest.console\vstest.console.xproj", "{4D89E2F0-2E9D-41CA-A394-322A2E9A2C0B}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{463031A2-7F16-4E38-9944-1F5161D04933}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.TestPlatform.Common", "src\Microsoft.TestPlatform.Common\Microsoft.TestPlatform.Common.xproj", "{17B61E60-B42C-4BFA-9CD0-83B33229DE0D}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.TestPlatform.CommunicationUtilities", "src\Microsoft.TestPlatform.CommunicationUtilities\Microsoft.TestPlatform.CommunicationUtilities.xproj", "{40047CF1-B2E9-407C-80F1-0B8DB9D688C5}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.TestPlatform.Client", "src\Microsoft.TestPlatform.Client\Microsoft.TestPlatform.Client.xproj", "{9BC20FBA-5B2E-47D1-A606-998E7D541397}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.TestPlatform.CoreUtilities", "src\Microsoft.TestPlatform.CoreUtilities\Microsoft.TestPlatform.CoreUtilities.xproj", "{0BA3ED0E-7946-4333-B429-7CDE4D47FD59}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.TestPlatform.CrossPlatEngine", "src\Microsoft.TestPlatform.CrossPlatEngine\Microsoft.TestPlatform.CrossPlatEngine.xproj", "{40B59DDA-AEFD-45C9-BE66-8D8684D88F2F}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.TestPlatform.ObjectModel", "src\Microsoft.TestPlatform.ObjectModel\Microsoft.TestPlatform.ObjectModel.xproj", "{7DF58EF4-20BE-4A7F-AF21-881639E7CD5E}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.TestPlatform.Client.UnitTests", "test\Microsoft.TestPlatform.Client.UnitTests\Microsoft.TestPlatform.Client.UnitTests.xproj", "{342AC5E0-6FB2-4FA3-97BA-268E42A4487C}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.TestPlatform.Common.UnitTests", "test\Microsoft.TestPlatform.Common.UnitTests\Microsoft.TestPlatform.Common.UnitTests.xproj", "{8A00286A-D8EA-4331-A5F5-CF76C6B7461C}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.TestPlatform.CommunicationUtilities.UnitTests", "test\Microsoft.TestPlatform.CommunicationUtilities.UnitTests\Microsoft.TestPlatform.CommunicationUtilities.UnitTests.xproj", "{5E3CBEC8-E52E-4FB1-A0D2-01F75E33721D}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.TestPlatform.CoreUtilities.UnitTests", "test\Microsoft.TestPlatform.CoreUtilities.UnitTests\Microsoft.TestPlatform.CoreUtilities.UnitTests.xproj", "{83EAF11C-3FD7-49DA-8673-9E81CC2BAC66}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.TestPlatform.CrossPlatEngine.UnitTests", "test\Microsoft.TestPlatform.CrossPlatEngine.UnitTests\Microsoft.TestPlatform.CrossPlatEngine.UnitTests.xproj", "{48C34B69-C0FF-4B10-B8D3-47C9A539B109}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "testhost.UnitTests", "test\testhost.UnitTests\testhost.UnitTests.xproj", "{9E729143-86DB-4ACB-83C5-8DD40D27FFE7}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "vstest.console.UnitTests", "test\vstest.console.UnitTests\vstest.console.UnitTests.xproj", "{6DEB7DC1-C8DF-465E-8B31-96248D3FFAD0}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "testhost.x86", "src\testhost.x86\testhost.x86.xproj", "{F69B451D-DDB5-43E3-844B-DAD8D4DADEFC}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.TestPlatform.ObjectModel.UnitTests", "test\Microsoft.TestPlatform.ObjectModel.UnitTests\Microsoft.TestPlatform.ObjectModel.UnitTests.xproj", "{FC30D066-C891-40C0-B94A-D7DBB50EBF8F}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "dogfood", "dogfood", "{707096D0-DCFB-44A2-979D-178740E5DADE}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.TestPlatform.E2ETest", "dogfood\Microsoft.TestPlatform.E2ETest\Microsoft.TestPlatform.E2ETest.xproj", "{FC45608D-1E74-4975-B799-BD19BBC8B203}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "scripts", "scripts", "{EE4C04E6-E836-409F-B431-51D02DBA1C62}" + ProjectSection(SolutionItems) = preProject + scripts\DisableUnitTesting.bat = scripts\DisableUnitTesting.bat + scripts\EnableUnitTesting.bat = scripts\EnableUnitTesting.bat + EndProjectSection +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.TestPlatform.VsTestConsole.TranslationLayer", "src\Microsoft.TestPlatform.VsTestConsole.TranslationLayer\Microsoft.TestPlatform.VsTestConsole.TranslationLayer.xproj", "{B5E3AF48-222F-4378-9ED9-F0238E2A278E}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.TestPlatform.VsTestConsole.TranslationLayer.UnitTests", "test\Microsoft.TestPlatform.VsTestConsole.TranslationLayer.UnitTests\Microsoft.TestPlatform.VsTestConsole.TranslationLayer.UnitTests.xproj", "{7DA16D44-71EA-436F-A201-573861DC21EA}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.TestPlatform.Utilities", "src\Microsoft.TestPlatform.Utilities\Microsoft.TestPlatform.Utilities.xproj", "{BCF6E952-BC36-4E24-B1C4-41988588C59D}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.TestPlatform.VSIXCreator", "src\Microsoft.TestPlatform.VSIXCreator\Microsoft.TestPlatform.VSIXCreator.xproj", "{109F2462-67A2-4418-A4E3-5D5EBC0628F0}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Logger", "Logger", "{10571509-B0E3-40EF-A897-8D7E5CA4BB20}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.TestPlatform.Extensions.TrxLogger", "src\Microsoft.TestPlatform.Extensions.TrxLogger\Microsoft.TestPlatform.Extensions.TrxLogger.xproj", "{60D876EE-F278-4BF8-BC8A-15B356895C6F}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Logger", "Logger", "{4894747C-5228-4099-BFAA-6A89AFC334AB}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests", "test\Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests\Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests.xproj", "{AB12D4B6-BBE7-4A69-9839-3FCD0C77A04F}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "testhost", "src\testhost\testhost.xproj", "{99D82E36-6B51-4DB9-BD37-ADBF373125ED}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.TestPlatform.Utilities.UnitTests", "test\Microsoft.TestPlatform.Utilities.UnitTests\Microsoft.TestPlatform.Utilities.UnitTests.xproj", "{78A40FA0-835F-453E-8883-95EAAC5F348D}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.TestPlatform.TranslationLayer.E2ETest", "dogfood\Microsoft.TestPlatform.TranslationLayer.E2ETest\Microsoft.TestPlatform.TranslationLayer.E2ETest.xproj", "{4EC14041-7804-4840-AE70-98BABDC8B0E2}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "UnitTestProject", "dogfood\UnitTestProject\UnitTestProject.xproj", "{0CC51428-B665-47B0-A093-042D31785928}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "datacollector", "src\datacollector\datacollector.xproj", "{3572E78C-5AA5-4F68-876D-FC5322677263}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "datacollector.x86", "src\datacollector.x86\datacollector.x86.xproj", "{00DFB5C7-3850-4A65-986B-713F200482D4}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {4D89E2F0-2E9D-41CA-A394-322A2E9A2C0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4D89E2F0-2E9D-41CA-A394-322A2E9A2C0B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4D89E2F0-2E9D-41CA-A394-322A2E9A2C0B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4D89E2F0-2E9D-41CA-A394-322A2E9A2C0B}.Release|Any CPU.Build.0 = Release|Any CPU + {17B61E60-B42C-4BFA-9CD0-83B33229DE0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {17B61E60-B42C-4BFA-9CD0-83B33229DE0D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {17B61E60-B42C-4BFA-9CD0-83B33229DE0D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {17B61E60-B42C-4BFA-9CD0-83B33229DE0D}.Release|Any CPU.Build.0 = Release|Any CPU + {40047CF1-B2E9-407C-80F1-0B8DB9D688C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {40047CF1-B2E9-407C-80F1-0B8DB9D688C5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {40047CF1-B2E9-407C-80F1-0B8DB9D688C5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {40047CF1-B2E9-407C-80F1-0B8DB9D688C5}.Release|Any CPU.Build.0 = Release|Any CPU + {9BC20FBA-5B2E-47D1-A606-998E7D541397}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9BC20FBA-5B2E-47D1-A606-998E7D541397}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9BC20FBA-5B2E-47D1-A606-998E7D541397}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9BC20FBA-5B2E-47D1-A606-998E7D541397}.Release|Any CPU.Build.0 = Release|Any CPU + {0BA3ED0E-7946-4333-B429-7CDE4D47FD59}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0BA3ED0E-7946-4333-B429-7CDE4D47FD59}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0BA3ED0E-7946-4333-B429-7CDE4D47FD59}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0BA3ED0E-7946-4333-B429-7CDE4D47FD59}.Release|Any CPU.Build.0 = Release|Any CPU + {40B59DDA-AEFD-45C9-BE66-8D8684D88F2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {40B59DDA-AEFD-45C9-BE66-8D8684D88F2F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {40B59DDA-AEFD-45C9-BE66-8D8684D88F2F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {40B59DDA-AEFD-45C9-BE66-8D8684D88F2F}.Release|Any CPU.Build.0 = Release|Any CPU + {7DF58EF4-20BE-4A7F-AF21-881639E7CD5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7DF58EF4-20BE-4A7F-AF21-881639E7CD5E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7DF58EF4-20BE-4A7F-AF21-881639E7CD5E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7DF58EF4-20BE-4A7F-AF21-881639E7CD5E}.Release|Any CPU.Build.0 = Release|Any CPU + {342AC5E0-6FB2-4FA3-97BA-268E42A4487C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {342AC5E0-6FB2-4FA3-97BA-268E42A4487C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {342AC5E0-6FB2-4FA3-97BA-268E42A4487C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {342AC5E0-6FB2-4FA3-97BA-268E42A4487C}.Release|Any CPU.Build.0 = Release|Any CPU + {8A00286A-D8EA-4331-A5F5-CF76C6B7461C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8A00286A-D8EA-4331-A5F5-CF76C6B7461C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8A00286A-D8EA-4331-A5F5-CF76C6B7461C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8A00286A-D8EA-4331-A5F5-CF76C6B7461C}.Release|Any CPU.Build.0 = Release|Any CPU + {5E3CBEC8-E52E-4FB1-A0D2-01F75E33721D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5E3CBEC8-E52E-4FB1-A0D2-01F75E33721D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5E3CBEC8-E52E-4FB1-A0D2-01F75E33721D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5E3CBEC8-E52E-4FB1-A0D2-01F75E33721D}.Release|Any CPU.Build.0 = Release|Any CPU + {83EAF11C-3FD7-49DA-8673-9E81CC2BAC66}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {83EAF11C-3FD7-49DA-8673-9E81CC2BAC66}.Debug|Any CPU.Build.0 = Debug|Any CPU + {83EAF11C-3FD7-49DA-8673-9E81CC2BAC66}.Release|Any CPU.ActiveCfg = Release|Any CPU + {83EAF11C-3FD7-49DA-8673-9E81CC2BAC66}.Release|Any CPU.Build.0 = Release|Any CPU + {48C34B69-C0FF-4B10-B8D3-47C9A539B109}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {48C34B69-C0FF-4B10-B8D3-47C9A539B109}.Debug|Any CPU.Build.0 = Debug|Any CPU + {48C34B69-C0FF-4B10-B8D3-47C9A539B109}.Release|Any CPU.ActiveCfg = Release|Any CPU + {48C34B69-C0FF-4B10-B8D3-47C9A539B109}.Release|Any CPU.Build.0 = Release|Any CPU + {9E729143-86DB-4ACB-83C5-8DD40D27FFE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9E729143-86DB-4ACB-83C5-8DD40D27FFE7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9E729143-86DB-4ACB-83C5-8DD40D27FFE7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9E729143-86DB-4ACB-83C5-8DD40D27FFE7}.Release|Any CPU.Build.0 = Release|Any CPU + {6DEB7DC1-C8DF-465E-8B31-96248D3FFAD0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6DEB7DC1-C8DF-465E-8B31-96248D3FFAD0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6DEB7DC1-C8DF-465E-8B31-96248D3FFAD0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6DEB7DC1-C8DF-465E-8B31-96248D3FFAD0}.Release|Any CPU.Build.0 = Release|Any CPU + {F69B451D-DDB5-43E3-844B-DAD8D4DADEFC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F69B451D-DDB5-43E3-844B-DAD8D4DADEFC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F69B451D-DDB5-43E3-844B-DAD8D4DADEFC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F69B451D-DDB5-43E3-844B-DAD8D4DADEFC}.Release|Any CPU.Build.0 = Release|Any CPU + {FC30D066-C891-40C0-B94A-D7DBB50EBF8F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FC30D066-C891-40C0-B94A-D7DBB50EBF8F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FC30D066-C891-40C0-B94A-D7DBB50EBF8F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FC30D066-C891-40C0-B94A-D7DBB50EBF8F}.Release|Any CPU.Build.0 = Release|Any CPU + {FC45608D-1E74-4975-B799-BD19BBC8B203}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FC45608D-1E74-4975-B799-BD19BBC8B203}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FC45608D-1E74-4975-B799-BD19BBC8B203}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FC45608D-1E74-4975-B799-BD19BBC8B203}.Release|Any CPU.Build.0 = Release|Any CPU + {B5E3AF48-222F-4378-9ED9-F0238E2A278E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B5E3AF48-222F-4378-9ED9-F0238E2A278E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B5E3AF48-222F-4378-9ED9-F0238E2A278E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B5E3AF48-222F-4378-9ED9-F0238E2A278E}.Release|Any CPU.Build.0 = Release|Any CPU + {7DA16D44-71EA-436F-A201-573861DC21EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7DA16D44-71EA-436F-A201-573861DC21EA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7DA16D44-71EA-436F-A201-573861DC21EA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7DA16D44-71EA-436F-A201-573861DC21EA}.Release|Any CPU.Build.0 = Release|Any CPU + {BCF6E952-BC36-4E24-B1C4-41988588C59D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BCF6E952-BC36-4E24-B1C4-41988588C59D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BCF6E952-BC36-4E24-B1C4-41988588C59D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BCF6E952-BC36-4E24-B1C4-41988588C59D}.Release|Any CPU.Build.0 = Release|Any CPU + {109F2462-67A2-4418-A4E3-5D5EBC0628F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {109F2462-67A2-4418-A4E3-5D5EBC0628F0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {109F2462-67A2-4418-A4E3-5D5EBC0628F0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {109F2462-67A2-4418-A4E3-5D5EBC0628F0}.Release|Any CPU.Build.0 = Release|Any CPU + {60D876EE-F278-4BF8-BC8A-15B356895C6F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {60D876EE-F278-4BF8-BC8A-15B356895C6F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {60D876EE-F278-4BF8-BC8A-15B356895C6F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {60D876EE-F278-4BF8-BC8A-15B356895C6F}.Release|Any CPU.Build.0 = Release|Any CPU + {AB12D4B6-BBE7-4A69-9839-3FCD0C77A04F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AB12D4B6-BBE7-4A69-9839-3FCD0C77A04F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AB12D4B6-BBE7-4A69-9839-3FCD0C77A04F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AB12D4B6-BBE7-4A69-9839-3FCD0C77A04F}.Release|Any CPU.Build.0 = Release|Any CPU + {99D82E36-6B51-4DB9-BD37-ADBF373125ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {99D82E36-6B51-4DB9-BD37-ADBF373125ED}.Debug|Any CPU.Build.0 = Debug|Any CPU + {99D82E36-6B51-4DB9-BD37-ADBF373125ED}.Release|Any CPU.ActiveCfg = Release|Any CPU + {99D82E36-6B51-4DB9-BD37-ADBF373125ED}.Release|Any CPU.Build.0 = Release|Any CPU + {78A40FA0-835F-453E-8883-95EAAC5F348D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {78A40FA0-835F-453E-8883-95EAAC5F348D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {78A40FA0-835F-453E-8883-95EAAC5F348D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {78A40FA0-835F-453E-8883-95EAAC5F348D}.Release|Any CPU.Build.0 = Release|Any CPU + {4EC14041-7804-4840-AE70-98BABDC8B0E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4EC14041-7804-4840-AE70-98BABDC8B0E2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4EC14041-7804-4840-AE70-98BABDC8B0E2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4EC14041-7804-4840-AE70-98BABDC8B0E2}.Release|Any CPU.Build.0 = Release|Any CPU + {0CC51428-B665-47B0-A093-042D31785928}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0CC51428-B665-47B0-A093-042D31785928}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0CC51428-B665-47B0-A093-042D31785928}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0CC51428-B665-47B0-A093-042D31785928}.Release|Any CPU.Build.0 = Release|Any CPU + {3572E78C-5AA5-4F68-876D-FC5322677263}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3572E78C-5AA5-4F68-876D-FC5322677263}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3572E78C-5AA5-4F68-876D-FC5322677263}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3572E78C-5AA5-4F68-876D-FC5322677263}.Release|Any CPU.Build.0 = Release|Any CPU + {00DFB5C7-3850-4A65-986B-713F200482D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {00DFB5C7-3850-4A65-986B-713F200482D4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {00DFB5C7-3850-4A65-986B-713F200482D4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {00DFB5C7-3850-4A65-986B-713F200482D4}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {4D89E2F0-2E9D-41CA-A394-322A2E9A2C0B} = {D8EF073C-279A-4279-912D-E9D4B0635E17} + {17B61E60-B42C-4BFA-9CD0-83B33229DE0D} = {D8EF073C-279A-4279-912D-E9D4B0635E17} + {40047CF1-B2E9-407C-80F1-0B8DB9D688C5} = {D8EF073C-279A-4279-912D-E9D4B0635E17} + {9BC20FBA-5B2E-47D1-A606-998E7D541397} = {D8EF073C-279A-4279-912D-E9D4B0635E17} + {0BA3ED0E-7946-4333-B429-7CDE4D47FD59} = {D8EF073C-279A-4279-912D-E9D4B0635E17} + {40B59DDA-AEFD-45C9-BE66-8D8684D88F2F} = {D8EF073C-279A-4279-912D-E9D4B0635E17} + {7DF58EF4-20BE-4A7F-AF21-881639E7CD5E} = {D8EF073C-279A-4279-912D-E9D4B0635E17} + {342AC5E0-6FB2-4FA3-97BA-268E42A4487C} = {463031A2-7F16-4E38-9944-1F5161D04933} + {8A00286A-D8EA-4331-A5F5-CF76C6B7461C} = {463031A2-7F16-4E38-9944-1F5161D04933} + {5E3CBEC8-E52E-4FB1-A0D2-01F75E33721D} = {463031A2-7F16-4E38-9944-1F5161D04933} + {83EAF11C-3FD7-49DA-8673-9E81CC2BAC66} = {463031A2-7F16-4E38-9944-1F5161D04933} + {48C34B69-C0FF-4B10-B8D3-47C9A539B109} = {463031A2-7F16-4E38-9944-1F5161D04933} + {9E729143-86DB-4ACB-83C5-8DD40D27FFE7} = {463031A2-7F16-4E38-9944-1F5161D04933} + {6DEB7DC1-C8DF-465E-8B31-96248D3FFAD0} = {463031A2-7F16-4E38-9944-1F5161D04933} + {F69B451D-DDB5-43E3-844B-DAD8D4DADEFC} = {D8EF073C-279A-4279-912D-E9D4B0635E17} + {FC30D066-C891-40C0-B94A-D7DBB50EBF8F} = {463031A2-7F16-4E38-9944-1F5161D04933} + {FC45608D-1E74-4975-B799-BD19BBC8B203} = {707096D0-DCFB-44A2-979D-178740E5DADE} + {B5E3AF48-222F-4378-9ED9-F0238E2A278E} = {D8EF073C-279A-4279-912D-E9D4B0635E17} + {7DA16D44-71EA-436F-A201-573861DC21EA} = {463031A2-7F16-4E38-9944-1F5161D04933} + {BCF6E952-BC36-4E24-B1C4-41988588C59D} = {D8EF073C-279A-4279-912D-E9D4B0635E17} + {109F2462-67A2-4418-A4E3-5D5EBC0628F0} = {D8EF073C-279A-4279-912D-E9D4B0635E17} + {10571509-B0E3-40EF-A897-8D7E5CA4BB20} = {D8EF073C-279A-4279-912D-E9D4B0635E17} + {60D876EE-F278-4BF8-BC8A-15B356895C6F} = {10571509-B0E3-40EF-A897-8D7E5CA4BB20} + {4894747C-5228-4099-BFAA-6A89AFC334AB} = {463031A2-7F16-4E38-9944-1F5161D04933} + {AB12D4B6-BBE7-4A69-9839-3FCD0C77A04F} = {4894747C-5228-4099-BFAA-6A89AFC334AB} + {99D82E36-6B51-4DB9-BD37-ADBF373125ED} = {D8EF073C-279A-4279-912D-E9D4B0635E17} + {78A40FA0-835F-453E-8883-95EAAC5F348D} = {463031A2-7F16-4E38-9944-1F5161D04933} + {4EC14041-7804-4840-AE70-98BABDC8B0E2} = {707096D0-DCFB-44A2-979D-178740E5DADE} + {0CC51428-B665-47B0-A093-042D31785928} = {707096D0-DCFB-44A2-979D-178740E5DADE} + {3572E78C-5AA5-4F68-876D-FC5322677263} = {D8EF073C-279A-4279-912D-E9D4B0635E17} + {00DFB5C7-3850-4A65-986B-713F200482D4} = {D8EF073C-279A-4279-912D-E9D4B0635E17} + EndGlobalSection +EndGlobal diff --git a/build.cmd b/build.cmd new file mode 100644 index 0000000000..27004d58a3 --- /dev/null +++ b/build.cmd @@ -0,0 +1,6 @@ +@echo off + +REM Copyright (c) Microsoft. All rights reserved. + +powershell -NoProfile -NoLogo -Command "%~dp0scripts\build.ps1 %*; exit $LastExitCode;" +if %errorlevel% neq 0 exit /b %errorlevel% diff --git a/dogfood/Microsoft.TestPlatform.E2ETest/Adapter/Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.dll b/dogfood/Microsoft.TestPlatform.E2ETest/Adapter/Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.dll new file mode 100644 index 0000000000000000000000000000000000000000..8668efadd5e90dacfb2affa7364e9fe5bb42b423 GIT binary patch literal 87552 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%0A;Euzto6$z=~z?R`45=ySb% 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 literal 0 HcmV?d00001 diff --git a/dogfood/Microsoft.TestPlatform.E2ETest/Adapter/Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface.dll b/dogfood/Microsoft.TestPlatform.E2ETest/Adapter/Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface.dll new file mode 100644 index 0000000000000000000000000000000000000000..886b05b5ea79191ba27c3a825d3c9506673e4a47 GIT binary patch literal 8192 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 literal 0 HcmV?d00001 diff --git a/dogfood/Microsoft.TestPlatform.E2ETest/Adapter/Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.dll b/dogfood/Microsoft.TestPlatform.E2ETest/Adapter/Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.dll new file mode 100644 index 0000000000000000000000000000000000000000..df77943e56ea4e4928bc54ac34d38c9a5c283261 GIT binary patch literal 10240 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 F{tsXb$aeq$ literal 0 HcmV?d00001 diff --git a/dogfood/Microsoft.TestPlatform.E2ETest/Adapter/Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll b/dogfood/Microsoft.TestPlatform.E2ETest/Adapter/Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll new file mode 100644 index 0000000000000000000000000000000000000000..a1eb785fe4934ea354d48fed5628fd5ad88a8f7a GIT binary patch literal 6656 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 literal 0 HcmV?d00001 diff --git a/dogfood/Microsoft.TestPlatform.E2ETest/Adapter/Microsoft.VisualStudio.TestPlatform.TestFramework.dll b/dogfood/Microsoft.TestPlatform.E2ETest/Adapter/Microsoft.VisualStudio.TestPlatform.TestFramework.dll new file mode 100644 index 0000000000000000000000000000000000000000..093fae050f406ac44d342f6bb9a4674521361316 GIT binary patch literal 50176 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 literal 0 HcmV?d00001 diff --git a/dogfood/Microsoft.TestPlatform.E2ETest/Microsoft.TestPlatform.E2ETest.xproj b/dogfood/Microsoft.TestPlatform.E2ETest/Microsoft.TestPlatform.E2ETest.xproj new file mode 100644 index 0000000000..6db376696a --- /dev/null +++ b/dogfood/Microsoft.TestPlatform.E2ETest/Microsoft.TestPlatform.E2ETest.xproj @@ -0,0 +1,19 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + fc45608d-1e74-4975-b799-bd19bbc8b203 + Microsoft.TestPlatform.E2ETest + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\ + v4.5.2 + + + 2.0 + + + diff --git a/dogfood/Microsoft.TestPlatform.E2ETest/Program.cs b/dogfood/Microsoft.TestPlatform.E2ETest/Program.cs new file mode 100644 index 0000000000..f7b7ea8bb7 --- /dev/null +++ b/dogfood/Microsoft.TestPlatform.E2ETest/Program.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.E2ETest +{ + using System.Diagnostics; + using System.IO; + + public class Program + { + public static void Main(string[] args) + { + // Spawn of vstest.console with a run tests from the current execting folder. + var executingLocation = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName); + System.Diagnostics.Debug.Assert(executingLocation != null, "executingLocation != null"); + + // Remove Microsoft.VisualStudio.TestPlatform.TestFramework.*.dll if they are present + if (File.Exists(Path.Combine(executingLocation, "Microsoft.VisualStudio.TestPlatform.TestFramework.dll"))) + { + File.Delete(Path.Combine(executingLocation, "Microsoft.VisualStudio.TestPlatform.TestFramework.dll")); + } + + if (File.Exists(Path.Combine(executingLocation, "Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll"))) + { + File.Delete(Path.Combine(executingLocation, "Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll")); + } + + // Start vstest.console with sample test assembly + var runnerLocation = Path.Combine(executingLocation, "vstest.console.exe"); + var testadapterPath = Path.Combine(executingLocation, "Adapter"); + var testAssembly = Path.Combine(executingLocation, "UnitTestProject.dll"); + + var arguments = string.Concat("\"", testAssembly, "\"", " /testadapterpath:\"", testadapterPath, "\""); + var process = new Process + { + StartInfo = + { + UseShellExecute = false, + CreateNoWindow = false, + FileName = runnerLocation, + Arguments = arguments + }, + EnableRaisingEvents = true + }; + process.Start(); + process.WaitForExit(); + } + } +} diff --git a/dogfood/Microsoft.TestPlatform.E2ETest/Properties/AssemblyInfo.cs b/dogfood/Microsoft.TestPlatform.E2ETest/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..c4497f1b41 --- /dev/null +++ b/dogfood/Microsoft.TestPlatform.E2ETest/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +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: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Microsoft.TestPlatform.E2ETest")] +[assembly: AssemblyTrademark("")] + +// 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("fc45608d-1e74-4975-b799-bd19bbc8b203")] diff --git a/dogfood/Microsoft.TestPlatform.E2ETest/project.json b/dogfood/Microsoft.TestPlatform.E2ETest/project.json new file mode 100644 index 0000000000..89a79b7176 --- /dev/null +++ b/dogfood/Microsoft.TestPlatform.E2ETest/project.json @@ -0,0 +1,42 @@ +{ + "version": "1.0.0-*", + "buildOptions": { + "emitEntryPoint": true, + "copyToOutput": { + "include": [ + "Adapter", + // Have to drop a time stamped version of testhost config because the build system does not honour the + // users config file when building from a dependent exe. It does honor while building from testhost.x86 project though. + "testhost.x86.exe.config", + "testhost.exe.config" + ] + } + }, + + "frameworks": { + "net46": { + "dependencies": { + "UnitTestProject": { + "target": "project" + } + } + } + }, + + "dependencies": { + "Microsoft.TestPlatform.Client": "15.0.0-*", + "Microsoft.TestPlatform.Common": "15.0.0-*", + "Microsoft.TestPlatform.CommunicationUtilities": "15.0.0-*", + "Microsoft.TestPlatform.CoreUtilities": "15.0.0-*", + "Microsoft.TestPlatform.CrossPlatEngine": "15.0.0-*", + "Microsoft.TestPlatform.ObjectModel": "15.0.0-*", + "testhost": "15.0.0-*", + "testhost.x86": "15.0.0-*", + "vstest.console": "15.0.0-*", + + "Microsoft.Internal.TestPlatform.Extensions": { + "type": "build", + "version": "15.0.0" + } + } +} diff --git a/dogfood/Microsoft.TestPlatform.E2ETest/testhost.exe.config b/dogfood/Microsoft.TestPlatform.E2ETest/testhost.exe.config new file mode 100644 index 0000000000..9f82ee613f --- /dev/null +++ b/dogfood/Microsoft.TestPlatform.E2ETest/testhost.exe.config @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dogfood/Microsoft.TestPlatform.E2ETest/testhost.x86.exe.config b/dogfood/Microsoft.TestPlatform.E2ETest/testhost.x86.exe.config new file mode 100644 index 0000000000..8846256c18 --- /dev/null +++ b/dogfood/Microsoft.TestPlatform.E2ETest/testhost.x86.exe.config @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dogfood/Microsoft.TestPlatform.TranslationLayer.E2ETest/Adapter/Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.dll b/dogfood/Microsoft.TestPlatform.TranslationLayer.E2ETest/Adapter/Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.dll new file mode 100644 index 0000000000000000000000000000000000000000..8668efadd5e90dacfb2affa7364e9fe5bb42b423 GIT binary patch literal 87552 zcmbrn34B!5*+2f=W$w(}nS@McG7|_(0wi2AGhyGi03m>ag1CVjh^Q!XAp;7;U?PfI zaY5Wbtpr?9tB70Os#wtK>jqYB-BQqM)fcO+UBm_d-{(1Zl8Hv!_xGOQawLLwQN%$>--Xl2x zz0+EiFv0t6)+mhN;IlO9nx*|W&(c=YMLGa{N>040WeZj;1HSuk*bAQI)w4S*2J+mh zx=h`CX0vUukt{rqz%%Dpjq0kZTd>H5Alpi?S<2w(AIVL~i6+6#T(0=BZ94gAb3O9< zDp5A(>xXHyuRGC03pHwF!T<2zE--e`1#XI`yO-5?Q?P>yJ@hG z_sDH=-6|$mM*I8eQlyRIj>PIdaj$zhn9tjIU(PGJP4m8{SWCjf6qha426l^F;nt1>o%hA-bV(?%?5neHG^ zoe+qXn~c@#&`QeFiHg;iBXG#NMnkm05GZeCjspivqS~aoDi8CjEps8H8o)A95hbt@ z#&UlVG2EeuY9|XVcNhX8-ESgY2kvKCF8sw#f7F5+1=0=3yOkQ+piX}3UQRHQby?Jm zCsarDdQQN|Hw(#yi(4T}l!uc80-`+6aiNx7!K#CMw$_$`Vh?U{ha&+q5p+l38Bl5e zuEh?dtbj_}p9=Whk!+GynWO3R98HS9?jkq=bQ(&hZuosp9v2?$RQ4Csm}A=Pe-m^c z`;e{F!g(#jMRnM=>5j%DH2g}4_}wuGhXd{#;OAMXa|781cLCC)*cv z(fVz_l+egCzW@IFmJ)x;@~cA1QRhyC*t^KIh7M1sF0}b^m1iB;Z+BA!d7{;OP$!yG z15pQExb4<5m<-Se_b@BZ%{tIN&Or=7&jbM*;P~uI?CuTMVYKH>MrIm zT2@kbiV(qAjWSezdj$fAtn1isry`;9M&>wBm~^EnSee5?UZrPBI3cJrbqDR*GEARd zz$Fg+$L&xRSwgdf--?GHMWs#yB45_cMu7id4D{i7W1wJOE(v|dr^6obJ%B64L` z_C#MO0T84x@I+E(<%M8yK%uDuy&eUIoI`^<9cd$G!bz~Iql~)sTuR_`N6^4>;Koz{ z`q1Q6LJ~OLsN2fowi0(Lpu<746r0dum8b*|7ap$&63F)738xGZ^tjWD<8l4zgoe+< zQAAG#bm?l+R;D+z6;`(ciMmI?^5w{0WhTLgOA3mHq?g&dh;mBMXUSWtY{RxTL!4jq*H}`*Eu- zqtg;$xFvV~vP0^w4ypg{ka~jEk=|!Jr2ex*YCnHR8Wwa&-O(ZSn+~bTKu3D#cSzmX zA@#)$so!)+?G@}OwW%Fan>wUE(;+ozccl054yhM(NPV_L>gOF&dxbhmZB~cWi#w#= z*&+3<4ynO#hpBZ)eWF9^haFObc^&EP+n$O(k;g|%Q-f38La;L@a`%I=*Q{#>TK5jX zo=dW0aAFTSUT-))i3JN;@N*U%kPpENvfLjDVt_R3CPAjIo&|qeBgSyE?s67vMPI1BcJOkIWrF+)@ueWm`6Ac;9+ z4-m`KohtOq$;fcI-M|EytMS@`*JT<>o3mCbW~oUb$jtN#}1!gXp1+4bi z)tFO5BwUd>9m7m{BcnX_$P;WK^;(nbH$jSwU9`bY6oes#|N@+p*BmYtIu z^b6PvN9w@dS6i)D=J}i@NDb!paF!yFvg^Ktgj0ubd;2Z1Je2J27t1k`XS-FN-3&)H z6L~FhHL*^=-D0=Rs^gVG6r{5AD5^YP*~x9&ft_f~9n@8uojNM0keubHu1c90dCOM1n-_M z9>>wlOH}P}F0*;;e<8ad`;Jb1@kI9m|Hh2dwBsQQDpo*+BDgCNl+v=CRS5Eu!z-&G z6?M;+l}T7i5qh79ZtOu%p!$foi%PRn-E&C}x*99KX*mns&Y-f84i$00!g|Csyf_62 zWkqX{Vr8uI4jql;$w;=za0B|ER!n!X=(G*@d_bv(<6sYR&-P5sc5V)2EIZQxnUIwl zV!5jk3MY#LF1lWO8O!DQgKi@bw+X;osP1dGP<1Z^r_w8fp47XxtqjnT2>oTdG+m^I zhMekS>4v)oQN<_QY!W^%|C78fPeQUABy;%4k=2f`Ta~=xO;*LvCdC znu2WIKGcAfxlD>g(qaF=QYz9(bAAD8nuU0X0e(4fY}+{N@N}*K$c;A~@h~1lj6FBB zw@)@G*fgvf4i#b5SmGBGS&&5%7a+DEJ$+v8B?dMrfCjfM@}6aPzRYjkHV z@{sO@hAVwxDnA=i1t(=Ig;J@T()}p<9m$@$do_|W1iI@HcjA?W zdPN*Q=%YN1urf!vN!tp=!~Q}&r)?S7N`Yba$Uh?Q(gEACJVTk+3D$AfCx+xD!-j*7 zMOAHH%R=dx`+*SkwWOoiTaXSxI_SCss1fWca1Wsf%L~zCUZ;dfbv3=}R)Uz_P&3+u zv9S{IZE;Zjb}Z^|nGuWz;?ZETJQ9cmH%0A;Euzto6$z=~z?R`45=ySb% zy`ktlC`#XlUR2NRMGJkZuZs98krUOQ_M~H1wDqTOZr+2Ac*B!a;ty@+K{lM65|E+l z|F^Y0yl(3L+-|C!p~}uOI(oS(*TeM8jWB_Cd$Ien5y8qTly5BNpgma<_T_|QK1DxO zH?e!T3F7Do;P001bI>4UhoTQRFy1wh0Cp%)#fl&SsE@_t?88>&HQC)}b~6iY47RW; zD7JJYa5D=*14bsLK>{GabreAYASAN{34oBw5+u-~67s3uDpXe!&5j+g|5lZDvr7AR zFYPv~BaNWK2nc zOL}0|QVD=CC`*t42!pc(34kypOOOBvL$d@4fG{jekN^ni;Z*@i0E7`)f&@SqnI%X7 zgi%?71V9*_B}jnxr{|;h=*I0Wa|;TsTz7tjth&Ec;fFYknr8a_9Nw(L4{#V&&-D8c zhS%+4(iSF}?pA*I+}rT5b>|Uq)OKyR)$kLKlDPvxY=XmMuwA=N=#REaj*~&I?qqme zwwHB~ZHvz!92VOmCkkOv7!X}~N>|X+wG%=kE)@sipnDfmh!0q`I)V~#P%T#GZcw6V zzrseTV=zb##WWq|ZAUG&p@yboVKBG{Bw3qAtPIM>j`}fV+=~FVosoYVu`tHl>1SHP z5nqbu`Y2NSUCV_(+7LzA*;o;&e#ECuoT7zc8GWQr3gBVp{4s8MxQ=Hh{3SajDvK*S zR~jjuA6>9;sZ(yxbGW+PR{0!avqw(O*0Tgan35$(0EDSof&@S~EK8682#0405&&UZ zmLLHTFy1JSlmG}vWC;>LJGH16{0n6hQ5AY=PKD~uv#^$3N>!_1a6GSWrKUU?c#@St zd&Ww}qp+L{H?*zQg-BF)eg{JQkmjPmlGf^|&|3Ao1)%EA3y?`gtkp5$GA81>^CC04 zsOOa|Q6v&fcHuCH`YM7r8k0F$sm3_iHFh`DOdHhNDDm9du+)<8!K+jMpyb8UMDU}0N1UZYVv}2V>f!8jPY+7;W9hlML*PY%_le0#_{z8zqu8BrsU!H|@ z=P`&>zHg)kcVNZiY=s!-J|RJ6MLnCOOgYTUSmsIaofc?hf1Slv^6)2t2}UZcJMV#K zxWD1lS|fEs2{`Y2k&eqok^r*CZA|o?G7=+kU-C3xtw+P8K`SBfP0+Zg5nt+m>c%?5H{xSA!M`S zchMi)?rwmnm2?U%Zfo3a`BNo0JVOIWrV8~4&NBm&Vf15YKsMx_$0PkJ=55xYIGn*- z{r+@;sPnUKboQlcZlu$lH00gve9odZ@SQRqUshj_LXe}`>kv3(HMbpcF^A8=lB;iM zFSCVZ{s$J7H!{aT5hh)uz?rOzMuuYXyRYDhk?3O-o_9R^PP^k-um3%g z`mV^%k1hHR_VwhtG@WupY&aU}Ab4y}OY*&k`X)~W>SGlr z7tHE(--a6Qu$4c%eb$hQyScsh(GiDs!L46*FILn{0%=!}cUhl8#}b9<49ovOtea}tYL{?thSm8qeAKNkP9HFzRhmnGBo)9Mxh~xs^w|EGU9VF9@sKhtYDwy{lEcu8mMOz7aL6PK1L_+K8BI- z%Y_K9wSSW9;%z=1qLPX>pWXs}=+`1<$~3QJo!GJ)jy8y%cT?C%ogd(e8`V&q;X4daRC|NHc4~|+3U%e3pCn4KpQ|oM-|C@gjlwomn;do ze?c%6#y&C!T=aji5Iq7G^6B?8pCS-Sgp-H+xd!dJ`9*Dp2GS8{GR)vcW{XcGeXBo1 z7-n1UUnNj1Of}zjKL@EI9=6;s5J=yvqXPONNFBs~m0`}?d-@wTH9RTwn(f=jF z7$1`TX#A9ThM2c>1X z|KLm|6WHkcCl{FR@8bRofuaFsB8&}ca{di0y$?e)9g3q#wQf)ca(%vcKQ#vPoN_Ak z^KbQRG`sX@*}!e&un_h%!(_FW^A$9^2LTkrho;^UgJ*0fAbRE3>Y1uiMLs>J$Q4I+ zY-OiQ6=G%^L{?#*C()s1j;Ds7SvAwzsbS7fs*&hWb0VwZ`J)6tI4Mhzz#QE{5n5?I zUa31@Ba}iYfjvfxO3+XBWq);0?iM-(tf`za2xcIMdo~Wr#7d_WJC=1QV~evA{bfrm z>eC}W=Nrhh1kk2YcGkNp38+$m<@qSa6z0~PFck7boT5DKuTUs_I2MlOSo8@9KouqbHuOe1?ti4tvrmlnRTqu)UngY*@~E}}URmxaks9;PDUs6^vyXAtA-^;jO;SUFv-_E%V1SGeqpx1zt{(xjEPx(|e zY-r09>sNXDR&s$n3CYV-5LpUHPP_qy_ierPq>XkDBUM`sQM`;U;Jk4An81w#zwDaJ zF;LW)5c>GxbNu|se1`;`;IM{CUIYD`vj)o^Uj8n6oCjk)VX9*Gj0X>s)RtbQx zAWM({2&ZQW5&+>8CVZrxslKXYjzre7{-`@xlUN-!G>HifXJl1M0E9EM1POp}vX=%w zvuPahU!@^2(Z&j3gKA^>2&py(#q&JH{LCtz`CloPn9wlS)4TeKJ-4l|Qf=Kw z=-qHy!hID+T+B=k0cO4jkUbw4E@0c`Y6HkcleT52N64Xaze0qJ^0tK>E)T30M9Anl z6gjUvxLOb)V^&~_bkr=5n6f=3+8fShH!SII5{4W~!!BR^sF-b}1{r!i^IQ`O^+Q$< zWIcoa$aZmI#H8>zl#szOQeJ!O_k#SJ%rr^UB2lc@lXrQlpP&XZfuAsQCLP~3VyhabL% zMf|{Si;HI^+LcM?LqEnVtRr=&7cyeExHvzKT34n!WtUo+yHM|2?6_Y>JHHHe;+4Ej zYlQ=Hx;lH4tYKu`(n{gwi~ATrE9D`kJPe>B1-5X}sD2-(VSSItJpJ$vpAh@*r>a!&5mz0veT(cHlHL|0acDJ=A$-m z{aM)k*dhx;l*G70*ebBFjhp!eI8Fr@-W}@X5kuNi)P|nviiiz$9CWZtYG<%BV>e`g zSH7v&^V3BiVz0glA=!n4J7E{@PN>S1f>GYc9KOY!bUFiYEY{|t!P={DZXatK$3hh% z7IT~~9IJ$5)#k)%rL?B5G^5WkX%MPsAlGxdBMiQC^>27|1`{7lz6~dZlyLmZ_YMrp zbOv+IrO(1R!8-7c#%2@=8X+fwyEkq*%u4swv2~0|wB=xr0+k{P0)|rsEab`hU6iBE z2kJcbV>&^DS)bDfR3w>DjlD0Fn63kw;r10ysHJa342Rrd)nHGBK(+z$%Nl{)m5#qs zjip$#my-`Y1iTyJO6tx2&U5EmKp7(GPU7}mi6mBIe9f(+yI^!C2}XG%bDXXyXc*XA zrGEm_&vqE56h^KAD8hb{XT#~wC1F^8y`CKg7lpb(Pk_fhD{s%;Fh`bpQFG5dtzqAu zU$us!c3T_j^i?c_x4Fd-#78q$N2P#oKS=wzHhNL!DDu!}SR zp%mWN2)QAIk};GRYoH`MtF^rrmZIcLiVGYvaJcD4;K>J6qc#(a3s}o5PAUXo@a)G*>}Z5<_-J zk{DY($*dC3ICU1tW6lW9C${6@<=KJ7G936Z-pKRo7-G;OakP<{f=5^n_#F71g`G8? zC-+4Bk*(!~9u!WL#A|Tq)zKDkBw+K4Oi=698DXt4EsxA?sHlWX3U}6ol2kgjCKDeRq~y-pCxM3^J&SVak{0b=7wM(bO8enC+FKQyd_AO{PP~K%>*Yc}|>Qtz{T^wGe6qFYrN!-<`nafcAz#za{4F?7!ZAXP@Wt zI&O*epQiTp`CZqZ=+nk>S>KDOw&iO;DVVF50HA>H15I87l@~x$lMR)-S;$6q&tBR3 zgT~0_s}@;aY}Kqz?)YGK#_yBvd|UY@U*UP<&(t*nGWQ|eHmarFajtn{$&*(JW8#TUh+jTTrfol8 z#YpnJH8UCXa@n+ZYY`0VctrrkX*paLF1MS9kzQ2 zIS%2t5p50Ko9#8O_uDlt)wa-{`ex6}l)ed~Z)#Q_mb12tCCC3+AGY4kWxwIQI0=Ao zUX~yM5LRRf5_q6>Uv?U2*mREI2abGa@YCnOD4@QE3$n3RasXfI^%~7*3yvWNnoJvuwzN1$AZj}E&{-AMWEBhI>-@!(;+Lfm{#^T%*HF| z9I^5U^^6&E)qFWzy+p0b-DC{yo#i@IBhc|;_GEZ8{vPh{SN z=it;21&%G8nJ_5DcUb2m5Ner#5Ke&+8g9AZVU+Iex48M)+ymM0);}UTH&71$fy2ij z{BF+M>)CcwL|q5Sb`;uBCz^3e)(3rZ?umI*L^ex!2jNss5?8`gmAc<@=CMts3A7E% znakmrDmcF(hYwb<>_xO3m1pTe-8l{ty2Cpo&dC6>EuoG~Q*rfNejA3RRM1LDyrdsf zAl}SqiTGoOjKC>)85jwjg1GNuuvcrm0_r!8#`C&P$J;qe8rAixX#Wsl1AuB_yRrNM zaG?MLZIvPWzG?5Y@;H=AcHV2EPxh&SCL9mQ#!01>3aSX9q;7=#%eS(5J^W&(Iw;EB zIrC?(guY5S%FXk;$HMZ`d`)Y_iS_@&{hayWX4j00G7psOP^i)`FZRnhj50*QykwvM zPCPFbMJI#xhV6a}Q1>>kiBRe`K2sp)Gf{lZAYyuU@|xsn_&biG;qTIJP}ms7Qx%pG zlL}0B^%y+Gb5V0xU8Mq9ZZ2;&c=7&wTMCWkn2&4aX`=!c3%bFIcU$I$u-1SAF~M6* zp<-Hdt~`V1PV#Mu*0JA<^*Kbng23FK{Q=&HR4?&rNeDYqX&J*GHu7gI&RbEiJ z1@`VdzlbL_<-o90cC>?b*^0=T^9jacB|1Sz7nBx{vUu!||?qF_F#4B6Rqj^M} zb_8w6mI2R;r?%C#g*>JDdKqNIMR$S^qU2Dp8_vDEi`Hr>+!#? zI85eHcbHf5Z{$dtnYHyC;D_bb7dGUrn2V;5J=|k*4>-!DT*6YG-r?o!j%@Nah@!j& z!#-pr&bmPbIxj69OrE?%HFpcgQ)tk}YjSVJ=&u}}uxA1*zi zL$opTy&sYpbd`7)3%#}NmSa+(MmKi>@D%z_-M`AJ=eU))LvcEYEp|lCudV&9vPe`on}+$GKDP_PU6FvmbT zL5latjwL#et9;KklG)*vBmf%?cc@^vjImbCKf<<|3qq!{7h=f z@3_=Uy!Z0B4!FE#aC*P?(z{oshy9%n%j?mfNkPpobIVJ8dkRUAE}sCAt5DwA08t=7 z2G=3xi_Ri;3J#pZ0qh9l;_l)s4gES_6qb%#=L?at7}vQiald;Ko+!77p6;%E`m`LM zt}_#~M7cDsF37CDJZ)9T_-VKF3H>;RH@M0El&s^t>jhrB?Ma1$sgG*6K2Dd8lC=4Y zMY>$mQrDjfbX*kr3v89^L3T^3#R;jCo$ht{uqiu8w@a=NIc*`lXxP?I$o{g!>G6FA zG*`SGhP?9n8`rO70~9^1+7T_|7ZnGU;59{e3D|90l7nzZ4Kewe0l#v|Vtnf(o(wp=8xcQbf?Ihv-WWulQc(0@R*{2+0Y2ce z=h60r2lPFUwNf%rv7Aw!K*TC}&kGL4K{cIc8%ts1(QG63u9mYrr=Y%ugH059R}(qn zcR)>g0h!SuMZRV`X*&l>7PZi03(Ex&OltU7dJKtWGe3?^81srEaQJOV~aJ?jEps zZE4ZF$j7nBhpb2KOg+-^?!oqjj3Fl_*f%pQ*%jus$Z#y#iI*c$AMUqe^}&rMisJyE z;jTv2VAgXf9=Iy($BYhFVVl6mwrqg+WzUEHmO0U&YJo$MM0S?S_m}V$D_mp>=Ine& zZ9Y9X$Sul+u=<|XJ7`JSsewd`cwf|N!IHgo6*TU6?D(42OziU(z+&u(4mpSBs5b0J zsS>ZcIs6f#B!BJ8evhj^_rCC%#cd|> zeB0aL&N2EsNMvvSLIa8gFttb(#5m+FnVDQ$e?6BvQ$G}(o?uF11#fu@hbcIl5BztCS(U4K}1ey{08;h<+<}h=NsLK== zLDE*&P7P)BeDw^+^O7?Hcn{W8nHZ6i3GWqLWCfv{QGKd6j{4YP76MNfA{28k=1hUK z7S9U2HJcaYS7z@{=K0lIe~O`QrONl_dcd~XTyMWXYBr9cQA{B#10!)5X5x@F166!+ zOTEV_KK>l)dJf|F#K~tGYKIS7F{<#CqhrjqJtt#=N3%!Rq60W>sL_2l>P_t|-qpLp2X^2PAU<<>8y54vVrhEYB%w`et}VFXS9Q`6wr8KH-e;%LklB zTMPSvvqF)0>jWv4ug64PHOS=0F}VhoCMUzP2fDa zx77DE_{P@ zRU%T!dOa7fnjVC5DnCa=KG2YkpkL*TNGyJ_*YeAB0v}Z1X;oez$Zrtg_KJhv2m9}; z@QvJZUkX0&Lp(2XQaOgZ3W!WNya=2K-#Yf#n8omGMZQ!F1JV_Tp%TmE{9y%joJo8? zGAo8wV#(Y@5d!>rmgS;~@cK&xdw@BS(F=Q#ak1IqarqU9d3{O>L2NN_9hmM0lsUh| zh28YF26!1*7*8xJMH!#MWvtGjuZ8f4e9-iI7BSjRx#Q}TyK4uG43x_&PM(tMpd{VR zL#_vs!e=bRQ*Ho~G!Lu^EXdwavIe;0F^kpgGmmu10Ox}rCD~v<&X*}_j&O1?w?^Pf#YHu$=qi3o9Tc^cT z^MHEKVvb(_3pjvtJ2V(2&K-F0@}nw)c5XFZ&hCRgOWn1c$NdL-9tT4 zY8{M0d`d1K0&2LH&FJx+@RKL#^^B`81I<9$;X57UVPQHZt7?3w6^MMm2B#HK#cM~z z3kVIESekl56k^veomK9s?v8#beWS_^z6;99#cj?WT8-M5F8~QPKmNGN-{Mx|SvG}> zU}7TIF(34BYWT$2;XqfRuGF_b=eL$ck3)ls^?EK~De6HXJ+6wvPWt2Wp&Y!OUcUkS z1}+kcNv6hNHlNtj0|o z1@(;;Khzn#mkypB1lw1#d-XID4pyF4<;mGD{#Hbh^LgpNk)B?^nUk2snM7Hz53+BZ z#`)6gw}PkFZ()g(pa)-=Yev|@MoRtd!n%!FXLAxD-6^Cyn1sC;t8$fIe~*y1350(@ z+1<>>4voaPS4h8N5_W1hV~^v}$jc?p(66`@Xu_<+;0@?R@eAYc+`(1-s|NKSG=L{v z`16b+c^vZMmWkt7*_H(z6gUTmm9Y$ zx)FbG;V*zc%n>Di%zJji)50Hy8Gd&WKdplGGxg%`e;Q`d+8zuWN*V6xdil^OtsKtW zgwPLmP4%_t>+THS5qMfDhyU7xVQ&csK)30tuHA=Qbfd^TEU;JiWj!q_>dKn8R)12F zPrFJkADU0kB9(kv*!}XMg><0Xl=4Dq>dx?)fg1){G`jnFQ!M(oz82TMm(^|RU()MlxlrvDUPxD_|k5DPcWtip~W?Iqo-;7}myEvU}f8 zDhjDUxF7V}FsKWq`pv26PNx>$UeKLBi!gk9G{b=d7(QRa@cs!5Kj_D>vXw0|lu@3pN>5Kl|hgVW{I*9UlyX5V1$aH1+YA;Ux>Zw!8tEdU3 zF_88nZk&!zb4eC-{-na9$4Wn`@M~lH;u~GsQ$u*ysXMp>X*wi_lH2W5ltan*DbRL6 zXZi8nxNOfzaoCA5{C;qF%uu?w^i!04*I0OrMd$Wm`ZOt@Q4=_>e>__(m3RCBt2_1Ru$3>{kbF~4#VH}-7sh%n$LXNh?~~Mfq&5s1N~0#QOt%EfluhQm-;a8U)3=qWzC&qpSLTj` z{XDf_GUo7$q5}RioLq#VC_5bX{T& zVtqY=>#nZw=3!N&3H9(@Q2UW7eT52KS|=TLfF=qC$(crw+umS*>UWn?Y+r@S%}=Z5gTTDrYYvS%$7 z8BCe&=d>!s8>a#erBk4Rdzn&`q4cO2;JCRuVIZ6giwhD~7JK)Q=Gbh6zNE6NvWV=#~5)JF|u-ejKDK++Y-$+Vj| z4|ffyPff=;%9~=#7rhvph5b$Dbrasvv`@5D4rJbJ`b>E5q#2t-UkY}+U?`rY)mUg8gMY$2*tCOT3WiTuGQ*BVGY? zzD136n#MW0X7n4Qrc)ESg5BX^b%G7>Fl_!J-XlX!nyhI}guMu0s|TJmnd3DI_QYVu zE~JYD%ZKE2e5qrtU>^wYGP+zdkRB=)-t!B(QZrBwr^2(QqlT`|!LFbUBKeK@*;RCX z4(~d;Rd~4cfp|C4)*Rj@x?Omf9DsK#-6I&=J{?|luV9}EubH+B#^d*Nx{Y=Sc7tGd z(1U{gR01tNARWPgZ?w-}Y zLG-&`&lg1LA5(4$L@6+p;Z%Y91^!Fm0Kl1z>w8!GW;SjXc#**81->otLBI&T6?qwH zH5Bdz{BHD{fF})j2k`e3_5qIT{~=&O;is_h{5V^X_f2B>Y8k_ugi;XW@OBBGn8)Gs$Fj}G1d0Ja5POtWABDSuvM^i8z%y%a09;p74A>=oGay?#fZP$B$C(400e=_09dJu@2e`|k zPk1uzOFkAZfXri|rvZ-%u!S#&xn^$+?*@DkHGXX4)71gQRViLu{d^?r^GlFQ85MPQ z0bO`b8U1bWiwM6ucn=_RHS&#p!wXyVi^6K(Q0i*F7va(1{RoFYMR*E*4$03;INh?E zgMgo)-cd6B4@E3;0X3Rq7=hH-r8{+36Bq2>3!7%j_P>;obcg1KwYY zx7cWS4MRnLwdVr`D$MEDAr+0zE1Xe^4usYQ-j9q1e!Pc`8FwMNlDP^ieql%u*b2d( zqYp<$^9_31!`2s0#eY8V4-b2?=XR_-FD)bdKGIf=TT^J#$AYb)?}uGe7@$rvQm=(h z4Yv~#g01nel)`A$xSI-tw8Fy%7v58tN5gxu&NcMNh+h{*Xn1eN))&7zWOreVju%Yn zjL{0gKGK$sdmg-RTd}ta6Lfqz$NNb8xVMJ8s%HtdHb4VDF6=^ef?X8oJm526^@248 z_742Au!PollGpkAVHJ6~$J^q2JX}J*QaoavUFl&D(+7W5*p>dr!$R2T>DDgq-RSp< z7dU+6_l4c)9l@Fc$B#6l-RRGPeWdmARq5T^#p^+bym%)J>xb_VSQVV3wSl@}k7K#l z$-{mNJ4-1cSW}<{c9v49;z?PT(Qv`8r@Kbl(K0&U!>$||iAu3U=W}_B3p+=p*aiD8 z(jy1^baZ(RRy!b>gZ;5)Kn}KZ!mu3dpZ&(PVZOs^CjwJGp}e4if;^p~4S@swkBwH) zNDtfJv z2Qc=shs~&F>;n&*IEk@?9`=aESSMcQ!p=Kt7%TU%(G`r1^01@x7@O%~+kK3k;bHfP zWP^uUlHLXnyHI#{dDzF}INsA9mfwZ3H$3c?&W!!9hdn5g8s^??nHynV!o&I$FqZbP zT8TH&!#0Y}*&cSXW>i#=@4c*bt>umQr`;bCKi_d5^kEWGzTtdH=%^svgY ztVLchgPkW#WvmBp(*T<?qRc1EIH4^mQ*r!o`=0EdB4KL>IO4!tB2hn zyvIDO%TVUM>S6i>#y<40Vk)*OLC0HL1ds9kqjED7+`kUopagki;VTRQE z1s?WMA*XkvV5$d$FG-_olD650ewE_%J_>UC>qD=57`MMZ^sR?+`|Cr!(@K)tUmu$4 zVch=u&RQEG7+V>wqL)1Ei;9)e0ko_i^VZTA6^o;TXv_>SmU>Mz(XJ!Pd~A$&W_IQ(_=XKBt8zKNp=y zgFWnx5zj>@QMF)AR5oyLbTXYU*e>Iiv44zCp=}=a(%AjcskG0-PM-2v^f2np(|%e{ z{~mELdN>UeY?txCSTi<_CJVNK-WU;yO{a@J$+voTi_M_z9+vR+i5*1`3D!iPjHrsu zqOS(C&NbA(>*&}qv<*8g3agDBM>BA<17#-lKAYyZVl!g1i9h-+T!v0#kXTVcDEO=I$i+mL63Kz*^gGx6CUqz^YQQs z+ASE@Q30)NH&(5pJ;LLW>*m-hnvA_n)Ge?Xv3fd5u(g4|!T0*_}$x6=i5iN`C6767~2<8iGt(v2REYo(EzJ>DD6qF@u<2xq1Qbg*Yrj7uE*n=zKA~bcwEyL(`SM; z1-Pa!rhj=nuIaUO$m4NMuO&ZkHDkt7J>nDN66z$_n!t<^TVj_`vBD@Z;a9Os>Bf;P zxq&(*o{X)d*97Bwcs6z=eXg)Tu;9hmRrGJcngYcI&jOSGd=e>KZuP?*oxmuS-VN5{ z;j1a)VO-Pesk4W1O|Pe(9`@|yJ+W)3(!<`I{06W=9(KvtcVZi8l!sk2_I+SA9#%8u z)7Z5%&BJC+`4ZSM9@cHtcd_f}Bo7-fDo}JCoi5m#z`x<;*VAGT3&LBkrxgmLrjl3v zH_$d*tWmB1@$e0VZ63w~p-J1Zw~+5)Tz@xGOt9UQ*ApuusuAoXZCeRrvjl6RZ6%L~ zZ=`i99?BDIMY>gCfidG^MH}f(B^j7Jt_QID1^Y-lz2stT6Frj??tUx4>JRKg4=Y4H+)ST&SRd5G&GeOEYia4Yp+&dQw6T&SU{i~lX~j6knrP;@IYqZq zakavF6fP>diw2KZ*!u%6D7udtCo1gHF&l~=q(?n$=*U}&9;QHz;{AE-T}8jf{yJkD z=nm(}q9^IhNeW9x2Ks+PFL~I%3U?PhMN4WG?-J+5qNnL*4@;TLwcpaWf~^ls^1V^C zt6g8Pi)K#dcxwYS`J3~1(QLui1m@+pK=MY9cYCMJ`MYVe$9uX{OVMunlgCT=`{CsH zFCK5W|MBp%^o7T}Hune2%m!D!se%`_b>HSYh;e*@s2HqZ6kJkLFix zr{}5bVT^IVbfD;YI!my%fjfE}Dtejf1ZxUB*29dyO#C%$&cmC0l*jkdBOdRw36;Q}_INAv zHs`-azY}atU_)L@(QBj~u55V+-t&733brP25Z?29D)e|qpv~a(SJ4dAz@) z9^Rm&#|xsY-=GB^kIU>$I^V-SMCxzK2^&f&a0sctNl$saqfldipyxa-XF|&#UG`6!T@Ym8&z#@XJ#r8x0_*>N3;|(Yq1+2H?(YvUlw`tmR$sf%^9lb+= zBN$U_pm%7q!tiyG$?E(VW#Q#Lc3dX(S{P=!4-{b8nT@wG0 zFfn1BbXEal+h!`P_k?qSJ%5y7()vH5H(IgA_($};U~2;(4qY4n3k{jYl4}FeVOIe= z+rxH58RPF(Gw-S>OJ1!o3Z!oYwnwlgx-q>q{t1;H&5})YLE`TCr*xx-U7CI%et_m4 zt9Zj|pN#*N!pAA>#q_iB&*`+|6?Sjl%keL0r-zkxdO7}g>N;ETp3nb7{2ye_QP}YX ze~o`lH6B*e^_%#2w9La+6j+Jxso(@9dFP~pgr+U>u;_@+2}9fGVHZs8mGEi#C$XfO zr3bY^f^k_t9u8{b1>+j)m$0=H6-IMwh9<(=r6+T|H8iDaQX*eFAXpPxybcp>~UhanByrZuc-AnWNf$9@cf_Cq_(r#KZcI+!Bjv zxqB={+S7^$JI_uOX$J&b8#sPweWHu@PY*kM=!JE7MI~Ck zU~2=(NmI41THNCeo8+UeS`Ux+{)9^t-LyV}tqFWK;R;~=J>K5xe$-tX0JnUd< zbAAtPlfty!BQ_*@X!m>ACnIi3lxnGYgx_=8*U_zsGHs_|O>|c8t%;u6`KL0kiRM(^ zljx;wp0BXgogPV4XqTR*utfe-2}fJEKw-0MpGs6}!%tV(gOTSFDQ&lht(vqok=AZI zgL&*djo~WoQNf-g-ZAT^h0kQ(bJVHpz2Sb^qE@T`-z<5l6?-5&KpVY~C3njhK3Kb1 zu=VuMF)t^EXuAb#qKtlTc&PTWhqdT9E7IQdux8_d@NjLvhyBBNJUmMKRIm+n+xXWL zqcu8iTE)|}>ctAXcSLdV5!#8a!VZk=TRc0rR793f8f_7VmwJ`6L z;*+)46&5&QY(M-zBc;ojx662}#4Vn$O%aS!Z!A7d`?H7LkbiaY0&T>(SuKt6Gqfp! z?J^D<`^(}pwPoiCkAB-_v~gBDJI~fmTcLQ*<3HOzTjRILvwha0c9M&di&k zZ*wHiX(xG(_KGJtlkO=#M|)E+E~yuuMRE;Yr0*(T(oS-zw$bC|>GQOu+5)J`yX-|2QHQIZ{b=q?two&__IHSGd zVLi1^ikE3`d)RII7sbolX*pN>z~jB9eNlXFJKlNPHy-au)Yy6Lcq_DUJ(u7bs?z^m zyy6GEK^{-ju^;~fUhU8DX8#Otg~$66XRs@@%LP+q{lgS)@FaWEcd(34+M5b-Gmb7F z$Ke;s7%m;ol(n^d184>IYfQ3wXX#lk(+#1Ubm2(m{zCNp6g>|8N?xP&2;={n7yZY@ zvYOjdlqCv&)Qe?Qcxb=2u%g@mJsK(ch62`n*dR`82-cz5SU*KS1v_Ip)fTh-%JD2; z)VX8IYl$`)73-%dI!Y{7%G!Pjdov*8EpM#m&m7oGSU;T&wUHo{b0+J_mHBaawph+D zPv+YHpJM%3&yOkVO4y#?!xDpjBldjl<5bd8Vk*p@_J2?7vSA$SRqP;V0 z*lJAL(uZYKYi(;yrksZSxRsf-7I6` zPd)gK*(mu{;V#mLaJ?Hit!ArKZJN@f&XjUf z{g2YAJR}9L*2Swe`a6099Vf-CpTnvb(x|U!<|~`o@Bj%vBXs5(G#Wl>(k_&VkN%7r zw5V8QREiIZj7nE&V+!sAAT6C#SkYDLDlbaDEsX!!7P-cIpfWnB+%eQ~+TNyZ2WnHJ z_t5&XUa=)7{NplLHavoT8-p%{{if8TPs;G8JwfSFJ(Yrkq?{F0ezi|}j8|o3c~SbD zTJeyd3M;PCFjss#*V^5n=s1gKJBLfFRPD69FX#8akzAT$$y}($g&#tlcF0KgLwdIM z*?xoeN;#{(h{s%wK8oqBWnPK>B^?x&ylyyiRf|(?hv_WOG8c|yuU74M5z=i>Q8lZ4 zfa7K*l^*Vm4XJ$-w=dZDULCAMv(l!Z8tGJ6X;X0(g~J-XDLt6V1@|F*AA_L^pNla9 zH(La<&uer*;8=J|c5GE;HCSY zB=9nU>j5>~@OxoM5$%xhV?uvM;9jBh)i|x00-pgap%(ygBL%RG=IJjCacHT|JHRV- zo}-7UsAe?6+IXfE)r`mgzr1b8bh=F6hS~N;**Sj9U=3a6)>v1$HMUXG>P>6MH=%4k z9C=s$wSvO}219)s1|IYM|$1yncMt{CgdQ#&p`A!oHB+^f+QB~aw9`apk;oi0?wGP1^cm(VtxVg2lZGLzVj7)q6Cy z3U{ltPQyRfreYTW_ffRFM)K<55Y7hXX%|dj$R|(-jp-8@R!%7acR|H=8e!gFQikv? zg^s>aZfVTZPR3pSdD^9kA^KjF{3yU@drs8oX#F>FWUtXrnpX+NO8bhhVp39lc)rguc;yW5j-aoz%#+Qa(3J`D~8yV^amj_FE&O9}W44Ikno4caL`0mI1y$ zrOISWhMPOg#K7$|6W&;YyQa&^`T@=>8%=L%!IG2AGV{rv^UcT1nkiGYcahd|v$KA= zl>CPy>&?B!BMAexHI?lUZI7D=jj7eYGdW+cnh#M5->-g1EYzSMbqW~tF)Tp%uEOoq zjPS);v(Be)%{rfnHS2t$)~xf{S+o9Cd>|;##zz3|0NkPfWpXtjTidK(I((w<&q!st zc|bh#G0;!;t<+BJb-M2v2|uRy8h5tuA0pF?@CCkaC0vHuztvZ!ak|f#cb9GRJ!4)n z_G#Y>=Dg^$zWw@)L;<~pTK<^!n$Hxzg%;*Cx7Kz zuPsI$#e^QyxUOT`A1V&FVj9ow6PG+1`!~^nYTWo*3p|sukAOo0n;i6^}uOm~jf}XkqsOx5(PduA-KHL18{`UAc{HIAC7wX+XIZdnWwcK2&{{uVz5$T=Y(z?TQ z{$|ce97HPbPpS5uruDBHjaL+>=Y5B`+=rmY=rvVic^@4#%f{yiuyZyf8CWDXA2fRP z9E~vhe`o#g1>3Q&#^oIGvFAi|Zu^}DE)z=z_$HP#2F^$L>cBy5RqyR|zLfuX-(9hr z0^@zX;x~a?U42*JA}Ng#TK%A>Kw-aq#(Zx0AMjD=<45feJY#Mw`$u4@cJjFH!Dj9A zN#((%+TxmIaHX-UbZl^@1J1;McfgQOe`gwe=G?5!#R=;=&+?OdHwLa1%dZtbxmhfXm_0jRiPr(w*W4Xk zC-mEdey!MiExay>9^l*J_k%07&uTvj&NIF({vx>7cz29r|H{+w^!QQsLkMelk7N7L zQ|%|kD|Sl>FZHcNU%J%S47k$Qed=ZQ2*d1b7zd3HM{cm!Nh}%9?ecN8?`~A=Z_Dhm(laa(ubd%IhlRmGE^Lu?+-%zvm@stwu zlP?UJ5_(-Mc~>ksAT|4Ep&amR+coBds4IaDK>%Sbj zGQ0zMZv^CB|px;hnlZfH0r$t~c&PZ@ko3hkf^@z6E*xFpeDC{f+Qm zUo-mBrM|DDe?cm~L4OTDV|;~~Kv$jnS^Nhbw_>3r1X=my*)slD|5z4y7G5BF3b|cm9>yjWzY=TgWd;OzZD^DNlpt z1vJm-<9j`Csc%tVPM7=quBgY+G+NTC5x7xqo7PC}tk))u8l1n^=#E|0y$1W$UgH&< z4em8gm+&v3XRpEe-D|utJd;07G#rgt&2%~u^PJH%P4rBY8kq)qGH{yEM;M>uln-T6 z(vMD)InpQQ3w;KnSLxFEj{JpMV)VoLW#}8xFPX2HzsX;uzf{Y8Cf|$QYrKkb&c{1% z*L2FKYT61om)-_EjXnd!=6yjv?#y%oTtW`uG8zGREgfGFps&aU{FashlC~1i)K&uq zv^9V@M*=L+t^kZ_*8mo4HvoR6xdmm|p+BR06~3ylte^^S>GkSTg`24_1(rxS`+d9!?J?8 zbQ9owd{tse_xW@WtrvKcz&ixq-@Q>}HVfQ}w+oka-zni|M8iIM5E}MN_yfG5@_6@& z+Kco&;O{}7sBtRu1U3n578umuLB9G5oU5};qrlB7Y%pcBKr%U8DzIAMT!Hh=5a??J zZWXv+=$en^%LI-WI8WewflUI}2y7O(Rp2uMcM04t@B@LG#o7#ky#$UII8op{f%646 z30xzvS>RTI&j{QlaKFG01ZsZKEU-*qFM;C)&J)-qaDRX$wV>on;CO-a1U3n57Wj;! z+bp?XpcdkAlfY(yHO3w$PDbPCiu30L5Ff%61532YWP zx8MW%N4I?umZU<4r2?x3ZWg#x;68yA6&Zom0_O^B6u491K7kYyNr8<5Hw)Y;aG#R5Ai>Vo- zL5Vh93+OTZOntGwOkbrp>TC5|^k?*lG0~W8Tw*+8JZ0$S5#~|mS!Sd8rujK$uN!>- z@CB`UtI~g%|3Uw|elt)MC=JvEjt!g@SP|G9*c5kv_Etp^c6l0SrHx6~Bv)HV&F@%751c)Po7YHF)=ff;cLOw_W2@57X^8+>x>t+A{Q&sog zp6-zbhcBD`{Z{r=SDiX_s_N7^r%u(aTX*9hZ2ZHGf4cFj8-KX5scmoDueLqf_P(~S zw0*1X$88tKUJ-j`?ESH)V$a0B8spwh19p5G5jG)gM%aS?TCu~j5&Ho#>;r7VHzYRW zuE03%8rzC{81N=8?s&;z`*I%pDh0f&v4Gia5pPiblJf9n*vs%{-VU6SOQ^TwS=maw z5j#R}zHtM>&hE_!pY7ou(Qh_fitx!j+Yp`_Wc+IaDaNlr+==f%_}#tNA$)mvKf*7M zUXSq8`-c%Gx(*`zeiv?BQafHUiSWV;Z$bFxLkzbh7(RNC;psYtpX_EhbrZvbV+?<< zk>Ptr8Q%0VhOcU4_@M@d?-xj`;J-oQ-@cLX_@xXVxrE_I1yX8f{MyY7UoP;61!qQT zIes1N-s|CB&2)|!~ZIEo$6!!-%EVk0OO7O z8QvoBcT3nM;a^Hiu9fy*Dv&1W*SF%tUq6RG-+nvrAKyNMu>Yc2gj4M~gm1Zm;a?;c z5Vl>%@SPVD^1-bPlM>Dd|qI z?G!q??4?3^MIis+eG8cVmoa>9;715A8vLIK|6~ip2euFr<-pJ$_IGKZ=~DKZwfxQ) zIsAkDZGb#`6WjJ@JK4L23mE^6A-oWUQ&4Q@7o_i>ynwmecCqaAKH~gz|0TfjM_zg< z^}P-|!Y=gPg=gA?^aTjnauGs)%c2eOPJ}M>n(tp+EeMa`b~P7x zwuPG`8VvA={cg|pf&Rw{2qIE(L>yI8Nh8DSRR zb#h^Y--7TCoRfB8iyuHZuO38LfYo(ik>8H^Td?2j;FR@4h@XL%@2IyTbbS!^{NEvTf%gdF??C7R@7;*M6Cs{(tH%)k4+vfLF7=-fe*~ec9_9P(z<3<-|ES)J z_+toN^_$qGcYyIe#D818AMy7g#Pdb$lRJn#f%pfo(r^&_5aJ(HA3^*>2wnAI^-;t> zg3wjJt3HnSM-jT}V_2);nFm5w{hm6D_>%}-_4~M`-9hXR5dVaF3h_Tch^Na~z2JRX zgf33*eg^SBMCjr)?q?DIBZRK{9R73EA0u?tpWr{dfsD{qPvbuar)r-@{Qt#&j`}=8 zS3QIO9Q6f+cv_7A9Q8$nuKII4jd0ap;MY}O!3`{q`YVJkcGdqs#J`5nRey^dNBtc_ zSN((fYlJ^UzN3DG&{aQ1zN7v-LKo}KuOt3ELRbBh`Uc`ZLFi&7`W!;#{5>EJLTItW zxBu%9;$(vJJ;WOjx~kFn0pd*vU947lCqN5ASG77nLVN>47wgymj(8hFSH+y?5#NLm zUkPx2f_OVZ7pELwK)A*EDIgahgq3jcPhE%r_q^e)b_c88I>aw=8W6t}A#8%vgmAml zf^dhk0pT@H8^U2{6L9t;gl;s%VH_NslegGjf(LoK^J=u!*D;yjERfiEmADv4O zKY|eY2*1lw#}Gmvolb3J69n*>0FKQRn88Cw>Z}#e511q z;X9n`5Wdstb}m#ObjIPYB8_^brZ+WxyQ#l9+5Cp)k2Dus?rnKP%abiVt+%%3 zTFb3JZ7ptC+VHCz9@y~chCkTwsSRx#U%pYb^|hUBdrjN-+BU@QjlCiEYq7V-{ybK{ ztUo*n#|vWyA1TILFzT5!|Gp9X3ZC(;hW!big|3EOfoF#yHO9XYJ9HI)E9vdT^U~F@ zt5NIfvX9}}^lH3)LaLHp{q@y&EmB8}8Rp+NV(-M0>eci%;u-4ddM~)L8gCiT6<6ck zQ={Hw4cMRVsiya_8hF=UUXAypyemRDN9Q^`$GsNw@lO2h!i=22Tzeh->Ms0s!=LWK zUoZap)Mc3aF30?MIcC4hF*jb0`R`>UgR%Hk?1J+5o#<1&`kva2a{{k-^4G|{{w%n+v0TAZ@^!h{2r>Gbw1cI3!^jZ z{4Rc<#NX!|?suMY9&o;ivVYmQ#rt^SRts9)@ zTSwgITW`VNcU5e|{m!Ra?spz*`J9{I_@G*BYjF~>4fv}^s~hmwh(Gp0?cFytJ>5Os zrS=XN{hO1yY-5=(0BV{Gg$Kebr)gEHL$U0tXqu%};0t!hq9VD~^R)V`Wf4J2CzS!kr_ zC(FL{bUdFa2wx40%Jf1Htpa$yK=prw+!`P$>r~D%Dz+_zfC0*c8W_o%! zU&t@b7Zyvz?|OBQ?pTi+KUgd*E}%3(uke>J28;f&WVVRp_-Ix-kSs1C#g-Y8y~0)w zuo<1KUm7k=6_Z)0ZW>(MjXHp(SwZf0ofP{lE2c$ zh`=ok=QuV?QM~cle4%JMZR0FImP;;_06&K520sLuX$nd-xwx=UC_=O<D+q^rT#HS0aVrZo5Nku@^b>VGZNwfkC)&0d-*r>bM4{>`cn9E+--)R`^^9 z6Mm@#-L5WvBspILDh#I5)XqMDSwY00cD8YJYG+!W)XqJ+SWXpcwMeRCi>kXIS%ETD z=kHIJ#8nC<&bSpahpmlY>4U6+EbKo3t8y$^o~x!64h%hu;=I`SYBWq_YvfdFn;^kl z`U{*Zs_9tf23SPx?4?>XrH6-MppGog&%mpxl1@RuJhV8UUkio8t<4CU zt*4n9geE45XQ%U8RfS3dmcyT_Zp=s_Un&e9#k z(qgX6v96J6W~drDlYY4j8&xA`)L+OImgX_@j>9FWfnZ#VDvpy4HPDa2#mbkf8xA4N zr<27rl&d;(%J`x+D;a@XI$J0%RTn!}%ogAU*T`YxFbZY=F6$;$BOUisg<^Uv4+mjk z!N(6L4;F+x9w;mtz<9}cG+JK;sZt+&lOq#8*HwYw+njJlI$EqhW9 zWtc@=6}(&vndGaK)wY>cteJ^Un)QvHpA8QVV$F%F4ipRX5q@E+(^5|rudrz9#6lXw zOS?3lsq7J16`NgBPc*E+^feq{9jjWTs&ZIn zUY5*iBAY4}N`*{0adWnWMc8C{F`X?WNc>nXS>{}n(DrsX4cq4z6Baz_7f)renURnc zYX(k-j6dh=(t=HehEkmENK)9vMWMJOYO0u>4<|vPw8~=<0#&e(0tpHq^Xa3RaW1Tb z2~bxtc4t{fh>6u9tR33S(FhWHKZ2b%Hozbm-8ZSMEh~MHEge1HT$E|IR60O95fGUl zNg8*VNH9mHn4Q!rp?Zyi;lQjoRk*{q*dk=BDl|z}W}=iAZaZ-S2{oS^DWrYH<=N!o zOu6XmVKa`OnuL|L@nkwZeKdc#Flzxte;y0yN|ij5{dTgmE3B{Wc_rILpOb?8cZ3~ zhD0#6!H_7bq%zi|tr8TDcEw78l2{2STU?fAd!!%}qdrE7+cz?HK^AGQ zk(I7pV$%3m0p*I-iSVitl8LTf0)>`l2n~@QiJ?0I)ySeoXhzfQ4;CPhR628LP*tXx zw1t{xo1Vo5Z~|*(4oV;qRxBmg4um9#w#vB@_=4Fkltf-+gcejNN^{|B(bH2f1Mn!ed|K`-!(-^XQzlHF8^k&j3G z(-Oy);S6)LrO-_tEdaoM9EzZ{?Fek1BwjV23*sb$zLot6j0k81*s>C2(Wzj$D0wqj zAyz`l=u+s(Rpw=bt&K`J%NW<8G9#s(sY+ndTCt#Yf!GI-<31K8c}Y$r%c;3Y8twyj zOO6(~?-~IY!xH2IRs@g*%$u&8tq%VaJnupCbiqEMl(~oY59%65HEJuC#T;Mw5KO#i91fdDJWPqO;>mnD3LMe<$rhwpPUV9(%MVK)EvC7>uXox; zm`SKKhzU9@K|-WINNI~BMapcypqp*=aL!HWD3rap1U&#r^8lmlEAdxn{o?*&;j~Q9 zVlLEJI*WaSRlvRbx~Hd0G$+!)@e-E4r)&xyoR}{WeN3H2arBX!4z!(<;wbjzPpyKn zJDM|5RJgY+OM&tt#Fuy-41lJP+TKd_OnB5J?wfPg2EH#h(1Jg)ml`= zBDLVQ1GVzT?kfBF5*+ap#cWswHrkVre!Fp0k!Pu4@salDlf^s2VpGW?ggKnggO*KV zE1laWBM7poWG*ZMDH&NTl?(GB$PW9VY&z}dLpWWspt&K~wujj_`$hJpIy15jnar(; z1(T4;&YDfhY9gb4DOJqM9%PtlA8?ki;LJkSrV3%6jMVD6QHH4sXnRqWar88FBSI37 z6tjBibllG+?~+AS_)JA~RjLh(1e00_65-+oPzXv@CT)8#+oiFs7~)~T!UX&$40xEf zrA7)1OU3N$TqKJQ7rdYlS0?GiEl5xZ zWOpHeP@^j$8B2-ChyDC4h{ET@6VkvJHqZ!jRMDTsIRrSZ=@A?_m;oCVjI)l&W(<7{ z_!Ih^!DKdnhXUIosr78PQOSZS0V2Y^gyGU8rpY-xM&}U)`0}EX#x*kIhb>w(4X%>p z9Zt^pIeDgzf;M5Eg6m{p(>Nh(P`HGWCk+ui3>Y`~HN=_^ozlBg>k`@Z?j$B6vb`?C zKP$d&h-`D(ea$F|8iyuoX3sBRrHKO()Ipi1%of#Q{}cf08SEMjN$2zk%J}F9Bsky! z0p|{q{F>%rEi3zYvf0T5W!ZXJHcdxgOjU(NJ((<>$d?vV*uRD9NI@c619m=KN{1+= zM09hwjtgcELh!O>FVN1&9uqB*@&iL3X(Ls*%XT#gcj zFl|SS>_n(Sw}ZnrAvR|)`>ZpB>V01AeuYr>l1i*=l%05v$!auOkl=t-Woe?bG#2z> z2ItP28JZ7Kwm6QupZ~+!6CTvm&)r%s|dnpl>Fo!*?bh}m| zu&w7rAO&kT0xzuwk@MkDRR645Jg?AaK1AQEq1r#xdwN+M4H0$IQb!9# z;}*)HK+1twN^)KvMFzOST9h-}LIQ{PD!&tWrtKF^sURA-Y?6URX~`Vz&}j%=`t|g>)Zg^(N1`*!E{CpulJ%I)Me*d7f zt2i~6%-`x4QLM0_Fe9Y(DL)bsH(0MV@f3lbj@Kw?1G$GwQB+FlHVDemV3?g z^c1*d56jsk3m0X0xu>pA=SYKPijKenM>+z_)hH4@<|A~4Pz5v z{ES=GFprtRj#F9Y7b=$b0T`x29Mr%A333+936xqn`s{RdPSS3~1XK`_bJj8~1<-Np zZ3PgHauu+`?Oh|c3S2VVWdZ4NZ6$(qMhr@j+`F%LdYYpTh%`0G(F(eMF`FX;#R~~E zU7xT4m?f*BGm9CVO17dD>>Va-%$(5@S*Zn^ic+=6=$XtYL3K7`-1y@1n4U{oR85A~ z0AnjNh1ptA#;w<_lR5o^Oas@`WzY+5i;foN6SP>EUW;5BTMAs=Vf#pGm6fP8Wq*># z3B*=ty9RHP0NnxoensV(eq}AV!6bK0lpT^iDjFgk2_3LQehy<7lr~Sp473=Wn>GOB zyHsYIlr>LwGrLeci39T_Zf#X{PSlbEQ6$B#W|3ygwJ1k`wxbac*Qdj$;jWti zcVjY{@pV12As&nhEC8g6x^Li9@U%_zH1-)x{S+uY2HIg3<5C4gt%PwkZqUZKHeT1f z7+L5gjNq-ST=hy0i!;4lfbq0D2f{>7;eofwlMf-3t=4NkB1~jU@EKBb;+C$3Sewr6FOJut7R6WT@G#K@-aX=R+9V7^XPYg(>?U2sZG=1wdvf zan_=QE`0D>Lc$ir-tgY_ETN+VCaZ*fc*CK5?z$p%tU|8V8t^JT?3XW)piXP7PPS z^Cvn071D>YNFtLB-2+e7dVIlSoQc#BUy)0QtY|u zQ~1zDR}1^NXj)*8f=kDO1e}o|ZU#%21n=bWsh|gmamZhgn1yACri+n0?C4tpNqv$t zNTcsTTnl}W+`F$2PUT!Skk{Z5MS`<@NsaN%J?V7C*2$<}#y&z&Dq?sN@)L&>Vo(AQ(#!&j-Y<1_pa{>(eC;Qn1aQh!n2ci;8Bh-*8lHE=S07kYtwBmLQU2 z6(#~&C2Nr!)9Y~iDw2l>0XWnfY)+UBOAMCXAc){zneIgbC^M0Bx+R4(oT)o1H&E6$n$_M{LOa4GC1*0t z-fW=tG$*oK8-bJ|5nyWs0_lsS7VIf0-D7J8Ba=5+BadtNlvCy{9Awc4R{|4qR8MHq zee;r>#AREDei{yM=vOyI95}V&DUJwC2%rp&S5poV#`Ca0vzQWeQ^hUP7Su%1Gh*w9 z<>uW~J4J=&*uJ$I=IFOM0=Tu7+7Or2qB+KA(`W(gH^zWY4Nm`*^g2`T4(qmoGv+{K zN#W_ZYzUfGqk*Q;64QQ-UT*Pe!zhkhkMh8bya`?6ZV3B?3D7)q(<#$cUA3l6W^2J8 zE%+sDX^q{5m0y@w;cmd>;q*^1NZdBmWmjdGjiWVkL$_xQ1U9ycE_S(vdDz~N283QM zRfjW{IP7|7z{u3_47R`Erm6{?W|~Xp_AkLmXeU|a3aL9xREA$3A*)He4Ga8mBw+uQ zIe&mNdU-I25G{2EPC&I*2hKaZR=OrY!YKJHb2O7-@+8)UdNW^5F5px`z*p(W1fK?x z!!^JGe{1ALxeKpVo=n5s89K(xrWDKkd$bsVR9c%b>p->${RlAG`9oT)v99B5Nj5k?CV_?`Jma(G8iDk zLAALYTN~%QB>K3Cg``&lQkDuu3#aI%;YSLmVK#9X33g=`p*%s@zXXwy180JRxrp<) z_}+|@Lsn|!w0)4UvlNc4=+_s>aDG}p$AJ0c+eDGt zrB6Y-!i9eYj=0PiBpl6$py6y+MJ{_rfKij6E-HF0c)13y;2ALp2-tB507~6>4{l2I zaN}YDcPJKdf0d8a4fwkUcV*p+`?EaU!-ymFxWBy=n{k`o6krE{F%IY$?(9^rod7pe!X>spgBB)$Nlq?G-IIVU zqP{G06Tt`_Lz|Mw$w&|8krQo6h&POqN!*gjK4D`TH+qtDp>(P4VdRy8lftc=NZZH9i(_GV3Zv-#*(C&25r_dj@%OH`e-v;6;Nk<6gWk+n3B0#eo;Se zPWtCl=~n8pQPeeqx@J)`<%l}E-|#?W;`%YPVNrO={4(mrgF}Rr8r#}2z}UMYq=DhQ zV5(hXkWuz)5#>1INqPuC1e{>!LMLud7F~g7?9wQE$?)k2vg(SROUgg4`uzOe>=GA=En6 zZ)*y*=^#q!lC-K;E~7nAuc$7?`cmK>b<$cDt#i?mHMkncP1pj`y4i4#`mX6zYEKPH z0oN)ior+MZ(EY*cUeKoIgimWrPA#6TUCv5_>ONO(wRA?+=ucQrSR$z5>hsm0B}rX% zF1&zR4ero3R_-A`_hNGmt>aRyeB6E6D7Fhu)m!4px`960jO~ z7-Oa9y);U4Rtf2=;q}w8R+tteGT#PvC_1Cy%|)?#^n9u{OO4rDqgHwfJ)~A~rmfbA z8dP>zDt0uYI#i+gK^uw5*@m`)7JbBU#_D{i6QNv1sjfA9!LkRE%kkG-P8vPYI>~yA zfY6tq1yGmS_OpcT^Kt3B)~}GJ&8^gKJJ;1}b?p+`--_S|cBMAHomJ^OmW=N}|E)&N zv$i*uk{y+(g&zjr)90A+)?xL7QMdI($ERY{uLxVDJ+~UIeyPO?=QJz1^rEz$9uWT} z^d~||t(Ho*hO>GS^CD)$K|kVWan$$VKH}>teCSHg`8uqa;N1^Jz3kOqrVl#AN?`uN z6EyHNDYiy@uq`!U!|S7FIU3rjz!%?A1BNe+g=>;?w~_P2lIWb;#P&tfH?EIL*f#CI z&x#Jg5(YjY*9T?rJjfYbA7$E0&iD+)wzgM6M$SixV?mwg8m54r&Ld7Ac3fJ7_foJL zJWu{smZ87Q(WUNi#id@tKGAMukwagcmQ_o$YGG@+B2iax)kuGPcx~*J&(gGH&_L~Y z9KvtTOewnYc3R-@tge)eXCt_rPs5q#bTltZ^)k!>jRP0y+O_x4crKqjU~w|#vUUk6 zy9j1%4mo*&apNHaEbFXu(Om$H>O&gU4Det=^)k{2+dVNx?0jw9Eb3uxdU&njC$3)V zvu?J~Lkl=$tov&GYSyw>;e^RvR`bRKC%9xFKd+Xp6E1w&4=ylmA3Wre8=M9-uRO%b zMZM@??Xyzaa*yTND~be5;W~Z%=d{@ptW#|zkG1oi4@G0cQSey1r)v3)+)>$%L0g;2 zv{IZ8L+@92oO?KATqS%_nx$DqHhM@`m**Y>nP3^I>Tu=!^5^sv&k0v=?QjXo-s#Po z>d_|8_2xsAnBJz+JSGQDuhEF@$f0EE@#Rv5imqivk3VZ?+H#w@p_b^S03|kwj_eSL zHyb{ea4cEYE^C}(jisipic$;lbq@lpE23DH zbp|;7Q5;JtWpqI=*r-1tPfCz+)f+9PDWA8@^8HaO9VMXW-6>R}8EAzkYIbNy#=v|U zHXK?MTsw>JF~h@M(GX`6L!$TU!j&R1d$1dbE^M{6=%Lkcb&-Z@81>S?+dO=)fdB0S z%sEMqP#X^JpJ?%n8bv=c*lcg8B2iY_K?ua_A}bP=ex$m{@@5r$`3XDOaB`*N=ZglA zxvR}Z0TAjk<(HC68?S9T?KMksThmH_o&mJZvH78yux!(FN>cLdz)=RY)_F2acBbLr z84cPog}Ss3;f7AgJ=s&PwFqgU^wBb?PVQCNp;6-O9ADinnh8T z=;xh^w~=DSEYmLE8mPIC9S2(zlE1k3x9h*+)JDvdFUK2;#6v}ou_LOUg))O)fEO@X1| zvs#P~)>ZM6(7>;}eEIvo_>IpzdU!(B!2;HMj?>(LpnWT&F^Rsi!E>Eh ztdZd5AMhu>(gtj|d24+6O-Ssh_we~NKsuVX#^(uW@s#9kQ4OAouiOC4W_Js|z*N@= z#KtX7Ym;L^D6{gsvjv}RLm7!Rc`lKa$#Kw%<}IqVsh+?s{JxUZ7IU{aEvhaSUwJmx z+0ne&akt?cGqI*tke+v9^{q~Pk--w%u>AVQt?_%^n9I&|T-*+9muTBi>AfiE*0nmZ zmL|Z^rC6*%NT3K)7dF)+a=#OkFg|5)8p)Q<4z$*3zppKZ)&eI!1wJ%yX=?5etoRiE zTRszuHOIivjuxm$0a-~;ZqWRPQs@*dv!p1Hop8N@aZdVtfQ3!v(kWA ztX0*y@s$=*rF;+u`G7>cv`I5}rQM=xCa<)|`G4DHRVU16!WO5nwp%f77yh=fHzH;n zbwRLnI@~rTLL&rQ+&ZOPjG)6|Lz~*_nw*X}OG&HH-%HTA4tE2UVP&hxKbyQ2P{Nqc zikb{2waAH^o}!J0jtj;+9h==vk`CdlxdWpG+1;i@MCTm{-O6Pmr*A@2R;~g^m=F}z40y)0YkAnRsAG!`m zTeDb+&0Cy}O1hy3rAgJt_>WA3a$ZcKkl{ef&48fnHg!oLE$kANaiyJ9w<(T4YrCYW zIll7B_{uFv+!9~8U9c$BNeX}a%DDc?>Yo#CBNUkf{UeaN1u~^0WI8MdiN=?orxA!5 zyb0hXDSP)b5|+K)mc4yL6IE4nsu?N)-FiOO(gl@=dTmn}6c_fbGlmF^wu;~9x0)e^ z32sFWic;ko6-@Sh^M>v+Z3HV_K7$c*p=(;Em(RqPABIioLBDU)OtW}EpdF0G7c~zp z-2TEalE*d)j=)MQLLuuG8mlae4{BLSyTX`V&Il~D=%S}*DpT8xIg{*d5%Hw)-sZf7 z=539lm$a^x6Q_(<*0Tl8ebXu!+7xYA6Nlw9@qYsaggHad8QbX9Q+PT%(D2u9Z`y$7 zcg7F3ubjaDunhO2Wl%UFjjZ_cCpjxD*Eb>?76C>^4+^Ak`3xim^Hj%1h&NI?0sR3g z>@Xx-&#knI8=C4-OLP1XZ!O);vG)5`+Nm=A?T2IWz46_M z2?6R7;}pL~P)%oBQ-i@GPE%6@^%abS06%1m9^_k#@_i4}J~SfT46d)-8((=$%;|lP ziRlDQ@Cvz@ri9J&2cg)Ff$hhPB(uOn5urhxG)ovxI8N`Ozb8N(z~%Wxx${}=d1^n*GsYJyb$vhEs$=owT>AsU5ah0%f6 z!l}I-N<;OYlQ0LJ7f*(*o-@AS9GD^LASY*XC=ATWHF^pS{Tzj^9o6zjCSnl*rkXJs z076DnDj?7Cm8av7&{v59Ki+|!^|v-r*r1c~y)Yc;DusM+>;m*-r4uDz&ygVpfZ&Es z!y*`v_|0&{8uVBwfuROT6sq6@G~fU-?Je0IK}+FFI|8M3y`mfxFTa4 zfx_R%Iq^3`EHRn3X}x*2p{cIJjWwYM%U|qp<@Ybl@0ZQ*-*mVr=+re~O6;s_Zf3&8 z;Ly;}Ht)r6BYYXKYy)M<_FnmT;dn)eErVYYH&O?{M zLxoIZ`651~XHOfj`pf}G!?%VLQG91XOGBkfP$mmseu4a(DuxH;Tatj;{=(m4fjxPAJ-+5XFAH`2#mjzqb9^);-JbQdi93yN{FXm#I(>AzFHb?h2c=&LDyHR$tS?{i{=c#6#kN&tD6+W(vS3g&b5sQ0R z`hnracGqI{^S#ZhAJc8)L#nl(z+D&;;&uqPj(AKHJO=k224sCbMo`rgSPw@4Gd_bR z2F;R$4ATpEXI*3bV*I@te;eIKm$1hW_ZWGO!FL!u573p2e1ySS22U~g9D}DBe0(z% zfCkYnvruGjU1R%Ux3QUi*|15E);6Vm$3?5?eFfivxKzm57x^Z-Dkh}a0gVzD2Z-dT|*-+5M zX+X|hq;eLKoZHwz^xtCeF4J7$(+AiRxJHurFiXPsaiU5tqo{u}9V##=Lq=olxiao$(k(VGu4)+cuZPzm~0{}O5^bI^2}Y09T0L1VGm4HeU%Ve!?UoI6gLY`~MO;S!Z`I3Z5-^)WKqEeU3PaV^ z)v}G~3Y#dk(yJqe%QY#R8phgO?v7z4z;&xE2wF^JrJuv6*9g!BuywDneJ`TzhZ$`@ z+{6KnH8<;D?%9E7yY+qr7g7)iF3p#*&^f^sR!H>O|Wp5MY+~#f~|g&{(@As0LxP;ZSjjY8<3H=_{Ht3aa>4R zwA9KHwlb?g@qeRNa}UQ?mLLKcyd|w1z(m*KZ0g560&xz$*{x><1zl-Vets)KtN?d zB5~*=muXP7#m$fbF1+Z+!$Us9RWj1AgEz7K4EGVy1z9&8!l=Mm1l2y-oPsjvYy|bd zO6f8rmy6#7a%eo)6Rliv)R!uJS8d(UQt=N0nlmr5sDOBN;Kf@|k z&Qh&%q>0~~*Z>zk9HM#@UI#n~uC!(O8Td~a5jWl!gTBG*zsVSUGHZEx6Y|6a&7n>a zSCh`!G}(`J3Mys&nz>?DR?d>eIblW)y#Z(1-6mK9xc4r5xAJ&r2P_6g8UM$~TKYSW z)8peWNXd@^GnXF||Br|e4EC!@TMz6gQchq@u+6&C33s?zx!jRB6Kkh6p~O$cHcKQ1 zdx}OtR-VuvCq)T`zfMjq|3~D+F7V((>M}6Srr0Jia4f0GY|xa(5FHMvp(QO?J|mL3L+dEr%N>`HW^|_p1dZ>9sYJNy#^+EPq$u8G zi4b{Q1PFb6u6{cnF)(ltV1_`jv2HuIc@gm%5MU-?_;Cr(O8At7pONr$5lymI^sysalanjGK9gYQ|v# z2L3{GGfQ)m7b*0IVZVeAO4ughLlQpB5_3X9I~r28s2iywGADRviI>xjkRGkr3~eokP7Cj@Mu~(xXfHU(~kyiWD^4W^Q*-ofNddT1!MJ3Z6W zgKqX@z|dqb1}xJ%gP(y^YH0UR_dr*Fx~pd$?-mW+ol58(aA zqNC>5ubmaw*qAF-aI=Ga!Cfb>-HUfQ`7We+x`+517v*X|W;gZ}3^>LvD8vOU*b zSHY|6PT?$Wi=*ljcO@5;YIRgY0yh@kWq`aEU1x)U=9n&iGm72hs9y8pE8eV@&mETZ zO+yJ>Hi(bU=azU`2JdW;Omi7#lV*9BH103ADfOp+{Jl@Gt!MC;t0HJi>w5}!dh>YK zx|qw(?DXsa8G1WCb9wCzd0)P|6Hzm{ll+F6!NFvI3Zg&M+vj)f9=f8sGW~X}trIVO z)4Zau^6%L2DF1)Yd%yB)7yZVjxBT9xuY1d`#Xo!G?AyNhXyKKcKK#=UT>rcM|L8pV zj^p2c^80(fboSPFJ^HCT-~QD<{_v;2^TmPG^(&JOH-ACH?|GJ9Z`Pus}bbj*IOSb;n+g@__ z^>5quy4UZ!?8x0`uP*FAnn=y>@6O!$)t<-yc2oc6KmKz6xBkn6H}w6(hxc!P;!`8P zdU9goFOCi#*>%maSLWU~H1&y|u@k#~yyMow?DDN&e)K<|e$~@&xM%sI2k(71`u0O~ z3@V{!TQS$vE42^X0l$sEJaz=0!W85K=iL~bR-}9Jx6+{0|G@A4El6V#_ypo#ME)}3 z`+)nh4N488+!sLa&7k*P#4knsM&NuDI6ndG$H4n*)G-cR{(CR}z5#l5fZYZBPXOoh zs0S*lUIq9E@s~#VKESR*{HKWj6g2Mxozp1u`8K7xhOyJ}GrfEX;(a^~_Orf!*^Yv{ zD+{jw70eq@c$9^CFzcW57UnDn9r$?=>Q-k#%Hz(`x!%Rx0-o=wbwib{9+2F< z7RhlCJJ)NPe}uw^Y~gT+^2N2}O6YY@HL>%xp@3pD)y0nD^x673bpH2g-j8bL)>n;f zarA|r=xOHjSN2Aju0*u5+T*H^aOyW^%dl#}I8wuYmKQgF6NUS1eWB{{yy3au-TVRy z1_BTkBnN)>S1zxB5q^1fD@ZH_h5A|F1^o`l1x&1L+gdMiE@0XisBS^{p5)pR^>q}j z!NRqRS}B>pRMd5@I&10G$55z7-@@9$LbjeuFx9>6eD!EKyBDZeo(t8|{8J#-;1})M z`QGfj9JBVm>N@zWg6E>ccS+WMxpNvM4pft{Mwq0c?}DD|{mw^0>ZbLRveukNCVa*7 zA3-EAV9{BMCrb5U>soJl&ThnHZtIR(`zbWP^2l%0im-ypv*q6eq3xEC9inKFsJ*xv z-cHb1BI|?I=X>4rHWUj4FG_puH$KnMud>aeXwXUED+T)HO+8n#_UKEWPoR(&Eo4Xa zI12No0lruLw^7O#p>poEvuLRQ=~$!(wF^z64jzTKZ#g`KB7v2wUBu4X=YIk8%OIAq zJ+s8@Xho-tVGPPY?;X&FO%Z{xRYeCaDiB`<^5tQ>^xH_)?8B){wauc{ei^g}D5Bag z{(aCVLBiQovBbQ&$GK)|&i8_wJiXFf0e3)oxE9Hq@x44QM)C?p&*!aq31@pH?mRx* zK`h*>iCce)UKwBR@b1Q^I}*-RMKxYVKKkR83!c4x%i{$?B`=%zQoPdByZWB4d$0Bi z8PDE*1xPm_Gkz+$Sn|DldhY%CQB8PQvEs`tn(9WmAKJr3pecWk(~O4f@ldz3i)(n( zFn&q~Um{6+da;h%*F1AEnul8mvxRiRIf?6=^0=`~l$sU!-VEws{v}e}hUbZAW zF4=pcJ$$`FFiUdxBx=H*MVX(pa4zNzbY2=)&`A-WS979;UH3XKksFuv{bgQCF0mFE^JH+dG}q1Twf!5ADSTiQG%!pyY6OrdQ@w&|U^#D3f+@d`xRK4-(#F zVcw@S;0u4YU&NA8Zoc%) zmE7nlhTg-iV&*Gf0QBDL+!_$8{A|%`nq__x2&0zr<+G~^QWFXZZ+HgVNp`@(flWB$ zay!?HZl#eVY34pn>czIvdDhWapmG3(M{^j&)7-%Ec!igI5cNyo0c%&?10OX?)%p5|3eD+UaF8-cn)F!=0Ai zVu{>K@;0(v$ z|I^tnsX^a~YWZWwvzB}&oNIRN+U4OkQ?@b7PpgCy>|tb6cX+(f*7tzs9L2?N*gM_h z4STcvR!cTz>j*7EQjC&;LkA+ll+{;7hT`OW)*;2-?mfmd({=>nGnYK&Q*^TFDBoz` zUIo(WP!0sAR#m)qva4s{MN}n%v37s7k|&%QsQ_jK>hQDmMJhOFM{FHps(N0` zxQJRC3mPyg;T)F%*!?0JO@g)BoN%ru%`R9!T_^m}$k15h0soJ7OV6&MopgqUi?t;p z{FYr{F6oVUpk-7K<_S)&km!*13o~HJc$SDszHWQao1m6V7Xcr{T~wTo<4B zPS4ScHSV+Vk`vx|#&{99C0Kt(mQx-%ukGn@pcLXz!0O{m0v@mI#ci@4Zn{t7QfuQd z;v*-en^m0q$~ zMhG8eBwpQ2==ZcW$lr8bmss!{6j)0)4m45$;acI_S;3nzyA8Vyh@}rbS zr+?vuT;#2J9rcW;7BE`WiVEKe%Bn>cGc@Tzgi#BXEs6Ex#P(39rMa@|5uFzmz|+_S05-*-jul)?w-ile#Q1f~Kd=M;QDAx9`s=%-hK26o4O zUjG~+b}=5kS5oAGG3?vcfD`W!{$920sPE#bnEm(Nx;{j2R+G2|jyEeGM{Hd2#^q`J z9>KjzM+Nn4{rCPS-`Bufj1t=48x0{m`!c7|3;;i)_;ciu67T2L*QIdJlouZH-<67` z^!+m2%GP%=>!-tlpgvH41Z{QDCT=umQFgWlrE3JI3;ovr_2FqYc%UvtNeB1w>F4}> zD#rW1^}ov_c?- z@L#v;!~H8=<_|}a@bA~v^(aG5l?9)#l;kiL=H_VB_b++ZHShDrK_L7)q~d7TVYF#h zT13t+;6)aW%q*Uj;GSdQb|uVHk3;x9aH0#n<$059KqX2?O8_4q@&0DsByLAZsoxM@ z@R1GcpahT?mY+<`qa4TJe-BUgij3%|R=mfZ_g{u1b#B=h5ZS0KyIMkO$;TckxB983 n2TiM#uFA61i2wWV|5y&Z+EI8?s6Ki9|FJ#zzYYIqaNvId@6<0u literal 0 HcmV?d00001 diff --git a/dogfood/Microsoft.TestPlatform.TranslationLayer.E2ETest/Adapter/Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface.dll b/dogfood/Microsoft.TestPlatform.TranslationLayer.E2ETest/Adapter/Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface.dll new file mode 100644 index 0000000000000000000000000000000000000000..886b05b5ea79191ba27c3a825d3c9506673e4a47 GIT binary patch literal 8192 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 literal 0 HcmV?d00001 diff --git a/dogfood/Microsoft.TestPlatform.TranslationLayer.E2ETest/Adapter/Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.dll b/dogfood/Microsoft.TestPlatform.TranslationLayer.E2ETest/Adapter/Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.dll new file mode 100644 index 0000000000000000000000000000000000000000..df77943e56ea4e4928bc54ac34d38c9a5c283261 GIT binary patch literal 10240 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 F{tsXb$aeq$ literal 0 HcmV?d00001 diff --git a/dogfood/Microsoft.TestPlatform.TranslationLayer.E2ETest/Adapter/Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll b/dogfood/Microsoft.TestPlatform.TranslationLayer.E2ETest/Adapter/Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll new file mode 100644 index 0000000000000000000000000000000000000000..a1eb785fe4934ea354d48fed5628fd5ad88a8f7a GIT binary patch literal 6656 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 literal 0 HcmV?d00001 diff --git a/dogfood/Microsoft.TestPlatform.TranslationLayer.E2ETest/Adapter/Microsoft.VisualStudio.TestPlatform.TestFramework.dll b/dogfood/Microsoft.TestPlatform.TranslationLayer.E2ETest/Adapter/Microsoft.VisualStudio.TestPlatform.TestFramework.dll new file mode 100644 index 0000000000000000000000000000000000000000..093fae050f406ac44d342f6bb9a4674521361316 GIT binary patch literal 50176 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 literal 0 HcmV?d00001 diff --git a/dogfood/Microsoft.TestPlatform.TranslationLayer.E2ETest/Microsoft.TestPlatform.TranslationLayer.E2ETest.xproj b/dogfood/Microsoft.TestPlatform.TranslationLayer.E2ETest/Microsoft.TestPlatform.TranslationLayer.E2ETest.xproj new file mode 100644 index 0000000000..1e693ee98c --- /dev/null +++ b/dogfood/Microsoft.TestPlatform.TranslationLayer.E2ETest/Microsoft.TestPlatform.TranslationLayer.E2ETest.xproj @@ -0,0 +1,19 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 4ec14041-7804-4840-ae70-98babdc8b0e2 + Microsoft.TestPlatform.TranslationLayer.E2ETest + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\ + v4.5.2 + + + 2.0 + + + diff --git a/dogfood/Microsoft.TestPlatform.TranslationLayer.E2ETest/Program.cs b/dogfood/Microsoft.TestPlatform.TranslationLayer.E2ETest/Program.cs new file mode 100644 index 0000000000..8a35d42ea4 --- /dev/null +++ b/dogfood/Microsoft.TestPlatform.TranslationLayer.E2ETest/Program.cs @@ -0,0 +1,178 @@ +using Microsoft.TestPlatform.VsTestConsole.TranslationLayer; +using Microsoft.TestPlatform.VsTestConsole.TranslationLayer.Interfaces; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.TestPlatform.TranslationLayer.E2ETest +{ + public class Program + { + public static void Main(string[] args) + { + // Spawn of vstest.console with a run tests from the current execting folder. + var executingLocation = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName); + var runnerLocation = Path.Combine(executingLocation, "vstest.console.exe"); + var testadapterPath = Path.Combine(executingLocation, "Adapter\\Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.dll"); + var testAssembly = Path.Combine(executingLocation, "UnitTestProject.dll"); + + IVsTestConsoleWrapper consoleWrapper = new VsTestConsoleWrapper(runnerLocation); + + consoleWrapper.StartSession(); + consoleWrapper.InitializeExtensions(new List() { testadapterPath }); + + var testCases = DiscoverTests(new List() { testAssembly }, consoleWrapper); + Console.WriteLine("Discovered Tests Count: " + testCases?.Count()); + Console.WriteLine("Discovered Test: " + testCases?.FirstOrDefault()?.DisplayName); + + Console.WriteLine("-------------------------------------------------------"); + + var testresults = RunSelectedTests(consoleWrapper, testCases); + Console.WriteLine("Run Selected Tests Count: " + testresults?.Count()); + Console.WriteLine("Run Selected Test Result: " + testresults?.FirstOrDefault()?.TestCase?.DisplayName + " :" + testresults?.FirstOrDefault()?.Outcome); + + Console.WriteLine("-------------------------------------------------------"); + + var testResults = RunAllTests(consoleWrapper, new List() { testAssembly }); + + Console.WriteLine("Run All Test Count: " + testresults?.Count()); + Console.WriteLine("Run All Test Result: " + testresults?.FirstOrDefault()?.TestCase?.DisplayName + " :" + testresults?.FirstOrDefault()?.Outcome); + + Console.WriteLine("-------------------------------------------------------"); + } + + static IEnumerable DiscoverTests(IEnumerable sources, IVsTestConsoleWrapper consoleWrapper) + { + var waitHandle = new AutoResetEvent(false); + var handler = new DiscoveryEventHandler(waitHandle); + consoleWrapper.DiscoverTests(sources, null, handler); + + waitHandle.WaitOne(); + + return handler.DiscoveredTestCases; + } + + static IEnumerable RunSelectedTests(IVsTestConsoleWrapper consoleWrapper, IEnumerable testCases) + { + var waitHandle = new AutoResetEvent(false); + var handler = new RunEventHandler(waitHandle); + consoleWrapper.RunTests(testCases, null, handler); + + waitHandle.WaitOne(); + return handler.TestResults; + } + + static IEnumerable RunAllTests(IVsTestConsoleWrapper consoleWrapper, IEnumerable sources) + { + var waitHandle = new AutoResetEvent(false); + var handler = new RunEventHandler(waitHandle); + consoleWrapper.RunTests(sources, null, handler); + + waitHandle.WaitOne(); + return handler.TestResults; + } + } + + public class DiscoveryEventHandler : ITestDiscoveryEventsHandler + { + private AutoResetEvent waitHandle; + + public List DiscoveredTestCases { get; private set; } + + public DiscoveryEventHandler(AutoResetEvent waitHandle) + { + this.waitHandle = waitHandle; + this.DiscoveredTestCases = new List(); + } + + public void HandleDiscoveredTests(IEnumerable discoveredTestCases) + { + Console.WriteLine("Discovery: " + discoveredTestCases.FirstOrDefault()?.DisplayName); + + if (discoveredTestCases != null) + { + this.DiscoveredTestCases.AddRange(discoveredTestCases); + } + } + + public void HandleDiscoveryComplete(long totalTests, IEnumerable lastChunk, bool isAborted) + { + if (lastChunk != null) + { + this.DiscoveredTestCases.AddRange(lastChunk); + } + + Console.WriteLine("DiscoveryComplete"); + waitHandle.Set(); + } + + public void HandleLogMessage(TestMessageLevel level, string message) + { + Console.WriteLine("Discovery Message: " + message); + } + + public void HandleRawMessage(string rawMessage) + { + // No op + } + } + + public class RunEventHandler : ITestRunEventsHandler + { + private AutoResetEvent waitHandle; + + public List TestResults { get; private set; } + + public RunEventHandler(AutoResetEvent waitHandle) + { + this.waitHandle = waitHandle; + this.TestResults = new List(); + } + + public void HandleLogMessage(TestMessageLevel level, string message) + { + Console.WriteLine("Run Message: " + message); + } + + public void HandleTestRunComplete( + TestRunCompleteEventArgs testRunCompleteArgs, + TestRunChangedEventArgs lastChunkArgs, + ICollection runContextAttachments, + ICollection executorUris) + { + if (lastChunkArgs != null && lastChunkArgs.NewTestResults != null) + { + this.TestResults.AddRange(lastChunkArgs.NewTestResults); + } + + Console.WriteLine("TestRunComplete"); + waitHandle.Set(); + } + + public void HandleTestRunStatsChange(TestRunChangedEventArgs testRunChangedArgs) + { + if (testRunChangedArgs != null && testRunChangedArgs.NewTestResults != null) + { + this.TestResults.AddRange(testRunChangedArgs.NewTestResults); + } + } + + public void HandleRawMessage(string rawMessage) + { + // No op + } + + public int LaunchProcessWithDebuggerAttached(TestProcessStartInfo testProcessStartInfo) + { + // No op + return -1; + } + } +} \ No newline at end of file diff --git a/dogfood/Microsoft.TestPlatform.TranslationLayer.E2ETest/Properties/AssemblyInfo.cs b/dogfood/Microsoft.TestPlatform.TranslationLayer.E2ETest/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..c658a544f2 --- /dev/null +++ b/dogfood/Microsoft.TestPlatform.TranslationLayer.E2ETest/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +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: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Microsoft.TestPlatform.TranslationLayer.E2ETest")] +[assembly: AssemblyTrademark("")] + +// 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("4ec14041-7804-4840-ae70-98babdc8b0e2")] diff --git a/dogfood/Microsoft.TestPlatform.TranslationLayer.E2ETest/project.json b/dogfood/Microsoft.TestPlatform.TranslationLayer.E2ETest/project.json new file mode 100644 index 0000000000..02a35b21d8 --- /dev/null +++ b/dogfood/Microsoft.TestPlatform.TranslationLayer.E2ETest/project.json @@ -0,0 +1,38 @@ +{ + "version": "1.0.0-*", + "buildOptions": { + "emitEntryPoint": true, + "copyToOutput": { + "include": [ + "Adapter", + // Have to drop a time stamped version of testhost config because the build system does not honour the + // users config file when building from a dependent exe. It does honor while building from testhost.x86 project though. + "testhost.x86.exe.config", + "testhost.exe.config" + + ] + } + }, + + "frameworks": { + "net46": { + "dependencies": { + "UnitTestProject": { + "target": "project" + } + } + } + }, + "dependencies": { + "Microsoft.TestPlatform.Client": "15.0.0-*", + "Microsoft.TestPlatform.Common": "15.0.0-*", + "Microsoft.TestPlatform.CommunicationUtilities": "15.0.0-*", + "Microsoft.TestPlatform.CoreUtilities": "15.0.0-*", + "Microsoft.TestPlatform.CrossPlatEngine": "15.0.0-*", + "Microsoft.TestPlatform.ObjectModel": "15.0.0-*", + "Microsoft.TestPlatform.VsTestConsole.TranslationLayer": "15.0.0-*", + "testhost": "15.0.0-*", + "testhost.x86": "15.0.0-*", + "vstest.console": "15.0.0-*" + } +} diff --git a/dogfood/Microsoft.TestPlatform.TranslationLayer.E2ETest/testhost.exe.config b/dogfood/Microsoft.TestPlatform.TranslationLayer.E2ETest/testhost.exe.config new file mode 100644 index 0000000000..9f82ee613f --- /dev/null +++ b/dogfood/Microsoft.TestPlatform.TranslationLayer.E2ETest/testhost.exe.config @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dogfood/Microsoft.TestPlatform.TranslationLayer.E2ETest/testhost.x86.exe.config b/dogfood/Microsoft.TestPlatform.TranslationLayer.E2ETest/testhost.x86.exe.config new file mode 100644 index 0000000000..8846256c18 --- /dev/null +++ b/dogfood/Microsoft.TestPlatform.TranslationLayer.E2ETest/testhost.x86.exe.config @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dogfood/UnitTestProject/Properties/AssemblyInfo.cs b/dogfood/UnitTestProject/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..0c91032fb2 --- /dev/null +++ b/dogfood/UnitTestProject/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +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("UnitTestProject")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("UnitTestProject")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[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("0cc51428-b665-47b0-a093-042d31785928")] + +// 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/dogfood/UnitTestProject/UnitTest.cs b/dogfood/UnitTestProject/UnitTest.cs new file mode 100644 index 0000000000..43d5dffdf0 --- /dev/null +++ b/dogfood/UnitTestProject/UnitTest.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace UnitTestProject +{ + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class UnitTest + { + [TestMethod] + public void TestMethod1() + { + } + } +} diff --git a/dogfood/UnitTestProject/UnitTestProject.xproj b/dogfood/UnitTestProject/UnitTestProject.xproj new file mode 100644 index 0000000000..d159fb7ed2 --- /dev/null +++ b/dogfood/UnitTestProject/UnitTestProject.xproj @@ -0,0 +1,21 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + 0CC51428-B665-47B0-A093-042D31785928 + UnitTestProject + .\obj + ..\..\artifacts\ + v4.5.2 + + + + 2.0 + + + diff --git a/dogfood/UnitTestProject/packages.config b/dogfood/UnitTestProject/packages.config new file mode 100644 index 0000000000..8b1e1f4e26 --- /dev/null +++ b/dogfood/UnitTestProject/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/dogfood/UnitTestProject/project.json b/dogfood/UnitTestProject/project.json new file mode 100644 index 0000000000..dd923ed714 --- /dev/null +++ b/dogfood/UnitTestProject/project.json @@ -0,0 +1,27 @@ +{ + "version": "1.0.0-*", + + "dependencies": { + "MSTest.TestFramework": "1.0.0-preview" + }, + + "frameworks": { + "netcoreapp1.0": { + "imports": [ + "dnxcore50", + "portable-net45+win8" + ], + "dependencies": { + "Microsoft.NETCore.App": { + "type": "platform", + "version": "1.0.0-rc2-3002702" + } + } + }, + "net46": { + "frameworkAssemblies": { + "System.Runtime": "" + } + } + } +} diff --git a/global.json b/global.json new file mode 100644 index 0000000000..c21a8dc79f --- /dev/null +++ b/global.json @@ -0,0 +1,4 @@ +{ + "projects": [ "src", "test" ], + "packages": "packages" +} diff --git a/scripts/DisableUnitTesting.bat b/scripts/DisableUnitTesting.bat new file mode 100644 index 0000000000..225df46341 --- /dev/null +++ b/scripts/DisableUnitTesting.bat @@ -0,0 +1,22 @@ + +@IF NOT DEFINED _ECHO @ECHO OFF + +@ECHO. +@ECHO Disabling Unit Testing + +PUSHD "%~dp0" + +setlocal enableextensions disabledelayedexpansion + + set "search=//"outputName" + set "replace="outputName" + + set "textFile="..\src\Microsoft.TestPlatform.ObjectModel\project.json"" + + for /f "delims=" %%i in ('type "%textFile%" ^& break ^> "%textFile%" ') do ( + set "line=%%i" + setlocal enabledelayedexpansion + set "line=!line:%search%=%replace%!" + >>"%textFile%" echo(!line! + endlocal + ) \ No newline at end of file diff --git a/scripts/EnableUnitTesting.bat b/scripts/EnableUnitTesting.bat new file mode 100644 index 0000000000..65d60a80ca --- /dev/null +++ b/scripts/EnableUnitTesting.bat @@ -0,0 +1,22 @@ + +@IF NOT DEFINED _ECHO @ECHO OFF + +@ECHO. +@ECHO Enabling Unit Testing + +PUSHD "%~dp0" + +setlocal enableextensions disabledelayedexpansion + + set "search="outputName" + set "replace=//"outputName" + + set "textFile="..\src\Microsoft.TestPlatform.ObjectModel\project.json"" + + for /f "delims=" %%i in ('type "%textFile%" ^& break ^> "%textFile%" ') do ( + set "line=%%i" + setlocal enabledelayedexpansion + set "line=!line:%search%=%replace%!" + >>"%textFile%" echo(!line! + endlocal + ) \ No newline at end of file diff --git a/scripts/build.ps1 b/scripts/build.ps1 new file mode 100644 index 0000000000..66e2f03eb8 --- /dev/null +++ b/scripts/build.ps1 @@ -0,0 +1,208 @@ +# Copyright (c) Microsoft. All rights reserved. +# Build script for Test Platform. + +[CmdletBinding()] +Param( + [Parameter(Mandatory=$false)] + [ValidateSet("Debug", "Release")] + [Alias("c")] + [System.String] $Configuration = "Debug" +) + +$ErrorActionPreference = "Stop" + +# +# Variables +# +Write-Verbose "Setup environment variables." +$env:TP_ROOT_DIR = (Get-Item (Split-Path $MyInvocation.MyCommand.Path)).Parent.FullName +$env:TP_TOOLS_DIR = Join-Path $env:TP_ROOT_DIR "tools" +$env:TP_PACKAGES_DIR = Join-Path $env:TP_ROOT_DIR "packages" +$env:TP_OUT_DIR = Join-Path $env:TP_ROOT_DIR "artifacts" + +# +# Dotnet configuration +# +# Disable first run since we want to control all package sources +Write-Verbose "Setup dotnet configuration." +$env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE = 1 +# Dotnet build doesn't support --packages yet. See https://github.com/dotnet/cli/issues/2712 +$env:NUGET_PACKAGES = $env:TP_PACKAGES_DIR + +# +# Build configuration +# +# Folders to build. TODO move to props +Write-Verbose "Setup build configuration." +$SourceFolders = @("src", "test") +$TargetFramework = "net46" +$TargetRuntime = "win7-x64" + +function Write-Log ([string] $message) +{ + $currentColor = $Host.UI.RawUI.ForegroundColor + $Host.UI.RawUI.ForegroundColor = "Green" + if ($message) + { + Write-Output "... $message" + } + $Host.UI.RawUI.ForegroundColor = $currentColor +} + +function Write-VerboseLog([string] $message) +{ + Write-Verbose $message +} + +function Remove-Tools +{ +} + +function Install-DotNetCli +{ + $timer = Start-Timer + + Write-Log "Install-DotNetCli: Get dotnet-install.ps1 script..." + $dotnetInstallRemoteScript = "https://raw.githubusercontent.com/dotnet/cli/rel/1.0.0/scripts/obtain/dotnet-install.ps1" + $dotnetInstallScript = Join-Path $env:TP_TOOLS_DIR "dotnet-install.ps1" + if (-not (Test-Path $env:TP_TOOLS_DIR)) { + New-Item $env:TP_TOOLS_DIR -Type Directory + } + + (New-Object System.Net.WebClient).DownloadFile($dotnetInstallRemoteScript, $dotnetInstallScript) + + if (-not (Test-Path $dotnetInstallScript)) { + Write-Error "Failed to download dotnet install script." + } + + Unblock-File $dotnetInstallScript + + Write-Log "Install-DotNetCli: Get the latest dotnet cli toolset..." + $dotnetInstallPath = Join-Path $env:TP_TOOLS_DIR "dotnet" + & $dotnetInstallScript -InstallDir $dotnetInstallPath -NoPath + + Write-Log "Install-DotNetCli: Complete. {$(Get-ElapsedTime($timer))}" +} + +function Restore-Package +{ + $timer = Start-Timer + Write-Log "Restore-Package: Start restoring packages to $env:TP_PACKAGES_DIR." + $dotnetExe = Get-DotNetPath + + foreach ($src in $SourceFolders) { + Write-Log "Restore-Package: Restore for source directory: $src" + & $dotnetExe restore $src --packages $env:TP_PACKAGES_DIR + } + + Write-Log "Restore-Package: Complete. {$(Get-ElapsedTime($timer))}" +} + +function Invoke-Build +{ + $timer = Start-Timer + Write-Log "Invoke-Build: Start build." + $dotnetExe = Get-DotNetPath + + foreach ($src in $SourceFolders) { + # Invoke build for each project.json since we want a custom output + # path. + Write-Log ".. Build: Source directory: $src" + #foreach ($fx in $TargetFramework) { + #Get-ChildItem -Recurse -Path $src -Include "project.json" | ForEach-Object { + #Write-Log ".. .. Build: Source: $_" + #$binPath = Join-Path $env:TP_OUT_DIR "$fx\$src\$($_.Directory.Name)\bin" + #$objPath = Join-Path $env:TP_OUT_DIR "$fx\$src\$($_.Directory.Name)\obj" + #Write-Verbose "$dotnetExe build $_ --output $binPath --build-base-path $objPath --framework $fx" + #& $dotnetExe build $_ --output $binPath --build-base-path $objPath --framework $fx + #Write-Log ".. .. Build: Complete." + #} + #} + Write-Verbose "$dotnetExe build $src\**\project.json --configuration $Configuration" + & $dotnetExe build $_ $src\**\project.json --configuration $Configuration + } + + Write-Log "Invoke-Build: Complete. {$(Get-ElapsedTime($timer))}" +} + +function Publish-Package +{ + $timer = Start-Timer + Write-Log "Publish-Package: Started." + $dotnetExe = Get-DotNetPath + $packageDir = Get-PackageDirectory + + Write-Log ".. Package: Publish package\project.json" + Write-Verbose "$dotnetExe publish src\package\project.json --runtime win7-x64 --framework net46 --no-build --configuration $Configuration --out $packageDir" + & $dotnetExe publish src\package\project.json --runtime win7-x64 --framework net46 --no-build --configuration $Configuration --output $packageDir + + Write-Log "Publish-Package: Complete. {$(Get-ElapsedTime($timer))}" +} + +function Create-VsixPackage +{ + $timer = Start-Timer + + # Copy vsix manifests + $packageDir = Get-PackageDirectory + $vsixManifests = @("*Content_Types*.xml", + "extension.vsixmanifest", + "testhost.x86.exe.config", + "testhost.exe.config") + foreach ($file in $vsixManifests) { + Copy-Item src\package\$file $packageDir -Force + } + + # Copy legacy dependencies + $legacyDir = Join-Path $env:TP_PACKAGES_DIR "Microsoft.Internal.TestPlatform.Extensions\15.0.0\contentFiles\any\any" + Copy-Item -Recurse $legacyDir\* $packageDir -Force + + # Zip the folder + # TODO remove vsix creator + & src\Microsoft.TestPlatform.VSIXCreator\bin\$Configuration\net461\Microsoft.TestPlatform.VSIXCreator.exe $packageDir $env:TP_OUT_DIR\$Configuration + + Write-Log "Publish-Package: Complete. {$(Get-ElapsedTime($timer))}" +} + +# +# Helper functions +# +function Get-DotNetPath +{ + $dotnetPath = Join-Path $env:TP_TOOLS_DIR "dotnet\dotnet.exe" + if (-not (Test-Path $dotnetPath)) { + Write-Error "Dotnet.exe not found at $dotnetPath. Did the dotnet cli installation succeed?" + } + + return $dotnetPath +} + +function Get-PackageDirectory +{ + return $(Join-Path $env:TP_OUT_DIR "$Configuration\$TargetFramework\$TargetRuntime") +} + +function Start-Timer +{ + return [System.Diagnostics.Stopwatch]::StartNew() +} + +function Get-ElapsedTime([System.Diagnostics.Stopwatch] $timer) +{ + $timer.Stop() + return $timer.Elapsed +} + +# Execute build +$timer = Start-Timer +Write-Log "Build started: args = '$args'" +Write-Log "Test platform environment variables: " +Get-ChildItem env: | Where-Object -FilterScript { $_.Name.StartsWith("TP_") } | Format-Table + +Install-DotNetCli +Restore-Package +Invoke-Build +Publish-Package +Create-VsixPackage + +Write-Log "Build complete. {$(Get-ElapsedTime($timer))}" diff --git a/scripts/key.snk b/scripts/key.snk new file mode 100644 index 0000000000000000000000000000000000000000..110b59c7b0d27388353dcf4116f721595f473e58 GIT binary patch literal 160 zcmV;R0AK$ABme*efB*oL000060ssI2Bme+XQ$aBR1ONa500968(fU`!uG#RTE`+KN zuKf+^=>2N!kB9pMc5H)8nUWr|JLj6&)!f0|n$k8CAp(#KayILlN=pn$R@96PlTucm;!K;}lU1BV%Wh@=~);)AxZ!P8VeqOH+#FjlK9EuV{ OWf&lBz>_phTGEsG5JRQ_ literal 0 HcmV?d00001 diff --git a/src/Microsoft.TestPlatform.Client/DesignMode/DesignModeClient.cs b/src/Microsoft.TestPlatform.Client/DesignMode/DesignModeClient.cs new file mode 100644 index 0000000000..9120decfa5 --- /dev/null +++ b/src/Microsoft.TestPlatform.Client/DesignMode/DesignModeClient.cs @@ -0,0 +1,238 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Client.DesignMode +{ + using System; + using System.Collections.Generic; + using System.Threading; + using System.Threading.Tasks; + + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; + using Microsoft.VisualStudio.TestPlatform.Client.RequestHelper; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + + /// + /// The design mode client. + /// + public class DesignModeClient : IDesignModeClient + { + private readonly ICommunicationManager communicationManager; + + private readonly IDataSerializer dataSerializer; + + private Action onAckMessageReceived; + + private object ackLockObject = new object(); + + /// + /// The timeout for the client to connect to the server. + /// + private const int ClientListenTimeOut = 5 * 1000; + + /// + /// Initializes a new instance of the class. + /// + public DesignModeClient() + : this(new SocketCommunicationManager(), JsonDataSerializer.Instance) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The communication manager. + /// + internal DesignModeClient(ICommunicationManager communicationManager, IDataSerializer dataSerializer) + { + this.communicationManager = communicationManager; + this.dataSerializer = dataSerializer; + } + + /// + /// Property exposing the Instance + /// + public static IDesignModeClient Instance { get; private set; } + + /// + /// Initializes DesignMode + /// + public static void Initialize() + { + Instance = new DesignModeClient(); + } + + /// + /// Creates a client and waits for server to accept connection asynchronously + /// + /// port number to connect + public void ConnectToClientAndProcessRequests(int port, ITestRequestManager testRequestManager) + { + EqtTrace.Info("Trying to connect to server on port : {0}", port); + this.communicationManager.SetupClientAsync(port); + this.communicationManager.SendMessage(MessageType.SessionConnected); + + // Wait for the connection to the server and listen for requests. + if (this.communicationManager.WaitForServerConnection(ClientListenTimeOut)) + { + this.ProcessRequests(testRequestManager); + } + else + { + EqtTrace.Info("Client timed out while connecting to the server."); + this.Dispose(); + throw new TimeoutException(); + } + } + + /// + /// End the connection + /// + public void Dispose() + { + this.communicationManager?.StopClient(); + } + + /// + /// Process Requests from the IDE + /// + /// + private void ProcessRequests(ITestRequestManager testRequestManager) + { + var isSessionEnd = false; + // Todo : Add exception handling here. Do we want some way of notifying the caller if for some reason deserialization throws exception. + do + { + var message = this.communicationManager.ReceiveMessage(); + switch (message.MessageType) + { + case MessageType.VersionCheck: + { + // At this point, we cannot add stuff to object model like "ProtocolVersionMessage" + // as that cannot be acessed from testwindow which still uses TP-V1 + // we are sending a version number as an integer for now + // TODO: Find a better way without breaking TW which using TP-V1 + var payload = 1; + this.communicationManager.SendMessage(MessageType.VersionCheck, payload); + break; + } + + case MessageType.ExtensionsInitialize: + { + var extensionPaths = this.communicationManager.DeserializePayload>(message); + testRequestManager.InitializeExtensions(extensionPaths); + break; + } + + case MessageType.StartDiscovery: + { + var discoveryPayload = message.Payload.ToObject(); + testRequestManager.DiscoverTests(discoveryPayload, new DesignModeTestEventsRegistrar(this)); + break; + } + + case MessageType.GetTestRunnerProcessStartInfoForRunAll: + case MessageType.GetTestRunnerProcessStartInfoForRunSelected: + { + var testRunPayload = + this.communicationManager.DeserializePayload( + message); + this.StartTestRun(testRunPayload, testRequestManager, skipTestHostLaunch: true); + break; + } + + case MessageType.TestRunAllSourcesWithDefaultHost: + case MessageType.TestRunSelectedTestCasesDefaultHost: + { + var testRunPayload = + this.communicationManager.DeserializePayload( + message); + this.StartTestRun(testRunPayload, testRequestManager, skipTestHostLaunch: false); + break; + } + + case MessageType.CancelTestRun: + { + testRequestManager.CancelTestRun(); + break; + } + + case MessageType.AbortTestRun: + { + testRequestManager.AbortTestRun(); + break; + } + case MessageType.CustomTestHostLaunchCallback: + { + this.onAckMessageReceived?.Invoke(message); + break; + } + case MessageType.SessionEnd: + { + EqtTrace.Info("Session End message received from server. Closing the connection."); + isSessionEnd = true; + this.Dispose(); + break; + } + + default: + { + EqtTrace.Info("Invalid Message types"); + break; + } + } + } + while (!isSessionEnd); + } + + /// + /// Send a custom host launch message to IDE + /// + /// Payload required to launch a custom host + public int LaunchCustomHost(TestProcessStartInfo testProcessStartInfo) + { + lock (ackLockObject) + { + var waitHandle = new AutoResetEvent(false); + Message ackMessage = null; + this.onAckMessageReceived = (ackRawMessage) => + { + ackMessage = ackRawMessage; + waitHandle.Set(); + }; + + this.communicationManager.SendMessage(MessageType.CustomTestHostLaunch, testProcessStartInfo); + waitHandle.WaitOne(ClientListenTimeOut); + + this.onAckMessageReceived = null; + return this.dataSerializer.DeserializePayload(ackMessage); + } + } + + /// + /// Send the raw messages to IDE + /// + /// + public void SendRawMessage(string rawMessage) + { + this.communicationManager.SendRawMessage(rawMessage); + } + + private void StartTestRun(TestRunRequestPayload testRunPayload, ITestRequestManager testRequestManager, bool skipTestHostLaunch) + { + Task.Run( + delegate + { + testRequestManager.ResetOptions(); + + var customLauncher = skipTestHostLaunch ? + DesignModeTestHostLauncherFactory.GetCustomHostLauncherForTestRun(this, testRunPayload) : null; + + testRequestManager.RunTests(testRunPayload, customLauncher, new DesignModeTestEventsRegistrar(this)); + }); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Client/DesignMode/DesignModeTestEventsRegistrar.cs b/src/Microsoft.TestPlatform.Client/DesignMode/DesignModeTestEventsRegistrar.cs new file mode 100644 index 0000000000..a70742c33a --- /dev/null +++ b/src/Microsoft.TestPlatform.Client/DesignMode/DesignModeTestEventsRegistrar.cs @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Client.DesignMode +{ + using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + + /// + /// Registers the discovery and test run events for designmode flow + /// + public class DesignModeTestEventsRegistrar : ITestDiscoveryEventsRegistrar, ITestRunEventsRegistrar + { + private IDesignModeClient designModeClient; + + public DesignModeTestEventsRegistrar(IDesignModeClient designModeClient) + { + this.designModeClient = designModeClient; + } + + #region ITestDiscoveryEventsRegistrar + + public void RegisterDiscoveryEvents(IDiscoveryRequest discoveryRequest) + { + discoveryRequest.OnRawMessageReceived += OnRawMessageReceived; + } + + public void UnregisterDiscoveryEvents(IDiscoveryRequest discoveryRequest) + { + discoveryRequest.OnRawMessageReceived -= OnRawMessageReceived; + } + + #endregion + + #region ITestRunEventsRegistrar + + public void RegisterTestRunEvents(ITestRunRequest testRunRequest) + { + testRunRequest.OnRawMessageReceived += OnRawMessageReceived; + } + + public void UnregisterTestRunEvents(ITestRunRequest testRunRequest) + { + testRunRequest.OnRawMessageReceived -= OnRawMessageReceived; + } + + #endregion + + /// + /// RawMessage received handler for getting rawmessages directly from the host + /// + /// + /// RawMessage from the testhost + private void OnRawMessageReceived(object sender, string rawMessage) + { + // Directly send the data to translation layer instead of deserializing it here + this.designModeClient.SendRawMessage(rawMessage); + } + } +} diff --git a/src/Microsoft.TestPlatform.Client/DesignMode/DesignModeTestHostLauncher.cs b/src/Microsoft.TestPlatform.Client/DesignMode/DesignModeTestHostLauncher.cs new file mode 100644 index 0000000000..3b1b50ad9b --- /dev/null +++ b/src/Microsoft.TestPlatform.Client/DesignMode/DesignModeTestHostLauncher.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Client.DesignMode +{ + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + + /// + /// DesignMode TestHost Launcher for hosting of test process + /// + internal class DesignModeTestHostLauncher : ITestHostLauncher + { + private IDesignModeClient designModeClient; + + public DesignModeTestHostLauncher(IDesignModeClient designModeClient) + { + this.designModeClient = designModeClient; + } + + public virtual bool IsDebug => false; + + public int LaunchTestHost(TestProcessStartInfo defaultTestHostStartInfo) + { + return this.designModeClient.LaunchCustomHost(defaultTestHostStartInfo); + } + } + + /// + /// DesignMode Debug Launcher to use if debugging enabled + /// + internal class DesignModeDebugTestHostLauncher : DesignModeTestHostLauncher + { + public DesignModeDebugTestHostLauncher(IDesignModeClient designModeClient) : base(designModeClient) + { + } + + public override bool IsDebug => true; + } +} diff --git a/src/Microsoft.TestPlatform.Client/DesignMode/DesignModeTestHostLauncherFactory.cs b/src/Microsoft.TestPlatform.Client/DesignMode/DesignModeTestHostLauncherFactory.cs new file mode 100644 index 0000000000..84242af75e --- /dev/null +++ b/src/Microsoft.TestPlatform.Client/DesignMode/DesignModeTestHostLauncherFactory.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Client.DesignMode +{ + using Microsoft.VisualStudio.TestPlatform.Client.RequestHelper; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + + /// + /// Factory for providing the design mode test host launchers + /// + public static class DesignModeTestHostLauncherFactory + { + private static ITestHostLauncher defaultLauncher; + + private static ITestHostLauncher debugLauncher; + + public static ITestHostLauncher GetCustomHostLauncherForTestRun(IDesignModeClient designModeClient, TestRunRequestPayload testRunRequestPayload) + { + ITestHostLauncher testHostLauncher = null; + if(!testRunRequestPayload.DebuggingEnabled) + { + testHostLauncher = defaultLauncher = defaultLauncher ?? new DesignModeTestHostLauncher(designModeClient); + } + else + { + testHostLauncher = debugLauncher = debugLauncher ?? new DesignModeDebugTestHostLauncher(designModeClient); + } + + return testHostLauncher; + } + } +} diff --git a/src/Microsoft.TestPlatform.Client/DesignMode/IDesignModeClient.cs b/src/Microsoft.TestPlatform.Client/DesignMode/IDesignModeClient.cs new file mode 100644 index 0000000000..089ddfb976 --- /dev/null +++ b/src/Microsoft.TestPlatform.Client/DesignMode/IDesignModeClient.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Client.DesignMode +{ + using Microsoft.VisualStudio.TestPlatform.Client.RequestHelper; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using System; + + /// + /// The interface for design mode client. + /// + public interface IDesignModeClient : IDisposable + { + /// + /// Setups client based on port + /// + /// port number to connect + void ConnectToClientAndProcessRequests(int port, ITestRequestManager testRequestManager); + + /// + /// Send the raw messages to IDE + /// + /// + void SendRawMessage(string rawMessage); + + /// + /// Send a custom host launch message to IDE + /// + /// Default TestHost Start Info + int LaunchCustomHost(TestProcessStartInfo defaultTestHostStartInfo); + } +} diff --git a/src/Microsoft.TestPlatform.Client/Discovery/DiscoveryRequest.cs b/src/Microsoft.TestPlatform.Client/Discovery/DiscoveryRequest.cs new file mode 100644 index 0000000000..ccb215991d --- /dev/null +++ b/src/Microsoft.TestPlatform.Client/Discovery/DiscoveryRequest.cs @@ -0,0 +1,402 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Client.Discovery +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + using Microsoft.VisualStudio.TestPlatform.Utilities; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; + + /// + /// The discovery request. + /// + public sealed class DiscoveryRequest : IDiscoveryRequest, ITestDiscoveryEventsHandler + { + /// + /// Initializes a new instance of the class. + /// + /// The criteria. + /// The discovery manager. + internal DiscoveryRequest(DiscoveryCriteria criteria, IProxyDiscoveryManager discoveryManager) + { + this.DiscoveryCriteria = criteria; + this.DiscoveryManager = discoveryManager; + } + + /// + /// Start the discovery request + /// + void IDiscoveryRequest.DiscoverAsync() + { + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose("DiscoveryRequest.DiscoverAsync: Starting."); + } + + lock (this.syncObject) + { + if (this.bDisposed) + { + throw new ObjectDisposedException("DiscoveryRequest"); + } + + // Reset the discovery completion event + this.discoveryCompleted.Reset(); + + this.discoveryInProgress = true; + try + { + this.DiscoveryManager.DiscoverTests(this.DiscoveryCriteria, this); + } + catch + { + this.discoveryInProgress = false; + throw; + } + } + + if (EqtTrace.IsInfoEnabled) + { + EqtTrace.Info("DiscoveryRequest.DiscoverAsync: Started."); + } + } + + /// + /// Aborts the test discovery. + /// + void IDiscoveryRequest.Abort() + { + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose("DiscoveryRequest.Abort: Aborting."); + } + + lock (this.syncObject) + { + if (this.bDisposed) + { + throw new ObjectDisposedException("DiscoveryRequest"); + } + + if (this.discoveryInProgress) + { + this.DiscoveryManager.Abort(); + } + else + { + if (EqtTrace.IsInfoEnabled) + { + EqtTrace.Info("DiscoveryRequest.Abort: No operation to abort."); + } + return; + } + } + + if (EqtTrace.IsInfoEnabled) + { + EqtTrace.Info("DiscoveryRequest.Abort: Aborted."); + } + } + + /// + /// Wait for discovery completion completion + /// + /// The timeout. + bool IRequest.WaitForCompletion(int timeout) + { + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose("DiscoveryRequest.WaitForCompletion: Waiting with timeout {0}.", timeout); + } + + if (this.bDisposed) + { + throw new ObjectDisposedException("DiscoveryRequest"); + } + + // This method is not synchronized as it can lead to dead-lock + // (the discoveryCompletionEvent cannot be raised unless that lock is released) + if (this.discoveryCompleted != null) + { + return this.discoveryCompleted.WaitOne(timeout); + } + + return true; + } + + /// + /// Raised when the test discovery completes. + /// + public event EventHandler OnDiscoveryComplete; + + /// + /// Raised when the message is received. + /// + /// TestRunMessageEventArgs should be renamed to more generic + public event EventHandler OnDiscoveryMessage; + + /// + /// Raised when new tests are discovered in this discovery request. + /// + public event EventHandler OnDiscoveredTests; + + /// + /// Raised when a discovery event related message is received from host + /// This is required if one wants to re-direct the message over the process boundary without any processing overhead + /// All the discovery events should come as raw messages as well as proper serialized events like OnDiscoveredTests + /// + public event EventHandler OnRawMessageReceived; + + /// + /// Specifies the discovery criterion + /// + public DiscoveryCriteria DiscoveryCriteria + { + get; + private set; + } + + /// + /// Get the status for the discovery + /// Returns true if discovery is in progress + /// + internal bool DiscoveryInProgress + { + get { return discoveryInProgress; } + } + + /// + /// Parent discovery manager + /// + internal IProxyDiscoveryManager DiscoveryManager { get; private set; } + + #region ITestDiscoveryEventsHandler Methods + + /// + /// Dispatch DiscoveryComplete event to listeners. + /// + public void HandleDiscoveryComplete(long totalTests, IEnumerable lastChunk, bool isAborted) + { + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose("DiscoveryRequest.DiscoveryComplete: Starting. Aborted:{0}, TotalTests:{1}", isAborted, totalTests); + } + lock (this.syncObject) + { + if (this.bDisposed) + { + if (EqtTrace.IsWarningEnabled) + { + EqtTrace.Warning("DiscoveryRequest.DiscoveryComplete: Ignoring as the object is disposed."); + } + return; + } + //If discovery event is already raised, ignore current one. + if (this.discoveryCompleted.WaitOne(0)) + { + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose("DiscoveryRequest.DiscoveryComplete:Ignoring duplicate DiscoveryComplete. Aborted:{0}, TotalTests:{1}", isAborted, totalTests); + } + return; + } + + try + { + // Raise onDiscoveredTests event if there are some tests in the last chunk. + // + // (We dont want to send the tests in the discovery complete event so that programming on top of + // RS client is easier i.e. user does not have to listen on discovery complete event.) + if (lastChunk != null && lastChunk.Count() > 0) + { + OnDiscoveredTests.SafeInvoke(this, new DiscoveredTestsEventArgs(lastChunk), "DiscoveryRequest.DiscoveryComplete"); + } + + OnDiscoveryComplete.SafeInvoke(this, new DiscoveryCompleteEventArgs(totalTests, isAborted), "DiscoveryRequest.DiscoveryComplete"); + } + finally + { + ManualResetEvent discoveryComplete = this.discoveryCompleted; + + // Notify the waiting handle that discovery is complete + if (discoveryComplete != null) + { + discoveryComplete.Set(); + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose("DiscoveryRequest.DiscoveryComplete: Notified the discovery complete event."); + } + } + else + { + if (EqtTrace.IsWarningEnabled) + { + EqtTrace.Warning("DiscoveryRequest.DiscoveryComplete: Discovery complete event was null."); + } + } + this.discoveryInProgress = false; + } + } + + if (EqtTrace.IsInfoEnabled) + { + EqtTrace.Info("DiscoveryRequest.DiscoveryComplete: Completed."); + } + } + + /// + /// Dispatch DiscoveredTest event to listeners. + /// + /// Instance of IDiscoveryManager that discovered tests. + /// Discovered test cases. + public void HandleDiscoveredTests(IEnumerable discoveredTestCases) + { + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose("DiscoveryRequest.SendDiscoveredTests: Starting."); + } + + lock (this.syncObject) + { + if (this.bDisposed) + { + if (EqtTrace.IsWarningEnabled) + { + EqtTrace.Warning("DiscoveryRequest.SendDiscoveredTests: Ignoring as the object is disposed."); + } + return; + } + + OnDiscoveredTests.SafeInvoke(this, new DiscoveredTestsEventArgs(discoveredTestCases), "DiscoveryRequest.OnDiscoveredTests"); + } + if (EqtTrace.IsInfoEnabled) + { + EqtTrace.Info("DiscoveryRequest.SendDiscoveredTests: Completed."); + } + } + + /// + /// Dispatch TestRunMessage event to listeners. + /// + /// Instnace of IDiscoveryManager that discovered tests + /// Ouput level of the message being sent. + /// Actual contents of the message + public void HandleLogMessage(TestMessageLevel level, string message) + { + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose("DiscoveryRequest.SendDiscoveryMessage: Starting."); + } + + lock (this.syncObject) + { + if (this.bDisposed) + { + if (EqtTrace.IsWarningEnabled) + { + EqtTrace.Warning("DiscoveryRequest.SendDiscoveryMessage: Ignoring as the object is disposed."); + } + return; + } + + OnDiscoveryMessage.SafeInvoke(this, new TestRunMessageEventArgs(level, message), "DiscoveryRequest.OnTestMessageRecieved"); + } + + if (EqtTrace.IsInfoEnabled) + { + EqtTrace.Info("DiscoveryRequest.SendDiscoveryMessage: Completed."); + } + } + + /// + /// Handle Raw message directly from the host + /// + /// + public void HandleRawMessage(string rawMessage) + { + this.OnRawMessageReceived?.Invoke(this, rawMessage); + } + + #endregion + + #region IDisposable implementation + + // Summary: + // Performs application-defined tasks associated with freeing, releasing, or + // resetting unmanaged resources. + public void Dispose() + { + Dispose(true); + + GC.SuppressFinalize(this); + } + + /// + /// Dispose the discovery request. + /// + /// + private void Dispose(bool disposing) + { + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose("DiscoveryRequest.Dispose: Starting."); + } + + lock (this.syncObject) + { + if (!this.bDisposed) + { + if (disposing) + { + if (this.discoveryCompleted != null) + { + this.discoveryCompleted.Dispose(); + } + } + + // Indicate that object has been disposed + this.discoveryCompleted = null; + this.bDisposed = true; + } + } + + if (EqtTrace.IsInfoEnabled) + { + EqtTrace.Info("DiscoveryRequest.Dispose: Completed."); + } + } + + #endregion + + #region privates fields + + /// + /// If this request has been disposed. + /// + private bool bDisposed = false; + + /// + /// It get set when current discovery request is completed. + /// + private ManualResetEvent discoveryCompleted = new ManualResetEvent(false); + + /// + /// Sync object for various operations + /// + private Object syncObject = new Object(); + + /// + /// Whether or not the test discovery is in progress. + /// + private bool discoveryInProgress; + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.Client/Execution/TestRunRequest.cs b/src/Microsoft.TestPlatform.Client/Execution/TestRunRequest.cs new file mode 100644 index 0000000000..ec77a77102 --- /dev/null +++ b/src/Microsoft.TestPlatform.Client/Execution/TestRunRequest.cs @@ -0,0 +1,448 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Client.Execution +{ + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + using Microsoft.VisualStudio.TestPlatform.Utilities; + using Resources = Microsoft.VisualStudio.TestPlatform.Client.Resources; + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Threading; + + public class TestRunRequest : ITestRunRequest, ITestRunEventsHandler + { + internal TestRunRequest(TestRunCriteria testRunCriteria, IProxyExecutionManager executionManager) + { + Debug.Assert(testRunCriteria != null, "Test run criteria cannot be null"); + Debug.Assert(executionManager != null, "ExecutionManager cannot be null"); + + EqtTrace.Verbose("TestRunRequest.ExecuteAsync: Creating test run request."); + this.testRunCriteria = testRunCriteria; + this.ExecutionManager = executionManager; + + this.State = TestRunState.Pending; + } + + #region ITestRunRequest + + /// + /// Execute the test run asynchronously + /// + public int ExecuteAsync() + { + EqtTrace.Verbose("TestRunRequest.ExecuteAsync: Starting."); + + lock (syncObject) + { + if (bDisposed) + { + throw new ObjectDisposedException("testRunRequest"); + } + + if (this.State != TestRunState.Pending) + { + throw new InvalidOperationException(Resources.InvalidStateForExecution); + } + + EqtTrace.Info("TestRunRequest.ExecuteAsync: Starting run with settings:{0}", testRunCriteria); + + // Waiting for warm up to be over. + EqtTrace.Verbose("TestRunRequest.ExecuteAsync: Wait for the first run request is over."); + + this.State = TestRunState.InProgress; + + // Reset the run completion event + // (This needs to be done before queuing the test run because if the test run finishes fast then runCompletion event can + // remain in non-signaled state even though run is actually complete. + runCompletionEvent.Reset(); + + try + { + runRequestTimeTracker = new Stopwatch(); + // Start the stop watch for calculating the test run time taken overall + runRequestTimeTracker.Start(); + int processId = this.ExecutionManager.StartTestRun(testRunCriteria, this); + EqtTrace.Info("TestRunRequest.ExecuteAsync: Started."); + + return processId; + } + catch + { + this.State = TestRunState.Pending; + throw; + } + } + } + + /// + /// Wait for the run completion + /// + public bool WaitForCompletion(int timeout) + { + EqtTrace.Verbose("TestRunRequest.WaitForCompletion: Waiting with timeout {0}.", timeout); + + if (bDisposed) + { + throw new ObjectDisposedException("testRunRequest"); + } + + if (this.State != TestRunState.InProgress + && !(this.State == TestRunState.Completed + || this.State == TestRunState.Canceled + || this.State == TestRunState.Aborted)) // If run is already terminated, then we should not throw an exception. + { + throw new InvalidOperationException(Resources.WaitForCompletionOperationIsNotAllowedWhenNoTestRunIsActive); + } + + // This method is not synchronized as it can lead to dead-lock + // (the runCompletionEvent cannot be raised unless that lock is released) + + // Wait for run completion (In case m_runCompletionEvent is closed, then waitOne will throw nice error) + if (runCompletionEvent != null) + { + return runCompletionEvent.WaitOne(timeout); + } + + return true; + } + + /// + /// Cancel the test run asynchronously + /// + public void CancelAsync() + { + EqtTrace.Verbose("TestRunRequest.CancelAsync: Canceling."); + + lock (syncObject) + { + if (bDisposed) + { + throw new ObjectDisposedException("testRunRequest"); + } + + if (this.State != TestRunState.InProgress) + { + EqtTrace.Info("Ignoring TestRunRequest.CancelAsync(). No test run in progress."); + } + else + { + // Inform the service about run cancellation + this.ExecutionManager.Cancel(); + } + } + + EqtTrace.Info("TestRunRequest.CancelAsync: Cancelled."); + } + + /// + /// Aborts the test run execution process. + /// + public void Abort() + { + EqtTrace.Verbose("TestRunRequest.Abort: Aborting."); + + lock (syncObject) + { + if (bDisposed) + { + throw new ObjectDisposedException("testRunRequest"); + } + + if (this.State != TestRunState.InProgress) + { + EqtTrace.Info("Ignoring TestRunRequest.Abort(). No test run in progress."); + } + else + { + this.ExecutionManager.Abort(); + } + } + + EqtTrace.Info("TestRunRequest.Abort: Aborted."); + } + + /// + /// Specifies the test run criteria + /// + public ITestRunConfiguration TestRunConfiguration + { + get { return testRunCriteria; } + } + + /// + /// State of the test run + /// + public TestRunState State { get; private set; } + + /// + /// Raised when the test run statistics change. + /// + public event EventHandler OnRunStatsChange; + + /// + /// Raised when the test message is received. + /// + public event EventHandler TestRunMessage; + + /// + /// Raised when the test run completes. + /// + public event EventHandler OnRunCompletion; + + /// + /// Raised when data collection message is received. + /// +#pragma warning disable 67 + public event EventHandler DataCollectionMessage; +#pragma warning restore 67 + + /// + /// Raised when a test run event raw message is received from host + /// This is required if one wants to re-direct the message over the process boundary without any processing overhead + /// All the run events should come as raw messages as well as proper serialized events like OnRunStatsChange + /// + public event EventHandler OnRawMessageReceived; + + /// + /// Parent execution manager + /// + internal IProxyExecutionManager ExecutionManager + { + get; private set; + } + + #endregion + + #region IDisposable implementation + + // Summary: + // Performs application-defined tasks associated with freeing, releasing, or + // resetting unmanaged resources. + public void Dispose() + { + Dispose(true); + + GC.SuppressFinalize(this); + } + + #endregion + + public TestRunCriteria TestRunCriteria + { + get { return testRunCriteria; } + } + + /// + /// Invoked when test run is complete + /// + public void HandleTestRunComplete(TestRunCompleteEventArgs runCompleteArgs, TestRunChangedEventArgs lastChunkArgs, ICollection runContextAttachments, ICollection executorUris) + { + bool isAborted = runCompleteArgs.IsAborted; + bool isCanceled = runCompleteArgs.IsCanceled; + + EqtTrace.Verbose("TestRunRequest:TestRunComplete: Starting. IsAborted:{0} IsCanceled:{1}.", isAborted, isCanceled); + + lock (syncObject) + { + // If this object is disposed, dont do anything + if (bDisposed) + { + EqtTrace.Warning("TestRunRequest.TestRunComplete: Ignoring as the object is disposed."); + return; + } + if (runCompletionEvent.WaitOne(0)) + { + EqtTrace.Info("TestRunRequest:TestRunComplete:Ignoring duplicate event. IsAborted:{0} IsCanceled:{1}.", isAborted, isCanceled); + return; + } + + try + { + if (runRequestTimeTracker != null) runRequestTimeTracker.Stop(); + + if (lastChunkArgs != null) + { + // Raised the changed event also + OnRunStatsChange.SafeInvoke(this, lastChunkArgs, "TestRun.RunStatsChanged"); + } + + TestRunCompleteEventArgs runCompletedEvent = new TestRunCompleteEventArgs(runCompleteArgs.TestRunStatistics, + runCompleteArgs.IsCanceled, + runCompleteArgs.IsAborted, + runCompleteArgs.Error, + null, + runRequestTimeTracker.Elapsed); + // Ignore the time sent (runCompleteArgs.ElapsedTimeInRunningTests) + // by either engines - as both calculate at different points + // If we use them, it would be an apples and oranges comparison i.e, between TAEF and Rocksteady + OnRunCompletion.SafeInvoke(this, runCompletedEvent, "TestRun.TestRunComplete"); + } + finally + { + if (isCanceled) + { + this.State = TestRunState.Canceled; + } + else if (isAborted) + { + this.State = TestRunState.Aborted; + } + else + { + this.State = TestRunState.Completed; + } + + // Notify the waiting handle that run is complete + runCompletionEvent.Set(); + + // Disposing off the resources held by the execution manager so that the test host process can shut down. + this.ExecutionManager?.Dispose(); + } + + EqtTrace.Info("TestRunRequest:TestRunComplete: Completed."); + } + } + + /// + /// Invoked when test run statistics change. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "1", Justification = "This is not an external event")] + public virtual void HandleTestRunStatsChange(TestRunChangedEventArgs testRunChangedArgs) + { + if (testRunChangedArgs != null) + { + EqtTrace.Verbose("TestRunRequest:SendTestRunStatsChange: Starting."); + if (testRunChangedArgs.ActiveTests != null) + { + // Do verbose check to save perf in iterating test cases + if (EqtTrace.IsVerboseEnabled) + { + foreach (TestCase testCase in testRunChangedArgs.ActiveTests) + { + EqtTrace.Verbose("InProgress is {0}", testCase.DisplayName); + } + } + } + + lock (syncObject) + { + // If this object is disposed, dont do anything + if (bDisposed) + { + EqtTrace.Warning("TestRunRequest.SendTestRunStatsChange: Ignoring as the object is disposed."); + return; + } + + // TODO: Invoke this event in a separate thread. + // For now, I am setting the ConcurrencyMode on the callback attribute to Multiple + OnRunStatsChange.SafeInvoke(this, testRunChangedArgs, "TestRun.RunStatsChanged"); + } + + EqtTrace.Info("TestRunRequest:SendTestRunStatsChange: Completed."); + } + } + + /// + /// Invoked when log messages are received + /// + public void HandleLogMessage(TestMessageLevel level, string message) + { + EqtTrace.Verbose("TestRunRequest:SendTestRunMessage: Starting."); + + lock (syncObject) + { + // If this object is disposed, dont do anything + if (bDisposed) + { + EqtTrace.Warning("TestRunRequest.SendTestRunMessage: Ignoring as the object is disposed."); + return; + } + TestRunMessage.SafeInvoke(this, new TestRunMessageEventArgs(level, message), "TestRun.LogMessages"); + } + + EqtTrace.Info("TestRunRequest:SendTestRunMessage: Completed."); + } + + /// + /// Handle Raw message directly from the host + /// + /// + public void HandleRawMessage(string rawMessage) + { + this.OnRawMessageReceived?.Invoke(this, rawMessage); + } + + /// + /// Launch process with debugger attached + /// + /// + /// processid + public int LaunchProcessWithDebuggerAttached(TestProcessStartInfo testProcessStartInfo) + { + int processId = -1; + + // Only launch while the test run is in progress and the launcher is a debug one + if (this.State == TestRunState.InProgress && this.testRunCriteria.TestHostLauncher.IsDebug) + { + processId = this.testRunCriteria.TestHostLauncher.LaunchTestHost(testProcessStartInfo); + } + + return processId; + } + + /// + /// Dispose the run + /// + /// + protected virtual void Dispose(bool disposing) + { + EqtTrace.Verbose("TestRunRequest.Dispose: Starting."); + + lock (syncObject) + { + if (!bDisposed) + { + if (disposing) + { + runCompletionEvent?.Dispose(); + } + + // Indicate that object has been disposed + runCompletionEvent = null; + bDisposed = true; + } + } + EqtTrace.Info("TestRunRequest.Dispose: Completed."); + } + + /// + /// The criteria/config for this test run request. + /// + internal TestRunCriteria testRunCriteria; + + /// + /// Specifies whether the run is disposed or not + /// + private bool bDisposed; + + /// + /// Sync object for various operations + /// + private Object syncObject = new Object(); + + /// + /// The run completion event which will be signalled on completion of test run. + /// + private ManualResetEvent runCompletionEvent = new ManualResetEvent(true); + + /// + /// Tracks the time taken by each run request + /// + private Stopwatch runRequestTimeTracker; + } +} diff --git a/src/Microsoft.TestPlatform.Client/Friends.cs b/src/Microsoft.TestPlatform.Client/Friends.cs new file mode 100644 index 0000000000..a79ee7a2c4 --- /dev/null +++ b/src/Microsoft.TestPlatform.Client/Friends.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Runtime.CompilerServices; + +#region Product Assemblies + +#endregion + +#region Test Assemblies + +[assembly: InternalsVisibleTo("Microsoft.TestPlatform.Client.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] + +#endregion diff --git a/src/Microsoft.TestPlatform.Client/Microsoft.TestPlatform.Client.xproj b/src/Microsoft.TestPlatform.Client/Microsoft.TestPlatform.Client.xproj new file mode 100644 index 0000000000..7eca21f781 --- /dev/null +++ b/src/Microsoft.TestPlatform.Client/Microsoft.TestPlatform.Client.xproj @@ -0,0 +1,22 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 9bc20fba-5b2e-47d1-a606-998e7d541397 + Microsoft.VisualStudio.TestPlatform.Client + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\ + v4.5.2 + + + 2.0 + + + True + + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Client/Properties/AssemblyInfo.cs b/src/Microsoft.TestPlatform.Client/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..4f97505857 --- /dev/null +++ b/src/Microsoft.TestPlatform.Client/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +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: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Microsoft.VisualStudio.TestPlatform.Client")] +[assembly: AssemblyTrademark("")] + +// 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("b0fab848-d0a2-49dd-8450-aa16bab3bbeb")] diff --git a/src/Microsoft.TestPlatform.Client/RequestHelper/DiscoveryRequestPayload.cs b/src/Microsoft.TestPlatform.Client/RequestHelper/DiscoveryRequestPayload.cs new file mode 100644 index 0000000000..cd3bb64e8d --- /dev/null +++ b/src/Microsoft.TestPlatform.Client/RequestHelper/DiscoveryRequestPayload.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Client.RequestHelper +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Runtime.Serialization; + using System.Threading.Tasks; + + /// + /// Class used to define the DiscoveryRequestPayload sent by the Vstest.console translation layers into design mode + /// + public class DiscoveryRequestPayload + { + /// + /// Settings used for the discovery request. + /// + [DataMember] + public IEnumerable Sources { get; set; } + + /// + /// Settings used for the discovery request. + /// + [DataMember] + public string RunSettings { get; set; } + } +} diff --git a/src/Microsoft.TestPlatform.Client/RequestHelper/ITestRequestManager.cs b/src/Microsoft.TestPlatform.Client/RequestHelper/ITestRequestManager.cs new file mode 100644 index 0000000000..b6cf7dc6f7 --- /dev/null +++ b/src/Microsoft.TestPlatform.Client/RequestHelper/ITestRequestManager.cs @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Client.RequestHelper +{ + using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; + using System; + using System.Collections.Generic; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces; + + /// + /// Defines the contract that commandline + /// + public interface ITestRequestManager + { + /// + /// Initializes the extensions while probing additional paths + /// + /// Paths to Additional extensions + void InitializeExtensions(IEnumerable pathToAdditionalExtensions); + + /// + /// Resets Vstest.console.exe Options + /// + void ResetOptions(); + + /// + /// Discover Tests given a list of sources, runsettings + /// + /// Discovery payload + /// Discovery events registrar - registers and unregisters discovery events + /// True, if successful + bool DiscoverTests(DiscoveryRequestPayload discoveryPayload, ITestDiscoveryEventsRegistrar disoveryEventsRegistrar); + + /// + /// Run Tests with given a test of sources + /// + /// Test Run Request payload + /// Custom testHostLauncher for the run + /// RunEvents registrar + /// True, if sucessful + bool RunTests(TestRunRequestPayload testRunRequestPayLoad, ITestHostLauncher customTestHostLauncher, ITestRunEventsRegistrar testRunEventsRegistrar); + + /// + /// Cancel the current TestRun request + /// + void CancelTestRun(); + + /// + /// Abort the current TestRun + /// + void AbortTestRun(); + } +} diff --git a/src/Microsoft.TestPlatform.Client/RequestHelper/TestRunRequestPayload.cs b/src/Microsoft.TestPlatform.Client/RequestHelper/TestRunRequestPayload.cs new file mode 100644 index 0000000000..4f03e5151d --- /dev/null +++ b/src/Microsoft.TestPlatform.Client/RequestHelper/TestRunRequestPayload.cs @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Client.RequestHelper +{ + using System.Collections.Generic; + using System.Runtime.Serialization; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + + /// + /// Class used to define the TestRunRequestPayload sent by the Vstest.console translation layers into design mode + /// + public class TestRunRequestPayload + { + /// + /// Gets or sets the sources for the test run request. + /// + /// + /// Making this a list instead of an IEnumerable because the json serializer fails to deserialize + /// if a linq query outputs the IEnumerable. + /// + [DataMember] + public List Sources { get; set; } + + /// + /// Gets or sets the test cases for the test run request. + /// + /// + /// Making this a list instead of an IEnumerable because the json serializer fails to deserialize + /// if a linq query outputs the IEnumerable. + /// + [DataMember] + public List TestCases { get; set; } + + /// + /// Gets or sets the settings used for the test run request. + /// + [DataMember] + public string RunSettings { get; set; } + + /// + /// Settings used for the Run request. + /// + [DataMember] + public bool KeepAlive { get; set; } + + /// + /// Is Debugging enabled + /// + [DataMember] + public bool DebuggingEnabled { get; set; } + } +} diff --git a/src/Microsoft.TestPlatform.Client/Resources.Designer.cs b/src/Microsoft.TestPlatform.Client/Resources.Designer.cs new file mode 100644 index 0000000000..f4a53e025a --- /dev/null +++ b/src/Microsoft.TestPlatform.Client/Resources.Designer.cs @@ -0,0 +1,80 @@ +//------------------------------------------------------------------------------ +// +// 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 Microsoft.VisualStudio.TestPlatform.Client { + using System; + using System.Reflection; + + + /// + /// 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.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + internal Resources() { + } + + /// + /// 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("Microsoft.TestPlatform.Client.Resources", typeof(Resources).GetTypeInfo().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 The test run could not be executed because the initial state was invalid.. + /// + public static string InvalidStateForExecution { + get { + return ResourceManager.GetString("InvalidStateForExecution", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Wait for completion operation is not allowed when there is no active test run. . + /// + public static string WaitForCompletionOperationIsNotAllowedWhenNoTestRunIsActive { + get { + return ResourceManager.GetString("WaitForCompletionOperationIsNotAllowedWhenNoTestRunIsActive", resourceCulture); + } + } + } +} diff --git a/src/Microsoft.TestPlatform.Client/Resources.resx b/src/Microsoft.TestPlatform.Client/Resources.resx new file mode 100644 index 0000000000..81055d4ee6 --- /dev/null +++ b/src/Microsoft.TestPlatform.Client/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + The test run could not be executed because the initial state was invalid. + + + Wait for completion operation is not allowed when there is no active test run. + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Client/TestPlatform.cs b/src/Microsoft.TestPlatform.Client/TestPlatform.cs new file mode 100644 index 0000000000..da85ffe434 --- /dev/null +++ b/src/Microsoft.TestPlatform.Client/TestPlatform.cs @@ -0,0 +1,124 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Client +{ + using System; + using System.Collections.Generic; + + using Microsoft.VisualStudio.TestPlatform.Client.Discovery; + using Microsoft.VisualStudio.TestPlatform.Client.Execution; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities; + + /// + /// Implementation for TestPlatform + /// + public class TestPlatform : ITestPlatform + { + /// + /// Initializes a new instance of the class. + /// + public TestPlatform() : this(new TestEngine()) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The test engine. + /// + protected TestPlatform(ITestEngine testEngine) + { + this.TestEngine = testEngine; + } + + /// + /// Gets or sets Test Engine instance + /// + private ITestEngine TestEngine { get; set; } + + /// + /// The create discovery request. + /// + /// The discovery criteria. + /// The . + /// Throws if parameter is null. + public IDiscoveryRequest CreateDiscoveryRequest(DiscoveryCriteria discoveryCriteria) + { + if (discoveryCriteria == null) + { + throw new ArgumentNullException("discoveryCriteria"); + } + + var runconfiguration = XmlRunSettingsUtilities.GetRunConfigurationNode(discoveryCriteria.RunSettings); + var testHostManager = this.TestEngine.GetDefaultTestHostManager(runconfiguration.TargetPlatform); + + var discoveryManager = this.TestEngine.GetDiscoveryManager(); + discoveryManager.Initialize(testHostManager); + + return new DiscoveryRequest(discoveryCriteria, discoveryManager); + } + + /// + /// The create test run request. + /// + /// The test run criteria. + /// The . + /// Throws if parameter is null. + public ITestRunRequest CreateTestRunRequest(TestRunCriteria testRunCriteria) + { + if (testRunCriteria == null) + { + throw new ArgumentNullException("testRunCriteria"); + } + + var runConfiguration = XmlRunSettingsUtilities.GetRunConfigurationNode(testRunCriteria.TestRunSettings); + var testHostManager = this.TestEngine.GetDefaultTestHostManager(runConfiguration.TargetPlatform); + + if (testRunCriteria.TestHostLauncher != null) + { + testHostManager.SetCustomLauncher(testRunCriteria.TestHostLauncher); + } + + var executionManager = this.TestEngine.GetExecutionManager(testRunCriteria); + executionManager.Initialize(testHostManager); + + return new TestRunRequest(testRunCriteria, executionManager); + } + + /// + /// The dispose. + /// + public void Dispose() + { + throw new NotImplementedException(); + } + + /// + /// The initialize. + /// + /// The path to additional extensions. + /// The load only well known extensions. + /// The force x86 discoverer. + public void Initialize(IEnumerable pathToAdditionalExtensions, bool loadOnlyWellKnownExtensions, bool forceX86Discoverer) + { + // TODO: ForceX86Discoverer options + this.TestEngine.GetExtensionManager() + .UseAdditionalExtensions(pathToAdditionalExtensions, loadOnlyWellKnownExtensions); + } + + /// + /// The update extensions. + /// + /// The path to additional extensions. + /// The load only well known extensions. + public void UpdateExtensions(IEnumerable pathToAdditionalExtensions, bool loadOnlyWellKnownExtensions) + { + this.TestEngine.GetExtensionManager() + .UseAdditionalExtensions(pathToAdditionalExtensions, loadOnlyWellKnownExtensions); + } + } +} diff --git a/src/Microsoft.TestPlatform.Client/TestPlatformFactory.cs b/src/Microsoft.TestPlatform.Client/TestPlatformFactory.cs new file mode 100644 index 0000000000..6bb5f2bddd --- /dev/null +++ b/src/Microsoft.TestPlatform.Client/TestPlatformFactory.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Client +{ + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + + /// + /// The factory class that provides an instance of the test platform. + /// + public class TestPlatformFactory + { + private static ITestPlatform testPlatform; + + /// + /// Gets an instance of the test platform. + /// + /// The instance. + public static ITestPlatform GetTestPlatform() + { + return testPlatform ?? (testPlatform = new TestPlatform()); + } + } +} diff --git a/src/Microsoft.TestPlatform.Client/project.json b/src/Microsoft.TestPlatform.Client/project.json new file mode 100644 index 0000000000..8f0e29c399 --- /dev/null +++ b/src/Microsoft.TestPlatform.Client/project.json @@ -0,0 +1,28 @@ +{ + "version": "15.0.0-*", + "buildOptions": { + "outputName": "Microsoft.VisualStudio.TestPlatform.Client", + "delaySign": true, + "keyFile": "../../scripts/key.snk", + "warningsAsErrors": true + }, + + "dependencies": { + "Microsoft.TestPlatform.ObjectModel": "15.0.0-*", + "Microsoft.TestPlatform.CoreUtilities": "15.0.0-*", + "Microsoft.TestPlatform.CrossPlatEngine": "15.0.0-*" + }, + + "frameworks": { + "netstandard1.5": { + "imports": [ + "dnxcore50", + "portable-net45+win8" + ], + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-24008" + } + }, + "net46": { } + } +} diff --git a/src/Microsoft.TestPlatform.Common/Constants.cs b/src/Microsoft.TestPlatform.Common/Constants.cs new file mode 100644 index 0000000000..d0d87b4290 --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/Constants.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Common +{ + /// + /// Defines the defaults/constants used across different components. + /// + public static class TestPlatformDefaults + { + /// + /// string in the vstest.console.exe.config that specifies the bound on no of jobs in the job queue. + /// + public const string MaxNumberOfEventsLoggerEventQueueCanHold = "MaxNumberOfEventsLoggerEventQueueCanHold"; + + /// + /// Default bound on the job queue. + /// + public const int DefaultMaxNumberOfEventsLoggerEventQueueCanHold = 500; + + /// + /// string in the vstest.console.exe.config that specifies the size bound on job queue. + /// + public const string MaxBytesLoggerEventQueueCanHold = "MaxBytesLoggerEventQueueCanHold"; + + /// + /// string in the rocksteady.exe.config that specifies whether or not we should try to keep memory usage by queue bounded. + /// + public const string EnableBoundsOnLoggerEventQueue = "EnableBoundsOnLoggerEventQueue"; + + /// + /// Default bound on the total size of all objects in the job queue. (25MB) + /// + public const int DefaultMaxBytesLoggerEventQueueCanHold = 25000000; + + /// + /// Default value of the boolean that determines whether or not job queue should be bounded. + /// + public const bool DefaultEnableBoundsOnLoggerEventQueue = true; + } +} diff --git a/src/Microsoft.TestPlatform.Common/DataCollection/BeforeTestRunStartResult.cs b/src/Microsoft.TestPlatform.Common/DataCollection/BeforeTestRunStartResult.cs new file mode 100644 index 0000000000..2670f73f30 --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/DataCollection/BeforeTestRunStartResult.cs @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Common.DataCollection +{ + using System.Collections.Generic; + using System.Runtime.Serialization; + + /// + /// The before test run start result. + /// + [DataContract] + public class BeforeTestRunStartResult + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The environment variables. + /// + /// + /// The are test case level events required. + /// + /// + /// The data Collection Events Port. + /// + public BeforeTestRunStartResult(IDictionary environmentVariables, bool areTestCaseLevelEventsRequired, int dataCollectionEventsPort) + { + this.EnvironmentVariables = environmentVariables; + this.AreTestCaseLevelEventsRequired = areTestCaseLevelEventsRequired; + this.DataCollectionEventsPort = dataCollectionEventsPort; + } + + /// + /// Gets the environment variable dictionary. + /// + [DataMember] + public IDictionary EnvironmentVariables { get; private set; } + + /// + /// Gets a value indicating whether test case level events are required or not + /// + [DataMember] + public bool AreTestCaseLevelEventsRequired { get; private set; } + + /// + /// Gets the data collection events port. + /// + [DataMember] + public int DataCollectionEventsPort { get; private set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Common/ExtensionFramework/TestDiscoveryExtensionManager.cs b/src/Microsoft.TestPlatform.Common/ExtensionFramework/TestDiscoveryExtensionManager.cs new file mode 100644 index 0000000000..0948ed8dfa --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/ExtensionFramework/TestDiscoveryExtensionManager.cs @@ -0,0 +1,202 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework +{ + using System; + using System.Collections.Generic; + + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.Utilities; + using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; + + /// + /// Responsible for managing the Test Discoverer extensions which are available. + /// + internal class TestDiscoveryExtensionManager + { + #region Fields + + private static TestDiscoveryExtensionManager testDiscoveryExtensionManager; + + #endregion + + #region Constructor + + /// + /// Default constructor. + /// + /// The factory should be used for getting instances of this type so the constructor is not public. + protected TestDiscoveryExtensionManager( + IEnumerable> discoverers, + IEnumerable>> unfilteredDiscoverers) + { + ValidateArg.NotNull>>(discoverers, "discoverers"); + ValidateArg.NotNull>>>(unfilteredDiscoverers, "unfilteredDiscoverers"); + + this.Discoverers = discoverers; + this.UnfilteredDiscoverers = unfilteredDiscoverers; + } + + #endregion + + #region Properties + + /// + /// Gets the unfiltered list of test discoverers which are available. + /// + /// + /// Used in the /ListDiscoverers command line argument processor to generically list out extensions. + /// + public IEnumerable>> UnfilteredDiscoverers { get; private set; } + + + /// + /// Gets the discoverers which are available for discovering tests. + /// + public IEnumerable> Discoverers { get; private set; } + + #endregion + + #region Factory + + /// + /// Gets an instance of the Test Discovery Extension Manager. + /// + /// + /// Instance of the Test Discovery Extension Manager + /// + /// + /// This would provide a discovery extension manager where extensions in + /// all the extension assemblies are discovered. This is cached. + /// + public static TestDiscoveryExtensionManager Create() + { + if (testDiscoveryExtensionManager == null) + { + IEnumerable>> unfilteredTestExtensions; + IEnumerable> testExtensions; + + TestPluginManager.Instance + .GetTestExtensions( + out unfilteredTestExtensions, + out testExtensions); + + testDiscoveryExtensionManager = new TestDiscoveryExtensionManager( + testExtensions, + unfilteredTestExtensions); + } + + return testDiscoveryExtensionManager; + } + + /// + /// Gets an instance of the Test Discovery Extension Manager for the extension. + /// + /// The extension assembly. + /// The instance. + /// + /// This would provide a discovery extension manager where extensions in + /// only the extension assembly provided are discovered. This is not cached + /// + public static TestDiscoveryExtensionManager GetDiscoveryExtensionManager(string extensionAssembly) + { + IEnumerable>> unfilteredTestExtensions; + IEnumerable> testExtensions; + + TestPluginManager.Instance + .GetTestExtensions( + extensionAssembly, + out unfilteredTestExtensions, + out testExtensions); + + return new TestDiscoveryExtensionManager( + testExtensions, + unfilteredTestExtensions); + } + + /// + /// Loads and Initializes all the extensions. + /// + /// The throw On Error. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + internal static void LoadAndInitializeAllExtensions(bool throwOnError) + { + try + { + var allDiscoverers = Create(); + + // Iterate throw the discoverers so that they are initialized + foreach (var discoverer in allDiscoverers.Discoverers) + { + // discoverer.value below is what initializes the extension types and hence is not under a EqtTrace.IsVerboseEnabled check. + EqtTrace.Verbose("TestDiscoveryManager: LoadExtensions: Created discoverer {0}", discoverer.Value); + } + } + catch (Exception ex) + { + if (EqtTrace.IsErrorEnabled) + { + EqtTrace.Error("TestDiscoveryManager: LoadExtensions: Exception occured while loading extensions {0}", ex); + } + + if (throwOnError) + { + throw; + } + } + } + + /// + /// Destroys the cache. + /// + internal static void Destroy() + { + testDiscoveryExtensionManager = null; + } + + #endregion + } + + /// + /// Hold data about the Test discoverer. + /// + internal class TestDiscovererMetadata : ITestDiscovererCapabilities + { + /// + /// The default constructor. + /// + /// The file Extensions. + /// The default Executor Uri. + public TestDiscovererMetadata(IReadOnlyCollection fileExtensions, string defaultExecutorUri) + { + if (fileExtensions != null && fileExtensions.Count > 0) + { + this.FileExtension = new List(fileExtensions); + } + + if (!string.IsNullOrWhiteSpace(defaultExecutorUri)) + { + this.DefaultExecutorUri = new Uri(defaultExecutorUri); + } + } + + /// + /// Gets file extensions supported by the discoverer. + /// + public IEnumerable FileExtension + { + get; + private set; + } + + /// + /// Gets the default executor Uri for this discoverer + /// + public Uri DefaultExecutorUri + { + get; + private set; + } + } +} diff --git a/src/Microsoft.TestPlatform.Common/ExtensionFramework/TestExecutorExtensionManager.cs b/src/Microsoft.TestPlatform.Common/ExtensionFramework/TestExecutorExtensionManager.cs new file mode 100644 index 0000000000..e2c9703f9e --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/ExtensionFramework/TestExecutorExtensionManager.cs @@ -0,0 +1,180 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework +{ + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.Utilities; + using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; + using Microsoft.VisualStudio.TestPlatform.Common.Logging; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + + /// + /// Manages the the Test Executor extensions. + /// + internal class TestExecutorExtensionManager : TestExtensionManager + { + #region Fields + + private static TestExecutorExtensionManager testExecutorExtensionManager; + private static object synclock = new object(); + + #endregion + + #region Constructor + + /// + /// Default constructor. + /// + /// The unfiltered Test Extensions. + /// The test Extensions. + /// The logger. + /// + /// This constructor is not public because instances should be retrieved using the + /// factory method. The constructor is protected for testing purposes. + /// + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] + protected TestExecutorExtensionManager( + IEnumerable>> unfilteredTestExtensions, + IEnumerable> testExtensions, + IMessageLogger logger) + : base(unfilteredTestExtensions, testExtensions, logger) + { + } + + #endregion + + #region Factory Methods + + /// + /// Creates the TestExecutorExtensionManager. + /// + /// Instance of the TestExecutorExtensionManager + internal static TestExecutorExtensionManager Create() + { + if (testExecutorExtensionManager == null) + { + lock (synclock) + { + if (testExecutorExtensionManager == null) + { + IEnumerable>> unfilteredTestExtensions; + IEnumerable> testExtensions; + + TestPluginManager.Instance + .GetTestExtensions( + out unfilteredTestExtensions, + out testExtensions); + + testExecutorExtensionManager = new TestExecutorExtensionManager( + unfilteredTestExtensions, testExtensions, TestSessionMessageLogger.Instance); + } + } + } + + return testExecutorExtensionManager; + } + + /// + /// Gets an instance of the Test Execution Extension Manager for the extension. + /// + /// The extension assembly. + /// The . + /// + /// This would provide an execution extension manager where extensions in + /// only the extension assembly provided are discovered. This is not cached. + /// + internal static TestExecutorExtensionManager GetExecutionExtensionManager(string extensionAssembly) + { + IEnumerable>> unfilteredTestExtensions; + IEnumerable> testExtensions; + + TestPluginManager.Instance + .GetTestExtensions( + extensionAssembly, + out unfilteredTestExtensions, + out testExtensions); + + // Todo aajohn . This can be optimized - The base class's populate map would be called repeatedly for the same extension assembly. + // Have a single instance of TestExecutorExtensionManager that keeps populating the map iteratively. + return new TestExecutorExtensionManager( + unfilteredTestExtensions, + testExtensions, + TestSessionMessageLogger.Instance); + } + + /// + /// Destroy the TestExecutorExtensionManager. + /// + internal static void Destroy() + { + lock (synclock) + { + testExecutorExtensionManager = null; + } + } + + /// + /// Load all the executors and fail on error + /// + /// Indicates whether this method should throw on error. + internal static void LoadAndInitializeAllExtensions(bool shouldThrowOnError) + { + var executorExtensionManager = Create(); + + try + { + foreach (var executor in executorExtensionManager.TestExtensions) + { + // Note: - The below Verbose call should not be under IsVerboseEnabled check as we want to + // call executor.Value even if logging is not enabled. + EqtTrace.Verbose("TestExecutorExtensionManager: Loading executor {0}", executor.Value); + } + } + catch (Exception ex) + { + if (EqtTrace.IsErrorEnabled) + { + EqtTrace.Error( + "TestExecutorExtensionManager: LoadAndInitialize: Exception occured while loading extensions {0}", + ex); + } + + if (shouldThrowOnError) + { + throw; + } + } + } + + #endregion + } + + /// + /// Holds data about the Test executor. + /// + internal class TestExecutorMetadata : ITestExecutorCapabilities + { + /// + /// The constructor + /// + /// Uri identifying the executor + public TestExecutorMetadata(string extensionUri) + { + this.ExtensionUri = extensionUri; + } + + /// + /// Gets Uri identifying the executor. + /// + public string ExtensionUri + { + get; + private set; + } + } +} diff --git a/src/Microsoft.TestPlatform.Common/ExtensionFramework/TestExtensionManager.cs b/src/Microsoft.TestPlatform.Common/ExtensionFramework/TestExtensionManager.cs new file mode 100644 index 0000000000..93e7f12b41 --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/ExtensionFramework/TestExtensionManager.cs @@ -0,0 +1,198 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework +{ + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.Utilities; + using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + using CommonResources = Microsoft.VisualStudio.TestPlatform.Common.Resources; + + /// + /// Generic base class for managing extensions and looking them up by their URI. + /// + /// The type of the extension. + /// The type of the metadata. + internal abstract class TestExtensionManager + where TMetadata : ITestExtensionCapabilities + { + #region Fields + + /// + /// Used for logging errors. + /// + private IMessageLogger logger; + + #endregion + + #region Constructor + + /// + /// Default constructor. + /// + /// + /// The unfiltered Test Extensions. + /// + /// + /// The test Extensions. + /// + /// + /// The logger. + /// + protected TestExtensionManager( + IEnumerable>> unfilteredTestExtensions, + IEnumerable> testExtensions, + IMessageLogger logger) + { + ValidateArg.NotNull>>>(unfilteredTestExtensions, "unfilteredTestExtensions"); + ValidateArg.NotNull>>(testExtensions, "testExtensions"); + ValidateArg.NotNull(logger, "logger"); + + this.logger = logger; + this.TestExtensions = testExtensions; + this.UnfilteredTestExtensions = unfilteredTestExtensions; + + // Populate the map to avoid threading issues + this.PopulateMap(); + } + + #endregion + + #region Properties + + /// + /// Gets unfiltered list of test extensions which are available. + /// + /// + /// When we populate the "TestExtensions" property it + /// will filter out extensions which are missing required pieces of metadata such + /// as the "ExtensionUri". This field is here so we can report on extensions which + /// are missing metadata. + /// + public IEnumerable>> UnfilteredTestExtensions + { + get; private set; + } + + /// + /// Gets filtered list of test extensions which are available. + /// + /// + /// When we populate the "TestExtensions" property it + /// will filter out extensions which are missing required pieces of metadata such + /// as the "ExtensionUri". This field is here so we can report on extensions which + /// are missing metadata. + /// + public IEnumerable> TestExtensions + { + get; + private set; + } + + /// + /// Gets mapping between test extension URI and test extension. + /// + public Dictionary> TestExtensionByUri + { + get; + private set; + } + + #endregion + + #region Public Methods + + /// + /// Looks up the test extension by its URI. + /// + /// The URI of the test extension to be looked up. + /// The test extension or null if one was not found. + public LazyExtension TryGetTestExtension(Uri extensionUri) + { + ValidateArg.NotNull(extensionUri, "extensionUri"); + + LazyExtension testExtension; + this.TestExtensionByUri.TryGetValue(extensionUri, out testExtension); + + return testExtension; + } + + /// + /// Looks up the test extension by its URI (passed as a string). + /// + /// The URI of the test extension to be looked up. + /// The test extension or null if one was not found. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1057:StringUriOverloadsCallSystemUriOverloads", Justification = "Case insensitiveness needs to be supported.")] + public LazyExtension TryGetTestExtension(string extensionUri) + { + ValidateArg.NotNull(extensionUri, "extensionUri"); + + LazyExtension testExtension = null; + foreach (var availableExtensionUri in this.TestExtensionByUri.Keys) + { + if (string.Compare(extensionUri, availableExtensionUri.AbsoluteUri, StringComparison.OrdinalIgnoreCase) == 0) + { + this.TestExtensionByUri.TryGetValue(availableExtensionUri, out testExtension); + break; + } + } + return testExtension; + } + + #endregion + + /// + /// Populate the extension map. + /// + private void PopulateMap() + { + this.TestExtensionByUri = new Dictionary>(); + + if (this.TestExtensions == null) + { + return; + } + + foreach (var extension in this.TestExtensions) + { + // Convert the extension uri string to an actual uri. + Uri uri = null; + try + { + uri = new Uri(extension.Metadata.ExtensionUri); + } + catch (FormatException e) + { + if (this.logger != null) + { + this.logger.SendMessage( + TestMessageLevel.Warning, + string.Format(CultureInfo.CurrentUICulture, CommonResources.InvalidExtensionUriFormat, extension.Metadata.ExtensionUri, e)); + } + } + + if (uri != null) + { + // Make sure we are not trying to add an extension with a duplicate uri. + if (!this.TestExtensionByUri.ContainsKey(uri)) + { + this.TestExtensionByUri.Add(uri, extension); + } + else + { + if (this.logger != null) + { + this.logger.SendMessage( + TestMessageLevel.Warning, + string.Format(CultureInfo.CurrentUICulture, CommonResources.DuplicateExtensionUri, extension.Metadata.ExtensionUri)); + } + } + } + } + } + } +} diff --git a/src/Microsoft.TestPlatform.Common/ExtensionFramework/TestPluginCache.cs b/src/Microsoft.TestPlatform.Common/ExtensionFramework/TestPluginCache.cs new file mode 100644 index 0000000000..c4b6170804 --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/ExtensionFramework/TestPluginCache.cs @@ -0,0 +1,566 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Reflection; + + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.Utilities; + using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; + using Microsoft.VisualStudio.TestPlatform.Common.Utilities; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + +#if NET46 + using System.Threading; +#else + using System.Runtime.Loader; +#endif + + /// + /// The test plugin cache. + /// + /// Making this a singleton to offer better unit testing. + public class TestPluginCache + { + #region Private Members + + private Dictionary resolvedAssemblies; + + /// + /// Specify the path to additional extensions + /// + private IEnumerable pathToAdditionalExtensions; + + /// + /// Specifies whether we should load only well known extensions or not. Default is "load all". + /// + private bool loadOnlyWellKnownExtensions; + + /// + /// Assembly resolver used to resolve the additional extensions + /// + private AssemblyResolver assemblyResolver; + + /// + /// Lock for additional extensions update + /// + private object lockForAdditionalExtensionsUpdate; + + private static TestPluginCache instance; + + private List defaultExtensionPaths; + + private IPathUtilities pathUtilities; + + private const string DefaultExtensionsFolder = "Extensions"; + + #endregion + + #region Constructor + + private TestPluginCache() + : this(new PathUtilities()) + { + } + + /// + /// Added for unit testing. + /// + /// + protected TestPluginCache(IPathUtilities pathUtilities) + { + this.resolvedAssemblies = new Dictionary(); + this.pathToAdditionalExtensions = null; + this.loadOnlyWellKnownExtensions = false; + this.lockForAdditionalExtensionsUpdate = new object(); + this.pathUtilities = pathUtilities; + } + + #endregion + + #region Public Properties + + public static TestPluginCache Instance + { + get + { + return instance ?? (instance = new TestPluginCache()); + } + internal set + { + instance = value; + } + } + + + /// + /// Gets whether default extensions discovery is done. + /// + public bool AreDefaultExtensionsDiscovered + { + get; + private set; + } + + /// + /// The test extensions discovered by the cache until now. + /// + /// Returns null if discovery of extensions is not done. + internal TestExtensions TestExtensions { get; private set; } + + /// + /// Path to additional extensions + /// + public IEnumerable PathToAdditionalExtensions + { + get + { + return this.pathToAdditionalExtensions; + } + } + + /// + /// Specific whether only well known extensions should be loaded or not + /// + public bool LoadOnlyWellKnownExtensions + { + get + { + return this.loadOnlyWellKnownExtensions; + } + } + + #endregion + + #region Public Methods + + /// + /// Gets the test extensions defined in the provided extension assembly. + /// + /// The extension assembly. + /// The test extension collection defined in this assembly. + public TestExtensions GetTestExtensions(string extensionAssembly) + { + // Check if extensions from this assembly have already been discovered. + var extensions = this.TestExtensions?.GetExtensionsDiscoveredFromAssembly(extensionAssembly); + + if (extensions != null) + { + return extensions; + } + + this.SetupAssemblyResolver(extensionAssembly); + + try + { + EqtTrace.Verbose("TestPluginCache: Discovering the extensions using extension path."); + + extensions = this.GetTestExtensions(new List { extensionAssembly }); + + // Add extensions discovered to the cache. + if (this.TestExtensions == null) + { + this.TestExtensions = extensions; + } + else + { + this.TestExtensions.AddExtensions(extensions); + } + + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose( + "TestPluginCache: Discovered extensions from '{0}'.", + extensionAssembly); + } + + this.LogExtensions(); + } +#if NET46 + catch (ThreadAbortException) + { + // Nothing to do here, we just do not want to do an EqtTrace.Fail for this thread + // being aborted as it is a legitimate exception to receive. + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose("TestPluginCache.DiscoverTestExtensions: Data extension discovery is being aborted due to a thread abort."); + } + } +#endif + catch (Exception e) + { + EqtTrace.Error("TestPluginCache: Discovery of extension from {0} failed! {1}", extensionAssembly, e); + throw; + } + + return extensions; + } + + /// + /// Performs discovery of data extensions. + /// + [System.Security.SecurityCritical] + public void DiscoverAllTestExtensions() + { + this.SetupAssemblyResolver(null); + + // Some times TestPlatform.core.dll assembly fails to load in the current appdomain (from devenv.exe). + // Reason for failures are not known. Below handler, again calls assembly.load() in failing assembly + // and that succeeds. + // Because of this assembly failure, below domain.CreateInstanceAndUnwrap() call fails with error + // "Unable to cast transparent proxy to type 'Microsoft.VisualStudio.TestPlatform.Core.TestPluginsFramework.TestPluginDiscoverer" +#if NET46 + AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomainAssemblyResolve); +#else + AssemblyLoadContext.Default.Resolving += this.CurrentDomainAssemblyResolve; +#endif + try + { + EqtTrace.Verbose("TestPluginCache: Discovering the extensions using extension path."); + + // Combine all the possible extensions - both default and additional + var allExtensionPaths = new List(this.DefaultExtensionPaths); + if (this.pathToAdditionalExtensions != null) + { + allExtensionPaths.AddRange(this.pathToAdditionalExtensions); + } + + // Discover the test extensions from candidate assemblies. + var extensions = this.GetTestExtensions(allExtensionPaths); + + if (this.TestExtensions == null) + { + this.TestExtensions = extensions; + } + else + { + this.TestExtensions.AddExtensions(extensions); + } + + this.AreDefaultExtensionsDiscovered = true; + + if (EqtTrace.IsVerboseEnabled) + { + var extensionString = this.pathToAdditionalExtensions != null + ? string.Join(",", this.pathToAdditionalExtensions.ToArray()) + : null; + EqtTrace.Verbose( + "TestPluginCache: Discovered the extensions using extension path '{0}'.", + extensionString); + } + + this.LogExtensions(); + } +#if NET46 + catch (ThreadAbortException) + { + // Nothing to do here, we just do not want to do an EqtTrace.Fail for this thread + // being aborted as it is a legitimate exception to receive. + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose("TestPluginCache.DiscoverTestExtensions: Data extension discovery is being aborted due to a thread abort."); + } + } +#endif + catch (Exception e) + { + EqtTrace.Error("TestPluginCache: Discovery failed! {0}", e); + throw; + } + finally + { +#if NET46 + AppDomain.CurrentDomain.AssemblyResolve -= new ResolveEventHandler(CurrentDomainAssemblyResolve); +#else + AssemblyLoadContext.Default.Resolving -= this.CurrentDomainAssemblyResolve; +#endif + + // clear the assemblies + lock (this.resolvedAssemblies) + { + this.resolvedAssemblies?.Clear(); + } + } + } + + /// + /// Use the parameter path to extensions + /// + public void UpdateAdditionalExtensions(IEnumerable additionalExtensionsPath, bool shouldLoadOnlyWellKnownExtensions) + { + lock (this.lockForAdditionalExtensionsUpdate) + { + EqtTrace.Verbose( + "TestPluginCache: Updating loadOnlyWellKnownExtensions from {0} to {1}.", + this.loadOnlyWellKnownExtensions, + shouldLoadOnlyWellKnownExtensions); + + this.loadOnlyWellKnownExtensions = shouldLoadOnlyWellKnownExtensions; + + if (additionalExtensionsPath == null || !additionalExtensionsPath.Any()) + { + return; + } + + string extensionString; + if (this.pathToAdditionalExtensions != null + && additionalExtensionsPath.Count() == this.pathToAdditionalExtensions.Count() + && additionalExtensionsPath.All(e => this.pathToAdditionalExtensions.Contains(e))) + { + extensionString = this.pathToAdditionalExtensions != null + ? string.Join(",", this.pathToAdditionalExtensions.ToArray()) + : null; + EqtTrace.Verbose( + "TestPluginCache: Ignoring the new extensions update as there is no change. Current extensions are '{0}'.", + extensionString); + + return; + } + + var newExtensionPaths = this.pathUtilities.GetUniqueValidPaths(additionalExtensionsPath); + + if (this.pathToAdditionalExtensions != null) + { + foreach (var existingExtension in this.pathToAdditionalExtensions) + { + newExtensionPaths.Add(existingExtension); + } + } + + // Use the new paths and set the extensions discovered to false so that the next time + // any one tries to get the additional extensions, we rediscover. + this.pathToAdditionalExtensions = newExtensionPaths.ToList(); + + this.AreDefaultExtensionsDiscovered = false; + + if (EqtTrace.IsVerboseEnabled) + { + var directories = + this.pathToAdditionalExtensions.Select(e => Path.GetDirectoryName(Path.GetFullPath(e))).Distinct(); + + var directoryString = directories != null ? string.Join(",", directories.ToArray()) : null; + EqtTrace.Verbose( + "TestPluginCache: Using directories for assembly resolution '{0}'.", + directoryString); + + extensionString = this.pathToAdditionalExtensions != null + ? string.Join(",", this.pathToAdditionalExtensions.ToArray()) + : null; + EqtTrace.Verbose("TestPluginCache: Updated the available extensions to '{0}'.", extensionString); + } + } + } + + #endregion + + #region Utility methods + + internal IEnumerable DefaultExtensionPaths + { + get + { + if (this.defaultExtensionPaths == null) + { + var extensionsFolder = Path.Combine(Path.GetDirectoryName(typeof(TestPluginCache).GetTypeInfo().Assembly.Location), DefaultExtensionsFolder); + + if (!this.DoesDirectoryExist(extensionsFolder)) + { + EqtTrace.Error("Default extensions folder does not exist"); + // Initialize and bail out. + this.defaultExtensionPaths = new List(); + } + else + { + // Scan all of the DLL's and EXE's + var dlls = this.GetFilesInDirectory(extensionsFolder, "*.dll"); + this.defaultExtensionPaths = new List(dlls); + var exes = this.GetFilesInDirectory(extensionsFolder, "*.exe"); + this.defaultExtensionPaths.AddRange(exes); + } + } + + return this.defaultExtensionPaths; + } + } + + /// + /// Checks if a directory exists + /// + /// + /// + /// Added to mock out FileSystem interaction for unit testing. + internal virtual bool DoesDirectoryExist(string path) + { + return Directory.Exists(path); + } + + /// + /// Gets files in a directory. + /// + /// + /// + /// + /// Added to mock out FileSystem interaction for unit testing. + internal virtual string[] GetFilesInDirectory(string path, string searchPattern) + { + return Directory.GetFiles(path, searchPattern); + } + + /// + /// Gets the test extensions defined in the extension assembly list. + /// + /// Extension assembly paths. + /// + /// Added to mock out dependency from the actual test plugin discovery as such. + internal virtual TestExtensions GetTestExtensions(IEnumerable extensionPaths) + { + var discoverer = new TestPluginDiscoverer(); + + return discoverer.GetTestExtensionsInformation(extensionPaths, this.loadOnlyWellKnownExtensions); + } + + /// + /// Gets the resoltuion paths for the extension assembly to falicitate assembly resolution. + /// + /// The extension assembly. + /// + internal IList GetResolutionPaths(string extensionAssembly) + { + var resolutionPaths = new List(); + + var extensionDirectory = Path.GetDirectoryName(Path.GetFullPath(extensionAssembly)); + resolutionPaths.Add(extensionDirectory); + + var currentDirectory = Path.GetDirectoryName(typeof(TestPluginCache).GetTypeInfo().Assembly.Location); + if (!resolutionPaths.Contains(currentDirectory)) + { + resolutionPaths.Add(currentDirectory); + } + + return resolutionPaths; + } + + /// + /// Gets the default set of resolution paths for the assembly resolution + /// + /// + [System.Security.SecurityCritical] + internal IList GetDefaultResolutionPaths() + { + var resolutionPaths = new List(); + + var extensionDirectories = this.pathToAdditionalExtensions?.Select(e => Path.GetDirectoryName(Path.GetFullPath(e))).Distinct(); + if (extensionDirectories != null && extensionDirectories.Any()) + { + resolutionPaths.AddRange(extensionDirectories); + } + + var currentDirectory = Path.GetDirectoryName(typeof(TestPluginCache).GetTypeInfo().Assembly.Location); + + if (!resolutionPaths.Contains(currentDirectory)) + { + resolutionPaths.Add(currentDirectory); + } + + var defaultExtensionsFolder = Path.Combine(Path.GetDirectoryName(typeof(TestPluginCache).GetTypeInfo().Assembly.Location), DefaultExtensionsFolder); + + if (this.DoesDirectoryExist(defaultExtensionsFolder) && !resolutionPaths.Contains(defaultExtensionsFolder)) + { + resolutionPaths.Add(defaultExtensionsFolder); + } + + return resolutionPaths; + } + + private void SetupAssemblyResolver(string extensionAssembly) + { + IList resolutionPaths; + + if (string.IsNullOrEmpty(extensionAssembly)) + { + resolutionPaths = this.GetDefaultResolutionPaths(); + } + else + { + resolutionPaths = this.GetResolutionPaths(extensionAssembly); + } + + //Add assembly resolver which can resolve the extensions from the specified directory. + if (this.assemblyResolver == null) + { + this.assemblyResolver = new AssemblyResolver(resolutionPaths); + } + else + { + this.assemblyResolver.AddSearchDirectories(resolutionPaths); + } + } + +#if NET46 + private Assembly CurrentDomainAssemblyResolve(object sender, ResolveEventArgs args) +#else + private Assembly CurrentDomainAssemblyResolve(AssemblyLoadContext loadContext, AssemblyName args) +#endif + { + var assemblyName = new AssemblyName(args.Name); + + Assembly assembly = null; + lock (this.resolvedAssemblies) + { + try + { + EqtTrace.Verbose("CurrentDomain_AssemblyResolve: Resolving assembly '{0}'.", args.Name); + + if (this.resolvedAssemblies.TryGetValue(args.Name, out assembly)) + { + return assembly; + } + + // Put it in the resolved assembly so that if below Assembly.Load call + // triggers another assembly resolution, then we dont end up in stack overflow + this.resolvedAssemblies[args.Name] = null; + + assembly = Assembly.Load(assemblyName); + + // Replace the value with the loaded assembly + this.resolvedAssemblies[args.Name] = assembly; + + return assembly; + } + finally + { + if (null == assembly) + { + EqtTrace.Verbose("CurrentDomainAssemblyResolve: Failed to resolve assembly '{0}'.", args.Name); + } + } + } + } + + /// + /// Log the extensions + /// + private void LogExtensions() + { + if (EqtTrace.IsVerboseEnabled) + { + var discoverers = this.TestExtensions.TestDiscoverers != null ? string.Join(",", this.TestExtensions.TestDiscoverers.Keys.ToArray()) : null; + EqtTrace.Verbose("TestPluginCache: Discoverers are '{0}'.", discoverers); + + var executors = this.TestExtensions.TestExecutors != null ? string.Join(",", this.TestExtensions.TestExecutors.Keys.ToArray()) : null; + EqtTrace.Verbose("TestPluginCache: Executors are '{0}'.", executors); + + var settingsProviders = this.TestExtensions.TestSettingsProviders != null ? string.Join(",", this.TestExtensions.TestSettingsProviders.Keys.ToArray()) : null; + EqtTrace.Verbose("TestPluginCache: Setting providers are '{0}'.", settingsProviders); + + var loggers = this.TestExtensions.TestLoggers != null ? string.Join(",", this.TestExtensions.TestLoggers.Keys.ToArray()) : null; + EqtTrace.Verbose("TestPluginCache: Loggers are '{0}'.", loggers); + } + } + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.Common/ExtensionFramework/TestPluginDiscoverer.cs b/src/Microsoft.TestPlatform.Common/ExtensionFramework/TestPluginDiscoverer.cs new file mode 100644 index 0000000000..3edad48fbd --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/ExtensionFramework/TestPluginDiscoverer.cs @@ -0,0 +1,284 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.IO; + using System.Linq; + using System.Reflection; + + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.Utilities; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + + /// + /// Discovers test extensions in a directory. + /// + internal class TestPluginDiscoverer + { + #region Fields + +#if WINDOWS_UAP + private static HashSet platformAssemblies = new HashSet(new string[] { + "MICROSOFT.VISUALSTUDIO.TESTPLATFORM.UNITTESTFRAMEWORK.DLL", + "MICROSOFT.VISUALSTUDIO.TESTPLATFORM.TESTEXECUTOR.CORE.DLL", + "MICROSOFT.VISUALSTUDIO.TESTPLATFORM.OBJECTMODEL.DLL", + "VSTEST_EXECUTIONENGINE_PLATFORMBRIDGE.DLL", + "VSTEST_EXECUTIONENGINE_PLATFORMBRIDGE.WINMD", + "VSTEST.EXECUTIONENGINE.WINDOWSPHONE.DLL", + "MICROSOFT.CSHARP.DLL", + "MICROSOFT.VISUALBASIC.DLL", + "CLRCOMPRESSION.DLL", + }); + + private const string SYSTEM_ASSEMBLY_PREFIX = "system."; +#endif + + #endregion + + #region Public Methods + + /// + /// Gets information about each of the test extensions available. + /// + /// The path to the extensions. + /// Should load only well known extensions or all. + /// The . + public TestExtensions GetTestExtensionsInformation( + IEnumerable pathToExtensions, + bool loadOnlyWellKnownExtensions) + { + Debug.Assert(pathToExtensions != null); + + var testExtensions = new TestExtensions + { + TestDiscoverers = + new Dictionary( + StringComparer.OrdinalIgnoreCase), + TestExecutors = + new Dictionary( + StringComparer.OrdinalIgnoreCase), + TestSettingsProviders = + new Dictionary ( + StringComparer.OrdinalIgnoreCase), + TestLoggers = + new Dictionary( + StringComparer.OrdinalIgnoreCase) + }; + + +#if !WINDOWS_UAP + + this.GetTestExtensionsFromFiles( + pathToExtensions.ToArray(), + loadOnlyWellKnownExtensions, + testExtensions.TestDiscoverers, + testExtensions.TestExecutors, + testExtensions.TestSettingsProviders, + testExtensions.TestLoggers); + +#else + var fileSearchTask = Windows.ApplicationModel.Package.Current.InstalledLocation.GetFilesAsync().AsTask(); + fileSearchTask.Wait(); + + var binaries = fileSearchTask.Result.Where(storageFile => + + (storageFile.Name.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) + || storageFile.Name.EndsWith(".exe", StringComparison.OrdinalIgnoreCase)) + && !storageFile.Name.StartsWith(SYSTEM_ASSEMBLY_PREFIX, StringComparison.OrdinalIgnoreCase) + && !platformAssemblies.Contains(storageFile.Name.ToUpperInvariant()) + + ). + Select(file => file.Name); + + GetTestExtensionsFromFiles( + binaries.ToArray(), + loadOnlyWellKnownExtensions, + testDiscoverers, + testExecutors, + testSettingsProviders, + testLoggers); + + // HACK HACK - In Release mode - managed dlls are packaged differently + // So, file search will not find them - do it manually + if (testDiscoverers.Count < 1) + { + GetTestExtensionsFromFiles( + new string[3] { + "Microsoft.VisualStudio.TestPlatform.Extensions.MSAppContainerAdapter.dll", + "Microsoft.VisualStudio.TestTools.CppUnitTestFramework.CppUnitTestExtension.dll", + "Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.dll",}, + loadOnlyWellKnownExtensions, + testDiscoverers, + testExecutors, + testSettingsProviders, + testLoggers); + } +#endif + return testExtensions; + } + + #endregion + + #region Private Methods + + /// + /// Gets test extension information from the given colletion of files. + /// + /// List of dll's to check for test extension availability + /// Should load only well known extensions or all. + /// Test discoverers collection to add to. + /// Test executors collection to add to. + /// Test settings providers collection to add to. + /// Test loggers collection to add to. + [SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.Reflection.Assembly.LoadFrom")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We would like to continue discovering all plugins even if some dll in Extensions folder is not able to be load properly")] + private void GetTestExtensionsFromFiles( + string[] files, + bool loadOnlyWellKnownExtensions, + Dictionary testDiscoverers, + Dictionary testExecutors, + Dictionary testSettingsProviders, + Dictionary testLoggers) + { + Debug.Assert(files != null, "null files"); + Debug.Assert(testDiscoverers != null, "null testDiscoverers"); + Debug.Assert(testExecutors != null, "null testExecutors"); + Debug.Assert(testSettingsProviders != null, "null testSettingsProviders"); + Debug.Assert(testLoggers != null, "null testLoggers"); + + // Todo: aajohn Do not see why loadOnlyWellKnowExtensions is still needed. + //AssemblyName executingAssemblyName = null; + //if (loadOnlyWellKnownExtensions) + //{ + // executingAssemblyName = new AssemblyName(typeof(TestPluginDiscoverer).GetTypeInfo().Assembly.FullName); + //} + + // Scan each of the files for data extensions. + foreach (var file in files) + { + Assembly assembly = null; + try + { + var assemblyName = Path.GetFileNameWithoutExtension(file); + assembly = Assembly.Load(new AssemblyName(assemblyName)); + + // Check whether this assembly is known or not. + //if (loadOnlyWellKnownExtensions && assembly != null) + //{ + // var extensionAssemblyName = new AssemblyName(assembly.FullName); + // if (!AssemblyUtilities.PublicKeyTokenMatches(extensionAssemblyName, executingAssemblyName)) + // { + // EqtTrace.Warning("TestPluginDiscoverer: Ignoring extensions in assembly {0} as it is not a known assembly.", assembly.FullName); + // continue; + // } + //} + } + catch (Exception e) + { + EqtTrace.Warning("TestPluginDiscoverer: Failed to load file '{0}'. Skipping test extension scan for this file. Error: {1}", file, e.ToString()); + continue; + } + + if (assembly != null) + { + this.GetTestExtensionsFromAssembly(assembly, testDiscoverers, testExecutors, testSettingsProviders, testLoggers); + } + } + } + + /// + /// Gets test extensions from a given assembly. + /// + /// Assembly to check for test extension availability + /// Test discoverers collection to add to. + /// Test executors collection to add to. + /// Test settings providers collection to add to. + /// Test loggers collection to add to. + private void GetTestExtensionsFromAssembly( + Assembly assembly, + Dictionary testDiscoverers, + Dictionary testExecutors, + Dictionary testSettingsProviders, + Dictionary testLoggers) + { + Debug.Assert(assembly != null, "null assembly"); + Debug.Assert(testDiscoverers != null, "null testDiscoverers"); + Debug.Assert(testExecutors != null, "null testExecutors"); + Debug.Assert(testSettingsProviders != null, "null testSettingsProviders"); + Debug.Assert(testLoggers != null, "null testLoggers"); + + Type[] types = null; + try + { + types = assembly.GetTypes(); + } + catch (ReflectionTypeLoadException e) + { + EqtTrace.Warning("TestPluginDiscoverer: Failed to get types from assembly '{0}'. Skipping test extension scan for this assembly. Error: {1}", assembly.FullName, e.ToString()); + + if (e.LoaderExceptions != null) + { + foreach (var ex in e.LoaderExceptions) + { + EqtTrace.Warning("LoaderExceptions: {0}", ex); + } + } + + return; + } + + if ((types != null) && (types.Length > 0)) + { + foreach (var type in types) + { + if (type.GetTypeInfo().IsClass && !type.GetTypeInfo().IsAbstract) + { + this.GetTestExtensionFromType(type, typeof(ITestDiscoverer), testDiscoverers); + this.GetTestExtensionFromType(type, typeof(ITestExecutor), testExecutors); + this.GetTestExtensionFromType(type, typeof(ITestLogger), testLoggers); + this.GetTestExtensionFromType(type, typeof(ISettingsProvider), testSettingsProviders); + } + } + } + } + + /// + /// Attempts to find a test extension from given type. + /// + /// Data type of the test plugin information + /// Type to inspect for being test extension + /// Test extension type to look for. + /// Test extensions collection to add to. + /// True if test extension is found, false otherwise. + private void GetTestExtensionFromType( + Type type, + Type extensionType, + Dictionary extensionCollection) + where TPluginInfo : TestPluginInformation + { + if (extensionType.GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) + { + var dataObject = Activator.CreateInstance(typeof(TPluginInfo), type); + var pluginInfo = (TPluginInfo)dataObject; + + if (extensionCollection.ContainsKey(pluginInfo.IdentifierData)) + { + EqtTrace.Error( + "TryGetTestExtensionFromType: Discovered multiple test extensions with identifier data '{0}'; keeping the first one.", + pluginInfo.IdentifierData); + } + else + { + extensionCollection.Add(pluginInfo.IdentifierData, pluginInfo); + } + } + } + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.Common/ExtensionFramework/TestPluginManager.cs b/src/Microsoft.TestPlatform.Common/ExtensionFramework/TestPluginManager.cs new file mode 100644 index 0000000000..454cc50123 --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/ExtensionFramework/TestPluginManager.cs @@ -0,0 +1,249 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework +{ + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Reflection; + + using Interfaces; + + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.Utilities; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + + /// + /// Manages test plugins information. + /// + internal class TestPluginManager + { + private static TestPluginManager instance; + + #region Public Static Methods + + /// + /// Gets the singleton instance of TestPluginManager. + /// + public static TestPluginManager Instance + { + get + { + if (instance == null) + { + instance = new TestPluginManager(); + } + + return instance; + } + } + + /// + /// Gets data type of test extension with given assembly qualified name. + /// + /// Assembly qualified name of the test extension + /// Data type of the test extension + public static Type GetTestExtensionType(string extensionTypeName) + { + Type extensionType = null; + try + { + extensionType = Type.GetType(extensionTypeName, true); + } + catch (Exception ex) + { + EqtTrace.Error( + "GetTestExtensionType: Failed to get type for test extension '{0}': {1}", + extensionTypeName, + ex); + throw; + } + + return extensionType; + } + + /// + /// Instantiates a given data type. + /// + /// Return type of the test extension + /// Data type of the extension to be instantiated + /// Test extension instance + public static T CreateTestExtension(Type extensionType) + { + if (extensionType == null) + { + throw new ArgumentNullException("extensionType"); + } + + EqtTrace.Info("TestPluginManager.CreateTestExtension: Attempting to load test extension: " + extensionType); + + try + { + object rawPlugin = Activator.CreateInstance(extensionType); + + T testExtension = (T)rawPlugin; + return testExtension; + } + catch (Exception ex) + { + if (ex is TargetInvocationException) + { + if (EqtTrace.IsErrorEnabled) + { + EqtTrace.Error("TestPluginManager.CreateTestExtension: Could not create instance of type: " + extensionType.ToString() + " Exception: " + ex); + } + throw; + } +#if NET46 + else if (ex is SystemException) + { + if (EqtTrace.IsErrorEnabled) + { + EqtTrace.Error("TestPluginManager.CreateTestExtension: Could not create instance of type: " + extensionType.ToString() + " Exception: " + ex); + } + throw; + } +#endif + if (EqtTrace.IsErrorEnabled) + { + EqtTrace.Error("TestPluginManager.CreateTestExtension: Could not create instance of type: " + extensionType.ToString() + " Exception: " + ex); + } + throw; + } + } + + #endregion + + #region Public Methods + + /// + /// Retrieves the test extension collections of given extension type. + /// + /// Type of the required extensions + /// Type of metadata of required extensions + /// Concrete type of metadata + /// Receives unfiltered list of test extensions + /// Receives test extensions filtered by Identifier data + [System.Security.SecuritySafeCritical] + public void GetTestExtensions( + out IEnumerable>> unfiltered, + out IEnumerable> filtered) where TMetadata : IMetadata + { + if (!TestPluginCache.Instance.AreDefaultExtensionsDiscovered) + { + TestPluginCache.Instance.DiscoverAllTestExtensions(); + } + + this.GetExtensions(TestPluginCache.Instance.TestExtensions, out unfiltered, out filtered); + } + + /// + /// Retrieves the test extension collections of given extension type for the provided extension assembly. + /// + /// The extension assembly. + /// Type of the required extensions + /// Type of metadata of required extensions + /// Concrete type of metadata + /// Receives unfiltered list of test extensions + /// Receives test extensions filtered by Identifier data + public void GetTestExtensions( + string extensionAssembly, + out IEnumerable>> unfiltered, + out IEnumerable> filtered) where TMetadata : IMetadata + { + var extensions = TestPluginCache.Instance.GetTestExtensions(extensionAssembly); + this.GetExtensions(extensions, out unfiltered, out filtered); + } + + #endregion + + #region Private Methods + + + /// + /// Prepares a List of TestPluginInformation> + /// + /// Type of TestPluginIInformation. + /// The dictionary containing plugin identifier data and its info. + /// Collection of test plugins information + private IEnumerable GetValuesFromDictionary(Dictionary dictionary) where T : TestPluginInformation + { + var values = new List(); + + foreach (var key in dictionary.Keys) + { + values.Add(dictionary[key]); + } + + return values; + } + + /// + /// Gets unfiltered and filtered extensions from the provided test extension collection. + /// + /// Type of the required extensions + /// Type of metadata of required extensions + /// Concrete type of metadata + /// The test extension collection list. + /// Receives unfiltered list of test extensions + /// Receives test extensions filtered by Identifier data + private void GetExtensions( + TestExtensions testExtensions, + out IEnumerable>> unfiltered, + out IEnumerable> filtered) where TMetadata : IMetadata + { + var unfilteredExtensions = new List>>(); + var filteredExtensions = new List>(); + + var testPlugins = this.GetExtensionsCollection(testExtensions, typeof(IExtension)); + foreach (var plugin in testPlugins) + { + var testExtension = new LazyExtension(plugin, typeof(TMetadata)); + if (!string.IsNullOrEmpty(plugin.IdentifierData)) + { + filteredExtensions.Add(testExtension); + } + + unfilteredExtensions.Add( + new LazyExtension>(plugin, new Dictionary())); + } + + unfiltered = unfilteredExtensions; + filtered = filteredExtensions; + } + + /// + /// Helper to fetch appropriate test plugin information collection depending upon the extension data type. + /// + /// The extensions. + /// Data type of the test extension + /// Collection of test extensions of the given type + private IEnumerable GetExtensionsCollection(TestExtensions extensions, Type extensionType) + { + if (typeof(ITestDiscoverer).GetTypeInfo().IsAssignableFrom(extensionType)) + { + return this.GetValuesFromDictionary(extensions.TestDiscoverers); + } + else if (typeof(ITestExecutor).GetTypeInfo().IsAssignableFrom(extensionType)) + { + return this.GetValuesFromDictionary(extensions.TestExecutors); + } + else if (typeof(ISettingsProvider).GetTypeInfo().IsAssignableFrom(extensionType)) + { + return this.GetValuesFromDictionary(extensions.TestSettingsProviders); + } + else if (typeof(ITestLogger).GetTypeInfo().IsAssignableFrom(extensionType)) + { + return this.GetValuesFromDictionary(extensions.TestLoggers); + } + else + { + EqtTrace.Info("TestExtensionsManager.GetExtesnionsCollection: Failed to get test extensions colleciton for type {0}", extensionType); + return null; + } + } + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.Common/ExtensionFramework/Utilities/LazyExtension.cs b/src/Microsoft.TestPlatform.Common/ExtensionFramework/Utilities/LazyExtension.cs new file mode 100644 index 0000000000..130cc31235 --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/ExtensionFramework/Utilities/LazyExtension.cs @@ -0,0 +1,190 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.Utilities +{ + using System; + using System.Linq; + + /// + /// Class to hold a test extension type + /// + /// Test extension type + /// Test extension metadata + public class LazyExtension + { + #region Private Members + + private static object synclock = new object(); + private TExtension extension; + private TMetadata metadata; + private TestPluginInformation testPluginInfo; + private Type metadataType; + private bool isExtensionCreated; + private Func extensionCreator; + + #endregion + + #region Constructors + + /// + /// The constructor. + /// + /// Test extension Instance + /// test extension metadata + public LazyExtension(TExtension instance, TMetadata metadata) + { + if (instance == null) + { + throw new ArgumentNullException("instance"); ; + } + + if (metadata == null) + { + throw new ArgumentNullException("instance"); ; + } + + this.extension = instance; + this.metadata = metadata; + this.isExtensionCreated = true; + } + + /// + /// The constructor. + /// + /// Test plugin to instantiated on demand. + /// Metadata type to instantiate on demand + public LazyExtension(TestPluginInformation pluginInfo, Type metadataType) + { + if (pluginInfo == null) + { + throw new ArgumentNullException("pluginInfo"); ; + } + + if (metadataType == null) + { + throw new ArgumentNullException("metadataType"); ; + } + + this.testPluginInfo = pluginInfo; + this.metadataType = metadataType; + this.isExtensionCreated = false; + } + + /// + /// The constructor. + /// + /// Test plugin to instantiated on demand + /// Test extension metadata + public LazyExtension(TestPluginInformation pluginInfo, TMetadata metadata) + { + if (pluginInfo == null) + { + throw new ArgumentNullException("pluginInfo"); ; + } + + if (metadata == null) + { + throw new ArgumentNullException("metadata"); ; + } + + this.testPluginInfo = pluginInfo; + this.metadata = metadata; + this.isExtensionCreated = false; + } + + /// + /// Delegate Constructor + /// + /// Test extension creator delegate + /// test extension metadata + public LazyExtension(Func creator, TMetadata metadata) + { + if (creator == null) + { + throw new ArgumentNullException("creator"); ; + } + + if (metadata == null) + { + throw new ArgumentNullException("metadata"); ; + } + + this.extensionCreator = creator; + this.metadata = metadata; + this.isExtensionCreated = false; + } + + #endregion + + #region Public Properties + + /// + /// Gets a value indicating whether is extension created. + /// + internal bool IsExtensionCreated + { + get + { + return this.isExtensionCreated; + } + } + + /// + /// Gets the test extension instance. + /// + public TExtension Value + { + get + { + if (!this.isExtensionCreated) + { + if (this.extensionCreator != null) + { + this.extension = this.extensionCreator(); + } + else if (this.extension == null) + { + lock (synclock) + { + if (this.extension == null && this.testPluginInfo != null) + { + var pluginType = TestPluginManager.GetTestExtensionType(this.testPluginInfo.AssemblyQualifiedName); + this.extension = TestPluginManager.CreateTestExtension(pluginType); + } + } + } + + this.isExtensionCreated = true; + } + + return this.extension; + } + } + + /// + /// Gets the test extension metadata + /// + public TMetadata Metadata + { + get + { + if (this.metadata == null) + { + lock (synclock) + { + if (this.metadata == null && this.testPluginInfo != null) + { + var parameters = this.testPluginInfo.Metadata == null ? null : this.testPluginInfo.Metadata.ToArray(); + var dataObject = Activator.CreateInstance(this.metadataType, parameters); + this.metadata = (TMetadata)dataObject; + } + } + } + + return this.metadata; + } + } + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.Common/ExtensionFramework/Utilities/TestDiscovererPluginInformation.cs b/src/Microsoft.TestPlatform.Common/ExtensionFramework/Utilities/TestDiscovererPluginInformation.cs new file mode 100644 index 0000000000..d2b5cd7309 --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/ExtensionFramework/Utilities/TestDiscovererPluginInformation.cs @@ -0,0 +1,108 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.Utilities +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + + using ObjectModel; + + /// + /// The test discoverer plugin information. + /// + internal class TestDiscovererPluginInformation : TestPluginInformation + { + /// + /// Default constructor + /// + /// Data type of the test discoverer + public TestDiscovererPluginInformation(Type testDiscovererType) + : base(testDiscovererType) + { + if (testDiscovererType != null) + { + this.FileExtensions = GetFileExtensions(testDiscovererType); + this.DefaultExecutorUri = GetDefaultExecutorUri(testDiscovererType); + } + } + + /// + /// Metadata for the test plugin + /// + public override ICollection Metadata + { + get + { + return new object[] { this.FileExtensions, this.DefaultExecutorUri }; + } + } + + /// + /// Gets collection of file extensions supported by discoverer plugin. + /// + public List FileExtensions + { + get; + private set; + } + + /// + /// Gets the Uri identifying the executor + /// + public string DefaultExecutorUri + { + get; + private set; + } + + /// + /// Helper to get file extensions from the FileExtensionAttribute on the discover plugin. + /// + /// Data type of the test discoverer + /// List of file extensions + private static List GetFileExtensions(Type testDicovererType) + { + var fileExtensions = new List(); + + var attributes = testDicovererType.GetTypeInfo().GetCustomAttributes(typeof(FileExtensionAttribute), false).ToArray(); + if (attributes != null && attributes.Length > 0) + { + foreach (var attribute in attributes) + { + var fileExtAttribute = (FileExtensionAttribute)attribute; + if (!string.IsNullOrEmpty(fileExtAttribute.FileExtension)) + { + fileExtensions.Add(fileExtAttribute.FileExtension); + } + } + } + + return fileExtensions; + } + + /// + /// Returns the value of default executor Uri on this type. 'Null' if not present. + /// + /// The test discoverer Type. + /// The default executor URI. + private static string GetDefaultExecutorUri(Type testDiscovererType) + { + string result = string.Empty; + + object[] attributes = testDiscovererType.GetTypeInfo().GetCustomAttributes(typeof(DefaultExecutorUriAttribute), false).ToArray(); + if (attributes != null && attributes.Length > 0) + { + DefaultExecutorUriAttribute executorUriAttribute = (DefaultExecutorUriAttribute)attributes[0]; + + if (!string.IsNullOrEmpty(executorUriAttribute.ExecutorUri)) + { + result = executorUriAttribute.ExecutorUri; + } + } + + return result; + } + } +} diff --git a/src/Microsoft.TestPlatform.Common/ExtensionFramework/Utilities/TestExecutorPluginInformation.cs b/src/Microsoft.TestPlatform.Common/ExtensionFramework/Utilities/TestExecutorPluginInformation.cs new file mode 100644 index 0000000000..d74dcdd8d4 --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/ExtensionFramework/Utilities/TestExecutorPluginInformation.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.Utilities +{ + using System; + + /// + /// The test executor plugin information. + /// + internal class TestExecutorPluginInformation : TestExtensionPluginInformation + { + /// + /// Default constructor + /// + /// The test Executor Type. + public TestExecutorPluginInformation(Type testExecutorType) + : base(testExecutorType) + { + } + } +} diff --git a/src/Microsoft.TestPlatform.Common/ExtensionFramework/Utilities/TestExtensionPluginInformation.cs b/src/Microsoft.TestPlatform.Common/ExtensionFramework/Utilities/TestExtensionPluginInformation.cs new file mode 100644 index 0000000000..211038b9ab --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/ExtensionFramework/Utilities/TestExtensionPluginInformation.cs @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.Utilities +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + + /// + /// The test extension plugin information. + /// + internal abstract class TestExtensionPluginInformation : TestPluginInformation + { + /// + /// Default constructor + /// + /// The test Logger Type. + public TestExtensionPluginInformation(Type type) + : base(type) + { + if (type != null) + { + this.ExtensionUri = GetExtensionUri(type); + } + } + + /// + /// Gets data value identifying the test plugin + /// + public override string IdentifierData + { + get + { + return this.ExtensionUri; + } + } + + /// + /// Metadata for the test plugin + /// + public override ICollection Metadata + { + get + { + return new object[] { this.ExtensionUri }; + } + } + + /// + /// Gets the Uri identifying the test extension. + /// + public string ExtensionUri + { + get; + private set; + } + + /// + /// Helper to get the Uri from the ExtensionUriAttribute on logger plugin. + /// + /// Data type of the test logger + /// Uri identifying the test logger + private static string GetExtensionUri(Type testLoggerType) + { + string extensionUri = string.Empty; + + object[] attributes = testLoggerType.GetTypeInfo().GetCustomAttributes(typeof(ExtensionUriAttribute), false).ToArray(); + if (attributes != null && attributes.Length > 0) + { + ExtensionUriAttribute extensionUriAttribute = (ExtensionUriAttribute)attributes[0]; + + if (!string.IsNullOrEmpty(extensionUriAttribute.ExtensionUri)) + { + extensionUri = extensionUriAttribute.ExtensionUri; + } + } + + return extensionUri; + } + } +} diff --git a/src/Microsoft.TestPlatform.Common/ExtensionFramework/Utilities/TestExtensions.cs b/src/Microsoft.TestPlatform.Common/ExtensionFramework/Utilities/TestExtensions.cs new file mode 100644 index 0000000000..a4687f068e --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/ExtensionFramework/Utilities/TestExtensions.cs @@ -0,0 +1,152 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.Utilities +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + + /// + /// The test extension information. + /// + public class TestExtensions + { + #region Properties + + /// + /// Gets or sets test discoverer extensions. + /// + internal Dictionary TestDiscoverers { get; set; } + + /// + /// Gets or sets test executor extensions. + /// + internal Dictionary TestExecutors { get; set; } + + /// + /// Gets or sets test setting provider extensions. + /// + internal Dictionary TestSettingsProviders { get; set; } + + /// + /// Gets or sets test logger extensions. + /// + internal Dictionary TestLoggers { get; set; } + + #endregion + + #region Internal methods + + /// + /// Adds the extensions specified to the current set of extensions. + /// + /// The test extensions to add. + internal void AddExtensions(TestExtensions extensions) + { + if (extensions == null) + { + return; + } + + this.TestDiscoverers = this.AddExtension(this.TestDiscoverers, extensions.TestDiscoverers); + this.TestExecutors = this.AddExtension(this.TestExecutors, extensions.TestExecutors); + this.TestSettingsProviders = this.AddExtension(this.TestSettingsProviders, extensions.TestSettingsProviders); + this.TestLoggers = this.AddExtension(this.TestLoggers, extensions.TestLoggers); + } + + /// + /// Gets the extensions already discovered that are defined in the specified assembly. + /// + /// The extension assembly. + /// The test extensions defined the extension assembly if it is already discovered. null if not. + internal TestExtensions GetExtensionsDiscoveredFromAssembly(string extensionAssembly) + { + var testExtensions = new TestExtensions(); + + testExtensions.TestDiscoverers = + this.GetExtensionsDiscoveredFromAssembly( + this.TestDiscoverers, + extensionAssembly); + testExtensions.TestExecutors = + this.GetExtensionsDiscoveredFromAssembly( + this.TestExecutors, + extensionAssembly); + testExtensions.TestSettingsProviders = + this.GetExtensionsDiscoveredFromAssembly( + this.TestSettingsProviders, + extensionAssembly); + testExtensions.TestLoggers = + this.GetExtensionsDiscoveredFromAssembly( + this.TestLoggers, + extensionAssembly); + + if (testExtensions.TestDiscoverers.Any() || testExtensions.TestExecutors.Any() || testExtensions.TestSettingsProviders.Any() || testExtensions.TestLoggers.Any()) + { + // This extension has already been discovered. + return testExtensions; + } + + return null; + } + + #endregion + + #region Private methods + + private Dictionary AddExtension( + Dictionary existingExtensions, + Dictionary newExtensions) + { + if (newExtensions == null) + { + return null; + } + + if (existingExtensions == null) + { + return newExtensions; + } + else + { + foreach (var extension in newExtensions) + { + if (existingExtensions.ContainsKey(extension.Key)) + { + EqtTrace.Warning( + "TestExtensions.AddExtensions: Attempt to add multiple test extensions with identifier data '{0}'", + extension.Key); + } + else + { + existingExtensions.Add(extension.Key, extension.Value); + } + } + return existingExtensions; + } + } + + private Dictionary GetExtensionsDiscoveredFromAssembly(Dictionary extensionCollection, string extensionAssembly) + { + var extensions = new Dictionary(); + if (extensionCollection != null) + { + foreach (var extension in extensionCollection) + { + var testPluginInformation = extension.Value as TestPluginInformation; + var extensionType = Type.GetType(testPluginInformation?.AssemblyQualifiedName); + if (string.Equals(extensionType.GetTypeInfo().Assembly.Location, extensionAssembly)) + { + extensions.Add(extension.Key, extension.Value); + } + } + } + + return extensions; + } + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.Common/ExtensionFramework/Utilities/TestLoggerPluginInformation.cs b/src/Microsoft.TestPlatform.Common/ExtensionFramework/Utilities/TestLoggerPluginInformation.cs new file mode 100644 index 0000000000..bf3763d7c2 --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/ExtensionFramework/Utilities/TestLoggerPluginInformation.cs @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.Utilities +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + + using ObjectModel; + + /// + /// The test logger plugin information. + /// + internal class TestLoggerPluginInformation : TestExtensionPluginInformation + { + /// + /// Default constructor + /// + /// The test Logger Type. + public TestLoggerPluginInformation(Type testLoggerType) + : base(testLoggerType) + { + this.FriendlyName = GetFriendlyName(testLoggerType); + } + + /// + /// Gets the Friendly Name identifying the logger + /// + public string FriendlyName + { + get; + private set; + } + + /// + /// Metadata for the test plugin + /// + public override ICollection Metadata + { + get + { + return new Object[] { this.ExtensionUri, this.FriendlyName }; + } + } + + /// + /// Helper to get the FriendlyName from the FriendlyNameAttribute on logger plugin. + /// + /// Data type of the test logger + /// FriendlyName identifying the test logger + private static string GetFriendlyName(Type testLoggerType) + { + string friendlyName = string.Empty; + + object[] attributes = testLoggerType.GetTypeInfo().GetCustomAttributes(typeof(FriendlyNameAttribute), false).ToArray(); + if (attributes != null && attributes.Length > 0) + { + FriendlyNameAttribute friendlyNameAttribute = (FriendlyNameAttribute)attributes[0]; + + if (!string.IsNullOrEmpty(friendlyNameAttribute.FriendlyName)) + { + friendlyName = friendlyNameAttribute.FriendlyName; + } + } + + return friendlyName; + } + } +} diff --git a/src/Microsoft.TestPlatform.Common/ExtensionFramework/Utilities/TestPluginInformation.cs b/src/Microsoft.TestPlatform.Common/ExtensionFramework/Utilities/TestPluginInformation.cs new file mode 100644 index 0000000000..39f8924b0e --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/ExtensionFramework/Utilities/TestPluginInformation.cs @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.Utilities +{ + using System; + using System.Collections.Generic; + + public abstract class TestPluginInformation + { + /// + /// Default constructor + /// + /// Data type of the test plugin + protected TestPluginInformation(Type testExtensionType) + { + if (testExtensionType != null) + { + this.AssemblyQualifiedName = testExtensionType.AssemblyQualifiedName; + } + } + + /// + /// Gets data value identifying the test plugin + /// + public virtual string IdentifierData + { + get + { + return this.AssemblyQualifiedName; + } + } + + /// + /// Metadata for the test plugin + /// + public virtual ICollection Metadata + { + get + { + return new object[] { this.AssemblyQualifiedName }; + } + } + + /// + /// Gets the Assembly qualified name of the plugin + /// + public string AssemblyQualifiedName + { + get; + private set; + } + } +} diff --git a/src/Microsoft.TestPlatform.Common/ExtensionFramework/Utilities/TestSettingsProviderPluginInformation.cs b/src/Microsoft.TestPlatform.Common/ExtensionFramework/Utilities/TestSettingsProviderPluginInformation.cs new file mode 100644 index 0000000000..7d6a6422f4 --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/ExtensionFramework/Utilities/TestSettingsProviderPluginInformation.cs @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.Utilities +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + + using ObjectModel; + + /// + /// The test settings provider plugin information. + /// + internal class TestSettingsProviderPluginInformation : TestPluginInformation + { + /// + /// Default constructor + /// + /// The test Settings Provider Type. + public TestSettingsProviderPluginInformation(Type testSettingsProviderType) + : base(testSettingsProviderType) + { + if (testSettingsProviderType != null) + { + this.SettingsName = GetTestSettingsName(testSettingsProviderType); + } + } + + /// + /// Gets data value identifying the test plugin + /// + public override string IdentifierData + { + get + { + return this.SettingsName; + } + } + + /// + /// Metadata for the test plugin + /// + public override ICollection Metadata + { + get + { + return new object[] { this.SettingsName }; + } + } + + /// + /// Gets name of test settings supported by plugin. + /// + public string SettingsName + { + get; + private set; + } + + /// + /// Helper to get the test settings name from SettingsNameAttribute on test setting provider plugin. + /// + /// Data type of test setting provider + /// Test settings name supported by plugin + private static string GetTestSettingsName(Type testSettingsProviderType) + { + string settingName = string.Empty; + + object[] attributes = testSettingsProviderType.GetTypeInfo().GetCustomAttributes(typeof(SettingsNameAttribute), false).ToArray(); + if (attributes != null && attributes.Length > 0) + { + SettingsNameAttribute settingsNameAttribute = (SettingsNameAttribute)attributes[0]; + + if (!string.IsNullOrEmpty(settingsNameAttribute.SettingsName)) + { + settingName = settingsNameAttribute.SettingsName; + } + } + + return settingName; + } + } +} diff --git a/src/Microsoft.TestPlatform.Common/Filtering/Condition.cs b/src/Microsoft.TestPlatform.Common/Filtering/Condition.cs new file mode 100644 index 0000000000..c815dc75f4 --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/Filtering/Condition.cs @@ -0,0 +1,262 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Common.Filtering +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.Linq; + using System.Text.RegularExpressions; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.Utilities; + + internal enum Operation + { + Equal, + NotEqual, + Contains, + } + + /// + /// Operator in order of precedence. + /// Predence(And) > Predence(Or) + /// Precdence of OpenBrace and CloseBrace operators is not used, instead parsing code takes care of same. + /// + internal enum Operator + { + None, + Or, + And, + OpenBrace, + CloseBrace, + } + + /// + /// Represents a condition in filter expression. + /// + internal class Condition + { + #region Fields + /// + /// String seperator used for parsing input string of format 'Operation' + /// ! is not a valid operation, but is required to filter the invalid patterns. + /// + private static string propertyNameValueSeperatorString = @"(\!\=)|(\=)|(\~)|(\!)"; + + + /// + /// Name of the property used in condition. + /// + internal string Name + { + get; + private set; + } + + /// + /// Value for the property. + /// + internal string Value + { + get; + private set; + } + + /// + /// Operation to be performed. + /// + internal Operation Operation + { + get; + private set; + } + #endregion + + #region Constructors + internal Condition(string name, Operation operation, string value) + { + this.Name = name; + this.Operation = operation; + this.Value = value; + } + #endregion + + + /// + /// Evaluate this condition for testObject. + /// + internal bool Evaluate(Func propertyValueProvider) + { + ValidateArg.NotNull(propertyValueProvider, "propertyValueProvider"); + var result = false; + var multiValue = this.GetPropertyValue(propertyValueProvider); + switch (this.Operation) + { + case Operation.Equal: + // if any value in multi-valued property matches 'this.Value', for Equal to evaluate true. + if (null != multiValue) + { + foreach (string propertyValue in multiValue) + { + result = result || string.Equals(propertyValue, Value, StringComparison.OrdinalIgnoreCase); + if (result) + { + break; + } + } + } + break; + + + case Operation.NotEqual: + // all values in multi-valued property should not match 'this.Value' for NotEqual to evaluate true. + result = true; + + // if value is null. + if (null != multiValue) + { + foreach (string propertyValue in multiValue) + { + result = result && !string.Equals(propertyValue, Value, StringComparison.OrdinalIgnoreCase); + if (!result) + { + break; + } + } + } + break; + + case Operation.Contains: + // if any value in mulit-valued property contains 'this.Value' for 'Contains' to be true. + if (null != multiValue) + { + foreach (string propertyValue in multiValue) + { + Debug.Assert(null != propertyValue, "PropertyValue can not be null."); + result = result || propertyValue.IndexOf(Value, StringComparison.OrdinalIgnoreCase) != -1; + if (result) + { + break; + } + } + } + break; + } + return result; + } + + + + + /// + /// Returns a condition object after parsing input string of format 'Operation' + /// + internal static Condition Parse(string conditionString) + { + string[] parts = Regex.Split(conditionString, propertyNameValueSeperatorString); + if (parts.Length != 3) + { + throw new FormatException(string.Format(CultureInfo.CurrentCulture, Common.Resources.TestCaseFilterFormatException, string.Format(CultureInfo.CurrentCulture, Common.Resources.InvalidCondition, conditionString))); + } + + for (int index = 0; index < 3; index++) + { + if (string.IsNullOrWhiteSpace(parts[index])) + { + throw new FormatException(string.Format(CultureInfo.CurrentCulture, Common.Resources.TestCaseFilterFormatException, string.Format(CultureInfo.CurrentCulture, Common.Resources.InvalidCondition, conditionString))); + } + parts[index] = parts[index].Trim(); + } + + Operation operation = GetOperator(parts[1]); + Condition condition = new Condition(parts[0], operation, parts[2]); + return condition; + } + + + + + /// + /// Check if condition validates any property in properties. + /// + internal bool ValidForProperties(IEnumerable properties, Func propertyProvider) + { + bool valid = false; + + if (properties.Contains(this.Name, StringComparer.OrdinalIgnoreCase)) + { + valid = true; + + // Check if operation ~ (Contains) is on property of type string. + if (this.Operation == Operation.Contains) + { + valid = this.ValidForContainsOperation(propertyProvider); + } + } + return valid; + } + + private bool ValidForContainsOperation(Func propertyProvider) + { + bool valid = true; + + // It is OK for propertyProvider to be null, no syntax check will happen. + + // Check validity of operator only if related TestProperty is non-null. + // if null, it might be custom validation ignore it. + if (null != propertyProvider) + { + TestProperty testProperty = propertyProvider(Name); + if (null != testProperty) + { + Type propertyType = testProperty.GetValueType(); + valid = typeof(string) == propertyType || + typeof(string[]) == propertyType; + } + } + return valid; + } + + /// + /// Return Operation corresponding to the operationString + /// + private static Operation GetOperator(string operationString) + { + switch (operationString) + { + case "=": + return Operation.Equal; + + case "!=": + return Operation.NotEqual; + + case "~": + return Operation.Contains; + } + throw new FormatException(string.Format(CultureInfo.CurrentCulture, Common.Resources.TestCaseFilterFormatException, string.Format(CultureInfo.CurrentCulture, Common.Resources.InvalidOperator, operationString))); + } + + /// + /// Returns property value for Property using propertValueProvider. + /// + private string[] GetPropertyValue(Func propertyValueProvider) + { + var propertyValue = propertyValueProvider(this.Name); + if (null != propertyValue) + { + var multiValue = propertyValue as string[]; + if (null == multiValue) + { + multiValue = new string[1]; + multiValue[0] = propertyValue.ToString(); + } + return multiValue; + } + + return null; + } + + } +} diff --git a/src/Microsoft.TestPlatform.Common/Filtering/FilterExpression.cs b/src/Microsoft.TestPlatform.Common/Filtering/FilterExpression.cs new file mode 100644 index 0000000000..2da06fabff --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/Filtering/FilterExpression.cs @@ -0,0 +1,333 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Common.Filtering +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.Linq; + using System.Text.RegularExpressions; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.Utilities; + + /// + /// Represents an expression tree. + /// Supports: + /// Logical Operators: &, | + /// Equality Operators: =, != + /// Parenthesis (, ) for grouping. + /// + internal class FilterExpression + { + + /// + /// Seperator string to seperate various tokens in input string. + /// + private static string filterExpressionSeperatorString = @"(\&)|(\|)|(\()|(\))"; + + /// + /// Condition, if expression is conditional expression. + /// + private Condition condition; + + /// + /// Left operand, when expression is logical expression. + /// + private FilterExpression left; + + /// + /// Right operand, when expression is logical expression. + /// + private FilterExpression right; + + /// + /// If logical expression is using logical And ('&') operator. + /// + private bool areJoinedByAnd; + + #region Constructors + + internal FilterExpression() + { + } + + private FilterExpression(Condition condition) + { + ValidateArg.NotNull(condition, "condition"); + this.condition = condition; + } + #endregion + + + /// + /// True, if filter expression is empty. + /// + internal bool IsEmpty + { + get + { + return + (this.left == null) && + (this.right == null) && + (this.condition == null); + } + } + + + + /// + /// Create a new filter expression 'And'ing 'this' with 'filter'. + /// + private FilterExpression And(FilterExpression filter) + { + if (this.IsEmpty) + { + return filter; + } + + if (filter.IsEmpty) + { + return this; + } + + var result = new FilterExpression(); + + result.left = this; + result.right = filter; + result.areJoinedByAnd = true; + return result; + } + + /// + /// Create a new filter expression 'Or'ing 'this' with 'filter'. + /// + private FilterExpression Or(FilterExpression filter) + { + var result = this.And(filter); + result.areJoinedByAnd = false; + return result; + } + + + /// + /// Process the given operator from the filterStack. + /// Puts back the result of operation back to filterStack. + /// + private static void ProcessOperator(Stack filterStack, Operator op) + { + if (op == Operator.And) + { + if (filterStack.Count < 2) + { + throw new FormatException(string.Format(CultureInfo.CurrentCulture, Common.Resources.TestCaseFilterFormatException, Common.Resources.MissingOperand)); + } + + var filterRight = filterStack.Pop(); + var filterLeft = filterStack.Pop(); + var result = filterLeft.And(filterRight); + filterStack.Push(result); + } + else if (op == Operator.Or) + { + if (filterStack.Count < 2) + { + throw new FormatException(string.Format(CultureInfo.CurrentCulture, Common.Resources.TestCaseFilterFormatException, Common.Resources.MissingOperand)); + } + + var filterRight = filterStack.Pop(); + var filterLeft = filterStack.Pop(); + var result = filterLeft.Or(filterRight); + filterStack.Push(result); + } + else if (op == Operator.OpenBrace) + { + throw new FormatException(string.Format(CultureInfo.CurrentCulture, Common.Resources.TestCaseFilterFormatException, Common.Resources.MissingCloseParenthesis)); + } + else + { + Debug.Assert(false, "ProcessOperator called for Unexpected operator."); + throw new FormatException(string.Format(CultureInfo.CurrentCulture, Common.Resources.TestCaseFilterFormatException, string.Empty)); + } + } + + + + /// + /// True, if filter is valid for given set of properties. + /// When False, invalidProperties would contain properties making filter invalid. + /// + internal string[] ValidForProperties(IEnumerable properties, Func propertyProvider) + { + string[] invalidProperties = null; + + if (null == properties) + { + // if null, initialize to empty list so that invalid properties can be found. + properties = new List(); + } + + bool valid = false; + if (this.condition != null) + { + valid = this.condition.ValidForProperties(properties, propertyProvider); + if (!valid) + { + invalidProperties = new string[1] { this.condition.Name }; + } + } + else + { + invalidProperties = this.left.ValidForProperties(properties, propertyProvider); + var invalidRight = this.right.ValidForProperties(properties, propertyProvider); + if (null == invalidProperties) + { + invalidProperties = invalidRight; + } + else if (null != invalidRight) + { + invalidProperties = invalidProperties.Concat(invalidRight).ToArray(); + } + } + return invalidProperties; + } + + + /// + /// Return FilterExpression after parsing the given filter expression. + /// + internal static FilterExpression Parse(string filterString) + { + ValidateArg.NotNull(filterString, "filterString"); + + // below parsing doesn't error out on pattern (), so explicitly search for that (empty parethesis). + var invalidInput = Regex.Match(filterString, @"\(\s*\)"); + if (invalidInput.Success) + { + throw new FormatException(string.Format(CultureInfo.CurrentCulture, Common.Resources.TestCaseFilterFormatException, Common.Resources.EmptyParenthesis)); + } + + var tokens = Regex.Split(filterString, filterExpressionSeperatorString); + var operatorStack = new Stack(); + var filterStack = new Stack(); + + // This is based on standard parsing of inorder expression using two stacks (operand stack and operator stack) + // Predence(And) > Predence(Or) + foreach (var inputToken in tokens) + { + var token = inputToken.Trim(); + if (string.IsNullOrEmpty(token)) + { + // ignore empty tokens + continue; + } + + switch (token) + { + case "&": + case "|": + Operator currentOperator = Operator.And; + if (string.Equals("|", token)) + { + currentOperator = Operator.Or; + } + + // Always put only higher priority operator on stack. + // if lesser prioriy -- pop up the stack and process the operator to maintain operator precedence. + // if equal priority -- pop up the stack and process the operator to maintain operator associativity. + // OpenBrace is special condition. & or | can come on top of OpenBrace for case like ((a=b)&c=d) + while (true) + { + bool isEmpty = operatorStack.Count == 0; + Operator stackTopOperator = isEmpty ? Operator.None : operatorStack.Peek(); + if (isEmpty || stackTopOperator == Operator.OpenBrace || stackTopOperator < currentOperator) + { + operatorStack.Push(currentOperator); + break; + } + stackTopOperator = operatorStack.Pop(); + ProcessOperator(filterStack, stackTopOperator); + } + break; + + case "(": + operatorStack.Push(Operator.OpenBrace); + break; + + case ")": + // process operators from the stack till OpenBrace is found. + // If stack is empty at any time, than matching OpenBrace is missing from the expression. + if (operatorStack.Count == 0) + { + throw new FormatException(string.Format(CultureInfo.CurrentCulture, Common.Resources.TestCaseFilterFormatException, Common.Resources.MissingOpenParenthesis)); + } + + Operator temp = operatorStack.Pop(); + while (temp != Operator.OpenBrace) + { + ProcessOperator(filterStack, temp); + if (operatorStack.Count == 0) + { + throw new FormatException(string.Format(CultureInfo.CurrentCulture, Common.Resources.TestCaseFilterFormatException, Common.Resources.MissingOpenParenthesis)); + } + temp = operatorStack.Pop(); + } + + break; + + default: + // push the operand to the operand stack. + Condition condition = Condition.Parse(token); + FilterExpression filter = new FilterExpression(condition); + filterStack.Push(filter); + break; + } + } + while (operatorStack.Count != 0) + { + Operator temp = operatorStack.Pop(); + ProcessOperator(filterStack, temp); + } + + if (filterStack.Count != 1) + { + throw new FormatException(string.Format(CultureInfo.CurrentCulture, Common.Resources.TestCaseFilterFormatException, Common.Resources.MissingOperator)); + } + + return filterStack.Pop(); + } + + /// + /// Evaluate filterExpression with given propertyValueProvider. + /// + /// The property Value Provider. + /// True if evaluation is successful. + internal bool Evaluate(Func propertyValueProvider) + { + ValidateArg.NotNull(propertyValueProvider, "propertyValueProvider"); + + Debug.Assert(!this.IsEmpty, "Filter expression is empty."); + bool filterResult = false; + if (null != this.condition) + { + filterResult = this.condition.Evaluate(propertyValueProvider); + } + else + { + // & or | operator + bool leftResult = this.left.Evaluate(propertyValueProvider); + bool rightResult = this.right.Evaluate(propertyValueProvider); + if (this.areJoinedByAnd) + { + filterResult = leftResult && rightResult; + } + else + { + filterResult = leftResult || rightResult; + } + } + return filterResult; + } + } +} diff --git a/src/Microsoft.TestPlatform.Common/Filtering/FilterExpressionWrapper.cs b/src/Microsoft.TestPlatform.Common/Filtering/FilterExpressionWrapper.cs new file mode 100644 index 0000000000..6205f81c3b --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/Filtering/FilterExpressionWrapper.cs @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Common.Filtering +{ + using System; + using System.Collections.Generic; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + + /// + /// Class holds information related to filtering criteria. + /// + public class FilterExpressionWrapper + { + /// + /// FilterExpression corresponding to filter criteria + /// + private FilterExpression filterExpression; + + /// + /// Initializes FilterExpressionWrapper with given filterString. + /// + public FilterExpressionWrapper(string filterString) + { + ValidateArg.NotNullOrEmpty(filterString, "filterString"); + + this.FilterString = filterString; + try + { + this.filterExpression = FilterExpression.Parse(filterString); + } + catch (FormatException ex) + { + this.ParseError = ex.Message; + } + } + + /// + /// User specified filter criteria. + /// + public string FilterString + { + get; + private set; + } + + /// + /// Parsing error (if any), when parsing 'FilterString' with built-in parser. + /// + public string ParseError + { + get; + private set; + } + + /// + /// Validate if underlying filter expression is valid for given set of supported properties. + /// + public string[] ValidForProperties(IEnumerable supportedProperties, Func propertyProvider) + { + string[] invalidProperties = null; + if (null != this.filterExpression) + { + invalidProperties = this.filterExpression.ValidForProperties(supportedProperties, propertyProvider); + } + return invalidProperties; + } + + /// + /// Evaluate filterExpression with given propertyValueProvider. + /// + public bool Evaluate(Func propertyValueProvider) + { + ValidateArg.NotNull(propertyValueProvider, "propertyValueProvider"); + if (null == this.filterExpression) + { + return false; + } + return this.filterExpression.Evaluate(propertyValueProvider); + } + + } +} diff --git a/src/Microsoft.TestPlatform.Common/Filtering/TestCaseFilterExpression.cs b/src/Microsoft.TestPlatform.Common/Filtering/TestCaseFilterExpression.cs new file mode 100644 index 0000000000..8a47e5f69e --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/Filtering/TestCaseFilterExpression.cs @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Common.Filtering +{ + using System; + using System.Collections.Generic; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using ObjectModel.Adapter; + + /// + /// Implements ITestCaseFilterExpression, providing test case filtering functionality. + /// + public class TestCaseFilterExpression : ITestCaseFilterExpression + { + private FilterExpressionWrapper filterWrapper; + + /// + /// If filter Expression is valid for performing TestCase matching + /// (has only supported properties, syntax etc) + /// + private bool validForMatch; + + + /// + /// Adapter specific filter expression. + /// + public TestCaseFilterExpression(FilterExpressionWrapper filterWrapper) + { + ValidateArg.NotNull(filterWrapper, "filterWrapper"); + this.filterWrapper = filterWrapper; + this.validForMatch = string.IsNullOrEmpty(filterWrapper.ParseError); + } + + /// + /// Validate if underlying filter expression is valid for given set of supported properties. + /// + public string[] ValidForProperties(IEnumerable supportedProperties, Func propertyProvider) + { + string[] invalidProperties = null; + if (null != this.filterWrapper && this.validForMatch) + { + invalidProperties = this.filterWrapper.ValidForProperties(supportedProperties, propertyProvider); + if (null != invalidProperties) + { + this.validForMatch = false; + } + } + + return invalidProperties; + } + + /// + /// User specified filter criteria. + /// + public string TestCaseFilterValue + { + get + { + return this.filterWrapper.FilterString; + } + } + + /// + /// Match test case with filter criteria. + /// + public bool MatchTestCase(TestCase testCase, Func propertyValueProvider) + { + ValidateArg.NotNull(testCase, "testCase"); + ValidateArg.NotNull(propertyValueProvider, "propertyValueProvider"); + if (!this.validForMatch) + { + return false; + } + + if (null == this.filterWrapper) + { + // can be null when parsing error occurs. Invalid filter results in no match. + return false; + } + + return this.filterWrapper.Evaluate(propertyValueProvider); + } + + } +} diff --git a/src/Microsoft.TestPlatform.Common/Friends.cs b/src/Microsoft.TestPlatform.Common/Friends.cs new file mode 100644 index 0000000000..188d22c6e3 --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/Friends.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Runtime.CompilerServices; + +#region Product Assemblies +[assembly: InternalsVisibleTo("Microsoft.TestPlatform.CrossPlatEngine, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] +[assembly: InternalsVisibleTo("vstest.console, PublicKey =002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] +#endregion + +#region Test Assemblies +[assembly: InternalsVisibleTo("Microsoft.TestPlatform.Common.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] +[assembly: InternalsVisibleTo("Microsoft.TestPlatform.CrossPlatEngine.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] +[assembly: InternalsVisibleTo("vstest.console.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] +[assembly: InternalsVisibleTo("Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] +#endregion \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/IParallelOperationManager.cs b/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/IParallelOperationManager.cs new file mode 100644 index 0000000000..a141ad90fb --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/IParallelOperationManager.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine +{ + /// + /// Interface defining the parallel operation manager + /// + public interface IParallelOperationManager + { + /// + /// Update the parallelism level of the manager + /// + /// Parallelism level + void UpdateParallelLevel(int parallelLevel); + } +} diff --git a/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/IParallelProxyExecutionManager.cs b/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/IParallelProxyExecutionManager.cs new file mode 100644 index 0000000000..2801a0cb28 --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/IParallelProxyExecutionManager.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine +{ + using System; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using System.Collections.Generic; + + /// + /// Interface defining the parallel execution manager + /// + public interface IParallelProxyExecutionManager : IParallelOperationManager, IProxyExecutionManager + { + /// + /// Handles Partial Run Complete event coming from a specific concurrent proxy exceution manager + /// Each concurrent proxy execution manager will signal the parallel execution manager when its complete + /// + /// Concurrent Execution manager that completed the run + /// RunCompleteArgs for the concurrent run + /// LastChunk testresults for the concurrent run + /// RunAttachments for the concurrent run + /// ExecutorURIs of the adapters involved in executing the tests + /// True if parallel run is complete + bool HandlePartialRunComplete( + IProxyExecutionManager proxyExecutionManager, + TestRunCompleteEventArgs testRunCompleteArgs, + TestRunChangedEventArgs lastChunkArgs, + ICollection runContextAttachments, + ICollection executorUris); + } +} diff --git a/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/IProxyDiscoveryManager.cs b/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/IProxyDiscoveryManager.cs new file mode 100644 index 0000000000..ea4a27bf00 --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/IProxyDiscoveryManager.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine +{ + using System; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + + /// + /// Orchestrates discovery operations for the engine communicating with the client. + /// + public interface IProxyDiscoveryManager : IProxyOperationManager + { + /// + /// Discovers tests + /// + /// Settings, parameters for the discovery request + /// EventHandler for handling discovery events from Engine + void DiscoverTests(DiscoveryCriteria discoveryCriteria, ITestDiscoveryEventsHandler eventHandler); + } +} diff --git a/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/IProxyExecutionManager.cs b/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/IProxyExecutionManager.cs new file mode 100644 index 0000000000..fb6fdcff3b --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/IProxyExecutionManager.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine +{ + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + + /// + /// Orchestrates test execution related functionality for the engine communicating with the client. + /// + public interface IProxyExecutionManager : IProxyOperationManager + { + /// + /// Starts the test run + /// + /// The settings/options for the test run. + /// EventHandler for handling execution events from Engine. + int StartTestRun(TestRunCriteria testRunCriteria, ITestRunEventsHandler eventHandler); + + /// + /// Cancels the test run + /// TODO: what's the difference between abort and cancel + /// + void Cancel(); + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/IProxyOperationManager.cs b/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/IProxyOperationManager.cs new file mode 100644 index 0000000000..fe0f5f6800 --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/IProxyOperationManager.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine +{ + using System; + + /// + /// Base interface for discovery and execution operations. + /// + public interface IProxyOperationManager : IDisposable + { + /// + /// Ensure that the engine is ready for test operations. + /// Usually includes starting up the test host process. + /// + /// + /// Manager for the test host process + /// + void Initialize(ITestHostManager testHostManager); + + /// + /// Aborts the test operation. + /// + void Abort(); + } +} diff --git a/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/ITestEngine.cs b/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/ITestEngine.cs new file mode 100644 index 0000000000..557ef280f0 --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/ITestEngine.cs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft. All rights reserved. +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine +{ + using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + + /// + /// Defines the functionality of a test engine. + /// + public interface ITestEngine + { + /// + /// Fetches the DiscoveryManager for this engine. This manager would provide all functionality required for discovery. + /// + /// ITestDiscoveryManager object that can do discovery + IProxyDiscoveryManager GetDiscoveryManager(); + + /// + /// Fetches the ExecutionManager for this engine. This manager would provide all functionality required for execution. + /// + /// TestRunCriteria of the current test run + /// ITestExecutionManager object that can do execution + IProxyExecutionManager GetExecutionManager(TestRunCriteria testRunCriteria); + + /// + /// Fetches the extension manager for this engine. This manager would provide extensibility features that this engine supports. + /// + /// ITestExtensionManager object that helps with extensibility + ITestExtensionManager GetExtensionManager(); + + /// + /// Fetches the Test Host manager for this engine. This manager would provide extensibility features that this engine supports. + /// + /// Architecture of the test run + /// Launcher for the test host process + ITestHostManager GetDefaultTestHostManager(Architecture architecture); + } +} diff --git a/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/ITestExtensionManager.cs b/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/ITestExtensionManager.cs new file mode 100644 index 0000000000..d3ab524f9d --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/ITestExtensionManager.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine +{ + using System.Collections.Generic; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + + /// + /// Orchestrates extensions for this engine. + /// + public interface ITestExtensionManager + { + /// + /// Update the extensions data + /// + void UseAdditionalExtensions(IEnumerable pathToAdditionalExtensions, bool loadOnlyWellKnownExtensions); + } +} diff --git a/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/ITestHostManager.cs b/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/ITestHostManager.cs new file mode 100644 index 0000000000..9c09b356fc --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/ITestHostManager.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine +{ + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces; + using System; + using System.Collections.Generic; + + /// + /// Interface for HostManager which manages test host processes for test engine. + /// + public interface ITestHostManager + { + /// + /// Sets a custom launcher + /// + /// Custom launcher to set + void SetCustomLauncher(ITestHostLauncher customTestHostLauncher); + + /// + /// Launches the test host for discovery/execution. + /// + /// Environment variables for the process. + /// The command line arguments to pass to the process. + /// ProcessId of launched Process. 0 means not launched. + int LaunchTestHost(IDictionary environmentVariables, IList commandLineArguments); + + /// + /// Gives the ProcessStartInfo for the test host process + /// + /// + /// + /// ProcessStartInfo of the test host + TestProcessStartInfo GetTestHostProcessStartInfo(IDictionary environmentVariables, IList commandLineArguments); + + /// + /// Register for the exit event. + /// + /// The callback on exit. + void RegisterForExitNotification(Action abortCallback); + + /// + /// Deregister for the exit event. + /// + void DeregisterForExitNotification(); + } +} diff --git a/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/TestExecutionContext.cs b/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/TestExecutionContext.cs new file mode 100644 index 0000000000..10d74199c5 --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/TestExecutionContext.cs @@ -0,0 +1,192 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.ClientProtocol +{ + using System; + using System.Diagnostics; + using System.Runtime.Serialization; + + using Newtonsoft.Json; + + /// + /// Stores information about test execution context. + /// + [DataContract] + public class TestExecutionContext + { + #region Constructors + + /// + /// The constructor. + /// + /// Frequency of run stats event. + /// Timeout that triggers sending results regardless of cache size. + /// Whether execution is out of process + /// Whether executor process should be kept running after test run completion + /// Whether data collection is enabled or not. + /// Indicates whether test case level events are required. + /// True if ExecutionContext is associated with Test run, false otherwise. + /// Filter criteria string for filtering tests. + public TestExecutionContext( + long frequencyOfRunStatsChangeEvent, + TimeSpan runStatsChangeEventTimeout, + bool inIsolation, + bool keepAlive, + bool isDataCollectionEnabled, + bool areTestCaseLevelEventsRequired, + bool hasTestRun, + bool isDebug, + string testCaseFilter) + { + this.FrequencyOfRunStatsChangeEvent = frequencyOfRunStatsChangeEvent; + this.RunStatsChangeEventTimeout = runStatsChangeEventTimeout; + this.InIsolation = inIsolation; + this.KeepAlive = keepAlive; + this.IsDataCollectionEnabled = isDataCollectionEnabled; + this.AreTestCaseLevelEventsRequired = areTestCaseLevelEventsRequired; + + this.IsDebug = isDebug; + + this.HasTestRun = hasTestRun; + this.TestCaseFilter = testCaseFilter; + } + + /// + /// The constructor. + /// + /// Frequency of run stats event. + /// Timeout that triggers sending results regardless of cache size. + /// Whether execution is out of process + /// Whether executor process should be kept running after test run completion + /// Indicates whether test case level events are required. + /// Indicates whether the tests are being debugged. + /// Filter criteria string for filtering tests. + /// This constructor is needed to re-create an instance on deserialization on the test host side. + [JsonConstructor] + public TestExecutionContext( + long frequencyOfRunStatsChangeEvent, + TimeSpan runStatsChangeEventTimeout, + bool inIsolation, + bool keepAlive, + bool areTestCaseLevelEventsRequired, + bool isDebug, + string testCaseFilter) + { + this.FrequencyOfRunStatsChangeEvent = frequencyOfRunStatsChangeEvent; + this.RunStatsChangeEventTimeout = runStatsChangeEventTimeout; + this.InIsolation = inIsolation; + this.KeepAlive = keepAlive; + this.AreTestCaseLevelEventsRequired = areTestCaseLevelEventsRequired; + + this.IsDebug = isDebug; + + this.TestCaseFilter = testCaseFilter; + } + + #endregion + + #region Properties + + /// + /// Gets frequency of run stats event. + /// + [DataMember] + public long FrequencyOfRunStatsChangeEvent + { + get; + private set; + } + + /// + /// Gets the timeout that triggers sending results regardless of cache size. + /// + [DataMember] + public TimeSpan RunStatsChangeEventTimeout + { + get; + private set; + } + + /// + /// Gets a value indicating whether execution is out of process. + /// + [DataMember] + public bool InIsolation + { + get; + private set; + } + + /// + /// Gets a value indicating whether executor process should be kept running after test run completion. + /// + [DataMember] + public bool KeepAlive + { + get; + private set; + } + + /// + /// Gets a value indicating whether test case level events need to be sent or not + /// + [DataMember] + public bool AreTestCaseLevelEventsRequired + { + get; + private set; + } + + /// + /// Gets a value indicating whether execution is in debug mode. + /// + [DataMember] + public bool IsDebug + { + get; + private set; + } + + /// + /// Gets the filter criteria for run with sources to filter test cases. + /// + [DataMember] + public string TestCaseFilter + { + get; + private set; + } + + /// + /// Gets or sets a value indicating whether data collection is enabled or not. + /// + /// This does not need to be serialized over to the test host process. + public bool IsDataCollectionEnabled + { + get; + set; + } + + /// + /// Gets a value indicating whether execution context is associated with a test run. + /// + public bool HasTestRun + { + get; + private set; + } + + /// + /// Gets or sets a configuration associated with this run. + /// + /// It is not serialized over wcf as the information is available in the runsettings + public RunConfiguration TestRunConfiguration + { + get; + set; + } + + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ITestCaseEventsHandler.cs b/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ITestCaseEventsHandler.cs new file mode 100644 index 0000000000..3d6bba273c --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ITestCaseEventsHandler.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine +{ + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + + /// + /// The Test Case level events. + /// + public interface ITestCaseEventsHandler + { + /// + /// Report start of executing a test case. + /// + /// Details of the test case whose execution is just started. + void SendTestCaseStart(TestCase testCase); + + /// + /// Report end of executing a test case. + /// + /// Details of the test case. + /// Result of the test case executed. + void SendTestCaseEnd(TestCase testCase, TestOutcome outcome); + + /// + /// Sends the test result + /// + /// The result. + void SendTestResult(TestResult result); + } +} diff --git a/src/Microsoft.TestPlatform.Common/Interfaces/Engine/TesthostProtocol/IDiscoveryManager.cs b/src/Microsoft.TestPlatform.Common/Interfaces/Engine/TesthostProtocol/IDiscoveryManager.cs new file mode 100644 index 0000000000..0eddf44313 --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/Interfaces/Engine/TesthostProtocol/IDiscoveryManager.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.TesthostProtocol +{ + using System.Collections.Generic; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + + /// + /// Orchestrates discovery operations for the engine communicating with the test host process. + /// + public interface IDiscoveryManager + { + /// + /// Initializes the discovery manager. + /// + /// The path to additional extensions. + void Initialize(IEnumerable pathToAdditionalExtensions); + + /// + /// Discovers tests + /// + /// Settings, parameters for the discovery request + /// EventHandler for handling discovery events from Engine + void DiscoverTests(DiscoveryCriteria discoveryCriteria, ITestDiscoveryEventsHandler eventHandler); + + /// + /// Aborts the test discovery. + /// + void Abort(); + } +} diff --git a/src/Microsoft.TestPlatform.Common/Interfaces/Engine/TesthostProtocol/IExecutionManager.cs b/src/Microsoft.TestPlatform.Common/Interfaces/Engine/TesthostProtocol/IExecutionManager.cs new file mode 100644 index 0000000000..3d616e9d0a --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/Interfaces/Engine/TesthostProtocol/IExecutionManager.cs @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.TesthostProtocol +{ + using System; + using System.Collections.Generic; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.ClientProtocol; + + /// + /// Orchestrates test execution related functionality for the engine communicating with the test host process. + /// + public interface IExecutionManager + { + /// + /// Initializes the execution manager. + /// + /// The path to additional extensions. + void Initialize(IEnumerable pathToAdditionalExtensions); + + /// + /// Starts the test run with sources. + /// + /// The adapter Source Map. + /// The run Settings. + /// The test Execution Context. + /// EventHandler for handling test cases level events from Engine. + /// EventHandler for handling execution events from Engine. + void StartTestRun(Dictionary> adapterSourceMap, string runSettings, TestExecutionContext testExecutionContext, ITestCaseEventsHandler testCaseEvents, ITestRunEventsHandler eventHandler); + + /// + /// Starts the test run with tests. + /// + /// The test list. + /// The run Settings. + /// The test Execution Context. + /// /// EventHandler for handling test cases level events from Engine. + /// EventHandler for handling execution events from Engine. + void StartTestRun(IEnumerable tests, string runSettings, TestExecutionContext testExecutionContext, ITestCaseEventsHandler testCaseEvents, ITestRunEventsHandler eventHandler); + + /// + /// Cancel the test execution. + /// + void Cancel(); + + /// + /// Aborts the test execution. + /// + void Abort(); + } +} diff --git a/src/Microsoft.TestPlatform.Common/Interfaces/Engine/TesthostProtocol/ITestHostManagerFactory.cs b/src/Microsoft.TestPlatform.Common/Interfaces/Engine/TesthostProtocol/ITestHostManagerFactory.cs new file mode 100644 index 0000000000..cc85fa66d8 --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/Interfaces/Engine/TesthostProtocol/ITestHostManagerFactory.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.TesthostProtocol +{ + /// + /// The factory that provides discovery and execution managers to the test host. + /// + public interface ITestHostManagerFactory + { + /// + /// The discovery manager instance for any discovery related operations inside of the test host. + /// + /// The discovery manager. + IDiscoveryManager GetDiscoveryManager(); + + /// + /// The execution manager instance for any discovery related operations inside of the test host. + /// + /// The execution manager. + IExecutionManager GetExecutionManager(); + } +} diff --git a/src/Microsoft.TestPlatform.Common/Interfaces/IPathUtilities.cs b/src/Microsoft.TestPlatform.Common/Interfaces/IPathUtilities.cs new file mode 100644 index 0000000000..73dcb4649b --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/Interfaces/IPathUtilities.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Common.Interfaces +{ + using System.Collections.Generic; + + /// + /// The utilities for file paths. + /// + public interface IPathUtilities + { + /// + /// Removes duplicate and invalid paths from parameter sourcePaths + /// + /// The source Paths. + /// The list of unique, valid extension paths. + HashSet GetUniqueValidPaths(IEnumerable sourcePaths); + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Common/Interfaces/IRunSettingsProvider.cs b/src/Microsoft.TestPlatform.Common/Interfaces/IRunSettingsProvider.cs new file mode 100644 index 0000000000..3ec49217de --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/Interfaces/IRunSettingsProvider.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Common.Interfaces +{ + /// + /// Provides access to the active run settings. + /// + internal interface IRunSettingsProvider + { + /// + /// The active run settings. + /// + RunSettings ActiveRunSettings { get; } + + /// + /// Set the active run settings. + /// + /// RunSettings to make the active Run Settings. + void SetActiveRunSettings(RunSettings runSettings); + } +} diff --git a/src/Microsoft.TestPlatform.Common/Interfaces/ISettingsProviderCapabilities.cs b/src/Microsoft.TestPlatform.Common/Interfaces/ISettingsProviderCapabilities.cs new file mode 100644 index 0000000000..34e86ee397 --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/Interfaces/ISettingsProviderCapabilities.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Common.Interfaces +{ + using System.Diagnostics.CodeAnalysis; + + /// + /// Metadata that is available from Settings Providers. + /// + [SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes", Justification = "This interface is only public due to limitations in MEF which require metadata interfaces to be public.")] + public interface ISettingsProviderCapabilities + { + /// + /// Gets the name of the settings section. + /// + string SettingsName { get; } + } +} diff --git a/src/Microsoft.TestPlatform.Common/Interfaces/ITestDiscovererCapabilities.cs b/src/Microsoft.TestPlatform.Common/Interfaces/ITestDiscovererCapabilities.cs new file mode 100644 index 0000000000..da17c176c9 --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/Interfaces/ITestDiscovererCapabilities.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Common.Interfaces +{ + using System; + using System.Collections.Generic; + + /// + /// Metadata that is available from Test Discoverers. + /// + public interface ITestDiscovererCapabilities + { + /// + /// List of file extensions that the test discoverer can process tests from. + /// + IEnumerable FileExtension { get; } + + /// + /// Default executor Uri for this discoverer + /// + Uri DefaultExecutorUri { get; } + } +} diff --git a/src/Microsoft.TestPlatform.Common/Interfaces/ITestDiscoveryEventsRegistrar.cs b/src/Microsoft.TestPlatform.Common/Interfaces/ITestDiscoveryEventsRegistrar.cs new file mode 100644 index 0000000000..5df14ca1de --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/Interfaces/ITestDiscoveryEventsRegistrar.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Common.Interfaces +{ + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + + public interface ITestDiscoveryEventsRegistrar + { + /// + /// Registers to receive discovery events from discovery request. + /// These events will then be broadcast to any registered loggers. + /// + /// The discovery request to register for events on. + void RegisterDiscoveryEvents(IDiscoveryRequest discoveryRequest); + + /// + /// Unregister the events from the discovery request. + /// + /// The discovery request from which events should be unregistered. + void UnregisterDiscoveryEvents(IDiscoveryRequest discoveryRequest); + } +} diff --git a/src/Microsoft.TestPlatform.Common/Interfaces/ITestExecutorCapabilities.cs b/src/Microsoft.TestPlatform.Common/Interfaces/ITestExecutorCapabilities.cs new file mode 100644 index 0000000000..55b427c072 --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/Interfaces/ITestExecutorCapabilities.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Common.Interfaces +{ + /// + /// Metadata that is available from Test Executors. + /// + internal interface ITestExecutorCapabilities : ITestExtensionCapabilities + { + } +} diff --git a/src/Microsoft.TestPlatform.Common/Interfaces/ITestExtensionCapabilities.cs b/src/Microsoft.TestPlatform.Common/Interfaces/ITestExtensionCapabilities.cs new file mode 100644 index 0000000000..ce7be2c8d4 --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/Interfaces/ITestExtensionCapabilities.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Common.Interfaces +{ + using System.Diagnostics.CodeAnalysis; + + /// + /// Basic metadata for extensions which are identified by a URI. + /// + public interface ITestExtensionCapabilities + { + /// + /// Gets the URI of the test extension. + /// + string ExtensionUri { get; } + } +} diff --git a/src/Microsoft.TestPlatform.Common/Interfaces/ITestLoggerCapabilities.cs b/src/Microsoft.TestPlatform.Common/Interfaces/ITestLoggerCapabilities.cs new file mode 100644 index 0000000000..76c56b2755 --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/Interfaces/ITestLoggerCapabilities.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Common.Interfaces +{ + using System.Diagnostics.CodeAnalysis; + + /// + /// Metadata that is available from Test Loggers. + /// + public interface ITestLoggerCapabilities : ITestExtensionCapabilities + { + /// specifies the friendly name corresponding to the logger. + string FriendlyName { get; } + } +} diff --git a/src/Microsoft.TestPlatform.Common/Interfaces/ITestRunEventsRegistrar.cs b/src/Microsoft.TestPlatform.Common/Interfaces/ITestRunEventsRegistrar.cs new file mode 100644 index 0000000000..a716d1e0d6 --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/Interfaces/ITestRunEventsRegistrar.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Common.Interfaces +{ + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + + public interface ITestRunEventsRegistrar + { + /// + /// Registers to receive events from the provided test run request. + /// These events will then be broadcast to any registered loggers. + /// + /// The run request to register for events on. + void RegisterTestRunEvents(ITestRunRequest testRunRequest); + + /// + /// Unregisters the events from the test run request. + /// + /// The run request from which events should be unregistered. + void UnregisterTestRunEvents(ITestRunRequest testRunRequest); + } +} diff --git a/src/Microsoft.TestPlatform.Common/Logging/InternalTestLoggerEvents.cs b/src/Microsoft.TestPlatform.Common/Logging/InternalTestLoggerEvents.cs new file mode 100644 index 0000000000..83443b9efc --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/Logging/InternalTestLoggerEvents.cs @@ -0,0 +1,349 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Common.Logging +{ + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + using Microsoft.VisualStudio.TestPlatform.Utilities; + using System; + using System.Collections.ObjectModel; + using System.Diagnostics; + +#if NET46 + using System.Configuration; +#endif + + /// + /// Exposes events that Test Loggers can register for and allows for them + /// to be raised through the IRunMessageLogger interface. + /// + internal class InternalTestLoggerEvents : TestLoggerEvents, IDisposable + { + #region Fields + + /// + /// Queue used for events which are to be sent to the loggers. + /// + /// + /// Using the queue accomplishes two things. + /// 1. Loggers do not need to be written to be thread safe because + /// we will only be raising one event to them at a time. + /// 2. Allows all events to go to all loggers even during initialization + /// because we queue up all events sent until raising of events to the + /// loggers is enabled + /// + private JobQueue loggerEventQueue; + + /// + /// Keeps track if we are disposed. + /// + private bool isDisposed = false; + + /// + /// Specifies whether logger event queue is bounded or not + /// + private bool isBoundsOnLoggerEventQueueEnabled; + + private TestSessionMessageLogger testSessionMessageLogger; +#endregion + + #region Constructor + + /// + /// Default constructor. + /// + public InternalTestLoggerEvents(TestSessionMessageLogger testSessionMessageLogger) + { + + // Initialize the queue and pause it. + // Note: The queue will be resumed when events are enabled. This is done so all + // loggers receive all messages. + this.isBoundsOnLoggerEventQueueEnabled = IsBoundsEnabledOnLoggerEventQueue(); + this.loggerEventQueue = new JobQueue( + this.ProcessQueuedJob, + "Test Logger", + GetMaxNumberOfJobsInQueue(), + GetMaxBytesQueueCanHold(), + this.isBoundsOnLoggerEventQueueEnabled, + (message) => EqtTrace.Error(message)); + this.loggerEventQueue.Pause(); + + // Register for events from the test run message logger so they + // can be raised to the loggers. + this.testSessionMessageLogger = testSessionMessageLogger; + this.testSessionMessageLogger.TestRunMessage += this.TestRunMessageHandler; + } + +#endregion + + #region Events + + /// + /// Raised when a test message is received. + /// + public override event EventHandler TestRunMessage; + + /// + /// Raised when a test result is received. + /// + public override event EventHandler TestResult; + + /// + /// Raised when a test run is complete. + /// + public override event EventHandler TestRunComplete; + +#endregion + + #region IDisposable + + /// + /// Waits for all pending messages to be processed by the loggers cleans up. + /// + public void Dispose() + { + if (this.isDisposed) + { + return; + } + this.isDisposed = true; + + // Unregister for test run messages. + this.testSessionMessageLogger.TestRunMessage -= this.TestRunMessageHandler; + + // Ensure that the queue is processed before returning. + this.loggerEventQueue.Resume(); + this.loggerEventQueue.Dispose(); + } + +#endregion + + #region Internal Methods + + /// + /// Enables sending of events to the loggers which are registered and flushes the queue. + /// + /// + /// By default events are disabled and will not be raised until this method is called. + /// This is done because during logger initialization, errors could be sent and we do not + /// want them broadcast out to the loggers until all loggers have been enabled. Without this + /// all loggers would not receive the errors which were sent prior to initialization finishing. + /// + internal void EnableEvents() + { + this.CheckDisposed(); + + this.loggerEventQueue.Resume(); + + // Allow currently queued events to flush from the queue. This is done so that information + // logged during initialization completes processing before we begin other tasks. This is + // important for instance when errors are logged during initilization and need to be output + // to the console before we begin outputing other information to the console. + this.loggerEventQueue.Flush(); + } + + /// + /// Raises a message event to the enabled loggers. + /// + /// Arguments to to be raised. + internal void RaiseMessage(TestRunMessageEventArgs args) + { + if (args == null) + { + throw new ArgumentNullException("args"); + } + + this.CheckDisposed(); + + // Sending 0 size as this event is not expected to contain any data. + this.SafeInvokeAsync(() => this.TestRunMessage, args, 0, "InternalTestLoggerEvents.SendMessage"); + } + + /// + /// Raises a test result event to the enabled loggers. + /// + /// Arguments to to be raised. + internal void RaiseTestResult(TestResultEventArgs args) + { + ValidateArg.NotNull(args, "args"); + + this.CheckDisposed(); + + // find the approx size of test result + int resultSize = 0; + if (this.isBoundsOnLoggerEventQueueEnabled) + { + resultSize = FindTestResultSize(args) * sizeof(char); + } + + this.SafeInvokeAsync(() => this.TestResult, args, resultSize, "InternalTestLoggerEvents.SendTestResult"); + } + + /// + /// Raise the test run complete event to test loggers and waits + /// for the events to be processed. + /// + /// Specifies the stats of the test run. + /// Specifies whether the test run is cancelled. + /// Specifies whether the test run is aborted. + /// Specifies the error that occurs during the test run. + /// Run level attachment sets + /// Time elapsed in just running the tests. + internal void CompleteTestRun(ITestRunStatistics stats, bool isCanceled, bool isAborted, Exception error, Collection attachmentSet, TimeSpan elapsedTime) + { + this.CheckDisposed(); + + var args = new TestRunCompleteEventArgs(stats, isCanceled, isAborted, error, attachmentSet, elapsedTime); + + // Sending 0 size as this event is not expected to contain any data. + this.SafeInvokeAsync(() => this.TestRunComplete, args, 0, "InternalTestLoggerEvents.SendTestRunComplete"); + + // Wait for the loggers to finish processing the messages for the run. + this.loggerEventQueue.Flush(); + } + +#endregion + + #region Private Members + + /// + /// Called when a test run message is sent through the ITestRunMessageLogger which is exported. + /// + private void TestRunMessageHandler(object sender, TestRunMessageEventArgs e) + { + // Broadcast the message to the loggers. + this.SafeInvokeAsync(() => this.TestRunMessage, e, 0, "InternalTestLoggerEvents.SendMessage"); + } + + /// + /// Invokes each of the subscribers of the event and handles exceptions which are thrown + /// ensuring that each handler is invoked even if one throws. + /// The actual calling of the subscribers is done on a background thread. + /// + private void SafeInvokeAsync(Func eventHandlersFactory, EventArgs args, int size, string traceDisplayName) + { + ValidateArg.NotNull>(eventHandlersFactory, "eventHandlersFactory"); + ValidateArg.NotNull(args, "args"); + + // Invoke the handlers on a background thread. + this.loggerEventQueue.QueueJob( + () => + { + var eventHandlers = eventHandlersFactory(); + eventHandlers?.SafeInvoke(this, args, traceDisplayName); + }, size); + } + + /// + /// Method called to process a job which is coming from the logger event queue. + /// + private void ProcessQueuedJob(Action action) + { + action(); + } + + /// + /// Throws if we are disposed. + /// + private void CheckDisposed() + { + if (this.isDisposed) + { + throw new ObjectDisposedException(typeof(TestLoggerEvents).FullName); + } + } + + /// + /// The method parses the config file of vstest.console.exe to see if the Max Job Queue Length is defined. + /// Return the Max Queue Length so defined or a default value specifed by TestPlatformDefaults.DefaultMaxLoggerEventsToCache + /// + private int GetMaxNumberOfJobsInQueue() + { + return GetSetting(TestPlatformDefaults.MaxNumberOfEventsLoggerEventQueueCanHold, + TestPlatformDefaults.DefaultMaxNumberOfEventsLoggerEventQueueCanHold); + } + + /// + /// The method parses the config file of vstest.console.exe to see if the Max Job Queue size is defined. + /// Return the Max Queue size so defined or a default value specifed by TestPlatformDefaults.DefaultMaxJobQueueSize + /// + private int GetMaxBytesQueueCanHold() + { + return GetSetting(TestPlatformDefaults.MaxBytesLoggerEventQueueCanHold, + TestPlatformDefaults.DefaultMaxBytesLoggerEventQueueCanHold); + } + + /// + /// Returns whether flow control on logger events queue should be enabled or not. Default is enabled. + /// + private static bool IsBoundsEnabledOnLoggerEventQueue() + { + bool enableBounds; +#if NET46 + string enableBoundsOnEventQueueIsDefined = ConfigurationManager.AppSettings[TestPlatformDefaults.EnableBoundsOnLoggerEventQueue]; +#else + string enableBoundsOnEventQueueIsDefined = null; +#endif + if (string.IsNullOrEmpty(enableBoundsOnEventQueueIsDefined)) + { + enableBounds = TestPlatformDefaults.DefaultEnableBoundsOnLoggerEventQueue; + } + else + { + if (!(bool.TryParse(enableBoundsOnEventQueueIsDefined, out enableBounds))) + { + enableBounds = TestPlatformDefaults.DefaultEnableBoundsOnLoggerEventQueue; + } + } + return enableBounds; + } + + /// + /// Returns the approximate size of a TestResult instance. + /// + private static int FindTestResultSize(TestResultEventArgs args) + { + Debug.Assert(args != null && args.Result != null); + + int size = 0; + + if (args.Result.Messages.Count != 0) + { + foreach (ObjectModel.TestResultMessage msg in args.Result.Messages) + { + if (!String.IsNullOrEmpty(msg.Text)) + size += msg.Text.Length; + } + } + return size; + } + + /// + /// Get the appsetting value for the parameter appSettingKey. Use the parameter defaultValue if + /// value is not there or is invalid. + /// + private int GetSetting(string appSettingKey, int defaultValue) + { + int value; +#if NET46 + string appSettingValue = ConfigurationManager.AppSettings[appSettingKey]; +#else + string appSettingValue = null; +#endif + if (string.IsNullOrEmpty(appSettingValue)) + { + value = defaultValue; + } + else if (!int.TryParse(appSettingValue, out value) || value < 1) + { + EqtTrace.Warning("Unacceptable value '{0}' of {1}. Using default {2}", appSettingValue, appSettingKey, defaultValue); + value = defaultValue; + } + + return value; + } + +#endregion + } +} diff --git a/src/Microsoft.TestPlatform.Common/Logging/TestLoggerExtensionManager.cs b/src/Microsoft.TestPlatform.Common/Logging/TestLoggerExtensionManager.cs new file mode 100644 index 0000000000..487e84759b --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/Logging/TestLoggerExtensionManager.cs @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Common.Logging +{ + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework; + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.Utilities; + using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + using System.Collections.Generic; + + /// + /// Manages loading and provides access to logging extensions implementing the + /// ITestLogger interface. + /// + internal class TestLoggerExtensionManager : TestExtensionManager + { + /// + /// Default constructor. + /// + /// + /// The unfiltered Test Extensions. + /// + /// + /// The test Extensions. + /// + /// + /// The logger. + /// + /// + /// The constructor is not public because the factory method should be used to get instances of this class. + /// + protected TestLoggerExtensionManager( + IEnumerable>> unfilteredTestExtensions, + IEnumerable> testExtensions, + IMessageLogger logger) + : base(unfilteredTestExtensions, testExtensions, logger) + { + } + + /// + /// Gets an instance of the TestLoggerExtensionManager. + /// + /// + /// The message Logger. + /// + /// + /// The TestLoggerExtensionManager. + /// + public static TestLoggerExtensionManager Create(IMessageLogger messageLogger) + { + IEnumerable> filteredTestExtensions; + IEnumerable>> unfilteredTestExtensions; + + TestPluginManager.Instance.GetTestExtensions( + out unfilteredTestExtensions, + out filteredTestExtensions); + + return new TestLoggerExtensionManager(unfilteredTestExtensions, filteredTestExtensions, messageLogger); + } + } + +} diff --git a/src/Microsoft.TestPlatform.Common/Logging/TestLoggerManager.cs b/src/Microsoft.TestPlatform.Common/Logging/TestLoggerManager.cs new file mode 100644 index 0000000000..f33df67691 --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/Logging/TestLoggerManager.cs @@ -0,0 +1,430 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Common.Logging +{ + using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + using System.Linq; + using CommonResources = Microsoft.VisualStudio.TestPlatform.Common.Resources; + + /// + /// Responsible for managing logger extensions and broadcasting results + /// and error/warning/informational messages to them. + /// + internal class TestLoggerManager : ITestDiscoveryEventsRegistrar, ITestRunEventsRegistrar, IDisposable + { + #region Fields + + private static readonly object Synclock = new object(); + private static TestLoggerManager testLoggerManager; + + /// + /// Test Logger Events instance which will be passed to loggers when they are initialized. + /// + private InternalTestLoggerEvents loggerEvents; + + /// + /// Used to keep track of which loggers have been initialized. + /// + private HashSet initializedLoggers = new HashSet(); + + /// + /// Keeps track if we are disposed. + /// + private bool isDisposed = false; + + /// + /// Run request that we have registered for events on. Used when + /// disposing to unregister for the events. + /// + private ITestRunRequest runRequest = null; + + /// + /// Gets an instance of the logger. + /// + private IMessageLogger messageLogger; + + private TestLoggerExtensionManager testLoggerExtensionManager; + private IDiscoveryRequest discoveryRequest; + + #endregion + + #region Constructor + + /// + /// Default constructor. + /// + protected TestLoggerManager() + { + this.messageLogger = TestSessionMessageLogger.Instance; + this.testLoggerExtensionManager = TestLoggerExtensionManager.Create(messageLogger); + this.loggerEvents = new InternalTestLoggerEvents((TestSessionMessageLogger)messageLogger); + } + + /// + /// Gets the instance. + /// + public static TestLoggerManager Instance + { + get + { + if (testLoggerManager == null) + { + lock (Synclock) + { + if (testLoggerManager == null) + { + testLoggerManager = new TestLoggerManager(); + } + } + } + return testLoggerManager; + } + protected set + { + testLoggerManager = value; + } + } + + #endregion + + #region Properties + + /// + /// Gets the logger events. + /// + public TestLoggerEvents LoggerEvents + { + get + { + return this.loggerEvents; + } + } + + /// + /// Gets the initialized loggers. + /// + /// This property is added to assist in testing + protected HashSet InitializedLoggers + { + get + { + return this.initializedLoggers; + } + } + + #endregion + + #region Public Methods + + /// + /// Adds the logger with the specified URI and parameters. + /// For ex. TfsPublisher takes parameters such as Platform, Flavor etc. + /// + /// URI of the logger to add. + /// Logger parameters. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "Case insensitive needs to be supported "), SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Third party loggers could potentially throw all kinds of exceptions.")] + public void AddLogger(Uri uri, Dictionary parameters) + { + ValidateArg.NotNull(uri, "uri"); + + this.CheckDisposed(); + + // If the logger has already been initialized just return. + if (this.initializedLoggers.Contains(uri.AbsoluteUri, StringComparer.OrdinalIgnoreCase)) + { + return; + } + this.initializedLoggers.Add(uri.AbsoluteUri); + + // Look up the extension and initialize it if one is found. + var extensionManager = this.testLoggerExtensionManager; + var logger = extensionManager.TryGetTestExtension(uri.AbsoluteUri); + + if (logger != null) + { + try + { + if (logger.Value is ITestLoggerWithParameters) + { + ((ITestLoggerWithParameters)logger.Value).Initialize(this.loggerEvents, this.UpdateLoggerParamters(parameters)); + } + else + { + // todo Read Output Directory from RunSettings + ((ITestLogger)logger.Value).Initialize(this.loggerEvents, null); + } + } + catch (Exception e) + { + this.messageLogger.SendMessage( + TestMessageLevel.Error, + string.Format( + CultureInfo.CurrentUICulture, + CommonResources.LoggerInitializationError, + logger.Metadata.ExtensionUri, + e)); + } + } + else + { + throw new InvalidOperationException( + String.Format( + CultureInfo.CurrentUICulture, + CommonResources.LoggerNotFound, + uri.OriginalString)); + } + } + + /// + /// Tries to get uri of the logger corresponding to the friendly name. If no such logger exists return null. + /// + /// The friendly Name. + /// The logger Uri. + /// + public bool TryGetUriFromFriendlyName(string friendlyName, out string loggerUri) + { + var extensionManager = this.testLoggerExtensionManager; + foreach (var extension in extensionManager.TestExtensions) + { + if (string.Compare(friendlyName, extension.Metadata.FriendlyName, StringComparison.OrdinalIgnoreCase) == 0) + { + loggerUri = extension.Metadata.ExtensionUri; + return true; + } + } + + loggerUri = null; + return false; + } + + /// + /// Registers to receive events from the provided test run request. + /// These events will then be broadcast to any registered loggers. + /// + /// The run request to register for events on. + public void RegisterTestRunEvents(ITestRunRequest testRunRequest) + { + ValidateArg.NotNull(testRunRequest, "testRunRequest"); + + this.CheckDisposed(); + + // Keep track of the run requests so we can unregister for the + // events when disposed. + this.runRequest = testRunRequest; + + // Redirect the events to the InternalTestLoggerEvents + testRunRequest.TestRunMessage += this.TestRunMessageHandler; + testRunRequest.OnRunStatsChange += this.TestRunStatsChangedHandler; + testRunRequest.OnRunCompletion += this.TestRunCompleteHandler; + testRunRequest.DataCollectionMessage += this.DataCollectionMessageHandler; + } + + /// + /// Registers to receive discovery events from discovery request. + /// These events will then be broadcast to any registered loggers. + /// + /// The discovery request to register for events on. + public void RegisterDiscoveryEvents(IDiscoveryRequest discoveryRequest) + { + ValidateArg.NotNull(discoveryRequest, "discoveryRequest"); + + this.CheckDisposed(); + this.discoveryRequest = discoveryRequest; + discoveryRequest.OnDiscoveryMessage += this.DiscoveryMessageHandler; + } + + /// + /// Unregisters the events from the test run request. + /// + /// The run request from which events should be unregistered. + public void UnregisterTestRunEvents(ITestRunRequest testRunRequest) + { + ValidateArg.NotNull(testRunRequest, "testRunRequest"); + + testRunRequest.TestRunMessage -= this.TestRunMessageHandler; + testRunRequest.OnRunStatsChange -= this.TestRunStatsChangedHandler; + testRunRequest.OnRunCompletion -= this.TestRunCompleteHandler; + this.runRequest.DataCollectionMessage -= this.DiscoveryMessageHandler; + } + + /// + /// Unregister the events from the discovery request. + /// + /// The discovery request from which events should be unregistered. + public void UnregisterDiscoveryEvents(IDiscoveryRequest discoveryRequest) + { + ValidateArg.NotNull(discoveryRequest, "discoveryRequest"); + discoveryRequest.OnDiscoveryMessage -= this.DiscoveryMessageHandler; + } + + /// + /// Enables sending of events to the loggers which are registered. + /// + /// + /// By default events are disabled and will not be raised until this method is called. + /// This is done because during logger initialization, errors could be sent and we do not + /// want them broadcast out to the loggers until all loggers have been enabled. Without this + /// all loggers would not receive the errors which were sent prior to initialization finishing. + /// + public void EnableLogging() + { + this.CheckDisposed(); + this.loggerEvents.EnableEvents(); + } + + /// + /// Ensure that all pending messages are sent to the loggers. + /// + public void Dispose() + { + this.Dispose(true); + + // Use SupressFinalize in case a subclass + // of this type implements a finalizer. + GC.SuppressFinalize(this); + } + + /// + /// Sends the error message to all registered loggers. + /// This is required so that out of test run execution errors + /// can also mark test run test run failure. + /// + /// + /// The e. + /// + public void SendTestRunError(TestRunMessageEventArgs e) + { + this.TestRunMessageHandler(null, e); + } + + /// + /// Ensure that all pending messages are sent to the loggers. + /// + /// + /// The disposing. + /// + protected virtual void Dispose(bool disposing) + { + if (!this.isDisposed) + { + if (disposing) + { + // Unregister from runrequests. + if (this.runRequest != null) + { + this.runRequest.TestRunMessage -= this.TestRunMessageHandler; + this.runRequest.OnRunStatsChange -= this.TestRunStatsChangedHandler; + this.runRequest.OnRunCompletion -= this.TestRunCompleteHandler; + this.runRequest.DataCollectionMessage -= this.DiscoveryMessageHandler; + } + + if (this.discoveryRequest != null) + { + this.discoveryRequest.OnDiscoveryMessage -= this.DiscoveryMessageHandler; + } + + this.loggerEvents.Dispose(); + } + + this.isDisposed = true; + } + } + + #endregion + + #region Private Members + + /// + /// Populates user supplied and default logger parameters. + /// + private Dictionary UpdateLoggerParamters(Dictionary parameters) + { + var loggerParams = new Dictionary(StringComparer.OrdinalIgnoreCase); + if (parameters != null) + { + loggerParams = new Dictionary(parameters, StringComparer.OrdinalIgnoreCase); + } + + // Add default logger parameters... + // todo Read Output Directory from RunSettings + loggerParams[DefaultLoggerParameterNames.TestRunDirectory] = null; + return loggerParams; + } + + private void CheckDisposed() + { + if (this.isDisposed) + { + throw new ObjectDisposedException(typeof(TestLoggerManager).FullName); + } + } + + #region Event Handlers + + /// + /// Called when a test run message is received. + /// + private void TestRunMessageHandler(object sender, TestRunMessageEventArgs e) + { + this.loggerEvents.RaiseMessage(e); + } + + /// + /// Called when a test run stats are changed. + /// + private void TestRunStatsChangedHandler(object sender, TestRunChangedEventArgs e) + { + foreach (TestResult result in e.NewTestResults) + { + this.loggerEvents.RaiseTestResult(new TestResultEventArgs(result)); + } + } + + /// + /// Called when a test run is complete. + /// + private void TestRunCompleteHandler(object sender, TestRunCompleteEventArgs e) + { + this.loggerEvents.CompleteTestRun(e.TestRunStatistics, e.IsCanceled, e.IsAborted, e.Error, e.AttachmentSets, e.ElapsedTimeInRunningTests); + } + + + /// + /// Called when data collection message is received. + /// + private void DataCollectionMessageHandler(object sender, DataCollectionMessageEventArgs e) + { + string message; + if (null == e.Uri) + { + // Message from data collection framework. + message = string.Format(CultureInfo.CurrentCulture, CommonResources.DataCollectionMessageFormat, e.Message); + } + else + { + // Message from individual data collector. + message = string.Format(CultureInfo.CurrentCulture, CommonResources.DataCollectorMessageFormat, e.FriendlyName, e.Message); + } + this.TestRunMessageHandler(sender, new TestRunMessageEventArgs(e.Level, message)); + } + + + /// + /// Send discovery message to all registered listeners. + /// + private void DiscoveryMessageHandler(object sender, TestRunMessageEventArgs e) + { + this.loggerEvents.RaiseMessage(e); + } + #endregion + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.Common/Logging/TestLoggerMetadata.cs b/src/Microsoft.TestPlatform.Common/Logging/TestLoggerMetadata.cs new file mode 100644 index 0000000000..584ecc60e7 --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/Logging/TestLoggerMetadata.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Common.Logging +{ + using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; + + /// + /// Hold data about the Test logger. + /// + public class TestLoggerMetadata : ITestLoggerCapabilities + { + /// + /// Constructor for TestLoggerMetadata + /// + /// + /// Uri identifying the logger. + /// + /// + /// The friendly Name. + /// + public TestLoggerMetadata(string extension, string friendlyName) + { + this.ExtensionUri = extension; + this.FriendlyName = friendlyName; + } + + /// + /// Gets Uri identifying the logger. + /// + public string ExtensionUri + { + get; + private set; + } + + /// + /// Gets Friendly Name identifying the logger. + /// + public string FriendlyName + { + get; + private set; + } + } +} diff --git a/src/Microsoft.TestPlatform.Common/Logging/TestSessionMessageLogger.cs b/src/Microsoft.TestPlatform.Common/Logging/TestSessionMessageLogger.cs new file mode 100644 index 0000000000..4507aedfe2 --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/Logging/TestSessionMessageLogger.cs @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Common.Logging +{ + using System; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + using Microsoft.VisualStudio.TestPlatform.Utilities; + + /// + /// The test session message logger. + /// + internal class TestSessionMessageLogger : IMessageLogger + { + private static TestSessionMessageLogger instance; + + /// + /// Initializes a new instance of the class. + /// + protected TestSessionMessageLogger() + { + this.TreatTestAdapterErrorsAsWarnings = Constants.DefaultTreatTestAdapterErrorsAsWarnings; + } + + /// + /// Raised when a discovery message is received. + /// + internal event EventHandler TestRunMessage; + + /// + /// Gets the instance of the singleton. + /// + internal static TestSessionMessageLogger Instance + { + get + { + return instance ?? (instance = new TestSessionMessageLogger()); + } + set + { + instance = value; + } + } + + /// + /// Gets or sets a value indicating whether to treat test adapter errors as warnings. + /// + internal bool TreatTestAdapterErrorsAsWarnings + { + get; + set; + } + + /// + /// Sends a message to all listeners. + /// + /// Level of the message. + /// The message to be sent. + public void SendMessage(TestMessageLevel testMessageLevel, string message) + { + if (string.IsNullOrWhiteSpace(message)) + { + throw new ArgumentException(CommonResources.CannotBeNullOrEmpty, "message"); + } + + if (this.TreatTestAdapterErrorsAsWarnings + && testMessageLevel == TestMessageLevel.Error) + { + // Downgrade the message severity to Warning... + testMessageLevel = TestMessageLevel.Warning; + } + + if (this.TestRunMessage != null) + { + var args = new TestRunMessageEventArgs(testMessageLevel, message); + this.TestRunMessage.SafeInvoke(this, args, "TestRunMessageLoggerProxy.SendMessage"); + } + } + + } +} diff --git a/src/Microsoft.TestPlatform.Common/Microsoft.TestPlatform.Common.xproj b/src/Microsoft.TestPlatform.Common/Microsoft.TestPlatform.Common.xproj new file mode 100644 index 0000000000..b8ce158d3b --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/Microsoft.TestPlatform.Common.xproj @@ -0,0 +1,19 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 17b61e60-b42c-4bfa-9cd0-83b33229de0d + Microsoft.VisualStudio.TestPlatform.Common + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\ + v4.5.2 + + + 2.0 + + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Common/Properties/AssemblyInfo.cs b/src/Microsoft.TestPlatform.Common/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..71a8de1623 --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +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: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Microsoft.TestPlatform.Common")] +[assembly: AssemblyTrademark("")] + +// 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("1911f8d4-f357-4a4b-a554-7f91fd144bcc")] diff --git a/src/Microsoft.TestPlatform.Common/Resources.Designer.cs b/src/Microsoft.TestPlatform.Common/Resources.Designer.cs new file mode 100644 index 0000000000..120051e1c6 --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/Resources.Designer.cs @@ -0,0 +1,251 @@ +//------------------------------------------------------------------------------ +// +// 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 Microsoft.VisualStudio.TestPlatform.Common { + using System; + using System.Reflection; + + + /// + /// 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.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + internal Resources() { + } + + /// + /// 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("Microsoft.TestPlatform.Common.Resources", typeof(Resources).GetTypeInfo().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 Diagnostic data adapter message: {0}. + /// + public static string DataCollectionMessageFormat { + get { + return ResourceManager.GetString("DataCollectionMessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Diagnostic data adapter ('{0}') message: {1}.. + /// + public static string DataCollectorMessageFormat { + get { + return ResourceManager.GetString("DataCollectorMessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Duplicate test extension URI '{0}'. Ignoring the duplicate extension.. + /// + public static string DuplicateExtensionUri { + get { + return ResourceManager.GetString("DuplicateExtensionUri", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Duplicate settings provider named '{0}'. Ignoring the duplicate provider.. + /// + public static string DuplicateSettingsName { + get { + return ResourceManager.GetString("DuplicateSettingsName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Duplicated run settings section named '{0}' found. Ignoring the duplicate settings.. + /// + public static string DuplicateSettingsProvided { + get { + return ResourceManager.GetString("DuplicateSettingsProvided", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Error: Empty parenthesis ( ). + /// + public static string EmptyParenthesis { + get { + return ResourceManager.GetString("EmptyParenthesis", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Error: Invalid Condition '{0}'. + /// + public static string InvalidCondition { + get { + return ResourceManager.GetString("InvalidCondition", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Test Extension has an invalid URI '{0}': {1}. + /// + public static string InvalidExtensionUriFormat { + get { + return ResourceManager.GetString("InvalidExtensionUriFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Error: Invalid operator '{0}'. + /// + public static string InvalidOperator { + get { + return ResourceManager.GetString("InvalidOperator", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Exception occurred while initializing logger with URI '{0}'. The logger will not be used. Exception: {1}. + /// + public static string LoggerInitializationError { + get { + return ResourceManager.GetString("LoggerInitializationError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Could not find a test logger with URI or FriendlyName '{0}'.. + /// + public static string LoggerNotFound { + get { + return ResourceManager.GetString("LoggerNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Error: Missing ')'. + /// + public static string MissingCloseParenthesis { + get { + return ResourceManager.GetString("MissingCloseParenthesis", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Error: Missing '('. + /// + public static string MissingOpenParenthesis { + get { + return ResourceManager.GetString("MissingOpenParenthesis", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Error: Missing operand. + /// + public static string MissingOperand { + get { + return ResourceManager.GetString("MissingOperand", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Missing Operator '|' or '&'. + /// + public static string MissingOperator { + get { + return ResourceManager.GetString("MissingOperator", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The Run Settings have already been loaded.. + /// + public static string RunSettingsAlreadyLoaded { + get { + return ResourceManager.GetString("RunSettingsAlreadyLoaded", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An error occurred while loading the run settings. Error: {0}. + /// + public static string RunSettingsParseError { + get { + return ResourceManager.GetString("RunSettingsParseError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid settings node specified. The name property of the settings node must be non-empty.. + /// + public static string SettingsNodeInvalidName { + get { + return ResourceManager.GetString("SettingsNodeInvalidName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An error occurred while initializing the settings provider named '{0}'. Error: {1}. + /// + public static string SettingsProviderInitializationError { + get { + return ResourceManager.GetString("SettingsProviderInitializationError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Settings Provider named '{0}' was not found. The settings can not be loaded.. + /// + public static string SettingsProviderNotFound { + get { + return ResourceManager.GetString("SettingsProviderNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Incorrect format for TestCaseFilter {0}. Specify the correct format and try again. Note that the incorrect format can lead to no test getting executed.. + /// + public static string TestCaseFilterFormatException { + get { + return ResourceManager.GetString("TestCaseFilterFormatException", resourceCulture); + } + } + } +} diff --git a/src/Microsoft.TestPlatform.Common/Resources.resx b/src/Microsoft.TestPlatform.Common/Resources.resx new file mode 100644 index 0000000000..fff7c2610d --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/Resources.resx @@ -0,0 +1,183 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + Diagnostic data adapter message: {0} + + + Diagnostic data adapter ('{0}') message: {1}. + + + Duplicate test extension URI '{0}'. Ignoring the duplicate extension. + + + Duplicate settings provider named '{0}'. Ignoring the duplicate provider. + + + Duplicated run settings section named '{0}' found. Ignoring the duplicate settings. + + + Error: Empty parenthesis ( ) + + + Error: Invalid Condition '{0}' + + + Test Extension has an invalid URI '{0}': {1} + + + Error: Invalid operator '{0}' + + + Exception occurred while initializing logger with URI '{0}'. The logger will not be used. Exception: {1} + + + Could not find a test logger with URI or FriendlyName '{0}'. + + + Error: Missing ')' + + + Error: Missing '(' + + + Error: Missing operand + + + Missing Operator '|' or '&' + + + The Run Settings have already been loaded. + + + An error occurred while loading the run settings. Error: {0} + + + Invalid settings node specified. The name property of the settings node must be non-empty. + + + An error occurred while initializing the settings provider named '{0}'. Error: {1} + + + Settings Provider named '{0}' was not found. The settings can not be loaded. + + + Incorrect format for TestCaseFilter {0}. Specify the correct format and try again. Note that the incorrect format can lead to no test getting executed. + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Common/RunSettings.cs b/src/Microsoft.TestPlatform.Common/RunSettings.cs new file mode 100644 index 0000000000..4258e71c4e --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/RunSettings.cs @@ -0,0 +1,277 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Common +{ + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + using System.IO; + using System.Xml; + + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.Utilities; + using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; + using Microsoft.VisualStudio.TestPlatform.Common.Logging; + using Microsoft.VisualStudio.TestPlatform.Common.SettingsProvider; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities; + + /// + /// Used for loading settings for a run. + /// + public class RunSettings : IRunSettings + { + #region Fields + + /// + /// Map of the settings names in the file to their associated settings provider. + /// + private Dictionary> settings; + + + /// + /// Used to keep track if settings have been loaded. + /// + private bool isSettingsLoaded; + + #endregion + + /// + /// Initializes a new instance of the class. + /// + public RunSettings() + { + this.settings = new Dictionary>(); + } + + #region Properties + + /// + /// Gets the settings in the form of Xml string. + /// + public string SettingsXml { get; private set; } + + #endregion + + #region Public Methods + + /// + /// Get the settings for the provided settings name. + /// + /// Name of the settings section to get. + /// The settings provider for the settings or null if one was not found. + public ISettingsProvider GetSettings(string settingsName) + { + if (StringUtilities.IsNullOrWhiteSpace(settingsName)) + { + throw new ArgumentException(CommonResources.CannotBeNullOrEmpty, "settingsName"); + } + + // Try and lookup the settings provider. + ISettingsProvider result = null; + LazyExtension provider; + this.settings.TryGetValue(settingsName, out provider); + + // If a provider was found, return it. + if (provider != null) + { + result = provider.Value; + } + + return result; + } + + /// + /// Load the settings from the provided xml string. + /// + /// xml string + public void LoadSettingsXml(string settings) + { + if (StringUtilities.IsNullOrWhiteSpace(settings)) + { + throw new ArgumentException(CommonResources.CannotBeNullOrEmpty, settings); + } + + using (var stringReader = new StringReader(settings)) + { + var reader = XmlReader.Create(stringReader, XmlRunSettingsUtilities.ReaderSettings); + this.ValidateAndSaveSettings(reader); + } + } + + /// + /// Initialize settings providers with the settings xml. + /// + /// The settings xml string. + public void InitializeSettingsProviders(string settings) + { + using (var stringReader = new StringReader(settings)) + { + var reader = XmlReader.Create(stringReader, XmlRunSettingsUtilities.ReaderSettings); + this.ReadRunSettings(reader); + } + } + + #endregion + + #region Private Methods + + /// + /// Validate the runsettings checking that it is well formed. + /// This would throw XML exception on failure. + /// + /// path to the run settings file + private void ValidateAndSaveSettings(XmlReader reader) + { + try + { + var dom = new XmlDocument(); + dom.Load(reader); + using (var writer = new StringWriter(CultureInfo.InvariantCulture)) + { + dom.Save(writer); + this.SettingsXml = writer.ToString(); + } + } + catch (Exception e) + { + throw new SettingsException( + string.Format( + CultureInfo.CurrentCulture, + Resources.RunSettingsParseError, + e.Message), + e); + } + } + + /// + /// Reads test run settings from XmlReader + /// + /// + private void ReadRunSettings(XmlReader reader) + { + // If settings have already been loaded, throw. + if (this.isSettingsLoaded) + { + throw new InvalidOperationException(Resources.RunSettingsAlreadyLoaded); + } + + this.isSettingsLoaded = true; + + try + { + // Read to the root element. + XmlReaderUtilities.ReadToRootNode(reader); + + // Read to the the first section. + reader.ReadToNextElement(); + + // Lookup the settings provider for each of the elements. + var settingsExtensionManager = SettingsProviderExtensionManager.Create(); + while (!reader.EOF) + { + this.LoadSection(reader, settingsExtensionManager); + reader.SkipToNextElement(); + } + } + catch (SettingsException) + { + throw; + } + catch (Exception e) + { + throw new SettingsException( + string.Format( + CultureInfo.CurrentCulture, + Resources.RunSettingsParseError, + e.Message), + e); + } + } + + /// + /// Loads the section for the current element of the reader. + /// + /// Reader to load the section from. + /// Settings extension manager to get the provider from. + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "This methods must not fail on crash in loading settings.")] + private void LoadSection(XmlReader reader, SettingsProviderExtensionManager settingsExtensionManager) + { + ValidateArg.NotNull(reader, "reader"); + ValidateArg.NotNull(settingsExtensionManager, "settingsExtensionManager"); + + // Check for duplicate settings + if (this.settings.ContainsKey(reader.Name)) + { + TestSessionMessageLogger.Instance.SendMessage( + TestMessageLevel.Error, + string.Format(CultureInfo.CurrentCulture, Resources.DuplicateSettingsProvided, reader.Name)); + + return; + } + + // Look up the section for this node. + var provider = settingsExtensionManager.GetSettingsProvider(reader.Name); + + if (provider != null) + { + try + { + // Have the provider load the settings. + provider.Value.Load(reader.ReadSubtree()); + } + catch (Exception e) + { + // Setup to throw the exception when the section is requested. + provider = CreateLazyThrower( + string.Format( + CultureInfo.CurrentCulture, + Resources.SettingsProviderInitializationError, + provider.Metadata.SettingsName, + e.Message), + provider.Metadata, + e); + } + } + else + { + // Setup to throw when this section is requested. + var metadata = new TestSettingsProviderMetadata(reader.Name); + + var message = string.Format( + CultureInfo.CurrentCulture, + Resources.SettingsProviderNotFound, + metadata.SettingsName); + + provider = CreateLazyThrower(message, metadata); + } + + // Cache the provider instance so it can be looked up later when the section is requested. + this.settings.Add(provider.Metadata.SettingsName, provider); + } + + /// + /// Creates a lazy instance which will throw a SettingsException when the value property is accessed. + /// + /// Message for the exception. + /// Metadata to use for the lazy instance. + /// Inner exception to include in the exception which is thrown. + /// Lazy instance setup to throw when the value property is accessed. + private static LazyExtension CreateLazyThrower( + string message, + ISettingsProviderCapabilities metadata, + Exception innerException = null) + { + return new LazyExtension( + () => + { + throw new SettingsException(message, innerException); + }, + metadata); + } + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.Common/RunSettingsManager.cs b/src/Microsoft.TestPlatform.Common/RunSettingsManager.cs new file mode 100644 index 0000000000..80bdbeee47 --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/RunSettingsManager.cs @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Common +{ + using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + + /// + /// Manages the active run settings. + /// + internal class RunSettingsManager : IRunSettingsProvider + { + #region private members + + private static object lockObject = new object(); + + private static RunSettingsManager runSettingsManagerInstance; + + #endregion + + #region Constructor + + /// + /// Default constructor. + /// + private RunSettingsManager() + { + this.ActiveRunSettings = new RunSettings(); + } + + + #endregion + + #region IRunSettingsProvider + + /// + /// Gets the active run settings. + /// + public RunSettings ActiveRunSettings { get; private set; } + + #endregion + + #region Public Methods + + public static RunSettingsManager Instance + { + get + { + if (runSettingsManagerInstance != null) + { + return runSettingsManagerInstance; + } + + lock (lockObject) + { + if (runSettingsManagerInstance == null) + { + runSettingsManagerInstance = new RunSettingsManager(); + } + } + + return runSettingsManagerInstance; + } + internal set + { + runSettingsManagerInstance = value; + } + } + + /// + /// Set the active run settings. + /// + /// RunSettings to make the active Run Settings. + public void SetActiveRunSettings(RunSettings runSettings) + { + ValidateArg.NotNull(runSettings, "runSettings"); + this.ActiveRunSettings = runSettings; + } + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.Common/SettingsProvider/SettingsProviderExtensionManager.cs b/src/Microsoft.TestPlatform.Common/SettingsProvider/SettingsProviderExtensionManager.cs new file mode 100644 index 0000000000..1ddbb171df --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/SettingsProvider/SettingsProviderExtensionManager.cs @@ -0,0 +1,245 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Common.SettingsProvider +{ + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework; + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.Utilities; + using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; + using Microsoft.VisualStudio.TestPlatform.Common.Logging; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + using Microsoft.VisualStudio.TestPlatform.Utilities; + + /// + /// Manages the settings provider extensions. + /// + /// + /// This is a non-shared instance because we want different settings provider instances to + /// be used for each run settings instance. + /// + public class SettingsProviderExtensionManager + { + #region Fields + + private static SettingsProviderExtensionManager settingsProviderExtensionManager; + private static object synclock = new object(); + + /// + /// The settings providers which are available. + /// + private IEnumerable> settingsProviders; + private Dictionary> settingsProvidersMap; + + /// + /// Used for logging errors. + /// + private IMessageLogger logger; + + #endregion + + #region Constructor + + /// + /// Initializes with the settings providers. + /// + /// + /// The settings providers are imported as non-shared because we need different settings provider + /// instances to be used for each run settings. + /// + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] + protected SettingsProviderExtensionManager( + IEnumerable> settingsProviders, + IEnumerable>> unfilteredSettingsProviders, + IMessageLogger logger) + { + ValidateArg.NotNull>>(settingsProviders, "settingsProviders"); + ValidateArg.NotNull>>>(unfilteredSettingsProviders, "unfilteredSettingsProviders"); + ValidateArg.NotNull(logger, "logger"); + + this.settingsProviders = settingsProviders; + this.UnfilteredSettingsProviders = unfilteredSettingsProviders; + this.logger = logger; + + // Populate the map to avoid threading issues + this.PopulateMap(); + } + + #endregion + + #region Properties + + /// + /// Gets the Unfiltered list of settings providers. Used for the /ListSettingsProviders command line argument. + /// + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] + public IEnumerable>> UnfilteredSettingsProviders { get; private set; } + + /// + /// Gets the map of settings name to settings provider. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] + public Dictionary> SettingsProvidersMap + { + get + { + return this.settingsProvidersMap; + } + } + + #endregion + + #region Static Methods + + /// + /// Creates an instance of the settings provider. + /// + /// Instance of the settings provider. + public static SettingsProviderExtensionManager Create() + { + if (settingsProviderExtensionManager == null) + { + lock (synclock) + { + if (settingsProviderExtensionManager == null) + { + IEnumerable>> unfilteredTestExtensions; + IEnumerable> testExtensions; + + TestPluginManager.Instance + .GetTestExtensions + ( + out unfilteredTestExtensions, + out testExtensions); + + settingsProviderExtensionManager = new SettingsProviderExtensionManager( + testExtensions, unfilteredTestExtensions, TestSessionMessageLogger.Instance); + } + } + } + + return settingsProviderExtensionManager; + } + + /// + /// Destroy the extension manager. + /// + public static void Destroy() + { + lock (synclock) + { + settingsProviderExtensionManager = null; + } + } + + /// + /// Load all the settings provider and fail on error + /// + /// Indicates whether this method should throw on error. + public static void LoadAndInitializeAllExtensions(bool shouldThrowOnError) + { + var extensionManager = SettingsProviderExtensionManager.Create(); + + try + { + foreach (var settingsProvider in extensionManager.SettingsProvidersMap) + { + // Note: - The below Verbose call should not be under IsVerboseEnabled check as we want to + // call executor.Value even if logging is not enabled. + EqtTrace.Verbose("SettingsProviderExtensionManager: Loading settings provider {0}", settingsProvider.Value.Value); + } + } + catch (Exception ex) + { + if (EqtTrace.IsErrorEnabled) + { + EqtTrace.Error("SettingsProviderExtensionManager: LoadAndInitialize: Exception occured while loading extensions {0}", ex); + } + + if (shouldThrowOnError) + { + throw; + } + } + } + + #endregion + + #region Public Methods + + /// + /// Gets the settings with the provided name. + /// + /// Name of the settings to get. + /// Settings provider with the provided name or null if one was not found. + internal LazyExtension GetSettingsProvider(string settingsName) + { + if (string.IsNullOrWhiteSpace(settingsName)) + { + throw new ArgumentException(CommonResources.CannotBeNullOrEmpty, "settingsName"); + } + + LazyExtension settingsProvider; + this.SettingsProvidersMap.TryGetValue(settingsName, out settingsProvider); + + return settingsProvider; + } + + #endregion + + + /// + /// Populate the settings provider map + /// + private void PopulateMap() + { + this.settingsProvidersMap = new Dictionary>(); + + foreach (var settingsProvider in this.settingsProviders) + { + if (this.settingsProvidersMap.ContainsKey(settingsProvider.Metadata.SettingsName)) + { + this.logger.SendMessage( + TestMessageLevel.Error, + string.Format( + CultureInfo.CurrentUICulture, + Common.Resources.DuplicateSettingsName, + settingsProvider.Metadata.SettingsName)); + } + else + { + this.settingsProvidersMap.Add(settingsProvider.Metadata.SettingsName, settingsProvider); + } + } + } + } + + /// + /// Hold data about the Test settings provider. + /// + internal class TestSettingsProviderMetadata : ISettingsProviderCapabilities + { + /// + /// The constructor + /// + /// Test settings name + public TestSettingsProviderMetadata(string settingsName) + { + this.SettingsName = settingsName; + } + + /// + /// Gets test settings name. + /// + public string SettingsName + { + get; + private set; + } + } +} diff --git a/src/Microsoft.TestPlatform.Common/Utilities/AssemblyResolver.cs b/src/Microsoft.TestPlatform.Common/Utilities/AssemblyResolver.cs new file mode 100644 index 0000000000..88506643cc --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/Utilities/AssemblyResolver.cs @@ -0,0 +1,260 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Common.Utilities +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Linq; + using System.Reflection; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + +#if !NET46 + using System.Runtime.Loader; +#endif + + internal class AssemblyResolver : IDisposable + { + /// + /// The directories to look for assemblies to resolve. + /// + private HashSet searchDirectories; + + /// + /// Dictionary of Assemblies discovered to date. Must be locked as it may + /// be accessed in a multi-threaded context. + /// + private Dictionary resolvedAssemblies; + + /// + /// Specifies whether the resolver is disposed or not + /// + private bool isDisposed; + + private static readonly string[] SupportedFileExtensions = new string[] { ".dll", ".exe" }; + + /// + /// Initializes a new instance of the class. + /// + /// The search directories. + [System.Security.SecurityCritical] + public AssemblyResolver(IEnumerable directories) + { + this.resolvedAssemblies = new Dictionary(); + + if (directories == null || !directories.Any()) + { + this.searchDirectories = new HashSet(); + } + else + { + this.searchDirectories = new HashSet(directories); + } +#if NET46 + AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(OnResolve); +#else + AssemblyLoadContext.Default.Resolving += this.OnResolve; +#endif + } + + + /// + /// Set the directories from which assemblies should be searched + /// + /// The search directories. + [System.Security.SecurityCritical] + internal void AddSearchDirectories(IEnumerable directories) + { + foreach (var directory in directories) + { + this.searchDirectories.Add(directory); + } + } + + + /// + /// Assembly Resolve event handler for App Domain - called when CLR loader cannot resolve assembly. + /// + [System.Security.SecuritySafeCritical] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.Reflection.Assembly.LoadFrom")] +#if NET46 + private Assembly OnResolve(object senderAppDomain, ResolveEventArgs args) +#else + private Assembly OnResolve(AssemblyLoadContext loadContext, AssemblyName args) +#endif + { + if (string.IsNullOrEmpty(args?.Name)) + { + Debug.Assert(false, "AssemblyResolver.OnResolve: args.Name is null or empty."); + return null; + } + + if (this.searchDirectories == null || this.searchDirectories.Count == 0) + { + return null; + } + + EqtTrace.Info("AssemblyResolver: {0}: Resolving assembly.", args.Name); + + // args.Name is like: "Microsoft.VisualStudio.TestTools.Common, Version=[VersionMajor].0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a". + + lock (this.resolvedAssemblies) + { + Assembly assembly; + if (this.resolvedAssemblies.TryGetValue(args.Name, out assembly)) + { + EqtTrace.Info("AssemblyResolver: {0}: Resolved from cache.", args.Name); + return (assembly); + } + + AssemblyName requestedName = null; + try + { + // Can throw ArgumentException, FileLoadException if arg is empty/wrong format, etc. Should not return null. + requestedName = new AssemblyName(args.Name); + } + catch (Exception ex) + { + if (EqtTrace.IsInfoEnabled) + { + EqtTrace.Info("AssemblyResolver: {0}: Failed to create assemblyName. Reason:{1} ", args.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 this.searchDirectories) + { + if (string.IsNullOrEmpty(dir)) + { + continue; + } + + + foreach (var extension in SupportedFileExtensions) + { + var assemblyPath = Path.Combine(dir, requestedName.Name + extension); + try + { + if (!File.Exists(assemblyPath)) + { + continue; + } + +#if NET46 + AssemblyName foundName = AssemblyName.GetAssemblyName(assemblyPath); + if (!this.RequestedAssemblyNameMatchesFound(requestedName, foundName)) + { + continue; // File exists but version/public key is wrong. Try next extension. + } + + // When file does not exist it throws FileNotFoundException. + assembly = Assembly.LoadFrom(assemblyPath); +#else + AssemblyName foundName = AssemblyLoadContext.GetAssemblyName(assemblyPath); + if (!this.RequestedAssemblyNameMatchesFound(requestedName, foundName)) + { + continue; // File exists but version/public key is wrong. Try next extension. + } + assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath); +#endif + this.resolvedAssemblies[args.Name] = assembly; + + EqtTrace.Info("AssemblyResolver: {0}: Resolved assembly. ", args.Name); + + return assembly; + } + catch (FileLoadException ex) + { + EqtTrace.Info("AssemblyResolver: {0}: Failed to load assembly. Reason:{1} ", args.Name, 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. + EqtTrace.Info("AssemblyResolver: {0}: Failed to load assembly. Reason:{1} ", args.Name, ex); + } + } + } + } + + return null; + } + + /// + /// Verifies that found assembly name matches requested to avoid security issues. + /// Looks only at PublicKeyToken and Version, empty matches anything. + /// VSWhidbey 415774. + /// + private bool RequestedAssemblyNameMatchesFound(AssemblyName requestedName, AssemblyName foundName) + { + Debug.Assert(requestedName != null); + Debug.Assert(foundName != null); + + var requestedPublicKey = requestedName.GetPublicKeyToken(); + if (requestedPublicKey != null) + { + var foundPublicKey = foundName.GetPublicKeyToken(); + if (foundPublicKey == null) + { + return false; + } + + for (var index = 0; index < requestedPublicKey.Length; ++index) + { + if (requestedPublicKey[index] != foundPublicKey[index]) + { + return false; + } + } + } + + if (requestedName.Version != null) + { + return requestedName.Version.Equals(foundName.Version); + } + + return true; + } + + + ~AssemblyResolver() + { + this.Dispose(false); + } + + public void Dispose() + { + this.Dispose(true); + + // Use SupressFinalize in case a subclass + // of this type implements a finalizer. + GC.SuppressFinalize(this); + } + + [System.Security.SecurityCritical] + protected virtual void Dispose(bool disposing) + { + if (!this.isDisposed) + { + if (disposing) + { +#if NET46 + AppDomain.CurrentDomain.AssemblyResolve -= new ResolveEventHandler(OnResolve); +#else + AssemblyLoadContext.Default.Resolving -= this.OnResolve; +#endif + } + + this.isDisposed = true; + } + } + } +} diff --git a/src/Microsoft.TestPlatform.Common/Utilities/ExceptionUtilities.cs b/src/Microsoft.TestPlatform.Common/Utilities/ExceptionUtilities.cs new file mode 100644 index 0000000000..3feaab1e2b --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/Utilities/ExceptionUtilities.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Common.Utilities +{ + using System; + + /// + /// Exception utilities. + /// + public class ExceptionUtilities + { + /// + /// Returns an exception message with all inner exceptions messages. + /// + /// The exception. + /// The formatted string message of the exception. + public static string GetExceptionMessage(Exception exception) + { + 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; + } + } +} diff --git a/src/Microsoft.TestPlatform.Common/Utilities/PathUtilities.cs b/src/Microsoft.TestPlatform.Common/Utilities/PathUtilities.cs new file mode 100644 index 0000000000..f14ed80715 --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/Utilities/PathUtilities.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Common.Utilities +{ + using System.Collections.Generic; + using System.IO; + using System.Reflection; + + using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + + /// + /// The utilities for file paths. + /// + internal class PathUtilities : IPathUtilities + { + /// + /// Removes duplicate and invalid paths from parameter sourcePaths + /// + /// The Paths. + /// The list of unique, valid extension paths. + public HashSet GetUniqueValidPaths(IEnumerable paths) + { + var result = new HashSet(); + + if (paths == null) + { + return result; + } + + foreach (var sourcePath in paths) + { + var fullPath = Path.GetFullPath(sourcePath); + + if (File.Exists(fullPath)) + { + result.Add(fullPath); + } + else + { + EqtTrace.Verbose("TestPluginCache: Ignoring extension {0} as it does not exist.", result); + } + } + + return result; + } + } +} diff --git a/src/Microsoft.TestPlatform.Common/Utilities/RunSettingsUtilities.cs b/src/Microsoft.TestPlatform.Common/Utilities/RunSettingsUtilities.cs new file mode 100644 index 0000000000..30ffa10eaa --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/Utilities/RunSettingsUtilities.cs @@ -0,0 +1,121 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Common.Utilities +{ + using System; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities; + + /// + /// Utility methods for RunSettings. + /// + public static class RunSettingsUtilities + { + /// + /// Create RunSettings object corresponding to settingsXml + /// + public static RunSettings CreateAndInitializeRunSettings(string settingsXml) + { + RunSettings settings = null; + + if (!StringUtilities.IsNullOrWhiteSpace(settingsXml)) + { + settings = new RunSettings(); + settings.LoadSettingsXml(settingsXml); + settings.InitializeSettingsProviders(settingsXml); + } + return settings; + } + + /// + /// Gets the test results directory from the run configuration + /// + /// Test run configuration + /// Test results directory + public static string GetTestResultsDirectory(RunConfiguration runConfiguration) + { + string resultsDirectory = null; + if (runConfiguration != null) + { + if (!runConfiguration.ResultsDirectorySet) + { + resultsDirectory = null; + } + else + { + resultsDirectory = Environment.ExpandEnvironmentVariables(runConfiguration.ResultsDirectory); + } + } + + return resultsDirectory; + } + + /// + /// Gets the solution directory from run configuration + /// + /// Test run configuration + /// Solution directory + public static string GetSolutionDirectory(RunConfiguration runConfiguration) + { + string solutionDirectory = null; + if (runConfiguration != null) + { + if (!string.IsNullOrEmpty(runConfiguration.SolutionDirectory)) + { + // Env var is expanded in run configuration + solutionDirectory = runConfiguration.SolutionDirectory; + } + } + + return solutionDirectory; + } + + /// + /// Gets the maximum CPU count from setting xml + /// + /// setting xml + /// Maximum CPU Count + public static int GetMaxCpuCount(string settingXml) + { + int cpuCount = Constants.DefaultCpuCount; + + if (string.IsNullOrEmpty(settingXml)) + { + return cpuCount; + } + + try + { + var configuration = XmlRunSettingsUtilities.GetRunConfigurationNode(settingXml); + cpuCount = GetMaxCpuCount(configuration); + } + catch (SettingsException ex) + { + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose("RunSettingsUtilities.GetMaxCpuCount: Unable to get maximum CPU count from Setting Xml. {0}", ex); + } + } + + return cpuCount; + } + + /// + /// Gets the maximum CPU count from run configuration + /// + /// Test run configuration + /// Maximum CPU Count + public static int GetMaxCpuCount(RunConfiguration runConfiguration) + { + int cpuCount = Constants.DefaultCpuCount; + + if (runConfiguration != null) + { + cpuCount = runConfiguration.MaxCpuCount; + } + + return cpuCount; + } + + } +} diff --git a/src/Microsoft.TestPlatform.Common/project.json b/src/Microsoft.TestPlatform.Common/project.json new file mode 100644 index 0000000000..699917ca1a --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/project.json @@ -0,0 +1,32 @@ +{ + "version": "15.0.0-*", + "buildOptions": { + "outputName": "Microsoft.VisualStudio.TestPlatform.Common", + "delaySign": true, + "keyFile": "../../scripts/key.snk", + "warningsAsErrors": true + }, + + "dependencies": { + "Microsoft.TestPlatform.ObjectModel": "15.0.0-*" + }, + + "frameworks": { + "netstandard1.5": { + "imports": [ + "dnxcore50", + "portable-net45+win8" + ], + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-24008", + "System.Runtime.Loader": "4.0.0-rc2-24027" + } + }, + "net46": { + "frameworkAssemblies": { + "System.Runtime.Serialization": "", + "System.Configuration": "" + } + } + } +} diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionRequestHandler.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionRequestHandler.cs new file mode 100644 index 0000000000..a5f594e64a --- /dev/null +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionRequestHandler.cs @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.DataCollection +{ + using System; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using System.Collections.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.DataCollection.Interfaces; + using Microsoft.VisualStudio.TestPlatform.Common.DataCollection; + + internal class DataCollectionRequestHandler : IDataCollectionRequestHandler, IDisposable + { + private readonly ICommunicationManager communicationManager; + private IDataSerializer dataSerializer; + + public DataCollectionRequestHandler() + : this(new SocketCommunicationManager(), JsonDataSerializer.Instance) + { + } + + internal DataCollectionRequestHandler(ICommunicationManager communicationManager, IDataSerializer dataSerializer) + { + this.communicationManager = communicationManager; + this.dataSerializer = dataSerializer; + } + + /// + /// The dispose. + /// + public void Dispose() + { + this.communicationManager?.StopClient(); + } + + /// + /// Closes the connection + /// + public void Close() + { + this.Dispose(); + EqtTrace.Info("Closing the connection !"); + } + + /// + /// Setups client based on port + /// + /// port number to connect + public void InitializeCommunication(int port) + { + this.communicationManager.SetupClientAsync(port); + } + + public bool WaitForRequestSenderConnection(int connectionTimeout) + { + return this.communicationManager.WaitForServerConnection(connectionTimeout); + } + + /// + /// Process requests. + /// + public void ProcessRequests() + { + bool isSessionEnd = false; + + do + { + var message = this.communicationManager.ReceiveMessage(); + switch (message.MessageType) + { + case MessageType.BeforeTestRunStart: + // TODO: Send actual BeforeTestRunStartResult + // string settingXml = message.Payload.ToObject(); + this.communicationManager.SendMessage(MessageType.BeforeTestRunStartResult, new BeforeTestRunStartResult(null, true, 0)); + break; + + case MessageType.AfterTestRunEnd: + // TODO: Send actual collection of AttachmentSet + this.communicationManager.SendMessage(MessageType.AfterTestRunEndResult, new Collection()); + EqtTrace.Info("Session End message received from server. Closing the connection."); + + // TODO: Check if we need a separate message for closing the session. + isSessionEnd = true; + this.Close(); + break; + default: + EqtTrace.Info("Invalid Message types"); + break; + } + } + while (!isSessionEnd); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionRequestSender.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionRequestSender.cs new file mode 100644 index 0000000000..22dbfb9baa --- /dev/null +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionRequestSender.cs @@ -0,0 +1,111 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.DataCollection +{ + using System; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces; + using System.Collections.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.DataCollection.Interfaces; + using Microsoft.VisualStudio.TestPlatform.Common.DataCollection; + + /// + /// Utility class that facilitates the IPC comunication. Acts as server. + /// + public sealed class DataCollectionRequestSender : IDataCollectionRequestSender, IDisposable + { + private ICommunicationManager communicationManager; + + private IDataSerializer dataSerializer; + + /// + /// Creates new instance of DataCollectionRequestSender. + /// + public DataCollectionRequestSender() : this(new SocketCommunicationManager(), JsonDataSerializer.Instance) + { + } + + /// + /// Creates new instance of DataCollectionRequestSender. + /// + /// + /// + internal DataCollectionRequestSender(ICommunicationManager communicationManager, IDataSerializer dataSerializer) + { + this.communicationManager = communicationManager; + this.dataSerializer = dataSerializer; + } + + /// + /// Creates an endpoint and listens for client connection asynchronously + /// + /// + public int InitializeCommunication() + { + var port = this.communicationManager.HostServer(); + this.communicationManager.AcceptClientAsync(); + return port; + } + + /// + /// Waits for Request Handler to be connected + /// + /// Time to wait for connection + /// True, if Handler is connected + public bool WaitForRequestHandlerConnection(int clientConnectionTimeout) + { + return this.communicationManager.WaitForClientConnection(clientConnectionTimeout); + } + + /// + /// The dispose. + /// + public void Dispose() + { + this.communicationManager?.StopServer(); + } + + /// + /// Closes the connection + /// + public void Close() + { + this.Dispose(); + EqtTrace.Info("Closing the connection"); + } + + /// + /// Sends the BeforeTestRunStart event and waits for result + /// + /// BeforeTestRunStartResult containing environment variables + public BeforeTestRunStartResult SendBeforeTestRunStartAndGetResult(string settingsXml) + { + this.communicationManager.SendMessage(MessageType.BeforeTestRunStart, settingsXml); + var message = this.communicationManager.ReceiveMessage(); + if (message.MessageType == MessageType.BeforeTestRunStartResult) + { + return dataSerializer.DeserializePayload(message); + } + + return null; + } + + /// + /// Sends the AfterTestRunStart event and waits for result + /// + /// DataCollector attachments + public Collection SendAfterTestRunStartAndGetResult() + { + this.communicationManager.SendMessage(MessageType.BeforeTestRunStart); + var message = this.communicationManager.ReceiveMessage(); + if (message.MessageType == MessageType.BeforeTestRunStartResult) + { + return dataSerializer.DeserializePayload>(message); + } + + return null; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/EventHandlers/TestCaseEventsHandler.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/EventHandlers/TestCaseEventsHandler.cs new file mode 100644 index 0000000000..eebf05cfc5 --- /dev/null +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/EventHandlers/TestCaseEventsHandler.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.EventHandlers +{ + using TestPlatform.ObjectModel; + using TestPlatform.ObjectModel.Engine; + + internal class TestCaseEventsHandler : ITestCaseEventsHandler + { + private TestRequestHandler requestHandler; + + public TestCaseEventsHandler(TestRequestHandler requestHandler) + { + this.requestHandler = requestHandler; + } + + public void SendTestCaseStart(TestCase testCase) + { + throw new System.NotImplementedException(); + } + + public void SendTestCaseEnd(TestCase testCase, TestOutcome outcome) + { + throw new System.NotImplementedException(); + } + + public void SendTestResult(TestResult result) + { + throw new System.NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/EventHandlers/TestDiscoveryEventHandler.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/EventHandlers/TestDiscoveryEventHandler.cs new file mode 100644 index 0000000000..921ff304f8 --- /dev/null +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/EventHandlers/TestDiscoveryEventHandler.cs @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities +{ + using System; + using System.Collections.Generic; + + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + using TestPlatform.ObjectModel.Client; + + /// + /// The test discovery event handler. + /// + public class TestDiscoveryEventHandler : ITestDiscoveryEventsHandler + { + private ITestRequestHandler requestHandler; + + /// + /// Initializes a new instance of the class. + /// + /// The client. + public TestDiscoveryEventHandler(ITestRequestHandler requestHandler) + { + this.requestHandler = requestHandler; + } + + /// + /// Handles discovered tests + /// + /// List of test cases + public void HandleDiscoveredTests(IEnumerable discoveredTestCases) + { + EqtTrace.Info("Test Cases found "); + this.requestHandler.SendTestCases(discoveredTestCases); + } + + /// + /// Handle discovery complete. + /// + /// The total tests. + /// The last chunk. + /// The is aborted. + public void HandleDiscoveryComplete(long totalTests, IEnumerable lastChunk, bool isAborted) + { + EqtTrace.Info(isAborted ? "Discover Aborted." : "Discover Finished."); + this.requestHandler.DiscoveryComplete(totalTests, lastChunk, isAborted); + } + + /// + /// The handle discovery message. + /// + /// Logging level. + /// Logging message. + public void HandleLogMessage(TestMessageLevel level, string message) + { + switch ((TestMessageLevel)level) + { + case TestMessageLevel.Informational: + EqtTrace.Info(message); + break; + + case TestMessageLevel.Warning: + EqtTrace.Warning(message); + break; + + case TestMessageLevel.Error: + EqtTrace.Error(message); + break; + + default: + EqtTrace.Info(message); + break; + } + + this.requestHandler.SendLog(level, message); + } + + public void HandleRawMessage(string rawMessage) + { + // No-Op + // TestHost at this point has no functionality where it requires rawmessage + } + } +} diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/EventHandlers/TestRunEventsHandler.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/EventHandlers/TestRunEventsHandler.cs new file mode 100644 index 0000000000..bbfbeb7d31 --- /dev/null +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/EventHandlers/TestRunEventsHandler.cs @@ -0,0 +1,99 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.EventHandlers +{ + using System; + using System.Collections.Generic; + + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + + /// + /// The test run events handler. + /// + public class TestRunEventsHandler : ITestRunEventsHandler + { + private ITestRequestHandler requestHandler; + + /// + /// Initializes a new instance of the class. + /// + /// The client. + public TestRunEventsHandler(ITestRequestHandler requestHandler) + { + this.requestHandler = requestHandler; + } + + /// + /// Handle test run stats change. + /// + /// The test run changed args. + public void HandleTestRunStatsChange(TestRunChangedEventArgs testRunChangedArgs) + { + EqtTrace.Info("Sending test run statistics"); + this.requestHandler.SendTestRunStatistics(testRunChangedArgs); + } + + /// + /// Handle test run complete. + /// + /// The test run complete args. + /// The last chunk args. + /// The run context attachments. + /// The executor uris. + public void HandleTestRunComplete(TestRunCompleteEventArgs testRunCompleteArgs, TestRunChangedEventArgs lastChunkArgs, ICollection runContextAttachments, ICollection executorUris) + { + EqtTrace.Info("Sending test run complete"); + this.requestHandler.SendExecutionComplete(testRunCompleteArgs, lastChunkArgs, runContextAttachments, executorUris); + } + + /// + /// Handles a test run message. + /// + /// The level. + /// The message. + public void HandleLogMessage(TestMessageLevel level, string message) + { + switch ((TestMessageLevel)level) + { + case TestMessageLevel.Informational: + EqtTrace.Info(message); + break; + + case TestMessageLevel.Warning: + EqtTrace.Warning(message); + break; + + case TestMessageLevel.Error: + EqtTrace.Error(message); + break; + + default: + EqtTrace.Info(message); + break; + } + + this.requestHandler.SendLog(level, message); + } + + public void HandleRawMessage(string rawMessage) + { + // No-Op + // TestHost at this point has no functionality where it requires rawmessage + } + + /// + /// Launches a process with a given process info under debugger + /// Adapter get to call into this to launch any additional processes under debugger + /// + /// Process start info + public int LaunchProcessWithDebuggerAttached(TestProcessStartInfo testProcessStartInfo) + { + EqtTrace.Info("Sending LaunchProcessWithDebuggerAttached on additional test process: {0}", testProcessStartInfo?.FileName); + return this.requestHandler.LaunchProcessWithDebuggerAttached(testProcessStartInfo); + } + } +} diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Friends.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/Friends.cs new file mode 100644 index 0000000000..b97ddb82b7 --- /dev/null +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Friends.cs @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Microsoft.TestPlatform.CommunicationUtilities.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] +[assembly: InternalsVisibleTo("vstest.console.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] +[assembly: InternalsVisibleTo("Microsoft.TestPlatform.CrossPlatEngine, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] +[assembly: InternalsVisibleTo("Microsoft.TestPlatform.CrossPlatEngine.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] +[assembly: InternalsVisibleTo("datacollector, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] +[assembly: InternalsVisibleTo("datacollector.x86, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Interfaces/ICommunicationManager.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/Interfaces/ICommunicationManager.cs new file mode 100644 index 0000000000..48c8c79132 --- /dev/null +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Interfaces/ICommunicationManager.cs @@ -0,0 +1,103 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces +{ + using Newtonsoft.Json; + using System.Threading.Tasks; + + /// + /// The Communication Manager interface. + /// + public interface ICommunicationManager + { + /// + /// Host a server and listens on endpoint for requests + /// + /// Port number of the listening endpoint + int HostServer(); + + /// + /// Accepts client connection asynchronously + /// + Task AcceptClientAsync(); + + /// + /// Waits for client to be connected to this server + /// Whoever hosting the server should use this method to + /// wait for a client to connect + /// + /// Time to wait for the connection + /// True, if Server got a connection from client + bool WaitForClientConnection(int connectionTimeout); + + + /// + /// Waits for server to be connected + /// Whoever creating the client and trying to connect to a server + /// should use this method to wait for connection to be established with server + /// + /// Time to wait for the connection + /// True, if Server got a connection from client + bool WaitForServerConnection(int connectionTimeout); + + /// + /// Stops any hosted server + /// + void StopServer(); + + /// + /// Creates a Client Channel and connects to server on given port number + /// + /// + Task SetupClientAsync(int portNumber); + + /// + /// Stops any client connected to server + /// + void StopClient(); + + /// + /// Writes message to the binary writer. + /// + /// Type of Message to be sent, for instance TestSessionStart + void SendMessage(string messageType); + + /// + /// Reads message from the binary reader + /// + /// Returns message read from the binary reader + Message ReceiveMessage(); + + /// + /// Reads message from the binary reader + /// + /// Raw message string + string ReceiveRawMessage(); + + /// + /// Writes message to the binary writer with payload + /// + /// Type of Message to be sent, for instance TestSessionStart + /// payload to be sent + void SendMessage(string messageType, object payload); + + /// + /// Send serialized raw message + /// + /// serialized message + void SendRawMessage(string rawMessage); + + /// + /// The send hand shake message. + /// + void SendHandShakeMessage(); + + /// + /// Deserializes the Message into actual TestPlatform objects + /// + /// The type of object to deserialize to. + /// Message object + /// TestPlatform object + T DeserializePayload(Message message); + } +} diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Interfaces/IDataCollectionRequestHandler.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/Interfaces/IDataCollectionRequestHandler.cs new file mode 100644 index 0000000000..63045ccd59 --- /dev/null +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Interfaces/IDataCollectionRequestHandler.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.DataCollection.Interfaces +{ + /// + /// The DataCollectionRequestHandler interface. + /// + internal interface IDataCollectionRequestHandler + { + /// + /// Setups client based on port + /// + /// port number to connect + void InitializeCommunication(int port); + + /// + /// Waits for Request Handler to connect to Request Sender + /// + /// Timeout for establishing connection + /// True if connected, false if timed-out + bool WaitForRequestSenderConnection(int connectionTimeout); + + /// + /// Listens to the commands from server + /// + void ProcessRequests(); + + /// + /// Closes the connection + /// + void Close(); + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Interfaces/IDataCollectionRequestSender.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/Interfaces/IDataCollectionRequestSender.cs new file mode 100644 index 0000000000..3c7c12c1ce --- /dev/null +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Interfaces/IDataCollectionRequestSender.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.DataCollection.Interfaces +{ + using System.Collections.ObjectModel; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.Common.DataCollection; + + /// + /// Defines contract to send test platform requests to test host + /// + internal interface IDataCollectionRequestSender + { + /// + /// Initializes the communication for sending requests + /// + /// Port Number of the communication channel + int InitializeCommunication(); + + /// + /// Waits for Request Handler to be connected + /// + /// Time to wait for connection + /// True, if Handler is connected + bool WaitForRequestHandlerConnection(int connectionTimeout); + + /// + /// Close the Sender + /// + void Close(); + + /// + /// Sends the BeforeTestRunStart event and waits for result + /// + /// + /// BeforeTestRunStartResult containing environment variables + BeforeTestRunStartResult SendBeforeTestRunStartAndGetResult(string settingXml); + + /// + /// Sends the AfterTestRunStart event and waits for result + /// + /// DataCollector attachments + Collection SendAfterTestRunStartAndGetResult(); + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Interfaces/IDataSerializer.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/Interfaces/IDataSerializer.cs new file mode 100644 index 0000000000..b80063931e --- /dev/null +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Interfaces/IDataSerializer.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces +{ + /// + /// IDataSerializer interface for serializing data + /// + public interface IDataSerializer + { + /// + /// Deserializes the raw message into Message + /// + /// Raw message off the IPC channel + /// Message object + Message DeserializeMessage(string rawMessage); + + /// + /// Deserializes the Message into actual TestPlatform objects + /// + /// The type of object to deserialize to. + /// Message object + /// TestPlatform object + T DeserializePayload(Message message); + + /// + /// Serializes and creates a raw message given a message type + /// + /// Message Type + /// Raw Serialized message + string SerializeObject(string messageType); + + /// + /// Serializes and creates a raw message given a message type and the object payload + /// + /// Message Type + /// Payload of the message + /// Raw Serialized message + string SerializeObject(string messageType, object payload); + } +} diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Interfaces/ITestRequestHandler.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/Interfaces/ITestRequestHandler.cs new file mode 100644 index 0000000000..a31f852d75 --- /dev/null +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Interfaces/ITestRequestHandler.cs @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces +{ + using System.Collections.Generic; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.TesthostProtocol; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + + /// + /// Defines the contract for handling test platform requests + /// + public interface ITestRequestHandler + { + /// + /// Setups client based on port + /// + /// port number to connect + void InitializeCommunication(int port); + + /// + /// Waits for Request Handler to connect to Request Sender + /// + /// Timeout for establishing connection + /// True if connected, false if timed-out + bool WaitForRequestSenderConnection(int connectionTimeout); + + /// + /// Listens to the commands from server + /// + /// the test host manager. + void ProcessRequests(ITestHostManagerFactory testHostManagerFactory); + + /// + /// Closes the connection + /// + void Close(); + + /// + /// The send test cases. + /// + /// The discovered test cases. + void SendTestCases(IEnumerable discoveredTestCases); + + /// + /// The send test run statistics. + /// + /// The test run changed args. + void SendTestRunStatistics(TestRunChangedEventArgs testRunChangedArgs); + + /// + /// Sends the logs back to the server. + /// + /// The message level. + /// The message. + void SendLog(TestMessageLevel messageLevel, string message); + + /// + /// The send execution complete. + /// + /// The test run complete args. + /// The last chunk args. + /// The run context attachments. + /// The executor uris. + void SendExecutionComplete(TestRunCompleteEventArgs testRunCompleteArgs, TestRunChangedEventArgs lastChunkArgs, ICollection runContextAttachments, ICollection executorUris); + + /// + /// The discovery complete handler + /// + /// The total Tests. + /// The last Chunk. + /// The is Aborted. + void DiscoveryComplete(long totalTests, IEnumerable lastChunk, bool isAborted); + + /// + /// Launches a process with a given process info under debugger + /// Adapter get to call into this to launch any additional processes under debugger + /// + /// Process start info + /// ProcessId of the launched process + int LaunchProcessWithDebuggerAttached(TestProcessStartInfo testProcessStartInfo); + } +} diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Interfaces/ITestRequestSender.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/Interfaces/ITestRequestSender.cs new file mode 100644 index 0000000000..652517ac61 --- /dev/null +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Interfaces/ITestRequestSender.cs @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft. All rights reserved. + + +namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces +{ + using System; + using System.Collections.Generic; + + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + + /// + /// Defines contract to send test platform requests to test host + /// + public interface ITestRequestSender : IDisposable + { + /// + /// Initializes the communication for sending requests + /// + /// Port Number of the communication channel + int InitializeCommunication(); + + /// + /// Waits for Request Handler to be connected + /// + /// Time to wait for connection + /// True, if Handler is connected + bool WaitForRequestHandlerConnection(int connectionTimeout); + + /// + /// Close the Sender + /// + void Close(); + + /// + /// Initializes the Discovery + /// + /// Paths to check for additional extensions + /// Load only well only extensions + void InitializeDiscovery(IEnumerable pathToAdditionalExtensions, bool loadOnlyWellKnownExtensions); + + /// + /// Initializes the Execution + /// + /// Paths to check for additional extensions + /// Load only well only extensions + void InitializeExecution(IEnumerable pathToAdditionalExtensions, bool loadOnlyWellKnownExtensions); + + /// + /// Discovers the tests + /// + /// DiscoveryCriteria for discovery + /// EventHandler for discovery events + void DiscoverTests(DiscoveryCriteria discoveryCriteria, ITestDiscoveryEventsHandler eventHandler); + + /// + /// Starts the TestRun with given sources and criteria + /// + /// RunCriteria for test run + /// EventHandler for test run events + void StartTestRun(TestRunCriteriaWithSources runCriteria, ITestRunEventsHandler eventHandler); + + /// + /// Starts the TestRun with given test cases and criteria + /// + /// RunCriteria for test run + /// EventHandler for test run events + void StartTestRun(TestRunCriteriaWithTests runCriteria, ITestRunEventsHandler eventHandler); + + /// + /// Ends the Session + /// + void EndSession(); + + /// + /// Send the request to cancel the test run + /// + void SendTestRunCancel(); + + /// + /// Send the request to abort the test run + /// + void SendTestRunAbort(); + } +} diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/JsonDataSerializer.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/JsonDataSerializer.cs new file mode 100644 index 0000000000..1b43d6fd71 --- /dev/null +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/JsonDataSerializer.cs @@ -0,0 +1,78 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities +{ + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel; + + using Newtonsoft.Json; + using Newtonsoft.Json.Linq; + + /// + /// JsonDataSerializes serializes and deserializes data using Json format + /// + public class JsonDataSerializer : IDataSerializer + { + private static JsonDataSerializer instance; + + private JsonDataSerializer() { } + + public static JsonDataSerializer Instance + { + get + { + return instance ?? (instance = new JsonDataSerializer()); + } + } + + public Message DeserializeMessage(string rawMessage) + { + return JsonConvert.DeserializeObject(rawMessage); + } + + public T DeserializePayload(Message message) + { + T retValue = default(T); + + // TODO: Currently we use json serializer auto only for non-testmessage types + // CHECK: Can't we just use auto for everything + if (MessageType.TestMessage.Equals(message.MessageType)) + { + retValue = message.Payload.ToObject(); + } + else + { + retValue = message.Payload.ToObject( + JsonSerializer.Create( + new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto })); + } + + return retValue; + } + + public string SerializeObject(string messageType) + { + return JsonConvert.SerializeObject(new Message { MessageType = messageType }); + } + + public string SerializeObject(string messageType, object payload) + { + JToken serializedPayload = null; + + // TODO: Currently we use json serializer auto only for non-testmessage types + // CHECK: Can't we just use auto for everything + if (MessageType.TestMessage.Equals(messageType)) + { + serializedPayload = JToken.FromObject(payload); + } + else + { + serializedPayload = JToken.FromObject(payload, + JsonSerializer.Create( + new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto })); + } + + return JsonConvert.SerializeObject(new Message { MessageType = messageType, Payload = serializedPayload }); + } + } +} diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Messages/DiscoveryCompletePayload.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/Messages/DiscoveryCompletePayload.cs new file mode 100644 index 0000000000..18c6dedd7b --- /dev/null +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Messages/DiscoveryCompletePayload.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel +{ + using System.Collections.Generic; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + + /// + /// The discovery complete payload. + /// + public class DiscoveryCompletePayload + { + /// + /// Gets or sets the total number of tests discovered. + /// + public long TotalTests { get; set; } + + /// + /// Gets or sets the last chunk of discovered tests. + /// + public IEnumerable LastDiscoveredTests { get; set; } + + /// + /// Gets or sets a value indicating whether discovery was aborted. + /// + public bool IsAborted { get; set; } + } +} diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Messages/Message.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/Messages/Message.cs new file mode 100644 index 0000000000..46426bd44c --- /dev/null +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Messages/Message.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities +{ + using Newtonsoft.Json; + using Newtonsoft.Json.Linq; + + public class Message + { + /// + /// Gets or sets the message type. + /// + public string MessageType { get; set; } + + /// + /// Gets or sets the payload. + /// + public JToken Payload { get; set; } + + /// + /// To string implementation. + /// + /// The . + public override string ToString() + { + return "(" + MessageType + ") -> " + (Payload == null ? "null" : Payload.ToString(Formatting.Indented)); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Messages/MessageType.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/Messages/MessageType.cs new file mode 100644 index 0000000000..a07b853e9b --- /dev/null +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Messages/MessageType.cs @@ -0,0 +1,165 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel +{ + /// + /// The message type. + /// + public static class MessageType + { + /// + /// The session start. + /// + public const string SessionStart = "TestSession.Start"; + + /// + /// The session end. + /// + public const string SessionEnd = "TestSession.Terminate"; + + /// + /// The is aborted. + /// + public const string SessionAbort = "TestSession.Abort"; + + /// + /// The session connected. + /// + public const string SessionConnected = "TestSession.Connected"; + + /// + /// Test Message + /// + public const string TestMessage = "TestSession.Message"; + + /// + /// Protocol Version + /// + public const string VersionCheck = "ProtocolVersion"; + + /// + /// The session start. + /// + public const string DiscoveryInitialize = "TestDiscovery.Initialize"; + + /// + /// The discovery started. + /// + public const string StartDiscovery = "TestDiscovery.Start"; + + /// + /// The test cases found. + /// + public const string TestCasesFound = "TestDiscovery.TestFound"; + + /// + /// The discovery complete. + /// + public const string DiscoveryComplete = "TestDiscovery.Completed"; + + /// + /// The session start. + /// + public const string ExecutionInitialize = "TestExecution.Initialize"; + + /// + /// Cancel the current test run + /// + public const string CancelTestRun = "TestExecution.Cancel"; + + /// + /// Cancel the current test run + /// + public const string AbortTestRun = "TestExecution.Abort"; + + /// + /// Start test execution. + /// + public const string StartTestExecutionWithSources = "TestExecution.StartWithSources"; + + /// + /// Start test execution. + /// + public const string StartTestExecutionWithTests = "TestExecution.StartWithTests"; + + /// + /// The test run stats change. + /// + public const string TestRunStatsChange = "TestExecution.StatsChange"; + + /// + /// The execution complete. + /// + public const string ExecutionComplete = "TestExecution.Completed"; + + /// + /// The message to get runner process startInfo for run all tests in given sources + /// + public const string GetTestRunnerProcessStartInfoForRunAll = "TestExecution.GetTestRunnerProcessStartInfoForRunAll"; + + /// + /// The message to get runner process startInfo for run selected tests + /// + public const string GetTestRunnerProcessStartInfoForRunSelected = "TestExecution.GetTestRunnerProcessStartInfoForRunSelected"; + + /// + /// CustomTestHostLaunch + /// + public const string CustomTestHostLaunch = "TestExecution.CustomTestHostLaunch"; + + /// + /// Custom Test Host launch callback + /// + public const string CustomTestHostLaunchCallback = "TestExecution.CustomTestHostLaunchCallback"; + + /// + /// Extensions Initialization + /// + public const string ExtensionsInitialize = "Extensions.Initialize"; + + /// + /// Start Test Run All Sources + /// + public const string TestRunAllSourcesWithDefaultHost = "TestExecution.RunAllWithDefaultHost"; + + /// + /// Start Test Run - Testcases + /// + public const string TestRunSelectedTestCasesDefaultHost = "TestExecution.RunSelectedWithDefaultHost"; + + /// + /// Launch Adapter Process With DebuggerAttached + /// + public const string LaunchAdapterProcessWithDebuggerAttached = "TestExecution.LaunchAdapterProcessWithDebuggerAttached"; + + /// + /// Launch Adapter Process With DebuggerAttached + /// + public const string LaunchAdapterProcessWithDebuggerAttachedCallback = "TestExecution.LaunchAdapterProcessWithDebuggerAttachedCallback"; + + #region DataCollector messages + + /// + /// Event message for Before Test Run Start + /// + public const string BeforeTestRunStart = "DataCollection.BeforeTestRunStart"; + + /// + /// Message for result for Before Test Run Start + /// + public const string BeforeTestRunStartResult = "DataCollection.BeforeTestRunStartResult"; + + /// + /// Event message for After Test Run End + /// + public const string AfterTestRunEnd = "DataCollection.AfterTestRunEnd"; + + /// + /// Message for attachments for After Test Run End Result + /// + public const string AfterTestRunEndResult = "DataCollection.AfterTestRunEndResult"; + + #endregion + + } +} diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Messages/TestMessagePayload.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/Messages/TestMessagePayload.cs new file mode 100644 index 0000000000..0067cc44e9 --- /dev/null +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Messages/TestMessagePayload.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel +{ + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + + /// + /// The test message payload. + /// + public class TestMessagePayload + { + /// + /// Gets or sets the message level. + /// + public TestMessageLevel MessageLevel { get; set; } + + /// + /// Gets or sets the message. + /// + public string Message { get; set; } + } +} diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Messages/TestRunCompletePayload.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/Messages/TestRunCompletePayload.cs new file mode 100644 index 0000000000..9139914e29 --- /dev/null +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Messages/TestRunCompletePayload.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel +{ + using System.Collections.Generic; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + + /// + /// The test run complete payload. + /// + public class TestRunCompletePayload + { + /// + /// Gets or sets the test run complete args. + /// + public TestRunCompleteEventArgs TestRunCompleteArgs { get; set; } + + /// + /// Gets or sets the last run tests. + /// + public TestRunChangedEventArgs LastRunTests { get; set; } + + /// + /// Gets or sets the run attachments. + /// + public ICollection RunAttachments { get; set; } + + /// + /// Gets or sets the executor uris that were used to run the tests. + /// + public ICollection ExecutorUris { get; set; } + } +} diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Messages/TestRunStatsPayload.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/Messages/TestRunStatsPayload.cs new file mode 100644 index 0000000000..e586356608 --- /dev/null +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Messages/TestRunStatsPayload.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel +{ + using System.Collections.Generic; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + + /// + /// The test run stats payload. + /// + public class TestRunStatsPayload + { + /// + /// Gets or sets the test run changed event args. + /// + public TestRunChangedEventArgs TestRunChangedArgs { get; set; } + + /// + /// Gets or sets the in progress test cases. + /// + public IEnumerable InProgressTestCases { get; set; } + } +} diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Microsoft.TestPlatform.CommunicationUtilities.xproj b/src/Microsoft.TestPlatform.CommunicationUtilities/Microsoft.TestPlatform.CommunicationUtilities.xproj new file mode 100644 index 0000000000..3602b837da --- /dev/null +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Microsoft.TestPlatform.CommunicationUtilities.xproj @@ -0,0 +1,19 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 40047cf1-b2e9-407c-80f1-0b8db9d688c5 + Microsoft.VisualStudio.TestPlatform.CommunicationUtilities + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\ + v4.5.2 + + + 2.0 + + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/ObjectModel/TestRunCriteriaWithSources.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/ObjectModel/TestRunCriteriaWithSources.cs new file mode 100644 index 0000000000..30cbfa29de --- /dev/null +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/ObjectModel/TestRunCriteriaWithSources.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel +{ + using System; + using System.Collections.Generic; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.ClientProtocol; + + using Newtonsoft.Json; + + /// + /// The test run criteria with sources. + /// + public class TestRunCriteriaWithSources + { + /// + /// Initializes a new instance of the class. + /// + /// The adapter source map. + /// The run settings. + /// The test Execution Context. + [JsonConstructor] + public TestRunCriteriaWithSources(Dictionary> adapterSourceMap, string runSettings, TestExecutionContext testExecutionContext) + { + this.AdapterSourceMap = adapterSourceMap; + this.RunSettings = runSettings; + this.TestExecutionContext = testExecutionContext; + } + + /// + /// Gets the adapter source map. + /// + public Dictionary> AdapterSourceMap { get; private set; } + + /// + /// Gets the run settings. + /// + public string RunSettings { get; private set; } + + /// + /// Gets or sets the test execution context. + /// + public TestExecutionContext TestExecutionContext { get; set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/ObjectModel/TestRunCriteriaWithTests.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/ObjectModel/TestRunCriteriaWithTests.cs new file mode 100644 index 0000000000..596742bfa9 --- /dev/null +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/ObjectModel/TestRunCriteriaWithTests.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel +{ + using System; + using System.Collections.Generic; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.ClientProtocol; + + using Newtonsoft.Json; + + /// + /// The test run criteria with tests. + /// + public class TestRunCriteriaWithTests + { + /// + /// Initializes a new instance of the class. + /// Ensure that names of constructor parameters match the public property names of the same for JSON serialization + /// + /// The tests. + /// The test run settings. + /// The test Execution Context. + [JsonConstructor] + public TestRunCriteriaWithTests(IEnumerable tests, string runSettings, TestExecutionContext testExecutionContext) + { + this.Tests = tests; + this.RunSettings = runSettings; + this.TestExecutionContext = testExecutionContext; + } + + /// + /// Gets the tests. + /// + public IEnumerable Tests { get; private set; } + + /// + /// Gets the test run settings. + /// + public string RunSettings { get; private set; } + + /// + /// Gets or sets the test execution context. + /// + public TestExecutionContext TestExecutionContext { get; set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Properties/AssemblyInfo.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..acfb9a6dae --- /dev/null +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +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: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Microsoft.VisualStudio.TestPlatform.CommunicationUtilities")] +[assembly: AssemblyTrademark("")] + +// 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("e6c539e9-d7c1-44fc-a49e-d2d52ec2c902")] diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/SocketCommunicationManager.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/SocketCommunicationManager.cs new file mode 100644 index 0000000000..81e5718220 --- /dev/null +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/SocketCommunicationManager.cs @@ -0,0 +1,257 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities +{ + using System.IO; + + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using System.Net; + using System.Net.Sockets; + using System.Threading; + using System.Threading.Tasks; + using System; + + /// + /// Facilitates communication using sockets + /// + public class SocketCommunicationManager : ICommunicationManager + { + /// + /// TCP Listener to host TCP channel and listen + /// + private TcpListener tcpListener; + + /// + /// TCP Client that can connect to a TCP listener + /// + private TcpClient tcpClient; + + /// + /// Binary Writer to write to channel stream + /// + private BinaryWriter binaryWriter; + + /// + /// Binary reader to read from channel stream + /// + private BinaryReader binaryReader; + + /// + /// Serializer for the data objects + /// + private IDataSerializer dataSerializer; + + /// + /// Event used to maintain client connection state + /// + private ManualResetEvent clientConnectedEvent = new ManualResetEvent(false); + + + /// + /// Event used to maintain client connection state + /// + private ManualResetEvent clientConnectionAcceptedEvent = new ManualResetEvent(false); + + /// + /// Sync object for sending messages + /// SendMessage over socket channel is NOT thread-safe + /// + private object sendSyncObject = new object(); + + public SocketCommunicationManager() : this(JsonDataSerializer.Instance) + { + } + + internal SocketCommunicationManager(IDataSerializer dataSerializer) + { + this.dataSerializer = dataSerializer; + } + + #region ServerMethods + + /// + /// Host TCP Socket Server and start listening + /// + /// + public int HostServer() + { + var endpoint = new IPEndPoint(IPAddress.Loopback, 0); + this.tcpListener = new TcpListener(endpoint); + this.tcpListener.Start(); + + var portNumber = ((IPEndPoint)this.tcpListener.LocalEndpoint).Port; + EqtTrace.Info("Listening on port : {0}", portNumber); + + return portNumber; + } + + /// + /// Accepts client async + /// + public async Task AcceptClientAsync() + { + + if (this.tcpListener != null) + { + this.clientConnectedEvent.Reset(); + + var client = await this.tcpListener.AcceptTcpClientAsync(); + var networkStream = client.GetStream(); + + this.binaryReader = new BinaryReader(networkStream); + this.binaryWriter = new BinaryWriter(networkStream); + + this.clientConnectedEvent.Set(); + + EqtTrace.Info("Accepted Client request and set the flag"); + } + } + + /// + /// Waits for Client Connection + /// + /// Time to Wait for the connection + /// True if Client is connected, false otherwise + public bool WaitForClientConnection(int clientConnectionTimeout) + { + return this.clientConnectedEvent.WaitOne(clientConnectionTimeout); + } + + /// + /// Stop Listener + /// + public void StopServer() + { + this.tcpListener?.Stop(); + this.tcpListener = null; + } + + #endregion + + #region ClientMethods + + /// + /// Connects to server async + /// + public async Task SetupClientAsync(int portNumber) + { + this.clientConnectionAcceptedEvent.Reset(); + EqtTrace.Info("Trying to connect to server on port : {0}", portNumber); + this.tcpClient = new TcpClient(); + await this.tcpClient.ConnectAsync(IPAddress.Loopback, portNumber); + var networkStream = this.tcpClient.GetStream(); + this.binaryReader = new BinaryReader(networkStream); + this.binaryWriter = new BinaryWriter(networkStream); + this.clientConnectionAcceptedEvent.Set(); + EqtTrace.Info("Connected to the server successfully "); + } + + /// + /// Waits for server to be connected + /// Whoever creating the client and trying to connect to a server + /// should use this method to wait for connection to be established with server + /// + /// Time to wait for the connection + /// True, if Server got a connection from client + public bool WaitForServerConnection(int connectionTimeout) + { + return this.clientConnectionAcceptedEvent.WaitOne(connectionTimeout); + } + + /// + /// Stop Listener + /// + public void StopClient() + { + this.tcpClient?.Dispose(); + this.tcpClient = null; + } + + #endregion + + /// + /// Writes message to the binary writer. + /// + /// Type of Message to be sent, for instance TestSessionStart + public void SendMessage(string messageType) + { + var serializedObject = this.dataSerializer.SerializeObject(messageType); + WriteAndFlushToChannel(serializedObject); + } + + /// + /// Reads message from the binary reader + /// + /// Returns message read from the binary reader + public Message ReceiveMessage() + { + var rawMessage = this.ReceiveRawMessage(); + return dataSerializer.DeserializeMessage(rawMessage); + } + + /// + /// Writes message to the binary writer with payload + /// + /// Type of Message to be sent, for instance TestSessionStart + /// payload to be sent + public void SendMessage(string messageType, object payload) + { + var rawMessage = this.dataSerializer.SerializeObject(messageType, payload); + WriteAndFlushToChannel(rawMessage); + } + + /// + /// The send hand shake message. + /// + public void SendHandShakeMessage() + { + this.SendMessage(MessageType.SessionStart); + } + + /// + /// Reads message from the binary reader + /// + /// Raw message string + public string ReceiveRawMessage() + { + return this.binaryReader.ReadString(); + } + + /// + /// Send serialized raw message + /// + /// serialized message + public void SendRawMessage(string rawMessage) + { + WriteAndFlushToChannel(rawMessage); + } + + /// + /// Writes the data on socket and flushes the buffer + /// + /// message to write + private void WriteAndFlushToChannel(string rawMessage) + { + // Writing Message on binarywriter is not Thread-Safe + // Need to sync one by one to avoid buffer corruption + lock (sendSyncObject) + { + this.binaryWriter.Write(rawMessage); + this.binaryWriter.Flush(); + } + } + + /// + /// Deserializes the Message into actual TestPlatform objects + /// + /// The type of object to deserialize to. + /// Message object + /// TestPlatform object + public T DeserializePayload(Message message) + { + return this.dataSerializer.DeserializePayload(message); + } + } +} diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/TestRequestHandler.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/TestRequestHandler.cs new file mode 100644 index 0000000000..1d07179f91 --- /dev/null +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/TestRequestHandler.cs @@ -0,0 +1,301 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities +{ + using System; + using System.Collections.Generic; + + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.EventHandlers; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.TesthostProtocol; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + + using Microsoft.VisualStudio.TestPlatform.Utilities; + using System.Threading; + + /// + /// Utility class to fecilitate the IPC comunication. Acts as Client. + /// + public class TestRequestHandler : IDisposable, ITestRequestHandler + { + private ICommunicationManager communicationManager; + + private IDataSerializer dataSerializer; + + private Action OnAckMessageRecieved; + + /// + /// The timeout for the client to connect to the server. + /// + private const int LaunchProcessWithDebuggerTimeout = 5 * 1000; + + public TestRequestHandler() + : this(new SocketCommunicationManager(), JsonDataSerializer.Instance) + { + } + + internal TestRequestHandler(ICommunicationManager communicationManager, IDataSerializer dataSerializer) + { + this.communicationManager = communicationManager; + this.dataSerializer = dataSerializer; + } + + /// + /// Setups client based on port + /// + /// port number to connect + public void InitializeCommunication(int port) + { + this.communicationManager.SetupClientAsync(port); + } + + public bool WaitForRequestSenderConnection(int connectionTimeout) + { + return this.communicationManager.WaitForServerConnection(connectionTimeout); + } + + /// + /// Listens to the commands from server + /// + /// the test host manager. + public void ProcessRequests(ITestHostManagerFactory testHostManagerFactory) + { + bool isSessionEnd = false; + + var jobQueue = new JobQueue( + (action) => { action(); }, + "TestHostOperationQueue", + 500, + 25000000, + true, + (message) => EqtTrace.Error(message)); + + do + { + var message = this.communicationManager.ReceiveMessage(); + switch (message.MessageType) + { + case MessageType.DiscoveryInitialize: + { + EqtTrace.Info("Discovery Session Initialize."); + var pathToAdditionalExtensions = message.Payload.ToObject>(); + jobQueue.QueueJob( + () => + testHostManagerFactory.GetDiscoveryManager().Initialize(pathToAdditionalExtensions), 0); + break; + } + + case MessageType.StartDiscovery: + { + EqtTrace.Info("Discovery started."); + + var discoveryEventsHandler = new TestDiscoveryEventHandler(this); + var discoveryCriteria = message.Payload.ToObject(); + jobQueue.QueueJob( + () => + testHostManagerFactory.GetDiscoveryManager() + .DiscoverTests(discoveryCriteria, discoveryEventsHandler), 0); + + break; + } + + case MessageType.ExecutionInitialize: + { + EqtTrace.Info("Discovery Session Initialize."); + var pathToAdditionalExtensions = message.Payload.ToObject>(); + jobQueue.QueueJob( + () => + testHostManagerFactory.GetExecutionManager().Initialize(pathToAdditionalExtensions), 0); + break; + } + + case MessageType.StartTestExecutionWithSources: + { + EqtTrace.Info("Execution started."); + var testRunEventsHandler = new TestRunEventsHandler(this); + + var testRunCriteriaWithSources = message.Payload.ToObject(); + jobQueue.QueueJob( + () => + testHostManagerFactory.GetExecutionManager() + .StartTestRun( + testRunCriteriaWithSources.AdapterSourceMap, + testRunCriteriaWithSources.RunSettings, + testRunCriteriaWithSources.TestExecutionContext, + null, + testRunEventsHandler), + 0); + + break; + } + + case MessageType.StartTestExecutionWithTests: + { + EqtTrace.Info("Execution started."); + var testRunEventsHandler = new TestRunEventsHandler(this); + + var testRunCriteriaWithTests = + this.communicationManager.DeserializePayload + (message); + + jobQueue.QueueJob( + () => + testHostManagerFactory.GetExecutionManager() + .StartTestRun( + testRunCriteriaWithTests.Tests, + testRunCriteriaWithTests.RunSettings, + testRunCriteriaWithTests.TestExecutionContext, + null, + testRunEventsHandler), + 0); + + break; + } + + case MessageType.CancelTestRun: + jobQueue.Pause(); + testHostManagerFactory.GetExecutionManager().Cancel(); + break; + + case MessageType.LaunchAdapterProcessWithDebuggerAttachedCallback: + this.OnAckMessageRecieved?.Invoke(message); + break; + + case MessageType.AbortTestRun: + jobQueue.Pause(); + testHostManagerFactory.GetExecutionManager().Abort(); + break; + + case MessageType.SessionEnd: + { + EqtTrace.Info("Session End message received from server. Closing the connection."); + isSessionEnd = true; + this.Close(); + break; + } + + case MessageType.SessionAbort: + { + // Dont do anything for now. + break; + } + + default: + { + EqtTrace.Info("Invalid Message types"); + break; + } + } + } + while (!isSessionEnd); + } + + public void Dispose() + { + this.communicationManager?.StopClient(); + } + + /// + /// Closes the connection + /// + public void Close() + { + this.Dispose(); + EqtTrace.Info("Closing the connection !"); + } + + /// + /// The send test cases. + /// + /// + /// The discovered test cases. + /// + public void SendTestCases(IEnumerable discoveredTestCases) + { + this.communicationManager.SendMessage(MessageType.TestCasesFound, discoveredTestCases); + } + + /// + /// Sends the current test run results. + /// + /// + public void SendTestRunStatistics(TestRunChangedEventArgs testRunChangedArgs) + { + this.communicationManager.SendMessage(MessageType.TestRunStatsChange, testRunChangedArgs); + } + + /// + /// Sends the logs back to the server. + /// + /// + /// + public void SendLog(TestMessageLevel messageLevel, string message) + { + var testMessagePayload = new TestMessagePayload {MessageLevel = messageLevel,Message = message}; + this.communicationManager.SendMessage(MessageType.TestMessage, testMessagePayload); + } + + /// + /// Sends a test run complete to the client. + /// + /// + /// + /// + /// + public void SendExecutionComplete( + TestRunCompleteEventArgs testRunCompleteArgs, + TestRunChangedEventArgs lastChunkArgs, + ICollection runContextAttachments, + ICollection executorUris) + { + var payload = new TestRunCompletePayload + { + TestRunCompleteArgs = testRunCompleteArgs, + LastRunTests = lastChunkArgs, + RunAttachments = runContextAttachments, + ExecutorUris = executorUris + }; + + this.communicationManager.SendMessage(MessageType.ExecutionComplete, payload); + } + + /// + /// The discovery complete handler + /// + public void DiscoveryComplete(long totalTests, IEnumerable lastChunk, bool isAborted) + { + var discoveryCompletePayload = new DiscoveryCompletePayload + { + TotalTests = totalTests, + LastDiscoveredTests = isAborted ? null : lastChunk, + IsAborted = isAborted + }; + + this.communicationManager.SendMessage(MessageType.DiscoveryComplete, discoveryCompletePayload); + } + + /// + /// Sends a message to launch process under debugger + /// + /// + public int LaunchProcessWithDebuggerAttached(TestProcessStartInfo testProcessStartInfo) + { + var waitHandle = new AutoResetEvent(false); + Message ackMessage = null; + this.OnAckMessageRecieved = (ackRawMessage) => + { + ackMessage = ackRawMessage; + waitHandle.Set(); + }; + + this.communicationManager.SendMessage(MessageType.LaunchAdapterProcessWithDebuggerAttached, testProcessStartInfo); + + waitHandle.WaitOne(); + this.OnAckMessageRecieved = null; + return this.dataSerializer.DeserializePayload(ackMessage); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/TestRequestSender.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/TestRequestSender.cs new file mode 100644 index 0000000000..ec691e078b --- /dev/null +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/TestRequestSender.cs @@ -0,0 +1,227 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities +{ + using System; + using ObjectModel; + using System.Collections.Generic; + using System.Threading.Tasks; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces; + + /// + /// Utility class that facilitates the IPC comunication. Acts as server. + /// + public sealed class TestRequestSender : ITestRequestSender + { + private ICommunicationManager communicationManager; + + private IDataSerializer dataSerializer; + + public TestRequestSender() : this(new SocketCommunicationManager(), JsonDataSerializer.Instance) + { + } + + internal TestRequestSender(ICommunicationManager communicationManager, IDataSerializer dataSerializer) + { + this.communicationManager = communicationManager; + this.dataSerializer = dataSerializer; + } + + /// + /// Creates an endpoint and listens for client connection asynchronously + /// + /// + public int InitializeCommunication() + { + var port = this.communicationManager.HostServer(); + this.communicationManager.AcceptClientAsync(); + return port; + } + + public bool WaitForRequestHandlerConnection(int clientConnectionTimeout) + { + return this.communicationManager.WaitForClientConnection(clientConnectionTimeout); + } + + public void Dispose() + { + this.communicationManager?.StopServer(); + } + + /// + /// Closes the connection + /// + public void Close() + { + this.Dispose(); + EqtTrace.Info("Closing the connection"); + } + + /// + /// Initializes the extensions in the test host. + /// + /// Path to additional extensions. + /// Flag to indicate if only well known extensions are to be loaded. + public void InitializeDiscovery(IEnumerable pathToAdditionalExtensions, bool loadOnlyWellKnownExtensions) + { + this.communicationManager.SendMessage(MessageType.DiscoveryInitialize, pathToAdditionalExtensions); + } + + /// + /// Initializes the extensions in the test host. + /// + /// Path to additional extensions. + /// Flag to indicate if only well known extensions are to be loaded. + public void InitializeExecution(IEnumerable pathToAdditionalExtensions, bool loadOnlyWellKnownExtensions) + { + this.communicationManager.SendMessage(MessageType.ExecutionInitialize, pathToAdditionalExtensions); + } + + /// + /// Discovers tests in the sources passed with the criteria specifief. + /// + /// The criteria for discovery. + /// The handler for discovery events from the test host. + public void DiscoverTests(DiscoveryCriteria discoveryCriteria, ITestDiscoveryEventsHandler discoveryEventsHandler) + { + this.communicationManager.SendMessage(MessageType.StartDiscovery, discoveryCriteria); + + var isDiscoveryComplete = false; + + // Cycle through the messages that the testhost sends. + // Currently each of the operations are not separate tasks since they should not each take much time. This is just a notification. + while (!isDiscoveryComplete) + { + var rawMessage = this.communicationManager.ReceiveRawMessage(); + + // Send raw message first to unblock handlers waiting to send message to IDEs + discoveryEventsHandler.HandleRawMessage(rawMessage); + + var message = this.dataSerializer.DeserializeMessage(rawMessage); + if (string.Equals(MessageType.TestCasesFound, message.MessageType)) + { + var testCases = this.dataSerializer.DeserializePayload>(message); + discoveryEventsHandler.HandleDiscoveredTests(testCases); + } + else if (string.Equals(MessageType.DiscoveryComplete, message.MessageType)) + { + var discoveryCompletePayload = this.dataSerializer.DeserializePayload(message); + discoveryEventsHandler.HandleDiscoveryComplete(discoveryCompletePayload.TotalTests, discoveryCompletePayload.LastDiscoveredTests, discoveryCompletePayload.IsAborted); + isDiscoveryComplete = true; + } + else if (string.Equals(MessageType.TestMessage, message.MessageType)) + { + var testMessagePayload = this.dataSerializer.DeserializePayload(message); + discoveryEventsHandler.HandleLogMessage(testMessagePayload.MessageLevel, testMessagePayload.Message); + } + } + } + + /// + /// Ends the session with the test host. + /// + public void EndSession() + { + this.communicationManager.SendMessage(MessageType.SessionEnd); + } + + /// + /// Executes tests on the sources specified with the criteria mentioned. + /// + /// The test run criteria. + /// The handler for execution events from the test host. + public void StartTestRun(TestRunCriteriaWithSources runCriteria, ITestRunEventsHandler eventHandler) + { + this.communicationManager.SendMessage(MessageType.StartTestExecutionWithSources, runCriteria); + + // This needs to happen asynchronously. + Task.Run(() => this.ListenAndReportTestResults(eventHandler)); + } + + /// + /// Executes the specified tests with the criteria mentioned. + /// + /// The test run criteria. + /// The handler for execution events from the test host. + public void StartTestRun(TestRunCriteriaWithTests runCriteria, ITestRunEventsHandler eventHandler) + { + this.communicationManager.SendMessage(MessageType.StartTestExecutionWithTests, runCriteria); + + // This needs to happen asynchronously. + Task.Run(() => this.ListenAndReportTestResults(eventHandler)); + } + + private void ListenAndReportTestResults(ITestRunEventsHandler testRunEventsHandler) + { + var isTestRunComplete = false; + + // Cycle through the messages that the testhost sends. + // Currently each of the operations are not separate tasks since they should not each take much time. This is just a notification. + while (!isTestRunComplete) + { + var rawMessage = this.communicationManager.ReceiveRawMessage(); + + // Send raw message first to unblock handlers waiting to send message to IDEs + testRunEventsHandler.HandleRawMessage(rawMessage); + + var message = this.dataSerializer.DeserializeMessage(rawMessage); + try + { + if (string.Equals(MessageType.TestRunStatsChange, message.MessageType)) + { + var testRunChangedArgs = dataSerializer.DeserializePayload(message); + testRunEventsHandler.HandleTestRunStatsChange(testRunChangedArgs); + } + else if (string.Equals(MessageType.ExecutionComplete, message.MessageType)) + { + var testRunCompletePayload = dataSerializer.DeserializePayload(message); + + testRunEventsHandler.HandleTestRunComplete( + testRunCompletePayload.TestRunCompleteArgs, + testRunCompletePayload.LastRunTests, + testRunCompletePayload.RunAttachments, + testRunCompletePayload.ExecutorUris); + isTestRunComplete = true; + } + else if (string.Equals(MessageType.TestMessage, message.MessageType)) + { + var testMessagePayload = this.dataSerializer.DeserializePayload(message); + testRunEventsHandler.HandleLogMessage(testMessagePayload.MessageLevel, testMessagePayload.Message); + } + else if (string.Equals(MessageType.LaunchAdapterProcessWithDebuggerAttached, message.MessageType)) + { + var testProcessStartInfo = this.dataSerializer.DeserializePayload(message); + int processId = testRunEventsHandler.LaunchProcessWithDebuggerAttached(testProcessStartInfo); + + this.communicationManager.SendMessage( + MessageType.LaunchAdapterProcessWithDebuggerAttachedCallback, processId); + } + } + catch (Exception exception) + { + EqtTrace.Error("Server: TestExecution: Message Deserialization failed with {0}", exception); + // notify of a test run complete and bail out. + testRunEventsHandler.HandleTestRunComplete(null, null, null, null); + isTestRunComplete = true; + } + } + } + + /// + /// Send the cancel message to test host + /// + public void SendTestRunCancel() + { + this.communicationManager.SendMessage(MessageType.CancelTestRun); + } + + public void SendTestRunAbort() + { + this.communicationManager.SendMessage(MessageType.AbortTestRun); + } + } +} diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/project.json b/src/Microsoft.TestPlatform.CommunicationUtilities/project.json new file mode 100644 index 0000000000..a6f18faafc --- /dev/null +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/project.json @@ -0,0 +1,29 @@ +{ + "version": "15.0.0-*", + "buildOptions": { + "delaySign": true, + "keyFile": "../../scripts/key.snk", + "warningsAsErrors": true + }, + + "dependencies": { + "Newtonsoft.Json": "7.0.1", + "Microsoft.TestPlatform.CoreUtilities": "15.0.0-*", + "Microsoft.TestPlatform.ObjectModel": "15.0.0-*", + "Microsoft.TestPlatform.Common": "15.0.0-*" + }, + + "frameworks": { + "netstandard1.4": { + "imports": [ + "dnxcore50", + "portable-net45+win8" + ], + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-24008", + "System.Runtime.Serialization.Primitives": "4.0.10-*" + } + }, + "net46": {} + } +} diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Friends.cs b/src/Microsoft.TestPlatform.CoreUtilities/Friends.cs new file mode 100644 index 0000000000..90caeb4d0b --- /dev/null +++ b/src/Microsoft.TestPlatform.CoreUtilities/Friends.cs @@ -0,0 +1,6 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("vstest.console, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] +[assembly: InternalsVisibleTo("vstest.console.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Helpers/FileHelper.cs b/src/Microsoft.TestPlatform.CoreUtilities/Helpers/FileHelper.cs new file mode 100644 index 0000000000..b09d635935 --- /dev/null +++ b/src/Microsoft.TestPlatform.CoreUtilities/Helpers/FileHelper.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Utilities.Helpers +{ + using System.IO; + + using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces; + + /// + /// The file helper. + /// + internal class FileHelper : IFileHelper + { + /// + /// Exists utility to check if file exists + /// + /// The path of file. + /// True if file exists . + public bool Exists(string path) + { + return File.Exists(path); + } + } +} diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Helpers/Interfaces/IFileHelper.cs b/src/Microsoft.TestPlatform.CoreUtilities/Helpers/Interfaces/IFileHelper.cs new file mode 100644 index 0000000000..1b7fa18f36 --- /dev/null +++ b/src/Microsoft.TestPlatform.CoreUtilities/Helpers/Interfaces/IFileHelper.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces +{ + /// + /// The FileHelper interface. + /// + internal interface IFileHelper + { + /// + /// Exists utility to check if file exists + /// + /// The path of file. + /// True if file exists . + bool Exists(string path); + } +} diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Microsoft.TestPlatform.CoreUtilities.xproj b/src/Microsoft.TestPlatform.CoreUtilities/Microsoft.TestPlatform.CoreUtilities.xproj new file mode 100644 index 0000000000..671043fe15 --- /dev/null +++ b/src/Microsoft.TestPlatform.CoreUtilities/Microsoft.TestPlatform.CoreUtilities.xproj @@ -0,0 +1,22 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 0ba3ed0e-7946-4333-b429-7cde4d47fd59 + Microsoft.VisualStudio.TestPlatform.CoreUtilities + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\ + v4.5.2 + + + 2.0 + + + True + + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Output/ConsoleColorHelper.cs b/src/Microsoft.TestPlatform.CoreUtilities/Output/ConsoleColorHelper.cs new file mode 100644 index 0000000000..0701e16271 --- /dev/null +++ b/src/Microsoft.TestPlatform.CoreUtilities/Output/ConsoleColorHelper.cs @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Utilities +{ + using System; + + /// + /// Colors the console output and restores it when disposed. + /// + public sealed class ConsoleColorHelper : IDisposable + { + #region Fields + + private bool m_isDisposed; + private ConsoleColor m_previousForgroundColor; + + #endregion + + #region Constructor + + /// + /// Initializes with the color to set the console forground to. + /// + /// Color to set the console forground to. + public ConsoleColorHelper(ConsoleColor foregroundColor) + { + m_previousForgroundColor = Console.ForegroundColor; + Console.ForegroundColor = foregroundColor; + } + + #endregion + + #region Methods + + /// + /// Restores the original forground color. + /// + public void Dispose() + { + Dispose(true); + + // Use SupressFinalize in case a subclass + // of this type implements a finalizer. + GC.SuppressFinalize(this); + } + + /// + /// Disposes the composition container. + /// + private void Dispose(bool disposing) + { + if (!m_isDisposed) + { + if (disposing) + { + Console.ForegroundColor = m_previousForgroundColor; + } + + m_isDisposed = true; + } + } + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Output/ConsoleOutput.cs b/src/Microsoft.TestPlatform.CoreUtilities/Output/ConsoleOutput.cs new file mode 100644 index 0000000000..f863ad0665 --- /dev/null +++ b/src/Microsoft.TestPlatform.CoreUtilities/Output/ConsoleOutput.cs @@ -0,0 +1,103 @@ +// --------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// +// Sends output to the console. +// +// --------------------------------------------------------------------------- + +namespace Microsoft.VisualStudio.TestPlatform.Utilities +{ + using System; + using System.IO; + using System.Text; + + /// + /// Sends output to the console. + /// + public class ConsoleOutput : IOutput + { + #region Fields + + private TextWriter m_standardOutput = null; + private TextWriter m_standardError = null; + private static Object s_lockObject = new object(); + private static ConsoleOutput s_consoleOutput = null; + + #endregion + + #region Constructor + + /// + /// Default constructor. + /// + internal ConsoleOutput() + { + m_standardOutput = Console.Out; + m_standardError = Console.Error; + } + + #endregion + + public static ConsoleOutput Instance + { + get + { + if (s_consoleOutput != null) + { + return s_consoleOutput; + } + + lock (s_lockObject) + { + if (s_consoleOutput == null) + { + s_consoleOutput = new ConsoleOutput(); + } + } + return s_consoleOutput; + } + } + + #region IOutput + + /// + /// Writes the message with a new line. + /// + /// Message to be output. + /// Level of the message. + public void WriteLine(string message, OutputLevel level) + { + Write(message, level); + Write(Environment.NewLine, level); + } + + /// + /// Writes the message with no new line. + /// + /// Message to be output. + /// Level of the message. + public void Write(string message, OutputLevel level) + { + switch (level) + { + case OutputLevel.Information: + case OutputLevel.Warning: + m_standardOutput.Write(message); + break; + + case OutputLevel.Error: + m_standardError.Write(message); + break; + + default: + m_standardOutput.Write("ConsoleOutput.WriteLine: The output level is unrecognized: {0}", level); + break; + } + } + + #endregion + + } +} diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Output/IOutput.cs b/src/Microsoft.TestPlatform.CoreUtilities/Output/IOutput.cs new file mode 100644 index 0000000000..d3b7ac9133 --- /dev/null +++ b/src/Microsoft.TestPlatform.CoreUtilities/Output/IOutput.cs @@ -0,0 +1,31 @@ +// --------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// +// Interface for outputing information under the command line. +// +// --------------------------------------------------------------------------- + +namespace Microsoft.VisualStudio.TestPlatform.Utilities +{ + /// + /// Interface for outputing information under the command line. + /// + public interface IOutput + { + /// + /// Writes the message with a new line. + /// + /// Message to be output. + /// Level of the message. + void WriteLine(string message, OutputLevel level); + + /// + /// Writes the message with no new line. + /// + /// Message to be output. + /// Level of the message. + void Write(string message, OutputLevel level); + } +} diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Output/OutputLevel.cs b/src/Microsoft.TestPlatform.CoreUtilities/Output/OutputLevel.cs new file mode 100644 index 0000000000..94aef5e4e3 --- /dev/null +++ b/src/Microsoft.TestPlatform.CoreUtilities/Output/OutputLevel.cs @@ -0,0 +1,32 @@ +// --------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// +// Defines the level of output. +// +// --------------------------------------------------------------------------- + +namespace Microsoft.VisualStudio.TestPlatform.Utilities +{ + /// + /// Defines the level of output. + /// + public enum OutputLevel + { + /// + /// Informational message. + /// + Information = 0, + + /// + /// Warning message. + /// + Warning = 1, + + /// + /// Error message. + /// + Error = 2, + } +} diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Output/OutputUtilities.cs b/src/Microsoft.TestPlatform.CoreUtilities/Output/OutputUtilities.cs new file mode 100644 index 0000000000..1365ce2c65 --- /dev/null +++ b/src/Microsoft.TestPlatform.CoreUtilities/Output/OutputUtilities.cs @@ -0,0 +1,115 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Utilities +{ + using System; + using System.Globalization; + + using Microsoft.VisualStudio.TestPlatform.CoreUtilities; + + /// + /// Utility Methods for sending output to IOutput. + /// + public static class OutputUtilities + { + #region Extension Methods + + /// + /// Output an error message. + /// + /// Output instance the method is being invoked with. + /// Format string for the error message. + /// Arguments to format into the format string. + public static void Error(this IOutput output, String format, params object[] args) + { + using (new ConsoleColorHelper(ConsoleColor.Red)) + { + Output(output, OutputLevel.Error, Resources.CommandLineError, format, args); + } + } + + /// + /// Output a warning message. + /// + /// Output instance the method is being invoked with. + /// Format string for the warning message. + /// Arguments to format into the format string. + public static void Warning(this IOutput output, string format, params object[] args) + { + using (new ConsoleColorHelper(ConsoleColor.Yellow)) + { + Output(output, OutputLevel.Warning, Resources.CommandLineWarning, format, args); + } + } + + /// + /// Output a informational message. + /// + /// Output instance the method is being invoked with. + /// Format string for the informational message. + /// Arguments to format into the format string. + public static void Information(this IOutput output, string format, params object[] args) + { + Output(output, OutputLevel.Information, Resources.CommandLineInformational, format, args); + } + + #endregion + + #region Private Methods + + /// + /// Formats the message. + /// + /// Format string for the message type. + /// Format string for the error message. + /// Arguments to format into the format string. + private static void Output(IOutput output, OutputLevel level, string messageTypeFormat, string format, params object[] args) + { + if (output == null) + { + throw new ArgumentNullException("output"); + } + + string message = Format(messageTypeFormat, format, args); + output.WriteLine(message, level); + } + + /// + /// Formats the message. + /// + /// Format string for the message type. + /// Format string for the error message. + /// Arguments to format into the format string. + private static string Format(string messageTypeFormat, string format, params object[] args) + { + if (format==null || String.IsNullOrEmpty(format.Trim())) + { + throw new ArgumentException(Resources.CannotBeNullOrEmpty, "format"); + } + + string message = null; + if (args != null && args.Length > 0) + { + message = + String.Format( + CultureInfo.CurrentCulture, + format, + args); + } + else + { + message = format; + } + + message = + String.Format( + CultureInfo.CurrentCulture, + messageTypeFormat, + message); + + return message; + } + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Properties/AssemblyInfo.cs b/src/Microsoft.TestPlatform.CoreUtilities/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..c4638b82d2 --- /dev/null +++ b/src/Microsoft.TestPlatform.CoreUtilities/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +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: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Microsoft.TestPlatform.CoreUtilities")] +[assembly: AssemblyTrademark("")] + +// 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("d472046e-ed17-4750-a2a3-29935b5215f6")] diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Resources.Designer.cs b/src/Microsoft.TestPlatform.CoreUtilities/Resources.Designer.cs new file mode 100644 index 0000000000..81efe445a7 --- /dev/null +++ b/src/Microsoft.TestPlatform.CoreUtilities/Resources.Designer.cs @@ -0,0 +1,125 @@ +//------------------------------------------------------------------------------ +// +// 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 Microsoft.VisualStudio.TestPlatform.CoreUtilities { + using System; + using System.Reflection; + + + /// + /// 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.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + internal Resources() { + } + + /// + /// 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("Microsoft.TestPlatform.CoreUtilities.Resources", typeof(Resources).GetTypeInfo().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 The parameter cannot be null or empty.. + /// + public static string CannotBeNullOrEmpty { + get { + return ResourceManager.GetString("CannotBeNullOrEmpty", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Error: {0}. + /// + public static string CommandLineError { + get { + return ResourceManager.GetString("CommandLineError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Information: {0}. + /// + public static string CommandLineInformational { + get { + return ResourceManager.GetString("CommandLineInformational", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Warning: {0}. + /// + public static string CommandLineWarning { + get { + return ResourceManager.GetString("CommandLineWarning", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unhandled exception occurred while processing a job from the '{0}' queue: {1}. + /// + public static string ExceptionFromJobProcessor { + get { + return ResourceManager.GetString("ExceptionFromJobProcessor", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The {0} queue has already been disposed.. + /// + public static string QueueAlreadyDisposed { + get { + return ResourceManager.GetString("QueueAlreadyDisposed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The {0} queue cannot be disposed while paused.. + /// + public static string QueuePausedDisposeError { + get { + return ResourceManager.GetString("QueuePausedDisposeError", resourceCulture); + } + } + } +} diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Resources.resx b/src/Microsoft.TestPlatform.CoreUtilities/Resources.resx new file mode 100644 index 0000000000..ceba2805f0 --- /dev/null +++ b/src/Microsoft.TestPlatform.CoreUtilities/Resources.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + The parameter cannot be null or empty. + + + Error: {0} + + + Information: {0} + + + Warning: {0} + + + Unhandled exception occurred while processing a job from the '{0}' queue: {1} + + + The {0} queue has already been disposed. + + + The {0} queue cannot be disposed while paused. + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Tracing/EqtTrace.cs b/src/Microsoft.TestPlatform.CoreUtilities/Tracing/EqtTrace.cs new file mode 100644 index 0000000000..28350e4966 --- /dev/null +++ b/src/Microsoft.TestPlatform.CoreUtilities/Tracing/EqtTrace.cs @@ -0,0 +1,772 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel +{ + using System; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + using System.Text; + +#if NET46 + using System.Configuration; + using Microsoft.Win32; + using Utilities; + using System.IO; +#endif + + /// + /// Wrapper class for tracing. + /// - Shortcut-methods for Error, Warning, Info, Verbose. + /// - Adds additional information to the trace: calling process name, PID, ThreadID, Time. + /// - Uses custom switch EqtTraceLevel from .config file. + /// - By default tracing if OFF. + /// - Our build environment always sets the /d:TRACE so this class is always enabled, + /// the Debug class is enabled only in debug builds (/d:DEBUG). + /// - We ignore exceptions thrown by underlying TraceSwitch (e.g. due to config file error). + /// We log ignored exceptions to system Application log. + /// We pass through exceptions thrown due to incorrect arguments to EqtTrace methods. + /// Usage: EqtTrace.Info("Here's how to trace info"); + /// + public static class EqtTrace + { + +#if NET46 + /// + /// To which system event log we log if cannot log to trace file. + /// + private const string SystemEventLogSource = "Application"; + + /// + /// Name of the trace listener. + /// + private const string ListenerName = "TptTraceListener"; + + /// + /// Maximum size of the trace log file (in kilobytes). + /// + private const string TraceLogMaxFileSizeInKB = "TraceLogMaxFileSizeInKb"; + + /// + /// The switch for tracing TestPlaform messages only. Initialize Trace listener as a part of this + /// to avoid a separate Static concstructor (to fix CA1810) + /// + private static readonly TraceSwitch traceLevelSwitch = new TraceSwitch("TpTraceLevel", null); + + // Current process name/id that called trace so that it's easier to read logs. + // We cache them for performance reason. + private static readonly string processName = GetProcessName(); + + private static readonly int processId = GetProcessId(); + + /// + /// We log to system log only this # of times in order not to overflow it. + /// + //private static int timesCanWriteToSystemLog = 10; + + /// Specifies whether the trace is initialized or not + /// + private static bool isInitialized = false; + + /// + /// Trace listener object to which all Testplatform traces are written. + /// + private static TraceListener listener; + + /// + /// Lock over initialization + /// + private static object initializationLock = new object(); + + private static int TraceFileSize = 0; + private static int DefaultTraceFileSize = 10240; // 10Mb. + + /// + /// Log file name to setup custom file for logging. + /// + private static string logFileName = null; +#endif + /// + /// Ensure the trace is initialized + /// + static void EnsureTraceIsInitialized() + { +#if NET46 + if (isInitialized) + { + return; + } + + lock (initializationLock) + { + if (isInitialized) + { + return; + } + + string logsDirectory = Path.GetTempPath(); + // Set the trace level and add the trace listener + + if (logFileName == null) + { + logFileName = + Path.Combine( + logsDirectory, + Path.GetFileNameWithoutExtension(Process.GetCurrentProcess().MainModule.FileName) + + ".TpTrace.log" + ); + } + + + string traceFileSizeStr = ConfigurationManager.AppSettings[TraceLogMaxFileSizeInKB]; + if (!int.TryParse(traceFileSizeStr, out TraceFileSize) + || TraceFileSize <= 0) + { + TraceFileSize = DefaultTraceFileSize; + } + + //listener = new RollingFileTraceListener(logFileName, ListenerName, TraceFileSize); + Trace.Listeners.Add(listener); + // Set the auto-flush flag, so that log entries are written immediately. This is done so that if the + // process exits or is killed, we will have as much log information as possible in the log. + Trace.AutoFlush = true; + + isInitialized = true; + } +#endif + } + +#if NET46 + /// + /// Setup remote trace listener in the child domain. + /// If calling domain, doesn't have tracing enabled nothing is done. + /// + /// + public static void SetupRemoteEqtTraceListeners(AppDomain childDomain) + { + if (null != childDomain) + { + RemoteEqtTrace remoteEqtTrace = (RemoteEqtTrace)childDomain.CreateInstanceFromAndUnwrap( + typeof(RemoteEqtTrace).Assembly.Location, + typeof(RemoteEqtTrace).FullName); + + remoteEqtTrace.TraceLevel = TraceLevel; + + if (!Enum.Equals(TraceLevel, TraceLevel.Off)) + { + + TraceListener tptListner = null; + foreach (TraceListener listener in Trace.Listeners) + { + if (String.Equals(listener.Name, ListenerName, StringComparison.OrdinalIgnoreCase)) + { + Debug.Assert(tptListner == null, "Multiple TptListeners found."); + tptListner = listener; + } + } + remoteEqtTrace.SetupRemoteListeners(tptListner); + } + } + } + + /// + /// Setup trace listenrs. It should be called when setting trace listener for child domain. + /// + /// + internal static void SetupRemoteListeners(TraceListener traceListener) + { + lock (initializationLock) + { + // Add new listeners. + if (null != listener) + { + Trace.Listeners.Add(listener); + listener = traceListener; + } + isInitialized = true; + } + } +#endif + + /// + /// Setup trace listenrs. It should be called when setting trace listener for child domain. + /// + /// + internal static void SetupRemoteListeners() + { + } + + #region TraceLevel, ShouldTrace + +#if NET46 + public static System.Diagnostics.TraceLevel TraceLevel + { + get + { + return traceLevelSwitch.Level; + } + + set + { + try + { + traceLevelSwitch.Level = value; + } + catch (ArgumentException e) + { + LogIgnoredException(e); + } + } + } +#endif + /// + /// Boolean flag to know if tracing error statements is enabled. + /// + public static bool IsErrorEnabled + { + get { return false; } + } + + /// + /// Boolean flag to know if tracing info statements is enabled. + /// + public static bool IsInfoEnabled + { + get { return false; } + } + + /// + /// Boolean flag to know if tracing verbose statements is enabled. + /// + public static bool IsVerboseEnabled + { + get { return false; } + } + + /// + /// Boolean flag to know if tracing warning statements is enabled. + /// + public static bool IsWarningEnabled + { + get { return false; } + } + + /// + /// returns true if tracing is enabled for the passed + /// trace level + /// + /// + /// + public static bool ShouldTrace() + { + return false; + } + #endregion + + #region Error + + /// + /// Prints an error message and prompts with a Debug dialog + /// + /// the error message + [ConditionalAttribute("TRACE")] + public static void Fail(string message) + { + Error(message); + } + + + /// + /// Combines together EqtTrace.Fail and Debug.Fail: + /// Prints an formatted error message and prompts with a Debug dialog. + /// + /// the formatted error message + /// arguments to the format + [ConditionalAttribute("TRACE")] + public static void Fail(string format, params object[] args) + { + if (IsErrorEnabled) + { + var message = string.Format(CultureInfo.InvariantCulture, format, args); + Error(message); + #if DEBUG + Debug.Fail(message); + #endif + } + } + + + + [ConditionalAttribute("TRACE")] + public static void Error(string message) + { + // Todo Add logging. + } + + /// + /// Only prints the message if the condition is true + /// + /// + /// + [ConditionalAttribute("TRACE")] + public static void ErrorIf(bool condition, string message) + { + if (condition) + { + Error(message); + } + } + + /// + /// Only prints the formatted message if the condition is false + /// + /// + /// + [ConditionalAttribute("TRACE")] + public static void ErrorUnless(bool condition, string message) + { + ErrorIf(!condition, message); + } + + /// + /// Prints the message if the condition is false. If the condition is true, + /// the message is instead printed at the specified trace level. + /// + /// + /// + [ConditionalAttribute("TRACE")] + public static void ErrorUnlessAlterTrace(bool condition, string message) + { + } + + [ConditionalAttribute("TRACE")] + public static void Error(string format, params object[] args) + { + if (IsErrorEnabled) + { + Debug.Assert(format != null); + } + } + + /// + /// Only prints the formatted message if the condition is false + /// + /// + /// + [ConditionalAttribute("TRACE")] + public static void ErrorUnless(bool condition, string format, params object[] args) + { + ErrorIf(!condition, format, args); + } + + /// + /// Prints the message if the condition is false. If the condition is true, + /// the message is instead printed at the specified trace level. + /// + /// + /// + [ConditionalAttribute("TRACE")] + public static void ErrorUnlessAlterTrace(bool condition, string format, params object[] args) + { + } + + /// + /// Only prints the formatted message if the condition is true + /// + /// + /// + [ConditionalAttribute("TRACE")] + public static void ErrorIf(bool condition, string format, params object[] args) + { + if (condition) + { + Error(format, args); + } + } + + /// + /// EqtTrace.Error and Debug.Fail combined in one call. + /// + /// The message to send to Debug.Fail and EqtTrace.Error. + /// Params to string.Format. + [ConditionalAttribute("TRACE")] + public static void ErrorAssert(string format, params object[] args) + { + } + + /// + /// Write a exception if tracing for error is enabled + /// + /// The exception to write. + [ConditionalAttribute("TRACE")] + public static void Error(Exception exceptionToTrace) + { + if (IsErrorEnabled) + { + Debug.Assert(exceptionToTrace != null); + } + } + + #endregion + + #region Warning + + [ConditionalAttribute("TRACE")] + public static void Warning(string message) + { + if (IsWarningEnabled) + { + // Add logging. + } + } + + /// + /// Only prints the formatted message if the condition is true + /// + /// + /// + [ConditionalAttribute("TRACE")] + public static void WarningIf(bool condition, string message) + { + if (condition) + { + Warning(message); + } + } + + /// + /// Only prints the formatted message if the condition is false + /// + /// + /// + [ConditionalAttribute("TRACE")] + public static void WarningUnless(bool condition, string message) + { + WarningIf(!condition, message); + } + + /// + /// Prints the message if the condition is false. If the condition is true, + /// the message is instead printed at the specified trace level. + /// + /// + /// + [ConditionalAttribute("TRACE")] + public static void WarningUnlessAlterTrace(bool condition, string message) + { + } + + [ConditionalAttribute("TRACE")] + public static void Warning(string format, params object[] args) + { + if (IsWarningEnabled) + { + Debug.Assert(format != null); + } + } + + [ConditionalAttribute("TRACE")] + public static void WarningIf(bool condition, string format, params object[] args) + { + if (condition) + { + Warning(format, args); + } + } + + /// + /// Only prints the formatted message if the condition is false + /// + /// + /// + [ConditionalAttribute("TRACE")] + public static void WarningUnless(bool condition, string format, params object[] args) + { + WarningIf(!condition, format, args); + } + + /// + /// Prints the message if the condition is false. If the condition is true, + /// the message is instead printed at the specified trace level. + /// + /// + /// + [ConditionalAttribute("TRACE")] + public static void WarningUnlessAlterTrace(bool condition, string format, params object[] args) + { + } + #endregion + + #region Info + + [ConditionalAttribute("TRACE")] + public static void Info(string message) + { + if (IsInfoEnabled) + { + // Add logging. + } + } + + [ConditionalAttribute("TRACE")] + public static void InfoIf(bool condition, string message) + { + if (condition) + { + Info(message); + } + } + + /// + /// Only prints the formatted message if the condition is false + /// + /// + /// + [ConditionalAttribute("TRACE")] + public static void InfoUnless(bool condition, string message) + { + InfoIf(!condition, message); + } + + /// + /// Prints the message if the condition is false. If the condition is true, + /// the message is instead printed at the specified trace level. + /// + /// + /// + [ConditionalAttribute("TRACE")] + public static void InfoUnlessAlterTrace(bool condition, string message) + { + } + + [ConditionalAttribute("TRACE")] + public static void Info(string format, params object[] args) + { + if (IsInfoEnabled) + { + Debug.Assert(format != null); + + // Check level before doing string.Format to avoid string creation if tracing is off. + } + } + + [ConditionalAttribute("TRACE")] + public static void InfoIf(bool condition, string format, params object[] args) + { + if (condition) + { + Info(format, args); + } + } + + /// + /// Only prints the formatted message if the condition is false + /// + /// + /// + [ConditionalAttribute("TRACE")] + public static void InfoUnless(bool condition, string format, params object[] args) + { + InfoIf(!condition, format, args); + } + + /// + /// Prints the message if the condition is false. If the condition is true, + /// the message is instead printed at the specified trace level. + /// + /// + /// + [ConditionalAttribute("TRACE")] + public static void InfoUnlessAlterTrace(bool condition, string format, params object[] args) + { + } + #endregion + + #region Verbose + + [ConditionalAttribute("TRACE")] + public static void Verbose(string message) + { + if (IsVerboseEnabled) + { + // Add logging. + } + } + + [ConditionalAttribute("TRACE")] + public static void VerboseIf(bool condition, string message) + { + if (condition) + { + Verbose(message); + } + } + + /// + /// Only prints the formatted message if the condition is false + /// + /// + /// + [ConditionalAttribute("TRACE")] + public static void VerboseUnless(bool condition, string message) + { + VerboseIf(!condition, message); + } + + /// + /// Prints the message if the condition is false. If the condition is true, + /// the message is instead printed at the specified trace level. + /// + /// + /// + [ConditionalAttribute("TRACE")] + public static void VerboseUnlessAlterTrace(bool condition, string message) + { + } + + [ConditionalAttribute("TRACE")] + public static void Verbose(string format, params object[] args) + { + if (IsVerboseEnabled) + { + Debug.Assert(format != null); + } + } + + [ConditionalAttribute("TRACE")] + public static void VerboseIf(bool condition, string format, params object[] args) + { + if (condition) + { + Verbose(format, args); + } + } + + /// + /// Only prints the formatted message if the condition is false + /// + /// + /// + [ConditionalAttribute("TRACE")] + public static void VerboseUnless(bool condition, string format, params object[] args) + { + VerboseIf(!condition, format, args); + } + + /// + /// Prints the message if the condition is false. If the condition is true, + /// the message is instead printed at the specified trace level. + /// + /// + /// + [ConditionalAttribute("TRACE")] + public static void VerboseUnlessAlterTrace(bool condition, string format, params object[] args) + { + } + #endregion + + #region Helpers + + /// + /// Formats an exception into a nice looking message. + /// + /// The exception to write. + /// The formatted string. + private static string FormatException(Exception exceptionToTrace) + { + // Prefix for each line + string prefix = Environment.NewLine + '\t'; + + // Format this exception + StringBuilder message = new StringBuilder(); + message.Append(string.Format(CultureInfo.InvariantCulture, + "Exception: {0}{1}Message: {2}{3}Stack Trace: {4}", + exceptionToTrace.GetType(), prefix, exceptionToTrace.Message, + prefix, exceptionToTrace.StackTrace)); + + // If there is base exception, add that to message + if (exceptionToTrace.GetBaseException() != null) + { + message.Append(string.Format(CultureInfo.InvariantCulture, + "{0}BaseExceptionMessage: {1}", + prefix, exceptionToTrace.GetBaseException().Message)); + } + + // If there is inner exception, add that to message + // We deliberately avoid recursive calls here. + if (exceptionToTrace.InnerException != null) + { + // Format same as outer exception except + // "InnerException" is prefixed to each line + Exception inner = exceptionToTrace.InnerException; + prefix += "InnerException"; + message.Append(string.Format(CultureInfo.InvariantCulture, + "{0}: {1}{2} Message: {3}{4} Stack Trace: {5}", + prefix, inner.GetType(), prefix, inner.Message, prefix, inner.StackTrace)); + + if (inner.GetBaseException() != null) + { + message.Append(string.Format(CultureInfo.InvariantCulture, + "{0}BaseExceptionMessage: {1}", + prefix, inner.GetBaseException().Message)); + } + } + + // Append a new line + message.Append(Environment.NewLine); + + return message.ToString(); + } + + /// + /// Get the process name. Note: we cache it, use m_processName. + /// + [SuppressMessage("Microsoft.Design", "CA1031")] + private static string GetProcessName() + { + return string.Empty; + } + + private static int GetProcessId() + { + return -1; + } + + private static void WriteAtLevel(string message) + { + } + + private static void WriteAtLevel(string format, params object[] args) + { + Debug.Assert(format != null); + } + + /// + /// Adds the message to the trace log. + /// The line becomes: + /// [I, PID, ThreadID, 2003/06/11 11:56:07.445] CallingAssemblyName: message. + /// + /// + /// The message to add to trace. + [SuppressMessage("Microsoft.Design", "CA1031")] + [SuppressMessage("Microsoft.Globalization", "CA1303")] + private static void WriteLine(string message) + { + } + + /// + /// Auxillary method: logs the exception that is being ignored. + /// + /// The exception to log. + [SuppressMessage("Microsoft.Design", "CA1031")] + private static void LogIgnoredException(Exception e) + { + } + + private static void WriteEventLogEntry(string message) + { + } + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Tracing/RemoteEqtTrace.cs b/src/Microsoft.TestPlatform.CoreUtilities/Tracing/RemoteEqtTrace.cs new file mode 100644 index 0000000000..7db4531d6d --- /dev/null +++ b/src/Microsoft.TestPlatform.CoreUtilities/Tracing/RemoteEqtTrace.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel +{ +#if NET46 + using System; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + + /// + /// A class used to expose EqtTrace functionality across AppDomains. + /// + /// + public sealed class RemoteEqtTrace : MarshalByRefObject + { + public TraceLevel TraceLevel + { + get + { + return EqtTrace.TraceLevel; + } + + set + { + EqtTrace.TraceLevel = value; + } + } + + + /// + /// Register listeners from parent domain in current domain. + /// + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Used in remote objects.")] + internal void SetupRemoteListeners(TraceListener listener) + { + EqtTrace.SetupRemoteListeners(listener); + } + } +#endif +} diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Tracing/RollingFileTraceListener.cs b/src/Microsoft.TestPlatform.CoreUtilities/Tracing/RollingFileTraceListener.cs new file mode 100644 index 0000000000..55d67e42a2 --- /dev/null +++ b/src/Microsoft.TestPlatform.CoreUtilities/Tracing/RollingFileTraceListener.cs @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel +{ + public class RollingFileTraceListener + { + // Todo: Fille this in to support EqtTracing for Desktop. + } +} diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Utilities/Job.cs b/src/Microsoft.TestPlatform.CoreUtilities/Utilities/Job.cs new file mode 100644 index 0000000000..f14a866aa0 --- /dev/null +++ b/src/Microsoft.TestPlatform.CoreUtilities/Utilities/Job.cs @@ -0,0 +1,94 @@ +// Copyright(c) Microsoft.All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Common.Utilities +{ + using System.Threading; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + + /// + /// Wrapper class around a job used to send additional information to the background thread. + /// + /// The type of the job. + internal class Job + { + #region Constructor + + /// + /// Initializes with the job to be processed. + /// + /// Job to be processed. + /// The size. + public Job(TPayload job, int size) + { + this.Payload = job; + this.Size = size; + } + + /// + /// Constructor used for creating special jobs. + /// + private Job() + { + this.Size = 0; + } + + #endregion + + #region Properties + + /// + /// Gets a special job that indicates the queue should shutdown. + /// + public static Job ShutdownJob + { + get + { + var shutdownJob = new Job(); + shutdownJob.Shutdown = true; + + return shutdownJob; + } + } + + /// + /// Gets the job to be processed. + /// + public TPayload Payload { get; private set; } + + /// + /// Gets a value indicating whether the background thread should shutdown. + /// + public bool Shutdown { get; private set; } + + /// + /// Gets the signal that this job is being processed. + /// + public ManualResetEvent WaitManualResetEvent { get; private set; } + + /// + /// Gets the size of this job instance. This is used to manage the total size of Job Queue. + /// + public int Size { get; private set; } + + #endregion + + #region Static Methods + + /// + /// Creates a job with a manual reset event that will be set when the job is processed. + /// + /// The wait Event. + /// The wait job. + public static Job CreateWaitJob(ManualResetEvent waitEvent) + { + ValidateArg.NotNull(waitEvent, "waitEvent"); + var waitJob = new Job(); + waitJob.WaitManualResetEvent = waitEvent; + + return waitJob; + } + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Utilities/JobQueue.cs b/src/Microsoft.TestPlatform.CoreUtilities/Utilities/JobQueue.cs new file mode 100644 index 0000000000..0c97808023 --- /dev/null +++ b/src/Microsoft.TestPlatform.CoreUtilities/Utilities/JobQueue.cs @@ -0,0 +1,377 @@ +// Copyright(c) Microsoft.All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Utilities +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + using System.Threading; + using System.Threading.Tasks; + + using Microsoft.VisualStudio.TestPlatform.Common.Utilities; + using Microsoft.VisualStudio.TestPlatform.CoreUtilities; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + + /// + /// Generic queue for processing jobs on a background thread. + /// + /// The type of the job that is being processed. + public class JobQueue : IDisposable + { + #region Fields + + /// + /// Handler which processes the individual jobs. + /// + private Action processJob; + + /// + /// Name used when displaying information or reporting errors about this queue. + /// + private string displayName; + + /// + /// The queue of jobs. + /// + private Queue> jobsQueue; + + /// + /// Signaled when a job is added to the queue. Used to wakeup the background thread. + /// + private ManualResetEvent jobAdded; + + /// + /// The maximum number of jobs the job queue may hold. + /// + private int maxNumberOfJobsInQueue; + + /// + /// The maximum total size of jobs the job queue may hold. + /// + private int maxBytesQueueCanHold; + + /// + /// Gives the approximate total size of objects in the queue. + /// + private int currentNumberOfBytesQueueIsHolding; + + /// + /// Tells whether the queue should be bounded on size and no of events. + /// + private bool enableBoundsOnQueue; + + /// + /// Used to pause and resume processing of the queue. By default the manual reset event is + /// set so the queue can continue processing. + /// + private ManualResetEvent queueProcessing; + + /// + /// The background thread which is processing the jobs. Used when disposing to wait + /// for the thread to complete. + /// + private Task backgroundJobProcessor; + + + /// + /// Keeps track of if we are disposed. + /// + private bool isDisposed; + + /// + /// Logs to this action any exception when processing jobs. + /// + private Action exceptionLogger; + + #endregion + + #region Constructor + + /// + /// Initializes with the action to handle the processing of the job. + /// + /// Action to handle the processing of the job. + /// Name to used when displaying information about this queue. + /// The max Queue Length. + /// The max Queue Size. + /// The enable Bounds. + /// The exception Logger. + public JobQueue(Action processJob, string displayName, int maxQueueLength, int maxQueueSize, bool enableBounds, Action exceptionLogger) + { + if (processJob == null) + { + throw new ArgumentNullException("processJob"); + } + + if (string.IsNullOrWhiteSpace(displayName)) + { + throw new ArgumentException(Resources.CannotBeNullOrEmpty, "displayName"); + } + + if (maxQueueLength < 1) + { + throw new ArgumentOutOfRangeException("maxQueueLength"); + } + + if (maxQueueSize < 1) + { + throw new ArgumentOutOfRangeException("maxQueueSize"); + } + + this.maxNumberOfJobsInQueue = maxQueueLength; + this.maxBytesQueueCanHold = maxQueueSize; + this.enableBoundsOnQueue = enableBounds; + + // Initialize defaults. + this.jobsQueue = new Queue>(); + this.jobAdded = new ManualResetEvent(false); + this.queueProcessing = new ManualResetEvent(true); + this.currentNumberOfBytesQueueIsHolding = 0; + this.isDisposed = false; + + // Save off the arguments. + this.processJob = processJob; + this.displayName = displayName; + this.exceptionLogger = exceptionLogger; + + // Setup the background thread to process the jobs. + this.backgroundJobProcessor = Task.Run(() => this.BackgroundJobProcessor()); + } + + #endregion + + #region Methods + + /// + /// Adds a job to the queue. + /// + /// Job to add to the queue. + /// The job Size. + public void QueueJob(T job, int jobSize) + { + this.CheckDisposed(); + + Debug.Assert(jobSize >= 0, "Job size should never be negative"); + + // Add the job and signal that a new job is available. + this.InternalQueueJob(new Job(job, jobSize)); + } + + /// + /// Pause the processing of queued jobs. + /// + public void Pause() + { + this.CheckDisposed(); + + // Do not allow any jobs to be processed. + this.queueProcessing.Reset(); + } + + /// + /// Resume the processing of queued jobs. + /// + public void Resume() + { + this.CheckDisposed(); + + // Resume processing of jobs. + this.queueProcessing.Set(); + } + + /// + /// Waits for all current jobs in the queue to be processed and then returns. + /// + public void Flush() + { + this.CheckDisposed(); + + // Create the wait job. + using (var waitEvent = new ManualResetEvent(false)) + { + var waitJob = Job.CreateWaitJob(waitEvent); + + // Queue the wait job and wait for it to be processed. + this.InternalQueueJob(waitJob); + + waitEvent.WaitOne(); + } + } + + /// + /// Waits for all pending jobs to complete and shutdown the background thread. + /// + public void Dispose() + { + if (this.isDisposed) + { + return; + } + + + // If the queue is paused, then throw. + if (!this.queueProcessing.WaitOne(0)) + { + throw new InvalidOperationException( + string.Format(CultureInfo.CurrentUICulture, Resources.QueuePausedDisposeError, this.displayName)); + } + + this.isDisposed = true; + + // Disable bounds on the queue so that any waiting threads can proceed. + lock (this.jobsQueue) + { + this.enableBoundsOnQueue = false; + Monitor.PulseAll(this.jobsQueue); + } + + // Flag the queue as being shutdown and wake up the background thread. + this.InternalQueueJob(Job.ShutdownJob); + + // Wait for the background thread to shutdown. + this.backgroundJobProcessor.Wait(); + + // Cleanup + this.jobAdded.Dispose(); + this.queueProcessing.Dispose(); + } + + #endregion + + #region Private Methods + + /// + /// Block the queue call. + /// A separate protected virtual method had to be made so that it can be over-ridden when writing unit test to check + /// if bounds on the queue are applied correctly. + /// + /// + protected virtual bool WaitForQueueToGetEmpty() + { + EqtTrace.Verbose("blocking on over filled queue."); + return Monitor.Wait(this.jobsQueue); + } + + /// + /// Queue the job and signal the background thread. + /// + /// Job to be queued. + private void InternalQueueJob(Job job) + { + // Add the job and signal that a new job is available. + lock (this.jobsQueue) + { + // If the queue is getting over filled wait till the background processor releases the thread. + while (this.enableBoundsOnQueue + && + ((this.jobsQueue.Count >= this.maxNumberOfJobsInQueue) + || + (this.currentNumberOfBytesQueueIsHolding >= this.maxBytesQueueCanHold))) + { + this.WaitForQueueToGetEmpty(); + } + this.jobsQueue.Enqueue(job); + this.currentNumberOfBytesQueueIsHolding += job.Size; + this.jobAdded.Set(); + } + } + + /// + /// Throws wen the queue has been disposed. + /// + private void CheckDisposed() + { + if (this.isDisposed) + { + throw new ObjectDisposedException( + string.Format(CultureInfo.CurrentUICulture, Resources.QueueAlreadyDisposed, this.displayName)); + } + } + + /// + /// Method which processes the jobs on the background thread. + /// + private void BackgroundJobProcessor() + { + bool shutdown = false; + + do + { + this.jobAdded.WaitOne(); + + // Pull all of the current jobs out of the queue. + List> jobs = new List>(); + lock (this.jobsQueue) + { + while (this.jobsQueue.Count != 0) + { + var job = this.jobsQueue.Dequeue(); + this.currentNumberOfBytesQueueIsHolding -= job.Size; + + // If this is a shutdown job, signal shutdown and stop adding jobs. + if (job.Shutdown) + { + shutdown = true; + break; + } + + jobs.Add(job); + } + + // Reset the manual reset event so we get notified of new jobs that are added. + this.jobAdded.Reset(); + + // Releases a thread waiting on the queue to get empty, to continue with the enquing process. + if (this.enableBoundsOnQueue) + { + Monitor.PulseAll(this.jobsQueue); + } + } + + // Process the jobs + foreach (var job in jobs) + { + // Wait for the queue to be open (not paused) and process the job. + this.queueProcessing.WaitOne(); + + // If this is a wait job, signal the manual reset event and continue. + if (job.WaitManualResetEvent != null) + { + job.WaitManualResetEvent.Set(); + } + else + { + this.SafeProcessJob(job.Payload); + } + } + } + while (!shutdown); + } + + /// + /// Executes the process job handler and logs any exceptions which occur. + /// + /// Job to be executed. + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "unknown action could throw all kinds of exceptions.")] + private void SafeProcessJob(T job) + { + try + { + this.processJob(job); + } + catch (Exception e) + { + this.exceptionLogger( + string.Format( + CultureInfo.CurrentUICulture, + Resources.ExceptionFromJobProcessor, + this.displayName, + e)); + } + } + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Utilities/MulticastDelegateUtilities.cs b/src/Microsoft.TestPlatform.CoreUtilities/Utilities/MulticastDelegateUtilities.cs new file mode 100644 index 0000000000..9315f66759 --- /dev/null +++ b/src/Microsoft.TestPlatform.CoreUtilities/Utilities/MulticastDelegateUtilities.cs @@ -0,0 +1,60 @@ +// Copyright(c) Microsoft.All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Utilities +{ + using System; + using System.Reflection; + + using Microsoft.VisualStudio.TestPlatform.CoreUtilities; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + + /// + /// Utility methods for MulticastDelegates. + /// + public static class MulticastDelegateUtilities + { + /// + /// Invokes each of the subscribers of the event and handles exceptions which are thrown + /// ensuring that each handler is invoked even if one throws. + /// + /// Event handler to invoke. + /// Sender to use when raising the event. + /// Arguments to provide. + /// Name to use when tracing out errors. + public static void SafeInvoke(this Delegate delegates, object sender, EventArgs args, string traceDisplayName) + { + if (args == null) + { + throw new ArgumentNullException(Resources.CannotBeNullOrEmpty, "args"); + } + + if (string.IsNullOrWhiteSpace(traceDisplayName)) + { + throw new ArgumentException(Resources.CannotBeNullOrEmpty, traceDisplayName); + } + + if (delegates != null) + { + foreach (Delegate handler in delegates.GetInvocationList()) + { + try + { + handler.DynamicInvoke(sender, args); + } + catch (TargetInvocationException e) + { + if (EqtTrace.IsErrorEnabled) + { + EqtTrace.Error( + "{0}: Exception occurred while calling handler of type {1} for {2}: {3}", + traceDisplayName, + handler.Target.GetType().FullName, + args.GetType().Name, + e); + } + } + } + } + } + } +} diff --git a/src/Microsoft.TestPlatform.CoreUtilities/ValidateArg.cs b/src/Microsoft.TestPlatform.CoreUtilities/ValidateArg.cs new file mode 100644 index 0000000000..ae2c8b8c8d --- /dev/null +++ b/src/Microsoft.TestPlatform.CoreUtilities/ValidateArg.cs @@ -0,0 +1,316 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.Linq; + using System.Reflection; + + /// + /// Helper to validate parameters. + /// + public static class ValidateArg + { + /// + /// Throws ArgumentNullException if the argument is null, otherwise passes it through. + /// + /// The argument to check. + /// The parameter name of the argument. + [DebuggerStepThrough] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This is shared source. This method may not be called in the current assembly.")] + public static T NotNull([ValidatedNotNull]T arg, string parameterName) + where T : class + { + if (arg == null) + { + throw new ArgumentNullException(parameterName); + } + return arg; + } + + [DebuggerStepThrough] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This is shared source. This method may not be called in the current assembly.")] + public static string NotNullOrEmpty([ValidatedNotNull]string arg, string parameterName) + { + if (string.IsNullOrEmpty(arg)) + { + throw new ArgumentNullException(parameterName); + } + return arg; + } + + /// + /// Throws ArgumentOutOfRangeException if the argument is less than zero. + /// + /// The argument to check. + /// The parameter name of the argument. + [DebuggerStepThrough] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This is shared source. This method may not be called in the current assembly.")] + public static void NotNegative(int arg, string parameterName) + { + if (arg < 0) + { + string message = String.Format(CultureInfo.CurrentCulture, ValidateArgStrings.Error_ArgumentIsNegative); + throw new ArgumentOutOfRangeException(parameterName, arg, message); + } + } + + /// + /// Throws ArgumentOutOfRangeException if the argument is less than zero. + /// + /// The argument to check. + /// The parameter name of the argument. + [DebuggerStepThrough] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This is shared source. This method may not be called in the current assembly.")] + public static void NotNegative(long arg, string parameterName) + { + if (arg < 0) + { + string message = String.Format(CultureInfo.CurrentCulture, ValidateArgStrings.Error_ArgumentIsNegative); + throw new ArgumentOutOfRangeException(parameterName, arg, message); + } + } + + /// + /// Throws ArgumentNullException if the string is null, ArgumentException if the string is empty. + /// + /// The argument to check. + /// The parameter name of the argument. + [DebuggerStepThrough] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This is shared source. This method may not be called in the current assembly.")] + public static void NotNullOrEmpty([ValidatedNotNull]IEnumerable arg, string parameterName) + { + NotNull(arg, parameterName); + + if (!arg.Any()) + { + string message = String.Format(CultureInfo.CurrentCulture, ValidateArgStrings.Error_ArgumentIsEmpty); + throw new ArgumentException(message, parameterName); + } + } + + /// + /// Throws ArgumentNullException if the argument is null, ArgumentException if the argument is not the correct type. + /// + /// The argument to check. + /// The parameter name of the argument. + /// The type of the expected argument. + [DebuggerStepThrough] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "This is shared source. This method may not be called in the current assembly.")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This is shared source. This method may not be called in the current assembly.")] + public static void TypeOf([ValidatedNotNull]object arg, string parameterName) where T : class + { + NotNull(arg, parameterName); + + if (!(arg is T)) + { + string message = String.Format(CultureInfo.CurrentCulture, ValidateArgStrings.Error_ArgumentNotTypeOf, typeof(T).FullName); + throw new ArgumentException(message, parameterName); + } + } + } + + /// + /// Helper to validate parameter properties. + /// + public static class ValidateArgProperty + { + /// + /// Throws ArgumentException if the argument is null. + /// + /// The argument to check (e.g. Param1.PropertyA). + /// The parameter name of the argument. + /// The property name of the argument. + [DebuggerStepThrough] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This is shared source. This method may not be called in the current assembly.")] + public static void NotNull([ValidatedNotNull]object arg, string parameterName, string propertyName) + { + if (arg == null) + { + string message = String.Format(CultureInfo.CurrentCulture, ValidateArgStrings.Error_ArgumentPropertyIsNull, propertyName); + throw new ArgumentNullException(parameterName, message); + } + } + + /// + /// Throws ArgumentException if the argument is less than zero. + /// + /// The argument to check (e.g. Param1.PropertyA). + /// The parameter name of the argument. + /// The property name of the argument. + [DebuggerStepThrough] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This is shared source. This method may not be called in the current assembly.")] + public static void NotNegative(int arg, string parameterName, string propertyName) + { + if (arg < 0) + { + string message = String.Format(CultureInfo.CurrentCulture, ValidateArgStrings.Error_ArgumentPropertyIsNegative, propertyName); + throw new ArgumentException(message, parameterName); + } + } + + /// + /// Throws ArgumentException if the argument string is null or empty. + /// + /// The argument to check (e.g. Param1.PropertyA). + /// The parameter name of the argument. + /// The property name of the argument. + [DebuggerStepThrough] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This is shared source. This method may not be called in the current assembly.")] + public static void NotNullOrEmpty([ValidatedNotNull]string arg, string parameterName, string propertyName) + { + NotNull(arg, parameterName, propertyName); + + if (String.IsNullOrEmpty(arg)) + { + string message = String.Format(CultureInfo.CurrentCulture, ValidateArgStrings.Error_ArgumentPropertyIsEmpty, propertyName); + throw new ArgumentException(message, parameterName); + } + } + + /// + /// Throws ArgumentException if the argument is null or is not the correct type. + /// + /// The argument to check (e.g. Param1.PropertyA). + /// The parameter name of the argument. + /// The property name of the argument. + /// The type of the expected argument. + [DebuggerStepThrough] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification="This simplifies the caller.")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This is shared source. This method may not be called in the current assembly.")] + public static void TypeOf([ValidatedNotNull]object arg, string parameterName, string propertyName) where T : class + { + NotNull(arg, parameterName, propertyName); + + if (!(arg is T)) + { + string message = String.Format(CultureInfo.CurrentCulture, ValidateArgStrings.Error_ArgumentPropertyNotTypeOf, propertyName, typeof(T).FullName); + throw new ArgumentException(message, parameterName); + } + } + } + + /// + /// 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", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class ValidateArgStrings { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal ValidateArgStrings() { + } + + /// + /// 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("Microsoft.VisualStudio.TestPlatform.ObjectModel.ValidateArgStrings", typeof(ValidateArgStrings).GetTypeInfo().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 The specified argument cannot be an empty string.. + /// + internal static string Error_ArgumentIsEmpty { + get { + return ResourceManager.GetString("Error_ArgumentIsEmpty", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The specified argument cannot be negative.. + /// + internal static string Error_ArgumentIsNegative { + get { + return ResourceManager.GetString("Error_ArgumentIsNegative", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The specified argument must have the following type: {0}.. + /// + internal static string Error_ArgumentNotTypeOf { + get { + return ResourceManager.GetString("Error_ArgumentNotTypeOf", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The specified argument has the following property, which cannot be an empty string: {0}.. + /// + internal static string Error_ArgumentPropertyIsEmpty { + get { + return ResourceManager.GetString("Error_ArgumentPropertyIsEmpty", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The specified argument has the following property, which cannot be negative: {0}.. + /// + internal static string Error_ArgumentPropertyIsNegative { + get { + return ResourceManager.GetString("Error_ArgumentPropertyIsNegative", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The specified argument has the following property, which cannot be null: {0}.. + /// + internal static string Error_ArgumentPropertyIsNull { + get { + return ResourceManager.GetString("Error_ArgumentPropertyIsNull", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The specified argument has the following property: {0}. This property must have the following type: {1}.. + /// + internal static string Error_ArgumentPropertyNotTypeOf { + get { + return ResourceManager.GetString("Error_ArgumentPropertyNotTypeOf", resourceCulture); + } + } + } + + /// + /// Secret attribute that tells the CA1062 validate arguments rule that this method validates the argument is not null. + /// + [AttributeUsage(AttributeTargets.Parameter)] + internal sealed class ValidatedNotNullAttribute : Attribute + { + } +} + diff --git a/src/Microsoft.TestPlatform.CoreUtilities/project.json b/src/Microsoft.TestPlatform.CoreUtilities/project.json new file mode 100644 index 0000000000..ec6e84aec6 --- /dev/null +++ b/src/Microsoft.TestPlatform.CoreUtilities/project.json @@ -0,0 +1,25 @@ +{ + "version": "15.0.0-*", + "buildOptions": { + "delaySign": true, + "keyFile": "../../scripts/key.snk", + "warningsAsErrors": true + }, + + "frameworks": { + "netstandard1.5": { + "imports": [ + "dnxcore50", + "portable-net45+win8" + ], + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-24008" + } + }, + "net46": { + "frameworkAssemblies": { + "System.Configuration": "" + } + } + } +} diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Adapter/FrameworkHandle.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Adapter/FrameworkHandle.cs new file mode 100644 index 0000000000..e38c9c2b72 --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Adapter/FrameworkHandle.cs @@ -0,0 +1,130 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Adapter +{ + using System; + using System.Collections.Generic; + using Execution; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.ClientProtocol; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + + /// + /// Handle to the framework which is passed to the test executors. + /// + internal class FrameworkHandle : TestExecutionRecorder, IFrameworkHandle, IDisposable + { + /// + /// boolean that gives the value of EnableShutdownAfterTestRun. + /// Default value is set to false in the constructor. + /// + private bool enableShutdownAfterTestRun; + + /// + /// Context in which the current run is executing. + /// + private TestExecutionContext testExecutionContext; + + /// + /// DebugLauncher for launching additional adapter processes under debugger + /// + private ITestRunEventsHandler testRunEventsHandler; + + /// + /// Specifies whether the handle is disposed or not + /// + private bool isDisposed; + + /// + /// Initializes a new instance of the class. + /// + /// The test case level events handler. + /// The test run cache. + /// The test execution context. + /// TestRun Events Handler + public FrameworkHandle(ITestCaseEventsHandler testCaseEventsHandler, ITestRunCache testRunCache, + TestExecutionContext testExecutionContext, ITestRunEventsHandler testRunEventsHandler) + : base(testCaseEventsHandler, testRunCache) + { + this.testExecutionContext = testExecutionContext; + this.testRunEventsHandler = testRunEventsHandler; + } + + + /// + /// Give a hint to the execution framework to enable the shutdown of execution process after the test run is complete. This should be used only in out of process test runs when IRunContext.KeepAlive is true + /// and should be used only when absolutely required as using it degrades the performance of the subsequent run. + /// It throws InvalidOperationException when it is attempted to be enabled when keepAlive is false. + /// + public bool EnableShutdownAfterTestRun + { + get + { + return this.enableShutdownAfterTestRun; + } + + set + { + this.enableShutdownAfterTestRun = value; + } + } + + /// + /// Launch the specified process with the debugger attached. + /// + /// File path to the exe to launch. + /// Working directory that process should use. + /// Command line arguments the process should be launched with. + /// Environment variables to be set in target process + /// Process ID of the started process. + public int LaunchProcessWithDebuggerAttached(string filePath, string workingDirectory, string arguments, IDictionary environmentVariables) + { + // If an adapter attempts to launch a process after the run is complete (=> this object is disposed) + // throw an error. + if (this.isDisposed) + { + throw new ObjectDisposedException("IFrameworkHandle"); + } + + // If it is not a debug run, then throw an error + if (!this.testExecutionContext.IsDebug) + { + throw new InvalidOperationException(CrossPlatEngine.Resources.LaunchDebugProcessNotAllowedForANonDebugRun); + } + + var processInfo = new TestProcessStartInfo() + { + Arguments = arguments, + EnvironmentVariables = environmentVariables, + FileName = filePath, + WorkingDirectory = workingDirectory + }; + + return this.testRunEventsHandler.LaunchProcessWithDebuggerAttached(processInfo); + } + + + public void Dispose() + { + this.Dispose(true); + + // Use SupressFinalize in case a subclass + // of this valueType implements a finalizer. + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + // If you need thread safety, use a lock around these + // operations, as well as in your methods that use the resource. + if (!this.isDisposed) + { + // Indicate that the instance has been disposed. + this.isDisposed = true; + } + } + } +} diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Adapter/RunContext.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Adapter/RunContext.cs new file mode 100644 index 0000000000..ac614b9964 --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Adapter/RunContext.cs @@ -0,0 +1,91 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Adapter +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + + using Microsoft.VisualStudio.TestPlatform.Common.Filtering; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Discovery; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; + + /// + /// Provides user specified runSettings and framework provided context of the run. + /// + public class RunContext : DiscoveryContext, IRunContext + { + /// + /// Gets a value indicating whether the execution process should be kept alive after the run is finished. + /// + public bool KeepAlive { get; internal set; } + + /// + /// Gets a value indicating whether the discovery or execution is happening in In-process or out-of-process. + /// + public bool InIsolation { get; internal set; } + + /// + /// Gets a value indicating whether data collection is enabled. + /// + public bool IsDataCollectionEnabled { get; internal set; } + + /// + /// Gets a value indicating whether the test is being debugged. + /// + public bool IsBeingDebugged { get; internal set; } + + /// + /// Gets the directory which should be used for storing result files/deployment files etc. + /// + public string TestRunDirectory { get; internal set; } + + /// + /// Gets the directory for Solution. + /// + public string SolutionDirectory { get; internal set; } + + /// + /// Gets or sets the FilterExpressionWrapper instance as created from filter string. + /// + internal FilterExpressionWrapper FilterExpressionWrapper + { + get; + set; + } + + /// + /// Returns TestCaseFilterExpression validated for supportedProperties. + /// If there is a parsing error or filter expression has unsupported properties, TestPlatformFormatException() is thrown. + /// + /// The supported Properties. + /// The property Provider. + /// The . + public ITestCaseFilterExpression GetTestCaseFilter(IEnumerable supportedProperties, Func propertyProvider) + { + TestCaseFilterExpression adapterSpecificTestCaseFilter = null; + if (this.FilterExpressionWrapper != null) + { + if (!string.IsNullOrEmpty(this.FilterExpressionWrapper.ParseError)) + { + throw new TestPlatformFormatException(this.FilterExpressionWrapper.ParseError, this.FilterExpressionWrapper.FilterString); + } + + adapterSpecificTestCaseFilter = new TestCaseFilterExpression(this.FilterExpressionWrapper); + var invalidProperties = adapterSpecificTestCaseFilter.ValidForProperties(supportedProperties, propertyProvider); + + if (invalidProperties != null) + { + var invalidPropertiesString = string.Join(CrossPlatEngine.Resources.StringSeperator, invalidProperties); + var validPropertiesSttring = supportedProperties == null ? string.Empty : string.Join(CrossPlatEngine.Resources.StringSeperator, supportedProperties.ToArray()); + var errorMessage = string.Format(CultureInfo.CurrentCulture, CrossPlatEngine.Resources.UnsupportedPropertiesInTestCaseFilter, invalidPropertiesString, validPropertiesSttring); + throw new TestPlatformFormatException(errorMessage, this.FilterExpressionWrapper.FilterString); + } + } + + return adapterSpecificTestCaseFilter; + } + } +} diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Adapter/TestCaseFilterExpression.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Adapter/TestCaseFilterExpression.cs new file mode 100644 index 0000000000..d7c7b6f2e4 --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Adapter/TestCaseFilterExpression.cs @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Adapter +{ + using System; + using System.Collections.Generic; + + using Microsoft.VisualStudio.TestPlatform.Common.Filtering; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; + + /// + /// Implements ITestCaseFilterExpression, providing test case filtering functionality. + /// + public class TestCaseFilterExpression : ITestCaseFilterExpression + { + private FilterExpressionWrapper filterWrapper; + + /// + /// If filter Expression is valid for performing TestCase matching + /// (has only supported properties, syntax etc) + /// + private bool validForMatch; + + + /// + /// Adapter specific filter expression. + /// + public TestCaseFilterExpression(FilterExpressionWrapper filterWrapper) + { + ValidateArg.NotNull(filterWrapper, "filterWrapper"); + this.filterWrapper = filterWrapper; + this.validForMatch = string.IsNullOrEmpty(filterWrapper.ParseError); + } + + /// + /// User specified filter criteria. + /// + public string TestCaseFilterValue + { + get + { + return this.filterWrapper.FilterString; + } + } + + /// + /// Validate if underlying filter expression is valid for given set of supported properties. + /// + public string[] ValidForProperties(IEnumerable supportedProperties, Func propertyProvider) + { + string[] invalidProperties = null; + if (null != this.filterWrapper && this.validForMatch) + { + invalidProperties = this.filterWrapper.ValidForProperties(supportedProperties, propertyProvider); + if (null != invalidProperties) + { + this.validForMatch = false; + } + } + return invalidProperties; + } + + /// + /// Match test case with filter criteria. + /// + public bool MatchTestCase(TestCase testCase, Func propertyValueProvider) + { + ValidateArg.NotNull(testCase, "testCase"); + ValidateArg.NotNull(propertyValueProvider, "propertyValueProvider"); + if (!this.validForMatch) + { + return false; + } + + if (null == this.filterWrapper) + { + // can be null when parsing error occurs. Invalid filter results in no match. + return false; + } + return this.filterWrapper.Evaluate(propertyValueProvider); + } + + } +} diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Adapter/TestExecutionRecorder.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Adapter/TestExecutionRecorder.cs new file mode 100644 index 0000000000..2f09091671 --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Adapter/TestExecutionRecorder.cs @@ -0,0 +1,103 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Adapter +{ + using System.Collections.Generic; + using System.Collections.ObjectModel; + + using Microsoft.VisualStudio.TestPlatform.Common.Logging; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Execution; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + + /// + /// The test execution recorder used for recording test results and test messages. + /// + internal class TestExecutionRecorder : TestSessionMessageLogger, ITestExecutionRecorder + { + private List attachmentSets; + private ITestRunCache testRunCache; + private ITestCaseEventsHandler testCaseEventsHandler; + + /// + /// Initializes a new instance of the class. + /// + /// The test Case Events Handler. + /// The test run cache. + public TestExecutionRecorder(ITestCaseEventsHandler testCaseEventsHandler, ITestRunCache testRunCache) + { + this.testRunCache = testRunCache; + this.testCaseEventsHandler = testCaseEventsHandler; + this.attachmentSets = new List(); + } + + /// + /// Gets the attachments received from adapters. + /// + internal Collection Attachments + { + get + { + return new Collection(this.attachmentSets); + } + } + + /// + /// Notify the framework about starting of the test case. + /// Framework sends this event to data collectors enabled in the run. If no data collector is enabled, then the event is ignored. + /// + /// test case which will be started. + public void RecordStart(TestCase testCase) + { + this.testRunCache.OnTestStarted(testCase); + + if (this.testCaseEventsHandler != null) + { + this.testCaseEventsHandler.SendTestCaseStart(testCase); + } + } + + /// + /// Notify the framework about the test result. + /// + /// Test Result to be sent to the framework. + /// Exception thrown by the framework when an executor attempts to send + /// test result to the framework when the test(s) is canceled. + public void RecordResult(TestResult testResult) + { + this.testRunCache.OnNewTestResult(testResult); + + if (this.testCaseEventsHandler != null) + { + this.testCaseEventsHandler.SendTestResult(testResult); + } + } + + /// + /// Notify the framework about completion of the test case. + /// Framework sends this event to data collectors enabled in the run. If no data collector is enabled, then the event is ignored. + /// + /// test case which has completed. + /// outcome of the test case. + public void RecordEnd(TestCase testCase, TestOutcome outcome) + { + this.testRunCache.OnTestCompletion(testCase); + + if (this.testCaseEventsHandler != null) + { + this.testCaseEventsHandler.SendTestCaseEnd(testCase, outcome); + } + } + + /// + /// Notify the framework about run level attachments. + /// + /// The attachment sets. + public void RecordAttachments(IList attachments) + { + this.attachmentSets.AddRange(attachments); + } + } +} diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelOperationManager.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelOperationManager.cs new file mode 100644 index 0000000000..de0cc91b70 --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelOperationManager.cs @@ -0,0 +1,155 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client +{ + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + + /// + /// Abstract class having common parallel manager implementation + /// + internal abstract class ParallelOperationManager : IParallelOperationManager, IDisposable + { + #region ConcurrentManagerInstanceData + + protected Func CreateNewConcurrentManager { get; set; } + + protected T[] concurrentManagerInstances; + + /// + /// Singleton Instance of this class + /// + protected static T instance = default(T); + + /// + /// Default number of Processes + /// + private int currentParallelLevel = 0; + + #endregion + + protected ParallelOperationManager(Func createNewManager, int parallelLevel) + { + this.CreateNewConcurrentManager = createNewManager; + // Update Parallel Level + UpdateParallelLevel(parallelLevel); + } + + /// + /// Updates the Concurrent Executors according to new parallel setting + /// + /// Number of Parallel Executors allowed + public void UpdateParallelLevel(int newParallelLevel) + { + if (concurrentManagerInstances == null) + { + // not initialized yet + // create rest of concurrent clients other than default one + concurrentManagerInstances = new T[newParallelLevel]; + for (int i = 0; i < newParallelLevel; i++) + { + concurrentManagerInstances[i] = CreateNewConcurrentManager(); + } + } + else if (currentParallelLevel != newParallelLevel) + { + var newManagerInstances = new List(); + + // If number of concurrent clients is less than the new level + // Create more concurrent clients and update the list + if (currentParallelLevel < newParallelLevel) + { + newManagerInstances.AddRange(concurrentManagerInstances); + for (int i = 0; i < newParallelLevel - currentParallelLevel; i++) + { + newManagerInstances.Add(CreateNewConcurrentManager()); + } + } + else + { + // If number of concurrent clients is more than the new level + // Dispose off the extra ones + for (int i = 0; i < newParallelLevel; i++) + { + newManagerInstances.Add(concurrentManagerInstances[i]); + } + + for (int i = newParallelLevel; i < currentParallelLevel; i++) + { + DisposeInstance(concurrentManagerInstances[i]); + } + } + + // Update the current concurrent executor collection + concurrentManagerInstances = newManagerInstances.ToArray(); + } + + // Update current parallel setting to new one + currentParallelLevel = newParallelLevel; + } + + public void Dispose() + { + if (concurrentManagerInstances != null) + { + foreach (var managerInstance in concurrentManagerInstances) + { + DisposeInstance(managerInstance); + } + } + + instance = default(T); + } + + protected void DoActionOnAllManagers(Action action, bool doActionsInParallel = false) + { + if (concurrentManagerInstances != null && concurrentManagerInstances.Length > 0) + { + var actionTasks = new Task[concurrentManagerInstances.Length]; + for (int i = 0; i < concurrentManagerInstances.Length; i++) + { + // Read the array before firing the task - beware of closures + var client = concurrentManagerInstances[i]; + if (doActionsInParallel) + { + actionTasks[i] = Task.Run(() => action(client)); + } + else + { + DoManagerAction(() => action(client)); + } + } + if (doActionsInParallel) DoManagerAction(() => Task.WaitAll(actionTasks)); + } + } + + private void DoManagerAction(Action action) + { + try + { + action(); + } + catch (Exception ex) + { + // Exception can occur if we are trying to cancel a test run on an executor where test run is not even fired + // we can safely ignore that as user is just cancelling the test run and we don't care about additional parallel executors + // as we will be disposing them off soon ansyway + if (EqtTrace.IsWarningEnabled) + { + EqtTrace.Warning("AbstractParallelOperationManager: Exception while invoking an action on Proxy Manager instance: {0}", ex); + } + } + } + + #region AbstractMethods + + protected abstract void DisposeInstance(T managerInstance); + + #endregion + } +} + diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelProxyExecutionManager.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelProxyExecutionManager.cs new file mode 100644 index 0000000000..73c806d89c --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelProxyExecutionManager.cs @@ -0,0 +1,294 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.Parallel +{ + using System; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; + using System.Collections.Generic; + using System.Threading.Tasks; + using System.Collections; + using System.Linq; + + /// + /// ParallelProxyExecutionManager that manages parallel execution + /// + internal class ParallelProxyExecutionManager : ParallelOperationManager, IParallelProxyExecutionManager + { + #region TestRunSpecificData + + private int runCompletedClients = 0; + + private TestRunCriteria actualTestRunCriteria; + + private IEnumerator sourceEnumerator; + + private IEnumerator testCaseListEnumerator; + + private bool hasSpecificTestsRun = false; + + private Task lastParallelRunCleanUpTask = null; + + private IDictionary concurrentManagerHandlerMap; + + #endregion + + #region Concurrency Keeper Objects + + /// + /// LockObject to iterate our sourceEnumerator in parallel + /// We can use the sourceEnumerator itself as lockObject, but since its a changing object - it's risky to use it as one + /// + private object sourceEnumeratorLockObject = new object(); + + /// + /// LockObject to update execution status in parallel + /// + private object executionStatusLockObject = new object(); + + #endregion + + public ParallelProxyExecutionManager(Func actualProxyManagerCreator, int parallelLevel) + : base(actualProxyManagerCreator, parallelLevel) + { + } + + #region IProxyExecutionManager + + public void Initialize(ITestHostManager testHostManager) + { + DoActionOnAllManagers((proxyManager) => proxyManager.Initialize(testHostManager), doActionsInParallel: true); + } + + public int StartTestRun(TestRunCriteria testRunCriteria, ITestRunEventsHandler eventHandler) + { + this.hasSpecificTestsRun = testRunCriteria.HasSpecificTests; + this.actualTestRunCriteria = testRunCriteria; + + if (hasSpecificTestsRun) + { + var testCasesBySource = new Dictionary>(); + foreach (var test in testRunCriteria.Tests) + { + if (!testCasesBySource.ContainsKey(test.Source)) + { + testCasesBySource.Add(test.Source, new List()); + } + testCasesBySource[test.Source].Add(test); + } + + // Do not use "Dictionary.ValueCollection.Enumerator" - it becomes undetermenstic once we go out of scope of this method + // Use "ToArray" to copy ValueColleciton to a simple array and use it's enumerator + // Set the enumerator for parallel yielding of testCases + // Whenever a concurrent executor becomes free, it picks up the next set of testCases using this enumerator + this.testCaseListEnumerator = testCasesBySource.Values.ToArray().GetEnumerator(); + } + else + { + // Set the enumerator for parallel yielding of sources + // Whenever a concurrent executor becomes free, it picks up the next source using this enumerator + this.sourceEnumerator = testRunCriteria.Sources.GetEnumerator(); + } + + return StartTestRunPrivate(eventHandler); + } + + public void Abort() + { + DoActionOnAllManagers((proxyManager) => proxyManager.Abort(), doActionsInParallel: true); + } + + public void Cancel() + { + DoActionOnAllManagers((proxyManager) => proxyManager.Cancel(), doActionsInParallel: true); + } + + #endregion + + #region IParallelProxyExecutionManager methods + + /// + /// Handles Partial Run Complete event coming from a specific concurrent proxy exceution manager + /// Each concurrent proxy execution manager will signal the parallel execution manager when its complete + /// + /// Concurrent Execution manager that completed the run + /// RunCompleteArgs for the concurrent run + /// LastChunk testresults for the concurrent run + /// RunAttachments for the concurrent run + /// ExecutorURIs of the adapters involved in executing the tests + /// True if parallel run is complete + public bool HandlePartialRunComplete( + IProxyExecutionManager proxyExecutionManager, + TestRunCompleteEventArgs testRunCompleteArgs, + TestRunChangedEventArgs lastChunkArgs, + ICollection runContextAttachments, + ICollection executorUris) + { + var allRunsCompleted = false; + + // In Case of Cancel or Abort, no need to trigger run for rest of the data + // If there are no more sources/testcases, a parallel executor is truly done with execution + if (testRunCompleteArgs.IsAborted || testRunCompleteArgs.IsCanceled || !StartTestRunOnConcurrentManager(proxyExecutionManager)) + { + lock (executionStatusLockObject) + { + // Each concurrent Executor calls this method + // So, we need to keep track of total runcomplete calls + runCompletedClients++; + allRunsCompleted = (runCompletedClients == concurrentManagerInstances.Length); + } + + // verify that all executors are done with the execution and there are no more sources/testcases to execute + if (allRunsCompleted) + { + // Reset enumerators + sourceEnumerator = null; + testCaseListEnumerator = null; + + // Dispose concurrent executors + // Do not do the cleanuptask in the current thread as we will unncessarily add to execution time + lastParallelRunCleanUpTask = Task.Run(() => + { + UpdateParallelLevel(0); + }); + } + } + + return allRunsCompleted; + } + + #endregion + + #region ParallelOperationManager Methods + + protected override void DisposeInstance(IProxyExecutionManager managerInstance) + { + if (managerInstance != null) + { + try + { + managerInstance.Dispose(); + } + catch (Exception) + { + // ignore any exceptions + } + } + } + + #endregion + + private int StartTestRunPrivate(ITestRunEventsHandler runEventsHandler) + { + // Cleanup Task for cleaning up the parallel executors except for the default one + // We do not do this in Sync so that this task does not add up to execution time + if (lastParallelRunCleanUpTask != null) + { + try + { + lastParallelRunCleanUpTask.Wait(); + } + catch (Exception ex) + { + // if there is an exception disposing off concurrent executors ignore it + if (EqtTrace.IsWarningEnabled) + { + EqtTrace.Warning("ParallelTestRunnerServiceClient: Exception while invoking an action on DiscoveryManager: {0}", ex); + } + } + lastParallelRunCleanUpTask = null; + } + + // Reset the runcomplete data + runCompletedClients = 0; + + // One data aggregator per parallel run + var runDataAggregator = new ParallelRunDataAggregator(); + concurrentManagerHandlerMap = new Dictionary(); + + for (int i = 0; i < concurrentManagerInstances.Length; i++) + { + var concurrentManager = concurrentManagerInstances[i]; + + var parallelEventsHandler = new ParallelRunEventsHandler(concurrentManager, runEventsHandler, + this, runDataAggregator); + concurrentManagerHandlerMap.Add(concurrentManager, parallelEventsHandler); + + Task.Run(() => StartTestRunOnConcurrentManager(concurrentManager)); + } + + return 1; + } + + /// + /// Triggers the execution for the next data object on the concurrent executor + /// Each concurrent executor calls this method, once its completed working on previous data + /// + /// + /// True, if execution triggered + private bool StartTestRunOnConcurrentManager(IProxyExecutionManager proxyExecutionManager) + { + TestRunCriteria testRunCriteria = null; + if (!hasSpecificTestsRun) + { + string nextSource = null; + if (FetchNextSource(sourceEnumerator, out nextSource)) + { + EqtTrace.Info("ProxyParallelExecutionManager: Triggering test run for next source: {0}", nextSource); + + testRunCriteria = new TestRunCriteria(new List() { nextSource }, + actualTestRunCriteria.FrequencyOfRunStatsChangeEvent, + actualTestRunCriteria.KeepAlive, + actualTestRunCriteria.TestRunSettings, + actualTestRunCriteria.RunStatsChangeEventTimeout, + actualTestRunCriteria.TestHostLauncher); + } + } + else + { + List nextSetOfTests = null; + if (FetchNextSource(testCaseListEnumerator, out nextSetOfTests)) + { + EqtTrace.Info("ProxyParallelExecutionManager: Triggering test run for next source: {0}", nextSetOfTests?.FirstOrDefault()?.Source); + + testRunCriteria = new TestRunCriteria( + nextSetOfTests, + actualTestRunCriteria.FrequencyOfRunStatsChangeEvent, + actualTestRunCriteria.KeepAlive, + actualTestRunCriteria.TestRunSettings, + actualTestRunCriteria.RunStatsChangeEventTimeout, + actualTestRunCriteria.TestHostLauncher); + } + } + + if (testRunCriteria != null) + { + proxyExecutionManager.StartTestRun(testRunCriteria, concurrentManagerHandlerMap[proxyExecutionManager]); + } + + return (testRunCriteria != null); + } + + /// + /// Fetches the next data object for the concurrent executor to work on + /// + /// sourcedata to work on - sourcefile or testCaseList + /// True, if data exists. False otherwise + private bool FetchNextSource(IEnumerator enumerator, out T source) + { + source = default(T); + var hasNext = false; + lock (sourceEnumeratorLockObject) + { + if (enumerator.MoveNext()) + { + source = (T)(enumerator.Current); + hasNext = (source != null); + } + } + + return hasNext; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelRunDataAggregator.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelRunDataAggregator.cs new file mode 100644 index 0000000000..e8dcfc77ac --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelRunDataAggregator.cs @@ -0,0 +1,123 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.Parallel +{ + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Linq; + using System.Threading.Tasks; + + /// + /// ParallelRunDataAggregator aggregates test run data from execution managers running in parallel + /// + internal class ParallelRunDataAggregator + { + #region PrivateFields + + private List executorUris; + + private List testRunStatsList; + + private object dataUpdateSyncObject = new object(); + + #endregion + + public ParallelRunDataAggregator() + { + ElapsedTime = TimeSpan.Zero; + RunContextAttachments = new List(); + RunCompleteArgsAttachments = new List(); + Exceptions = new List(); + executorUris = new List(); + testRunStatsList = new List(); + + IsAborted = false; + IsCanceled = false; + } + + #region Public Properties + + public TimeSpan ElapsedTime { get; set; } + + public List RunContextAttachments { get; } + + public List RunCompleteArgsAttachments { get; } + + public List Exceptions { get; } + + public HashSet ExecutorUris => new HashSet(executorUris); + + public bool IsAborted { get; private set; } + + public bool IsCanceled { get; private set; } + + #endregion + + #region Public Methods + + public ITestRunStatistics GetAggregatedRunStats() + { + var testOutcomeMap = new Dictionary(); + long totalTests = 0; + if (testRunStatsList.Count > 0) + { + foreach (var runStats in testRunStatsList) + { + foreach (var outcome in runStats.Stats.Keys) + { + if (!testOutcomeMap.ContainsKey(outcome)) + { + testOutcomeMap.Add(outcome, 0); + } + testOutcomeMap[outcome] += runStats.Stats[outcome]; + } + totalTests += runStats.ExecutedTests; + } + } + + var overallRunStats = new TestRunStatistics(testOutcomeMap); + overallRunStats.ExecutedTests = totalTests; + return overallRunStats; + } + + public Exception GetAggregatedException() + { + if (Exceptions == null || Exceptions.Count < 1) return null; + + return new AggregateException(Exceptions); + } + + /// + /// Aggregate Run Data + /// Must be thread-safe as this is expected to be called by parallel managers + /// + public void Aggregate( + ITestRunStatistics testRunStats, + ICollection executorUris, + Exception exception, + TimeSpan elapsedTime, + bool isAborted, + bool isCanceled, + ICollection runContextAttachments, + Collection runCompleteArgsAttachments) + { + lock (dataUpdateSyncObject) + { + this.IsAborted = this.IsAborted || isAborted; + this.IsCanceled = this.IsCanceled || isCanceled; + + ElapsedTime = TimeSpan.FromMilliseconds(Math.Max(ElapsedTime.TotalMilliseconds, elapsedTime.TotalMilliseconds)); + if (runContextAttachments != null) RunContextAttachments.AddRange(runContextAttachments); + if (runCompleteArgsAttachments != null) RunCompleteArgsAttachments.AddRange(runCompleteArgsAttachments); + if (exception != null) Exceptions.Add(exception); + if (executorUris != null) this.executorUris.AddRange(executorUris); + if (testRunStats != null) testRunStatsList.Add(testRunStats); + } + } + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelRunEventsHandler.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelRunEventsHandler.cs new file mode 100644 index 0000000000..cf52f9dcd5 --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelRunEventsHandler.cs @@ -0,0 +1,156 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.Parallel +{ + using System.Collections.Generic; + using System.Collections.ObjectModel; + + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + + /// + /// ParallelRunEventsHandler for handling the run events in case of parallel execution + /// + internal class ParallelRunEventsHandler : ITestRunEventsHandler + { + private IProxyExecutionManager proxyExecutionManager; + + private ITestRunEventsHandler actualRunEventsHandler; + + private IParallelProxyExecutionManager parallelProxyExecutionManager; + + private ParallelRunDataAggregator runDataAggregator; + + private IDataSerializer dataSerializer; + + public ParallelRunEventsHandler(IProxyExecutionManager proxyExecutionManager, + ITestRunEventsHandler actualRunEventsHandler, + IParallelProxyExecutionManager parallelProxyExecutionManager, + ParallelRunDataAggregator runDataAggregator) : + this(proxyExecutionManager, actualRunEventsHandler, parallelProxyExecutionManager, runDataAggregator, JsonDataSerializer.Instance) + { + } + + + internal ParallelRunEventsHandler(IProxyExecutionManager proxyExecutionManager, + ITestRunEventsHandler actualRunEventsHandler, + IParallelProxyExecutionManager parallelProxyExecutionManager, + ParallelRunDataAggregator runDataAggregator, + IDataSerializer dataSerializer) + { + this.proxyExecutionManager = proxyExecutionManager; + this.actualRunEventsHandler = actualRunEventsHandler; + this.parallelProxyExecutionManager = parallelProxyExecutionManager; + this.runDataAggregator = runDataAggregator; + this.dataSerializer = dataSerializer; + } + + /// + /// Handles the Run Complete event from a parallel proxy manager + /// + public void HandleTestRunComplete( + TestRunCompleteEventArgs testRunCompleteArgs, + TestRunChangedEventArgs lastChunkArgs, + ICollection runContextAttachments, + ICollection executorUris) + { + // we get run complete events from each executor process + // so we cannot "complete" the actual executor operation until all sources/testcases are consumed + // We should not block last chunk results while we aggregate overall run data + if (lastChunkArgs != null) + { + ConvertToRawMessageAndSend(MessageType.TestRunStatsChange, lastChunkArgs); + HandleTestRunStatsChange(lastChunkArgs); + } + + // Update runstats, executorUris, etc. + // we need this data when we send the final runcomplete + runDataAggregator.Aggregate( + testRunCompleteArgs.TestRunStatistics, + executorUris, + testRunCompleteArgs.Error, + testRunCompleteArgs.ElapsedTimeInRunningTests, + testRunCompleteArgs.IsAborted, + testRunCompleteArgs.IsCanceled, + runContextAttachments, + testRunCompleteArgs.AttachmentSets); + + // Do not send TestRunComplete to actual test run handler + // We need to see if there are still sources to execute - let the parallel manager decide + var parallelRunComplete = this.parallelProxyExecutionManager.HandlePartialRunComplete( + this.proxyExecutionManager, + testRunCompleteArgs, + null, // lastChunk should be null as we already sent this data above + runContextAttachments, + executorUris); + + if (parallelRunComplete) + { + // todo : Merge Code Coverage files here + var completedArgs = new TestRunCompleteEventArgs(runDataAggregator.GetAggregatedRunStats(), + runDataAggregator.IsCanceled, + runDataAggregator.IsAborted, + runDataAggregator.GetAggregatedException(), + new Collection(runDataAggregator.RunCompleteArgsAttachments), + runDataAggregator.ElapsedTime); + + // In case of sequential execution - RawMessage would have contained a 'TestRunCompletePayload' object + // To send a rawmessge - we need to create rawmessage from an aggregated payload object + var testRunCompletePayload = new TestRunCompletePayload() + { + ExecutorUris = runDataAggregator.ExecutorUris, + LastRunTests = null, + RunAttachments = runDataAggregator.RunContextAttachments, + TestRunCompleteArgs = completedArgs + }; + + // we have to send rawmessages as we block the runcomplete actual raw messages + ConvertToRawMessageAndSend(MessageType.ExecutionComplete, testRunCompletePayload); + + // send actual test runcomplete to clients + this.actualRunEventsHandler.HandleTestRunComplete( + completedArgs, null, runDataAggregator.RunContextAttachments, runDataAggregator.ExecutorUris); + } + } + + public void HandleRawMessage(string rawMessage) + { + // In case of parallel - we can send everything but handle complete + // HandleComplete is not true-end of the overall execution as we only get completion of one executor here + // Always aggregate data, deserialize and raw for complete events + var message = this.dataSerializer.DeserializeMessage(rawMessage); + + // Do not deserialize further - just send if not execution complete + if(!string.Equals(MessageType.ExecutionComplete, message.MessageType)) + { + this.actualRunEventsHandler.HandleRawMessage(rawMessage); + } + } + + public void HandleTestRunStatsChange(TestRunChangedEventArgs testRunChangedArgs) + { + this.actualRunEventsHandler.HandleTestRunStatsChange(testRunChangedArgs); + } + + public void HandleLogMessage(TestMessageLevel level, string message) + { + this.actualRunEventsHandler.HandleLogMessage(level, message); + } + + public int LaunchProcessWithDebuggerAttached(TestProcessStartInfo testProcessStartInfo) + { + return this.actualRunEventsHandler.LaunchProcessWithDebuggerAttached(testProcessStartInfo); + } + + private void ConvertToRawMessageAndSend(string messageType, object payload) + { + var rawMessage = this.dataSerializer.SerializeObject(messageType, payload); + this.actualRunEventsHandler.HandleRawMessage(rawMessage); + } + } +} diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyDiscoveryManager.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyDiscoveryManager.cs new file mode 100644 index 0000000000..8b7a999c71 --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyDiscoveryManager.cs @@ -0,0 +1,91 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client +{ + using System.Linq; + + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; + + /// + /// Orchestrates discovery operations for the engine communicating with the client. + /// + public class ProxyDiscoveryManager : ProxyOperationManager, IProxyDiscoveryManager + { + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + public ProxyDiscoveryManager() + : base() + { + } + + /// + /// Constructor with Dependency injection. Used for unit testing. + /// + /// + /// The request Sender. + /// + /// + /// Test host Manager instance + /// + /// + /// The client Connection Timeout + /// + internal ProxyDiscoveryManager(ITestRequestSender requestSender, ITestHostManager testHostManager, int clientConnectionTimeout) + : base(requestSender, testHostManager, clientConnectionTimeout) + { + } + + #endregion + + #region IProxyDiscoveryManager implementation. + + /// + /// Ensure that the discovery component of engine is ready for discovery usually by loading extensions. + /// + /// + /// The manager for the test host. + /// + /// + /// The test Run Settings. + /// + public override void Initialize(ITestHostManager testHostManager) + { + base.Initialize(testHostManager); + + // Only send this if needed. + if (TestPluginCache.Instance.PathToAdditionalExtensions != null + && TestPluginCache.Instance.PathToAdditionalExtensions.Any()) + { + // Ensure that the client is conected. + this.EnsureInitialized(); + + this.RequestSender.InitializeDiscovery( + TestPluginCache.Instance.PathToAdditionalExtensions, + TestPluginCache.Instance.LoadOnlyWellKnownExtensions); + } + } + + /// + /// Discovers tests + /// + /// Settings, parameters for the discovery request + /// EventHandler for handling discovery events from Engine + public void DiscoverTests(DiscoveryCriteria discoveryCriteria, ITestDiscoveryEventsHandler eventHandler) + { + // Ensure that initialize is called. + this.EnsureInitialized(); + + this.RequestSender.DiscoverTests(discoveryCriteria, eventHandler); + + this.RequestSender.EndSession(); + } + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyExecutionManager.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyExecutionManager.cs new file mode 100644 index 0000000000..89cf2cc033 --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyExecutionManager.cs @@ -0,0 +1,132 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client +{ + using System.Linq; + + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.ClientProtocol; + + /// + /// Orchestrates test execution operations for the engine communicating with the client. + /// + internal class ProxyExecutionManager : ProxyOperationManager, IProxyExecutionManager + { + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + public ProxyExecutionManager() + : base() + { + } + + /// + /// Constructor with Dependency injection. Used for unit testing. + /// + /// Request Sender instance + /// Test host manager instance + /// The client Connection Timeout + internal ProxyExecutionManager(ITestRequestSender requestSender, ITestHostManager testHostManager, int clientConnectionTimeout) + : base(requestSender, testHostManager, clientConnectionTimeout) + { + } + + #endregion + + #region IProxyExecutionManager implementation. + + /// + /// Ensure that the Execution component of engine is ready for execution usually by loading extensions. + /// + /// Manager for the test host. + public override void Initialize(ITestHostManager testHostManager) + { + base.Initialize(testHostManager); + + // Only send this if needed. + if (TestPluginCache.Instance.PathToAdditionalExtensions != null + && TestPluginCache.Instance.PathToAdditionalExtensions.Any()) + { + // Ensure that the client is conected. + this.EnsureInitialized(); + + this.RequestSender.InitializeExecution( + TestPluginCache.Instance.PathToAdditionalExtensions, + TestPluginCache.Instance.LoadOnlyWellKnownExtensions); + } + } + + /// + /// Starts the test run + /// + /// The settings/options for the test run. + /// EventHandler for handling execution events from Engine. + /// The process id of the runner executing tests. + public virtual int StartTestRun(TestRunCriteria testRunCriteria, ITestRunEventsHandler eventHandler) + { + // Ensure that initialize is called. + this.EnsureInitialized(); + var executionContext = new TestExecutionContext( + testRunCriteria.FrequencyOfRunStatsChangeEvent, + testRunCriteria.RunStatsChangeEventTimeout, + inIsolation: false, + keepAlive: testRunCriteria.KeepAlive, + isDataCollectionEnabled: false, + areTestCaseLevelEventsRequired: false, + hasTestRun: true, + isDebug: (testRunCriteria.TestHostLauncher != null && testRunCriteria.TestHostLauncher.IsDebug), + testCaseFilter: testRunCriteria.TestCaseFilter); + + if (testRunCriteria.HasSpecificSources) + { + var runRequest = new TestRunCriteriaWithSources( + testRunCriteria.AdapterSourceMap, + testRunCriteria.TestRunSettings, + executionContext); + + this.RequestSender.StartTestRun(runRequest, eventHandler); + } + else + { + var runRequest = new TestRunCriteriaWithTests( + testRunCriteria.Tests, + testRunCriteria.TestRunSettings, + executionContext); + + this.RequestSender.StartTestRun(runRequest, eventHandler); + } + + return 0; + } + + /// + /// Cancels the test run. + /// + public virtual void Cancel() + { + this.RequestSender.SendTestRunCancel(); + } + + /// + /// Aborts the test run. + /// + public override void Abort() + { + this.RequestSender.SendTestRunAbort(); + } + + public override void Dispose() + { + this.RequestSender?.EndSession(); + base.Dispose(); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyExecutionManagerWithDataCollection.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyExecutionManagerWithDataCollection.cs new file mode 100644 index 0000000000..2c4a2c8540 --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyExecutionManagerWithDataCollection.cs @@ -0,0 +1,176 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client +{ + using System; + using System.Collections.Generic; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.DataCollection; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.DataCollection.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + /// + /// The proxy execution manager with data collection. + /// + internal class ProxyExecutionManagerWithDataCollection : ProxyExecutionManager + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The proxy Data Collection Manager. + /// + public ProxyExecutionManagerWithDataCollection(IProxyDataCollectionManager proxyDataCollectionManager) : base() + { + this.ProxyDataCollectionManager = proxyDataCollectionManager; + this.DataCollectionRunEventsHandler = new DataCollectionRunEventsHandler(); + } + + /// + /// Gets the data collection run events handler. + /// + internal DataCollectionRunEventsHandler DataCollectionRunEventsHandler + { + get; private set; + } + + /// + /// Gets the proxy data collection manager. + /// + internal IProxyDataCollectionManager ProxyDataCollectionManager + { + get; private set; + } + + /// + /// Ensure that the Execution component of engine is ready for execution usually by loading extensions. + /// + /// + /// The test Host Manager. + /// + public override void Initialize(ITestHostManager testHostManager) + { + DataCollectionParameters dataCollectionParameters = null; + try + { + dataCollectionParameters = (this.ProxyDataCollectionManager == null) + ? DataCollectionParameters.CreateDefaultParameterInstance() + : this.ProxyDataCollectionManager.BeforeTestRunStart( + resetDataCollectors: true, + isRunStartingNow: true, + runEventsHandler: this.DataCollectionRunEventsHandler); + } + catch + { + try + { + // On failure in calling BeforeTestRunStart, call AfterTestRunEnd to end DataCollectionProcess + if (this.ProxyDataCollectionManager != null) + { + this.ProxyDataCollectionManager.AfterTestRunEnd(isCanceled: true, runEventsHandler: this.DataCollectionRunEventsHandler); + } + } + catch (Exception ex) + { + // There is an issue with Data Collector, skipping data collection and continuing with test run. + if (EqtTrace.IsErrorEnabled) + { + EqtTrace.Error("TestEngine: Error occured while communicating with DataCollection Process: {0}", ex); + } + + if (EqtTrace.IsWarningEnabled) + { + EqtTrace.Warning("TestEngine: Skipping Data Collection"); + } + } + } + + // todo : pass dataCollectionParameters.EnvironmentVariables while initializaing testhostprocess. + base.Initialize(testHostManager); + } + + /// + /// Starts the test run + /// + /// The settings/options for the test run. + /// EventHandler for handling execution events from Engine. + /// The process id of the runner executing tests. + public override int StartTestRun(TestRunCriteria testRunCriteria, ITestRunEventsHandler eventHandler) + { + var currentEventHandler = eventHandler; + if (this.ProxyDataCollectionManager != null) + { + currentEventHandler = new DataCollectionTestRunEventsHandler(eventHandler, this.ProxyDataCollectionManager); + } + + // Log all the exceptions that has occured while initializing DataCollectionClient + if (this.DataCollectionRunEventsHandler?.ExceptionMessages?.Count > 0) + { + foreach (var message in this.DataCollectionRunEventsHandler.ExceptionMessages) + { + currentEventHandler.HandleLogMessage(TestMessageLevel.Error, message); + } + } + + return base.StartTestRun(testRunCriteria, currentEventHandler); + } + + /// + /// Cancels the test run. + /// + public override void Cancel() + { + base.Cancel(); + } + + public override void Dispose() + { + base.Dispose(); + } + } + + /// + /// Handles Log events and stores them in list. Messages in the list will be emptied after test execution begins. + /// + internal class DataCollectionRunEventsHandler : ITestMessageEventHandler + { + /// + /// The constructor. + /// + public DataCollectionRunEventsHandler() + { + this.ExceptionMessages = new List(); + } + + /// + /// Gets the exception messages. + /// + public List ExceptionMessages { get; private set; } + + /// + /// The handle log message. + /// + /// + /// The level. + /// + /// + /// The message. + /// public void HandleLogMessage(TestMessageLevel level, string message) + { + this.ExceptionMessages.Add(message); + } + /// + /// The handle raw message. + /// + /// + /// The raw message. + /// + /// + /// + public void HandleRawMessage(string rawMessage) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyOperationManager.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyOperationManager.cs new file mode 100644 index 0000000000..bafb7b8da2 --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyOperationManager.cs @@ -0,0 +1,142 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client +{ + using System; + using System.Collections.Generic; + using System.Globalization; + + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; + + using Constants = Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Constants; + + /// + /// Base class for any operations that the client needs to drive through the engine. + /// + public class ProxyOperationManager : IProxyOperationManager + { + private ITestHostManager testHostManager; + + private bool isInitialized; + + private int connectionTimeout; + + #region Constructors. + + /// + /// Initializes a new instance of the class. + /// + public ProxyOperationManager() + : this(new TestRequestSender(), null, Constants.ClientConnectionTimeout) + { + } + + /// + /// Constructor with Dependency injection. Used for unit testing. + /// + /// Request Sender instance. + /// Test host manager instance. + /// Client Connection Timeout. + internal ProxyOperationManager(ITestRequestSender requestSender, ITestHostManager testHostManager, int clientConnectionTimeout) + { + this.RequestSender = requestSender; + this.connectionTimeout = clientConnectionTimeout; + this.testHostManager = testHostManager; + this.isInitialized = false; + } + + #endregion + + #region Properties + + /// + /// Gets or sets the server for communication. + /// + protected ITestRequestSender RequestSender { get; set; } + + #endregion + + #region IProxyOperationManager implementation. + + /// + /// Ensure that the engine is ready for test operations. + /// Usually includes starting up the test host process. + /// + /// Manager for launching and maintaining the test host process + public virtual void Initialize(ITestHostManager testHostManager) + { + this.testHostManager = testHostManager; + + var portNumber = this.RequestSender.InitializeCommunication(); + + // TODO: Fix the environment variables usage + this.testHostManager.LaunchTestHost(null, this.GetCommandLineArguments(portNumber)); + + this.isInitialized = true; + } + + /// + /// Dispose for this instance. + /// + public virtual void Dispose() + { + // Do Nothing. + } + + /// + /// Aborts the test discovery. + /// + public virtual void Abort() + { + throw new NotImplementedException(); + } + + #endregion + + #region protected methods + + /// + /// The ensure initialized. + /// + protected void EnsureInitialized() + { + if (!this.isInitialized) + { + this.Initialize(this.testHostManager); + } + + // Wait for a timeout for the client to connect. + var isHandlerConnected = this.RequestSender.WaitForRequestHandlerConnection(this.connectionTimeout); + + if (!isHandlerConnected) + { + throw new TestPlatformException( + string.Format(CultureInfo.CurrentUICulture, CrossPlatEngine.Resources.InitializationFailed)); + } + } + + #endregion + + #region private methods + + /// + /// Setup the command line options to include port. + /// + /// The port number. + /// The commandLine arguments as a list. + private IList GetCommandLineArguments(int portNumber) + { + var commandlineArguments = new List(); + + commandlineArguments.Add(Constants.PortOption); + commandlineArguments.Add(portNumber.ToString()); + + return commandlineArguments; + } + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Constants.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Constants.cs new file mode 100644 index 0000000000..672d560ad7 --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Constants.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine +{ + using System.Runtime.InteropServices; + + /// + /// The set of constants used throughout this project. + /// + public class Constants + { + /// + /// The port option to be specified to the test host process. + /// + internal const string PortOption = "--port"; + + /// + /// The connection timeout for clients in milliseconds. + /// + internal const int ClientConnectionTimeout = 5000 * 1000; + } +} diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/DataCollection/DataCollectionLauncher.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/DataCollection/DataCollectionLauncher.cs new file mode 100644 index 0000000000..0e17f48489 --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/DataCollection/DataCollectionLauncher.cs @@ -0,0 +1,100 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.DataCollection +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Reflection; + + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.DataCollection.Interfaces; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + + /// + /// The datacollection launcher. + /// This works for Desktop local scenarios + /// + public class DataCollectionLauncher : IDataCollectionLauncher + { + private const string X64DataCollectorProcessName = "datacollector.exe"; + private const string X86DataCollectorProcessName = "datacollector.x86.exe"; + private const string DotnetProcessName = "dotnet.exe"; + private const string DotnetProcessNameXPlat = "dotnet"; + + private string dataCollectorProcessName; + private Process dataCollectorProcess; + private IProcessHelper processHelper; + + /// + /// The constructor. + /// + /// + /// The architecture. + /// + public DataCollectionLauncher() + : this(new ProcessHelper()) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The architecture. + /// The process helper. + internal DataCollectionLauncher(IProcessHelper processHelper) + { + this.processHelper = processHelper; + this.dataCollectorProcess = null; + } + + /// + /// Initialize with desired architecture for the host + /// + /// architecture for the host + public void Initialize(Architecture architecture) + { + this.dataCollectorProcessName = (architecture == Architecture.X86) ? X86DataCollectorProcessName : X64DataCollectorProcessName; + } + + /// + /// Launches the test host for discovery/execution. + /// + /// Environment variables for the process. + /// The command line arguments to pass to the process. + /// ProcessId of launched Process. 0 means not launched. + public virtual int LaunchDataCollector(IDictionary environmentVariables, IList commandLineArguments) + { + var currentWorkingDirectory = Path.GetDirectoryName(typeof(DataCollectionLauncher).GetTypeInfo().Assembly.Location); + string dataCollectorProcessPath, processWorkingDirectory = null; + + // TODO: DRY: Move this code to a common place + // If we are running in the dotnet.exe context we do not want to launch dataCollector.exe but dotnet.exe with the dataCollector assembly. + // Since dotnet.exe is already built for multiple platforms this would avoid building dataCollector.exe also in multiple platforms. + var currentProcessFileName = this.processHelper.GetCurrentProcessFileName(); + if (currentProcessFileName.EndsWith(DotnetProcessName) || currentProcessFileName.EndsWith(DotnetProcessNameXPlat)) + { + dataCollectorProcessPath = currentProcessFileName; + var dataCollectorAssemblyPath = Path.Combine(currentWorkingDirectory, this.dataCollectorProcessName.Replace("exe", "dll")); + commandLineArguments.Insert(0, dataCollectorAssemblyPath); + processWorkingDirectory = Path.GetDirectoryName(currentProcessFileName); + } + else + { + dataCollectorProcessPath = Path.Combine(currentWorkingDirectory, this.dataCollectorProcessName); + // For IDEs and other scenario - Current directory should be the working directory - not the vstest.console.exe location + // For VS - this becomes the solution directory for example + // "TestResults" directory will be created at "current directory" of test host + processWorkingDirectory = Directory.GetCurrentDirectory(); + } + + var argumentsString = string.Join(" ", commandLineArguments); + + this.dataCollectorProcess = this.processHelper.LaunchProcess(dataCollectorProcessPath, argumentsString, processWorkingDirectory); + return this.dataCollectorProcess.Id; + } + + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/DataCollection/DataCollectionParameters.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/DataCollection/DataCollectionParameters.cs new file mode 100644 index 0000000000..d171f2c410 --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/DataCollection/DataCollectionParameters.cs @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.DataCollection +{ + using System.Collections.Generic; + + /// + /// The data collection parameters. + /// + public class DataCollectionParameters + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The is data collection started. + /// + /// + /// The are test case level events required. + /// + /// + /// The environment variables. + /// + /// + /// The data Collection Events Port. + /// + public DataCollectionParameters( + bool isDataCollectionStarted, + bool areTestCaseLevelEventsRequired, + IDictionary environmentVariables, + int dataCollectionEventsPort) + { + this.IsDataCollectionStarted = isDataCollectionStarted; + this.AreTestCaseLevelEventsRequired = areTestCaseLevelEventsRequired; + this.EnvironmentVariables = environmentVariables; + this.DataCollectionEventsPort = dataCollectionEventsPort; + } + + /// + /// Gets a value indicating whether DataCollection is started + /// + public bool IsDataCollectionStarted { get; private set; } + + /// + /// Gets a value indicating whether any of the enabled data collectors + /// registered for test case level events + /// + public bool AreTestCaseLevelEventsRequired { get; private set; } + + /// + /// Gets BeforeTestRunStart Call on the DataCollectors can yield/return a set of environment variables + /// + public IDictionary EnvironmentVariables { get; private set; } + + /// + /// Gets the data collection events port. + /// + public int DataCollectionEventsPort { get; private set; } + + /// + /// The create default parameter instance. + /// + /// + /// The . + /// + public static DataCollectionParameters CreateDefaultParameterInstance() + { + return new DataCollectionParameters(isDataCollectionStarted: false, areTestCaseLevelEventsRequired: false, environmentVariables: null, dataCollectionEventsPort: 0); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/DataCollection/DataCollectionTestRunEventsHandler.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/DataCollection/DataCollectionTestRunEventsHandler.cs new file mode 100644 index 0000000000..225feb8894 --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/DataCollection/DataCollectionTestRunEventsHandler.cs @@ -0,0 +1,152 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.DataCollection +{ + using System.Collections.Generic; + using System.Linq; + + + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.DataCollection.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + using System.Collections.ObjectModel; + using System; + /// + /// Handles DataCollection attachments by calling DataCollection Process on Test Run Complete. + /// Existing functionality of ITestRunEventsHandler is decorated with aditional call to Data Collection Process. + /// + internal class DataCollectionTestRunEventsHandler : ITestRunEventsHandler + { + private IProxyDataCollectionManager proxyDataCollectionManager; + private ITestRunEventsHandler testRunEventsHandler; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The base test run events handler. + /// + /// + /// The proxy execution manager. + /// + public DataCollectionTestRunEventsHandler(ITestRunEventsHandler baseTestRunEventsHandler, IProxyDataCollectionManager proxyDataCollectionManager) + { + this.proxyDataCollectionManager = proxyDataCollectionManager; + this.testRunEventsHandler = baseTestRunEventsHandler; + } + + /// + /// The handle log message. + /// + /// + /// The level. + /// + /// + /// The message. + /// + public void HandleLogMessage(TestMessageLevel level, string message) + { + this.testRunEventsHandler.HandleLogMessage(level, message); + } + + /// + /// The handle raw message. + /// + /// + /// The raw message. + /// + public void HandleRawMessage(string rawMessage) + { + this.testRunEventsHandler.HandleRawMessage(rawMessage); + } + + /// + /// The handle test run complete. + /// + /// + /// The test run complete args. + /// + /// + /// The last chunk args. + /// + /// + /// The run context attachments. + /// + /// + /// The executor uris. + /// + public void HandleTestRunComplete(TestRunCompleteEventArgs testRunCompleteArgs, TestRunChangedEventArgs lastChunkArgs, ICollection runContextAttachments, ICollection executorUris) + { + var attachmentSet = this.proxyDataCollectionManager?.AfterTestRunEnd(false, this); + attachmentSet = DataCollectionTestRunEventsHandler.GetCombinedAttachmentSets(attachmentSet, runContextAttachments); + this.testRunEventsHandler.HandleTestRunComplete(testRunCompleteArgs, lastChunkArgs, attachmentSet, executorUris); + } + + /// + /// The handle test run stats change. + /// + /// + /// The test run changed args. + /// + public void HandleTestRunStatsChange(TestRunChangedEventArgs testRunChangedArgs) + { + this.testRunEventsHandler.HandleTestRunStatsChange(testRunChangedArgs); + } + + /// + /// Launches a process with a given process info under debugger + /// Adapter get to call into this to launch any additional processes under debugger + /// + /// Process start info + /// ProcessId of the launched process + public int LaunchProcessWithDebuggerAttached(TestProcessStartInfo testProcessStartInfo) + { + return this.testRunEventsHandler.LaunchProcessWithDebuggerAttached(testProcessStartInfo); + } + + /// + /// The get combined attachment sets. + /// + /// + /// The run attachments. + /// + /// + /// The runcontext attachments. + /// + /// + /// The . + /// + internal static Collection GetCombinedAttachmentSets(Collection runAttachments, ICollection runcontextAttachments) + { + if (null == runcontextAttachments || runcontextAttachments.Count == 0) + { + return runAttachments; + } + + if (null == runAttachments) + { + return new Collection(runcontextAttachments.ToList()); + } + + foreach (var attachmentSet in runcontextAttachments) + { + var attSet = runAttachments.Where(item => Uri.Equals(item.Uri, attachmentSet.Uri)).FirstOrDefault(); + if (null == attSet) + { + runAttachments.Add(attachmentSet); + } + else + { + foreach (var attachment in attachmentSet.Attachments) + { + attSet.Attachments.Add(attachment); + } + } + } + + return runAttachments; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/DataCollection/Interfaces/IDataCollectionLauncher.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/DataCollection/Interfaces/IDataCollectionLauncher.cs new file mode 100644 index 0000000000..f156fc85a0 --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/DataCollection/Interfaces/IDataCollectionLauncher.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.DataCollection.Interfaces +{ + using System.Collections.Generic; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + + /// + /// The DataCollectionLauncher interface. + /// + internal interface IDataCollectionLauncher + { + /// + /// The launch data collector. + /// + /// + /// The environment variables. + /// + /// + /// The command line arguments. + /// + /// + /// The . + /// + int LaunchDataCollector(IDictionary environmentVariables, IList commandLineArguments); + + /// + /// The initialize. + /// + /// + /// The architecture. + /// + void Initialize(Architecture architecture); + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/DataCollection/Interfaces/IDataCollectorsSettingsProvider.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/DataCollection/Interfaces/IDataCollectorsSettingsProvider.cs new file mode 100644 index 0000000000..e86dcb7a91 --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/DataCollection/Interfaces/IDataCollectorsSettingsProvider.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.DataCollection.Interfaces +{ + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; + + /// + /// The DataCollectorsSettingsProvider interface. + /// + public interface IDataCollectorsSettingsProvider : ISettingsProvider + { + /// + /// Gets run specific data collection settings. + /// + DataCollectionRunSettings Settings { get; } + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/DataCollection/Interfaces/IProxyDataCollectionManager.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/DataCollection/Interfaces/IProxyDataCollectionManager.cs new file mode 100644 index 0000000000..a5c38abcfd --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/DataCollection/Interfaces/IProxyDataCollectionManager.cs @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.DataCollection.Interfaces +{ + using System; + using System.Collections.ObjectModel; + + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.DataCollection.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + + /// + /// The ProxyDataCollectionManager interface. + /// + public interface IProxyDataCollectionManager : IDisposable + { + /// + /// Invoked before starting of test run + /// + /// + /// Bool value to reset and reinitialize datacollectors. + /// + /// + /// Bool value to specify if the test execution has started or not. + /// + /// + /// The run Events Handler. + /// + /// + /// BeforeTestRunStartResult object + /// + DataCollectionParameters BeforeTestRunStart( + bool resetDataCollectors, + bool isRunStartingNow, + ITestMessageEventHandler runEventsHandler); + + /// + /// Invoked after ending of test run + /// + /// + /// The is Canceled. + /// + /// + /// The run Events Handler. + /// + /// + /// The . + /// + Collection AfterTestRunEnd(bool isCanceled, ITestMessageEventHandler runEventsHandler); + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/DataCollection/ProxyDataCollectionManager.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/DataCollection/ProxyDataCollectionManager.cs new file mode 100644 index 0000000000..17c59eb2e2 --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/DataCollection/ProxyDataCollectionManager.cs @@ -0,0 +1,192 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.DataCollection +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.DataCollection.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.DataCollection.Interfaces; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.DataCollection; + + /// + /// The test data collection client. + /// + internal class ProxyDataCollectionManager : IProxyDataCollectionManager + { + private const string PortOption = "--port"; + + private IDataCollectionRequestSender dataCollectionRequestSender; + private IDataCollectionLauncher dataCollectionLauncher; + private string settingsXml; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The arch. + /// + /// + /// The settings Xml. + /// + public ProxyDataCollectionManager(Architecture arch, string settingsXml) + : this(arch, settingsXml, new DataCollectionRequestSender(), new DataCollectionLauncher()) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The data collection request sender. + /// + /// + /// The data collection launcher. + /// + internal ProxyDataCollectionManager(Architecture arch, string settingsXml, IDataCollectionRequestSender dataCollectionRequestSender, IDataCollectionLauncher dataCollectionLauncher) + { + this.settingsXml = settingsXml; + this.dataCollectionRequestSender = dataCollectionRequestSender; + this.dataCollectionLauncher = dataCollectionLauncher; + this.InitializeSocketCommunication(arch); + } + + + /// + /// Invoked after ending of test run + /// + /// + /// The is Canceled. + /// + /// + /// The run Events Handler. + /// + /// + /// The . + /// + public Collection AfterTestRunEnd(bool isCanceled, ITestMessageEventHandler runEventsHandler) + { + Collection attachmentSet = null; + this.InvokeDataCollectionServiceAction( + () => + { + attachmentSet = this.dataCollectionRequestSender.SendAfterTestRunStartAndGetResult(); + }, + runEventsHandler); + return attachmentSet; + } + + /// + /// Invoked before starting of test run + /// + /// + /// The reset Data Collectors. + /// + /// + /// The is Run Starting Now. + /// + /// + /// The run Events Handler. + /// + /// + /// BeforeTestRunStartResult object + /// + public DataCollectionParameters BeforeTestRunStart( + bool resetDataCollectors, + bool isRunStartingNow, + ITestMessageEventHandler runEventsHandler) + { + bool areTestCaseLevelEventsRequired = false; + bool isDataCollectionStarted = false; + IDictionary environmentVariables = null; + + var dataCollectionEventsPort = 0; + this.InvokeDataCollectionServiceAction( + () => + { + var result = this.dataCollectionRequestSender.SendBeforeTestRunStartAndGetResult(settingsXml); + areTestCaseLevelEventsRequired = result.AreTestCaseLevelEventsRequired; + environmentVariables = result.EnvironmentVariables; + dataCollectionEventsPort = result.DataCollectionEventsPort; + }, + runEventsHandler); + return new DataCollectionParameters( + isDataCollectionStarted, + areTestCaseLevelEventsRequired, + environmentVariables, + dataCollectionEventsPort); + } + + /// + /// The dispose. + /// + public void Dispose() + { + this.dataCollectionRequestSender.Close(); + } + + /// + /// The initialize socket communication. + /// + /// + /// The arch. + /// + internal void InitializeSocketCommunication(Architecture arch) + { + var port = this.dataCollectionRequestSender.InitializeCommunication(); + + this.dataCollectionLauncher.Initialize(arch); + this.dataCollectionLauncher.LaunchDataCollector(null, this.GetCommandLineArguments(port)); + this.dataCollectionRequestSender.WaitForRequestHandlerConnection(connectionTimeout: 5000); + } + + private void InvokeDataCollectionServiceAction(Action action, ITestMessageEventHandler runEventsHandler) + { + try + { + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose("ProxyDataCollectionManager.InvokeDataCollectionServiceAction: Starting."); + } + + action(); + if (EqtTrace.IsInfoEnabled) + { + EqtTrace.Info("ProxyDataCollectionManager.InvokeDataCollectionServiceAction: Completed."); + } + } + catch (Exception ex) + { + if (EqtTrace.IsWarningEnabled) + { + EqtTrace.Warning("ProxyDataCollectionManager.InvokeDataCollectionServiceAction: TestPlatformException = {0}.", ex); + } + + this.HandleExceptionMessage(runEventsHandler, ex); + } + } + + private void HandleExceptionMessage(ITestMessageEventHandler runEventsHandler, Exception exception) + { + if (EqtTrace.IsErrorEnabled) + { + EqtTrace.Error(exception); + } + + runEventsHandler.HandleLogMessage(ObjectModel.Logging.TestMessageLevel.Error, exception.Message); + } + + private IList GetCommandLineArguments(int portNumber) + { + var commandlineArguments = new List(); + + commandlineArguments.Add(PortOption); + commandlineArguments.Add(portNumber.ToString()); + + return commandlineArguments; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Discovery/DiscovererEnumerator.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Discovery/DiscovererEnumerator.cs new file mode 100644 index 0000000000..64948a4b4d --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Discovery/DiscovererEnumerator.cs @@ -0,0 +1,250 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Discovery +{ + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + using System.IO; + using System.Linq; + + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework; + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.Utilities; + using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + using Microsoft.VisualStudio.TestPlatform.Common.Logging; + + /// + /// Enumerates through all the discoverers. + /// + internal class DiscovererEnumerator + { + private DiscoveryResultCache discoveryResultCache; + + /// + /// Initializes a new instance of the class. + /// + /// The discovery result cache. + public DiscovererEnumerator(DiscoveryResultCache discoveryResultCache) + { + this.discoveryResultCache = discoveryResultCache; + } + + /// + /// Discovers tests from the sources. + /// + /// The test extension source map. + /// The settings. + /// The logger. + internal void LoadTests(IDictionary> testExtensionSourceMap, IRunSettings settings, IMessageLogger logger) + { + foreach (var kvp in testExtensionSourceMap) + { + this.LoadTestsFromAnExtension(kvp.Key, kvp.Value, settings, logger); + } + } + + /// + /// Loads test cases from individual source. + /// Discovery extensions update progress through ITestCaseDiscoverySink. + /// Discovery extensions sends discovery messages through TestRunMessageLoggerProxy + /// + /// The extension Assembly. + /// The sources. + /// The settings. + /// The logger. + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", + Justification = "This methods must invoke all possible discoverers and not fail or crash in any one.")] + private void LoadTestsFromAnExtension(string extensionAssembly, IEnumerable sources, IRunSettings settings, IMessageLogger logger) + { + var discovererToSourcesMap = GetDiscovererToSourcesMap(extensionAssembly, sources, logger); + + // Warning is logged for in the inner function + if (discovererToSourcesMap == null || discovererToSourcesMap.Count() == 0) + { + return; + } + + var context = new DiscoveryContext { RunSettings = settings }; + + // Set on the logger the TreatAdapterErrorAsWarning setting from runsettings. + this.SetAdapterLoggingSettings(logger, settings); + + var discoverySink = new TestCaseDiscoverySink(this.discoveryResultCache); + + foreach (var discoverer in discovererToSourcesMap.Keys) + { + Type discovererType = null; + + // See if discoverer can be instantiated successfully else move next. + try + { + discovererType = discoverer.Value.GetType(); + } + catch (Exception e) + { + var mesage = string.Format( + CultureInfo.CurrentUICulture, + CrossPlatEngine.Resources.DiscovererInstantiationException, + e.Message); + logger.SendMessage(TestMessageLevel.Warning, mesage); + EqtTrace.Error(e); + + continue; + } + + // if instantiated successfully, get tests + try + { + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose( + "DiscoveryContext.LoadTests: Loading tests for {0}", + discoverer.Value.GetType().FullName); + } + + discoverer.Value.DiscoverTests(discovererToSourcesMap[discoverer], context, logger, discoverySink); + + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose( + "DiscoveryContext.LoadTests: Done loading tests for {0}", + discoverer.Value.GetType().FullName); + } + } + catch (Exception e) + { + var message = string.Format( + CultureInfo.CurrentUICulture, + CrossPlatEngine.Resources.ExceptionFromLoadTests, + discovererType.Name, + e.Message); + + logger.SendMessage(TestMessageLevel.Error, message); + EqtTrace.Error(e); + } + } + } + + private void SetAdapterLoggingSettings(IMessageLogger messageLogger, IRunSettings runSettings) + { + var discoveryMessageLogger = messageLogger as TestSessionMessageLogger; + if (discoveryMessageLogger != null && runSettings != null) + { +#if Todo + // Todo: Enable this when RunSettings is enabled. + IRunConfigurationSettingsProvider runConfigurationSettingsProvider = + (IRunConfigurationSettingsProvider)runSettings.GetSettings(ObjectModel.Constants.RunConfigurationSettingsName); + if (runConfigurationSettingsProvider != null + && runConfigurationSettingsProvider.Settings != null) + { + discoveryMessageLogger.TreatTestAdapterErrorsAsWarnings + = runConfigurationSettingsProvider.Settings.TreatTestAdapterErrorsAsWarnings; + } +#endif + } + } + + /// + /// Get the discoverers matching with the parameter sources + /// + /// The extension assembly. + /// The sources. + /// The logger instance. + /// The map between an extension type and a source. + internal static Dictionary, IEnumerable> + GetDiscovererToSourcesMap(string extensionAssembly, IEnumerable sources, IMessageLogger logger) + { + var allDiscoverers = GetDiscoverers(extensionAssembly, throwOnError: true); + + if (allDiscoverers == null || !allDiscoverers.Any()) + { + // No discoverer available, log a warning + logger.SendMessage( + TestMessageLevel.Warning, + String.Format(CultureInfo.CurrentCulture, CrossPlatEngine.Resources.NoDiscovererRegistered)); + + return null; + } + + var result = + new Dictionary, IEnumerable>(); + var sourcesForWhichNoDiscovererIsAvailable = new List(sources); + + foreach (var discoverer in allDiscoverers) + { + // Find the sources which this discoverer can look at. + // Based on whether it is registered for a matching file extension or no file extensions at all. + var matchingSources = (from source in sources + where + (discoverer.Metadata.FileExtension == null + || discoverer.Metadata.FileExtension.Contains( + Path.GetExtension(source), + StringComparer.OrdinalIgnoreCase)) + select source).ToList(); // ToList is required to actually execute the query + + + // Update the source list for which no matching source is available. + if (matchingSources.Any()) + { + sourcesForWhichNoDiscovererIsAvailable = + sourcesForWhichNoDiscovererIsAvailable.Except(matchingSources, StringComparer.OrdinalIgnoreCase) + .ToList(); + result.Add(discoverer, matchingSources); + } + } + + if (EqtTrace.IsWarningEnabled && sourcesForWhichNoDiscovererIsAvailable != null) + { + foreach (var source in sourcesForWhichNoDiscovererIsAvailable) + { + // Log a warning to logfile, not to the "default logger for discovery time messages". + EqtTrace.Warning( + "No test discoverer is registered to perform discovery for the type of test source '{0}'. Register a test discoverer for this source type and try again.", + source); + } + } + + return result; + } + + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", + "CA1006:DoNotNestGenericTypesInMemberSignatures")] + private static IEnumerable> GetDiscoverers( + string extensionAssembly, + bool throwOnError) + { + try + { + if (string.IsNullOrEmpty(extensionAssembly) || string.Equals(extensionAssembly, ObjectModel.Constants.UnspecifiedAdapterPath)) + { + // full discovery. + return TestDiscoveryExtensionManager.Create().Discoverers; + } + else + { + return TestDiscoveryExtensionManager.GetDiscoveryExtensionManager(extensionAssembly).Discoverers; + } + } + catch (Exception ex) + { + EqtTrace.Error( + "TestDiscoveryManager: LoadExtensions: Exception occured while loading extensions {0}", + ex); + + if (throwOnError) + { + throw; + } + + return null; + } + } + + } +} diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Discovery/DiscoveryContext.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Discovery/DiscoveryContext.cs new file mode 100644 index 0000000000..485038afd6 --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Discovery/DiscoveryContext.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Discovery +{ + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; + + /// + /// Specifies the user specified RunSettings and framework provided context of the discovery. + /// + public class DiscoveryContext : IDiscoveryContext + { + /// + /// Gets the run settings specified for this request. + /// + public IRunSettings RunSettings { get; internal set; } + } +} diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Discovery/DiscoveryManager.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Discovery/DiscoveryManager.cs new file mode 100644 index 0000000000..5c50b1b751 --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Discovery/DiscoveryManager.cs @@ -0,0 +1,216 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Discovery +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.IO; + using System.Linq; + + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework; + using Microsoft.VisualStudio.TestPlatform.Common.Logging; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.TesthostProtocol; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + using Microsoft.VisualStudio.TestPlatform.Utilities; + using Microsoft.VisualStudio.TestPlatform.Common.Utilities; + /// + /// Orchestrates discovery operations for the engine communicating with the test host process. + /// + public class DiscoveryManager : IDiscoveryManager + { + private TestSessionMessageLogger sessionMessageLogger; + + private ITestDiscoveryEventsHandler testDiscoveryEventsHandler; + + /// + /// Initializes a new instance of the class. + /// + public DiscoveryManager() + { + this.sessionMessageLogger = TestSessionMessageLogger.Instance; + this.sessionMessageLogger.TestRunMessage += this.TestSessionMessageHandler; + } + + /// + /// Initializes the discovery manager. + /// + /// The path to additional extensions. + public void Initialize(IEnumerable pathToAdditionalExtensions) + { + // Start using these additional extensions + TestPluginCache.Instance.UpdateAdditionalExtensions(pathToAdditionalExtensions, shouldLoadOnlyWellKnownExtensions: false); + + // Load and Initialize extensions. + TestDiscoveryExtensionManager.LoadAndInitializeAllExtensions(false); + } + + /// + /// Discovers tests + /// + /// Settings, parameters for the discovery request + /// EventHandler for handling discovery events from Engine + public void DiscoverTests(DiscoveryCriteria discoveryCriteria, ITestDiscoveryEventsHandler eventHandler) + { + var discoveryResultCache = new DiscoveryResultCache( + discoveryCriteria.FrequencyOfDiscoveredTestsEvent, + discoveryCriteria.DiscoveredTestEventTimeout, + this.OnReportTestCases); + + try + { + EqtTrace.Info("TestDiscoveryManager.DoDiscovery: Background test discovery started."); + + this.testDiscoveryEventsHandler = eventHandler; + + var verifiedExtensionSourceMap = new Dictionary>(); + + // Validate the sources + foreach (var kvp in discoveryCriteria.AdapterSourceMap) + { + var verifiedSources = GetValidSources(kvp.Value, this.sessionMessageLogger); + if (verifiedSources.Any()) + { + verifiedExtensionSourceMap.Add(kvp.Key, kvp.Value); + } + } + + // If there are sources to discover + if (verifiedExtensionSourceMap.Any()) + { + new DiscovererEnumerator(discoveryResultCache).LoadTests( + verifiedExtensionSourceMap, + RunSettingsUtilities.CreateAndInitializeRunSettings(discoveryCriteria.RunSettings), + this.sessionMessageLogger); + } + } + finally + { + // Discovery complete. Raise the DiscoveryCompleteEvent. + EqtTrace.Verbose("TestDiscoveryManager.DoDiscovery: Background Test Discovery complete."); + + var totalDiscoveredTestCount = discoveryResultCache.TotalDiscoveredTests; + var lastChunk = discoveryResultCache.Tests; + + EqtTrace.Verbose("TestDiscoveryManager.DiscoveryComplete: Calling DiscoveryComplete callback."); + + if (eventHandler != null) + { + eventHandler.HandleDiscoveryComplete(totalDiscoveredTestCount, lastChunk, false); + } + else + { + EqtTrace.Warning( + "DiscoveryManager: Could not pass the discovery complete message as the callback is null."); + } + + EqtTrace.Verbose("TestDiscoveryManager.DiscoveryComplete: Called DiscoveryComplete callback."); + + this.testDiscoveryEventsHandler = null; + } + } + + /// + /// Aborts the test discovery. + /// + public void Abort() + { + // do nothing for now. + } + + private void OnReportTestCases(IEnumerable testCases) + { + if (this.testDiscoveryEventsHandler != null) + { + this.testDiscoveryEventsHandler.HandleDiscoveredTests(testCases); + } + else + { + if (EqtTrace.IsWarningEnabled) + { + EqtTrace.Warning("DiscoveryManager: Could not pass the test results as the callback is null."); + } + } + } + + /// + /// Verify/Normalize the test source files. + /// + /// Paths to source file to look for tests in. + /// The list of verified sources. + [System.Security.SecuritySafeCritical] + internal static IEnumerable GetValidSources(IEnumerable sources, IMessageLogger logger) + { + Debug.Assert(sources != null, "sources"); + var verifiedSources = new HashSet(StringComparer.OrdinalIgnoreCase); + + foreach (string source in sources) + { + if (!File.Exists(source)) + { + var errorMessage = string.Format(CultureInfo.CurrentCulture, CrossPlatEngine.Resources.FileNotFound, source); + logger.SendMessage(TestMessageLevel.Warning, errorMessage); + + continue; + } + + if (!verifiedSources.Add(source)) + { + var errorMessage = string.Format(CultureInfo.CurrentCulture, CrossPlatEngine.Resources.DuplicateSource, source); + logger.SendMessage(TestMessageLevel.Warning, errorMessage); + + continue; + } + } + + // No valid source is found => we cannot discover. + if (!verifiedSources.Any()) + { + var sourcesString = string.Join(",", sources.ToArray()); + var errorMessage = string.Format(CultureInfo.CurrentCulture, CrossPlatEngine.Resources.NoValidSourceFoundForDiscovery, sourcesString); + logger.SendMessage(TestMessageLevel.Warning, errorMessage); + + EqtTrace.Warning("TestDiscoveryManager: None of the source {0} is valid. ", sourcesString); + + return verifiedSources; + } + + // Log the sources from where tests are being discovered + if (EqtTrace.IsInfoEnabled) + { + EqtTrace.Info("TestDiscoveryManager: Discovering tests from sources {0}", string.Join(",", verifiedSources.ToArray())); + } + + return verifiedSources; + } + + private void TestSessionMessageHandler(object sender, TestRunMessageEventArgs e) + { + if (EqtTrace.IsInfoEnabled) + { + EqtTrace.Info( + "TestDiscoveryManager.RunMessage: calling TestRunMessage({0}, {1}) callback.", + e.Level, + e.Message); + } + + if (this.testDiscoveryEventsHandler != null) + { + this.testDiscoveryEventsHandler.HandleLogMessage(e.Level, e.Message); + } + else + { + if (EqtTrace.IsWarningEnabled) + { + EqtTrace.Warning( + "DiscoveryManager: Could not pass the log message '{0}' as the callback is null.", + e.Message); + } + } + } + } +} diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Discovery/DiscoveryResultCache.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Discovery/DiscoveryResultCache.cs new file mode 100644 index 0000000000..d7c85b656a --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Discovery/DiscoveryResultCache.cs @@ -0,0 +1,139 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Discovery +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.Utilities; + + /// + /// The discovery result cache. + /// + internal class DiscoveryResultCache + { + #region private members + + /// + /// Callback used when cache is full. + /// + private OnReportTestCases onReportTestCases; + + /// + /// Total tests discovered in this request + /// + private long totalDiscoveredTests; + + /// + /// Max size of the test case buffer + /// + private long cacheSize; + + /// + /// Timeout that triggers sending test cases regardless of cache size. + /// + private TimeSpan cacheTimeout; + + /// + /// Last time test cases were sent. + /// + private DateTime lastUpdate; + + /// + /// Test case buffer + /// + private List tests; + + /// + /// Sync object + /// + private object syncObject = new object(); + + #endregion + + /// + /// Initializes a new instance of the class. + /// + /// The cache size. + /// The discovered test event timeout. + /// The on report test cases. + public DiscoveryResultCache(long cacheSize, TimeSpan discoveredTestEventTimeout, OnReportTestCases onReportTestCases) + { + Debug.Assert(cacheSize > 0, "Buffer size cannot be less than zero"); + Debug.Assert(onReportTestCases != null, "Callback which listens for cache size limit cannot be null."); + Debug.Assert(discoveredTestEventTimeout > TimeSpan.MinValue, "The cache timeout must be greater than min value."); + + this.cacheSize = cacheSize; + this.onReportTestCases = onReportTestCases; + this.lastUpdate = DateTime.Now; + this.cacheTimeout = discoveredTestEventTimeout; + + this.tests = new List(); + this.totalDiscoveredTests = 0; + } + + /// + /// Called when the cache is ready to report some discovered test cases. + /// + public delegate void OnReportTestCases(ICollection tests); + + /// + /// Gets the tests present in the cache currently + /// + public IList Tests + { + get + { + // This needs to new list to avoid concurrency issues. + return new List(this.tests); + } + } + + /// + /// Gets the total discovered tests + /// + public long TotalDiscoveredTests + { + get + { + return this.totalDiscoveredTests; + } + } + + /// + /// Adds a test to the cache. + /// + /// The test. + public void AddTest(TestCase test) + { + Debug.Assert(null != test, "DiscoveryResultCache.AddTest called with no new test."); + + if (test == null) + { + EqtTrace.Warning("DiscoveryResultCache.AddTest: An attempt was made to add a 'null' test"); + return; + } + + lock (this.syncObject) + { + this.tests.Add(test); + this.totalDiscoveredTests++; + + // Send test cases when the specified cache size has been reached or + // after the specified cache timeout has been hit. + var timeDelta = DateTime.Now - this.lastUpdate; + if (this.tests.Count >= this.cacheSize || (timeDelta > this.cacheTimeout && this.tests.Count > 0)) + { + // Pass on the buffer to the listener and clear the old one + this.onReportTestCases(this.tests); + this.tests = new List(); + this.lastUpdate = DateTime.Now; + + EqtTrace.Verbose("DiscoveryResultCache.AddTest: Notified the onReportTestCases callback."); + } + } + } + } +} diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Discovery/TestCaseDiscoverySink.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Discovery/TestCaseDiscoverySink.cs new file mode 100644 index 0000000000..b2be81afc7 --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Discovery/TestCaseDiscoverySink.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Discovery +{ + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; + + /// + /// The test case discovery sink. + /// + internal class TestCaseDiscoverySink : ITestCaseDiscoverySink + { + private DiscoveryResultCache discoveryRequestCache; + + /// + /// Initializes a new instance of the class. + /// + /// The discovery request cache. + internal TestCaseDiscoverySink(DiscoveryResultCache discoveryRequestCache) + { + this.discoveryRequestCache = discoveryRequestCache; + } + + /// + /// Sends the test case to the discovery cache. + /// + /// The discovered test. + public void SendTestCase(TestCase discoveredTest) + { + if (this.discoveryRequestCache != null) + { + this.discoveryRequestCache.AddTest(discoveredTest); + } + } + } +} diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/EventHandlers/TestCaseEventsHandler.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/EventHandlers/TestCaseEventsHandler.cs new file mode 100644 index 0000000000..c902d330cf --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/EventHandlers/TestCaseEventsHandler.cs @@ -0,0 +1,74 @@ + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.EventHandlers +{ + + using System; +#if !NET46 + using System.Runtime.Loader; +#endif + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Execution; + + using TestPlatform.ObjectModel; + using TestPlatform.ObjectModel.Engine; + + /// + /// The test case events handler. + /// + internal class TestCaseEventsHandler : ITestCaseEventsHandler + { + + private InProcDataCollectionExtensionManager inProcDataCollectionExtensionManager; + + private ITestCaseEventsHandler testCaseEvents; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The in proc tidc helper. + /// + public TestCaseEventsHandler(InProcDataCollectionExtensionManager inProcDCExtMgr, ITestCaseEventsHandler testCaseEvents) + { + this.inProcDataCollectionExtensionManager = inProcDCExtMgr; + this.testCaseEvents = testCaseEvents; + } + + /// + /// The send test case start. + /// + /// + /// The test case. + /// + public void SendTestCaseStart(TestCase testCase) + { + this.inProcDataCollectionExtensionManager.TriggerTestCaseStart(testCase); + this.testCaseEvents?.SendTestCaseStart(testCase); + } + + /// + /// The send test case end. + /// + /// + /// The test case. + /// + /// + /// The outcome. + /// + public void SendTestCaseEnd(TestCase testCase, TestOutcome outcome) + { + this.inProcDataCollectionExtensionManager.TriggerTestCaseEnd(testCase, outcome); + this.testCaseEvents?.SendTestCaseEnd(testCase, outcome); + } + + /// + /// The send test result. + /// + /// + /// The result. + /// + public void SendTestResult(TestResult result) + { + this.testCaseEvents?.SendTestResult(result); + } + } +} diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Execution/BaseRunTests.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Execution/BaseRunTests.cs new file mode 100644 index 0000000000..1f63e78a19 --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Execution/BaseRunTests.cs @@ -0,0 +1,421 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Execution +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + using System.IO; + + using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework; + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.Utilities; + using Microsoft.VisualStudio.TestPlatform.Common.Utilities; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Adapter; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.ClientProtocol; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities; + using System.Threading.Tasks; + + /// + /// The base run tests. + /// + internal abstract class BaseRunTests + { + #region private fields + + private string runSettings; + private TestExecutionContext testExecutionContext; + private ITestRunEventsHandler testRunEventsHandler; + private ITestRunCache testRunCache; + + /// + /// Specifies that the test run cancellation is requested + /// + private volatile bool isCancellationRequested; + + /// + /// Active executor which is executing the tests currently + /// + private ITestExecutor activeExecutor; + private ITestCaseEventsHandler testCaseEventsHandler; + private RunContext runContext; + private FrameworkHandle frameworkHandle; + + private ICollection executorUrisThatRanTests; + + #endregion + + #region Constructor + + /// + /// Initializes a new instance of the class. + /// + /// The test run cache. + /// The run settings. + /// The test execution context. + /// The test case events handler. + /// The test run events handler. + public BaseRunTests(ITestRunCache testRunCache, string runSettings, TestExecutionContext testExecutionContext, ITestCaseEventsHandler testCaseEventsHandler, ITestRunEventsHandler testRunEventsHandler) + { + this.testRunCache = testRunCache; + this.runSettings = runSettings; + this.testExecutionContext = testExecutionContext; + this.testCaseEventsHandler = testCaseEventsHandler; + this.testRunEventsHandler = testRunEventsHandler; + + this.isCancellationRequested = false; + this.SetContext(); + } + + private void SetContext() + { + this.runContext = new RunContext(); + this.runContext.RunSettings = RunSettingsUtilities.CreateAndInitializeRunSettings(this.runSettings); + this.runContext.KeepAlive = this.testExecutionContext.KeepAlive; + this.runContext.InIsolation = this.testExecutionContext.InIsolation; + this.runContext.IsDataCollectionEnabled = this.testExecutionContext.IsDataCollectionEnabled; + this.runContext.IsBeingDebugged = this.testExecutionContext.IsDebug; + + var runConfig = XmlRunSettingsUtilities.GetRunConfigurationNode(this.runSettings); + this.runContext.TestRunDirectory = RunSettingsUtilities.GetTestResultsDirectory(runConfig); + this.runContext.SolutionDirectory = RunSettingsUtilities.GetSolutionDirectory(runConfig); + + this.frameworkHandle = new FrameworkHandle( + this.testCaseEventsHandler, + this.testRunCache, + this.testExecutionContext, + this.testRunEventsHandler); + + this.executorUrisThatRanTests = new List(); + } + + #endregion + + #region Properties + + /// + /// Gets the run settings. + /// + protected string RunSettings => this.runSettings; + + /// + /// Gets the test execution context. + /// + protected TestExecutionContext TestExecutionContext => this.testExecutionContext; + + /// + /// Gets the test run events handler. + /// + protected ITestRunEventsHandler TestRunEventsHandler => this.testRunEventsHandler; + + /// + /// Gets the test run cache. + /// + protected ITestRunCache TestRunCache => this.testRunCache; + + protected bool IsCancellationRequested => this.isCancellationRequested; + + protected RunContext RunContext => this.runContext; + + protected FrameworkHandle FrameworkHandle => this.frameworkHandle; + + protected ICollection ExecutorUrisThatRanTests => this.executorUrisThatRanTests; + + #endregion + + #region Public methods + + public void RunTests() + { + try + { + this.RunTestsInternal(); + } + catch (Exception ex) + { + if (EqtTrace.IsErrorEnabled) + { + EqtTrace.Error("TestExecutionManager.RunTests: Failed to run the tests. Reason: {0}.", ex); + } + + var runCompletionError = new Exception(ex.Message, ex.InnerException); + + try + { + this.RaiseTestRunComplete(runCompletionError, this.isCancellationRequested, true, false, TimeSpan.Zero); + } + catch (Exception ex2) + { + if (EqtTrace.IsErrorEnabled) + { + EqtTrace.Error("TestExecutionManager.RunTests: Failed to raise runCompletion error. Reason: {0}.", ex2); + } + + // Todo: aajohn this does not crash the process currently because of the job queue. + // Let the process crash + throw; + } + + // No need to crash the process if the exception is a IO exception. bugfix #575875 + if (ex is FileNotFoundException + || ex is ArgumentException) + { + return; + } + + // Let the process crash. + throw; + } + + EqtTrace.Verbose("TestExecutionManager.RunTests: Run is complete."); + } + + internal void Abort() + { + EqtTrace.Verbose("BaseRunTests.Abort: Calling RaiseTestRunComplete"); + this.RaiseTestRunComplete(exception: null, canceled: this.isCancellationRequested, aborted: true, adapterHintToShutdownAfterRun: false, elapsedTime: TimeSpan.Zero); + } + + /// + /// Cancel the current run by setting cancellation token for active executor + /// + internal void Cancel() + { + ITestExecutor activeExecutor = this.activeExecutor; + isCancellationRequested = true; + if (activeExecutor != null) + { + Task.Run(() => CancelTestRunInternal(this.activeExecutor)); + } + } + + private void CancelTestRunInternal(ITestExecutor executor) + { + try + { + activeExecutor.Cancel(); + } + catch (Exception e) + { + EqtTrace.Info("{0}.Cancel threw an exception: {1} ", executor.GetType().FullName, e); + } + } + + #endregion + + #region Abstract methods + + protected abstract void BeforeRaisingTestRunComplete(bool exceptionsHitDuringRunTests); + + protected abstract IEnumerable> GetExecutorUriExtensionMap(IFrameworkHandle testExecutorFrameworkHandle, RunContext runContext); + + protected abstract void InvokeExecutor(LazyExtension executor, Tuple executorUriExtensionTuple, RunContext runContext, IFrameworkHandle frameworkHandle); + + #endregion + + #region Private methods + + private void RunTestsInternal() + { + long totalTests = 0; + + var executorUriExtensionMap = this.GetExecutorUriExtensionMap(this.frameworkHandle, this.runContext); + + // Set on the logger the TreatAdapterErrorAsWarning setting from runsettings. + this.SetAdapterLoggingSettings(); + + var stopwatch = new Stopwatch(); + stopwatch.Start(); + + var exceptionsHitDuringRunTests = this.RunTestInternalWithExecutors( + executorUriExtensionMap, + totalTests); + + stopwatch.Stop(); + + this.BeforeRaisingTestRunComplete(exceptionsHitDuringRunTests); + + // Send the test run complete event. + this.RaiseTestRunComplete(null, this.isCancellationRequested, false, this.frameworkHandle.EnableShutdownAfterTestRun, stopwatch.Elapsed); + } + + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "This methods must call all possible executors and not fail on crash in any executor.")] + private bool RunTestInternalWithExecutors(IEnumerable> executorUriExtensionMap, long totalTests) + { + // Call the executor for each group of tests. + var exceptionsHitDuringRunTests = false; + + foreach (var executorUriExtensionTuple in executorUriExtensionMap) + { + // Get the executor + var extensionManager = this.GetExecutorExtensionManager(executorUriExtensionTuple.Item2); + + // Look up the executor. + var executor = extensionManager.TryGetTestExtension(executorUriExtensionTuple.Item1); + if (executor != null) + { + try + { + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose( + "TestExecutionManager.RunTests: Running tests for {0}", + executor.Metadata.ExtensionUri); + } + + // set the active executor + this.activeExecutor = executor.Value; + + // If test run cancellation is requested, skip the next executor + if (this.isCancellationRequested) + { + break; + } + + // Run the tests. + this.InvokeExecutor(executor, executorUriExtensionTuple, this.runContext, this.frameworkHandle); + + // Identify whether the executor did run any tests at all + if (this.testRunCache.TotalExecutedTests > totalTests) + { + this.executorUrisThatRanTests.Add(executorUriExtensionTuple.Item1.AbsoluteUri); + totalTests = this.testRunCache.TotalExecutedTests; + } + + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose( + "TestExecutionManager.RunTests: Completed running tests for {0}", + executor.Metadata.ExtensionUri); + } + } + catch (Exception e) + { + exceptionsHitDuringRunTests = true; + + if (EqtTrace.IsErrorEnabled) + { + EqtTrace.Error( + "TestExecutionManager.RunTests: An exception occurred while invoking executor {0}. {1}.", + executorUriExtensionTuple.Item1, + e); + } + + this.TestRunEventsHandler?.HandleLogMessage( + TestMessageLevel.Error, + string.Format( + CultureInfo.CurrentCulture, + CrossPlatEngine.Resources.ExceptionFromRunTests, + executorUriExtensionTuple.Item1, + ExceptionUtilities.GetExceptionMessage(e))); + } + finally + { + this.activeExecutor = null; + } + } + else + { + // Commenting this out because of a compatibility issue with Microsoft.Dotnet.ProjectModel released on nuGet.org. + //var runtimeVersion = string.Concat(PlatformServices.Default.Runtime.RuntimeType, " ", + // PlatformServices.Default.Runtime.RuntimeVersion); + var runtimeVersion = " "; + this.TestRunEventsHandler?.HandleLogMessage( + TestMessageLevel.Warning, + string.Format(CultureInfo.CurrentUICulture, CrossPlatEngine.Resources.NoMatchingExecutor, executorUriExtensionTuple.Item1, runtimeVersion)); + } + } + + return exceptionsHitDuringRunTests; + } + + private TestExecutorExtensionManager GetExecutorExtensionManager(string extensionAssembly) + { + try + { + if (string.IsNullOrEmpty(extensionAssembly) + || string.Equals(extensionAssembly, ObjectModel.Constants.UnspecifiedAdapterPath)) + { + // full execution. Since the extension manager is cached this can be created multiple times without harming performance. + return TestExecutorExtensionManager.Create(); + } + else + { + return TestExecutorExtensionManager.GetExecutionExtensionManager(extensionAssembly); + } + } + catch (Exception ex) + { + EqtTrace.Error( + "TestExecutorExtensionManager: Get Executor Extensions: Exception occured while loading extensions {0}", + ex); + + return null; + } + } + + private void SetAdapterLoggingSettings() + { + // Todo: aajohn enable the below once runsettings is in. + //var sessionMessageLogger = testExecutorFrameworkHandle as TestSessionMessageLogger; + //if (sessionMessageLogger != null + // && testExecutionContext != null + // && testExecutionContext.TestRunConfiguration != null) + //{ + // sessionMessageLogger.TreatTestAdapterErrorsAsWarnings + // = testExecutionContext.TestRunConfiguration.TreatTestAdapterErrorsAsWarnings; + //} + } + + /// + /// Raise the test run complete event. + /// + private void RaiseTestRunComplete( + Exception exception, + bool canceled, + bool aborted, + bool adapterHintToShutdownAfterRun, + TimeSpan elapsedTime) + { + var runStats = this.testRunCache != null + ? this.testRunCache.TestRunStatistics + : new TestRunStatistics(new Dictionary()); + var lastChunk = this.testRunCache != null + ? this.testRunCache.GetLastChunk() + : new List(); + if (this.testRunEventsHandler != null) + { + Collection attachments = this.frameworkHandle?.Attachments; + var testRunCompleteEventArgs = new TestRunCompleteEventArgs( + runStats, + canceled, + aborted, + exception, + attachments, + elapsedTime); + + var testRunChangedEventArgs = new TestRunChangedEventArgs(runStats, lastChunk, null); + + this.testRunEventsHandler.HandleTestRunComplete( + testRunCompleteEventArgs, + testRunChangedEventArgs, + attachments, + this.executorUrisThatRanTests); + } + else + { + EqtTrace.Warning("Could not pass run completion as the callback is null. Aborted :{0}", aborted); + } + } + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Execution/ExecutionManager.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Execution/ExecutionManager.cs new file mode 100644 index 0000000000..986f860cda --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Execution/ExecutionManager.cs @@ -0,0 +1,217 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Execution +{ + using System; + using System.Collections.Generic; + using System.ComponentModel; + using System.Threading; + using System.Threading.Tasks; + + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework; + using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; + using Microsoft.VisualStudio.TestPlatform.Common.SettingsProvider; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Discovery; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.EventHandlers; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.ClientProtocol; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.TesthostProtocol; + using ObjectModel.Utilities; + /// + /// Orchestrates test execution related functionality for the engine communicating with the test host process. + /// + public class ExecutionManager : IExecutionManager + { + private ITestRunEventsHandler testRunEventsHandler; + + private BaseRunTests activeTestRun; + + #region IExecutionManager Implementation + + /// + /// Initializes the execution manager. + /// + /// The path to additional extensions. + public void Initialize(IEnumerable pathToAdditionalExtensions) + { + // Start using these additional extensions + TestPluginCache.Instance.UpdateAdditionalExtensions(pathToAdditionalExtensions, shouldLoadOnlyWellKnownExtensions: false); + this.LoadExtensions(); + } + + /// + /// Starts the test run + /// + /// The adapter Source Map. + /// The run Settings. + /// The test Execution Context. + /// EventHandler for handling test cases level events from Engine. + /// EventHandler for handling execution events from Engine. + public void StartTestRun(Dictionary> adapterSourceMap, string runSettings, TestExecutionContext testExecutionContext, ITestCaseEventsHandler testCaseEvents, ITestRunEventsHandler runEventsHandler) + { + this.testRunEventsHandler = runEventsHandler; + var testCaseEventsHandler = testCaseEvents; + var inProcDataCollectionExtensionManager = new InProcDataCollectionExtensionManager(runSettings); + + try + { + if (inProcDataCollectionExtensionManager.IsInProcDataCollectionEnabled) + { + testCaseEventsHandler = new TestCaseEventsHandler(inProcDataCollectionExtensionManager, testCaseEvents); + inProcDataCollectionExtensionManager.TriggerTestSessionStart(); + } + + using (var testRunCache = new TestRunCache( + testExecutionContext.FrequencyOfRunStatsChangeEvent, + testExecutionContext.RunStatsChangeEventTimeout, + this.OnCacheHit)) + { + activeTestRun = new RunTestsWithSources( + adapterSourceMap, + testRunCache, + runSettings, + testExecutionContext, + testCaseEventsHandler, + runEventsHandler); + activeTestRun.RunTests(); + } + } + finally + { + if (inProcDataCollectionExtensionManager.IsInProcDataCollectionEnabled) + { + inProcDataCollectionExtensionManager.TriggerTestSessionEnd(); + } + activeTestRun = null; + } + } + + /// + /// Starts the test run with tests. + /// + /// The test list. + /// The run Settings. + /// The test Execution Context. + /// EventHandler for handling test cases level events from Engine. + /// EventHandler for handling execution events from Engine. + public void StartTestRun(IEnumerable tests, string runSettings, TestExecutionContext testExecutionContext, ITestCaseEventsHandler testCaseEvents, ITestRunEventsHandler runEventsHandler) + { + this.testRunEventsHandler = runEventsHandler; + var testCaseEventsHandler = testCaseEvents; + var inProcDataCollectionExtensionManager = new InProcDataCollectionExtensionManager(runSettings); + + try + { + if (inProcDataCollectionExtensionManager.IsInProcDataCollectionEnabled) + { + testCaseEventsHandler = new TestCaseEventsHandler(inProcDataCollectionExtensionManager, testCaseEvents); + inProcDataCollectionExtensionManager.TriggerTestSessionStart(); + } + + using (var testRunCache = new TestRunCache( + testExecutionContext.FrequencyOfRunStatsChangeEvent, + testExecutionContext.RunStatsChangeEventTimeout, + this.OnCacheHit)) + { + var runTestsWithTests = new RunTestsWithTests(tests, testRunCache, runSettings, testExecutionContext, testCaseEventsHandler, runEventsHandler); + activeTestRun = runTestsWithTests; + runTestsWithTests.RunTests(); + } + } + finally + { + if (inProcDataCollectionExtensionManager.IsInProcDataCollectionEnabled) + { + inProcDataCollectionExtensionManager.TriggerTestSessionEnd(); + } + activeTestRun = null; + } + } + + /// + /// Cancel the test execution. + /// + public void Cancel() + { + if (this.activeTestRun == null) + { + var testRunCompleteEventArgs = new TestRunCompleteEventArgs(null, true, false, null, null, TimeSpan.Zero); + this.testRunEventsHandler.HandleTestRunComplete(testRunCompleteEventArgs, null, null, null); + } + else + { + this.activeTestRun.Cancel(); + } + } + + /// + /// Aborts the test execution. + /// + public void Abort() + { + if (this.activeTestRun == null) + { + var testRunCompleteEventArgs = new TestRunCompleteEventArgs(null, false, true, null, null, TimeSpan.Zero); + this.testRunEventsHandler.HandleTestRunComplete(testRunCompleteEventArgs, null, null, null); + } + else + { + this.activeTestRun.Abort(); + } + + } + + #endregion + + #region private methods + + private void OnCacheHit(TestRunStatistics testRunStats, ICollection results, ICollection inProgressTests) + { + if (this.testRunEventsHandler != null) + { + var testRunChangedEventArgs = new TestRunChangedEventArgs(testRunStats, results, inProgressTests); + this.testRunEventsHandler.HandleTestRunStatsChange(testRunChangedEventArgs); + } + else + { + if (EqtTrace.IsWarningEnabled) + { + EqtTrace.Warning("Could not pass the message for changes in test run statistics as the callback is null."); + } + } + } + + private void LoadExtensions() + { + try + { + // Load the extensions on creation so that we dont have to spend time during first execution. + EqtTrace.Verbose("TestExecutorService: Loading the extensions"); + + TestDiscoveryExtensionManager.LoadAndInitializeAllExtensions(false); + + EqtTrace.Verbose("TestExecutorService: Loaded the discoverers"); + + TestExecutorExtensionManager.LoadAndInitializeAllExtensions(false); + + EqtTrace.Verbose("TestExecutorService: Loaded the executors"); + + SettingsProviderExtensionManager.LoadAndInitializeAllExtensions(false); + + EqtTrace.Verbose("TestExecutorService: Loaded the settings providers"); + EqtTrace.Info("TestExecutorService: Loaded the extensions"); + } + catch (Exception ex) + { + if (EqtTrace.IsWarningEnabled) + { + EqtTrace.Warning("TestExecutorWebService: Exception occured while calling test connection. {0}", ex); + } + } + } + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Execution/InProcDataCollectionExtensionManager.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Execution/InProcDataCollectionExtensionManager.cs new file mode 100644 index 0000000000..76571829d5 --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Execution/InProcDataCollectionExtensionManager.cs @@ -0,0 +1,220 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Execution +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Linq; + using System.Reflection; +#if !NET46 + using System.Runtime.Loader; +#endif + + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.InProcDataCollector; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities; + using ObjectModel.DataCollector.InProcDataCollector; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine; + + /// + /// The in process data collection extension manager. + /// + public class InProcDataCollectionExtensionManager + { + private IDictionary inProcDataCollectors; + + public InProcDataCollectionExtensionManager() + { + this.inProcDataCollectors = new Dictionary(); + } + + public InProcDataCollectionExtensionManager(string runsettings) + { + this.inProcDataCollectors = new Dictionary(); + InProcDataCollectionUtilities.ReadInProcDataCollectionRunSettings(runsettings); + this.InitializeInProcDataCollectors(); + } + + public bool IsInProcDataCollectionEnabled + { + get + { + return InProcDataCollectionUtilities.InProcDataCollectionEnabled; + } + } + + public Collection InProcDataCollectorSettingsCollection { get; private set; } + + /// + /// Loads all the inproc data collector dlls + /// + public void InitializeInProcDataCollectors() + { + try + { + if (this.IsInProcDataCollectionEnabled) + { + this.InProcDataCollectorSettingsCollection = InProcDataCollectionUtilities.GetInProcDataCollectorSettings(); + + var interfaceType = typeof(InProcDataCollection); + foreach (var inProcDc in this.InProcDataCollectorSettingsCollection) + { + var assembly = this.LoadInProcDataCollectorExtension(inProcDc.CodeBase); + var type = + assembly?.GetTypes() + .FirstOrDefault(x => (x.AssemblyQualifiedName.Equals(inProcDc.AssemblyQualifiedName) && interfaceType.GetTypeInfo().IsAssignableFrom(x))); + if (type != null && !this.inProcDataCollectors.ContainsKey(type.AssemblyQualifiedName))//this.inProcDataCollectionTypes.Contains(type) + { + this.inProcDataCollectors[type.AssemblyQualifiedName] = CreateObjectFromType(type); + } + } + } + } + catch (Exception ex) + { + EqtTrace.Error("Error occured while Initializing the datacollectors : {0}", ex); + } + } + + /// + /// The trigger session start. + /// + public virtual void TriggerTestSessionStart() + { + TestSessionStartArgs testSessionStartArgs = new TestSessionStartArgs(); + this.TriggerInProcDataCollectionMethods(Constants.TestSessionStartMethodName, testSessionStartArgs); + } + + + + /// + /// The trigger session end. + /// + public virtual void TriggerTestSessionEnd() + { + var testSessionEndArgs = new TestSessionEndArgs(); + this.TriggerInProcDataCollectionMethods(Constants.TestSessionEndMethodName, testSessionEndArgs); + } + + /// + /// The trigger test case start. + /// + public virtual void TriggerTestCaseStart(TestCase testCase) + { + var testCaseStartArgs = new TestCaseStartArgs(testCase); + this.TriggerInProcDataCollectionMethods(Constants.TestCaseStartMethodName, testCaseStartArgs); + + } + + /// + /// The trigger test case end. + /// + public virtual void TriggerTestCaseEnd(TestCase testCase, TestOutcome outcome) + { + var testCaseEndArgs = new TestCaseEndArgs(testCase, outcome); + this.TriggerInProcDataCollectionMethods(Constants.TestCaseEndMethodName, testCaseEndArgs); + } + + /// + /// Loads the assembly into the default context based on the codebase path + /// + /// + /// + private Assembly LoadInProcDataCollectorExtension(string codeBase) + { + Assembly assembly = null; + try + { +#if NET46 + assembly = Assembly.LoadFrom(codeBase); +#else + assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(codeBase); +#endif + + } + catch (Exception ex) + { + EqtTrace.Error( + "Error occured while loading the InProcDataCollector : {0} , Exception Details : {1}", codeBase, ex); + } + + return assembly; + } + + private static object CreateObjectFromType(Type type) + { + object obj = null; + + var typeInfo = type.GetTypeInfo(); + var constructorInfo = typeInfo?.GetConstructor(Type.EmptyTypes); + obj = constructorInfo?.Invoke(new object[] { }); + + return obj; + } + + private static MethodInfo GetMethodInfoFromType(Type type, string funcName, Type[] argumentTypes) + { + MethodInfo methodInfo = null; + + var typeInfo = type.GetTypeInfo(); + methodInfo = typeInfo?.GetMethod(funcName, argumentTypes); + return methodInfo; + } + + private void TriggerInProcDataCollectionMethods(string methodName, InProcDataCollectionArgs methodArg) + { + try + { + foreach (var entry in this.inProcDataCollectors) + { + var methodInfo = GetMethodInfoFromType(entry.Value.GetType(), methodName, new[] { methodArg.GetType() }); + + if (methodName.Equals(Constants.TestSessionStartMethodName)) + { + var testSessionStartArgs = (TestSessionStartArgs)methodArg; + var config = + this.InProcDataCollectorSettingsCollection.FirstOrDefault( + x => x.AssemblyQualifiedName.Equals(entry.Key))? + .Configuration.OuterXml; + testSessionStartArgs.Configuration = config; + methodInfo?.Invoke(entry.Value, new[] { testSessionStartArgs }); + } + else + { + methodInfo?.Invoke(entry.Value, new[] { methodArg }); + } + } + } + catch (Exception ex) + { + EqtTrace.Error("Error occured while Triggering the {0} method : {1}", methodName, ex); + } + } + } + + public static class Constants + { + /// + /// The test session start method name. + /// + public const string TestSessionStartMethodName = "TestSessionStart"; + + /// + /// The test session end method name. + /// + public const string TestSessionEndMethodName = "TestSessionEnd"; + + /// + /// The test case start method name. + /// + public const string TestCaseStartMethodName = "TestCaseStart"; + + /// + /// The test case end method name. + /// + public const string TestCaseEndMethodName = "TestCaseEnd"; + + } +} diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Execution/RunTestsWithSources.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Execution/RunTestsWithSources.cs new file mode 100644 index 0000000000..f4bcf42966 --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Execution/RunTestsWithSources.cs @@ -0,0 +1,166 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Execution +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + using System.Threading.Tasks; + + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.Utilities; + using Microsoft.VisualStudio.TestPlatform.Common.Filtering; + using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Adapter; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Discovery; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.ClientProtocol; + + using ObjectModel.Logging; + using ObjectModel.Client; + internal class RunTestsWithSources : BaseRunTests + { + private Dictionary> adapterSourceMap; + + private Dictionary, IEnumerable> executorUriVsSourceList; + + public RunTestsWithSources(Dictionary> adapterSourceMap, ITestRunCache testRunCache, string runSettings, TestExecutionContext testExecutionContext, ITestCaseEventsHandler testCaseEventsHandler, ITestRunEventsHandler testRunEventsHandler) + : this(adapterSourceMap, testRunCache, runSettings, testExecutionContext, testCaseEventsHandler, testRunEventsHandler, null) + { + } + + /// + /// Used for unit testing only. + /// + /// + /// + /// + /// + /// + /// + /// + internal RunTestsWithSources(Dictionary> adapterSourceMap, ITestRunCache testRunCache, string runSettings, TestExecutionContext testExecutionContext, ITestCaseEventsHandler testCaseEventsHandler, ITestRunEventsHandler testRunEventsHandler, Dictionary, IEnumerable> executorUriVsSourceList) + : base(testRunCache, runSettings, testExecutionContext, testCaseEventsHandler, testRunEventsHandler) + { + this.adapterSourceMap = adapterSourceMap; + this.executorUriVsSourceList = executorUriVsSourceList; + } + + protected override void BeforeRaisingTestRunComplete(bool exceptionsHitDuringRunTests) + { + // If run was with sources and no test was executed and cancellation was not requested, + // then raise a warning saying that no test was present in the sources. + // The warning is raised only if total no of tests that have been run is zero. + if (!exceptionsHitDuringRunTests && this.executorUriVsSourceList?.Count > 0 && !this.IsCancellationRequested + && this.TestRunCache?.TotalExecutedTests <= 0) + { + IEnumerable sources = new List(); + var sourcesArray = this.adapterSourceMap.Values.Aggregate(sources, (current, enumerable) => current.Concat(enumerable)).ToArray(); + var sourcesString = string.Join(" ", sourcesArray); + + this.TestRunEventsHandler?.HandleLogMessage( + TestMessageLevel.Warning, + string.Format( + CultureInfo.CurrentUICulture, + CrossPlatEngine.Resources.TestRunFailed_NoTestsAreAvailableInTheSources, + sourcesString)); + } + } + + protected override IEnumerable> GetExecutorUriExtensionMap(IFrameworkHandle testExecutorFrameworkHandle, RunContext runContext) + { + this.executorUriVsSourceList = this.GetExecutorVsSourcesList(testExecutorFrameworkHandle); + var executorUris = this.executorUriVsSourceList.Keys; + + if (!string.IsNullOrEmpty(this.TestExecutionContext.TestCaseFilter)) + { + runContext.FilterExpressionWrapper = new FilterExpressionWrapper(this.TestExecutionContext.TestCaseFilter); + } + else + { + runContext.FilterExpressionWrapper = null; + } + + return executorUris; + } + + protected override void InvokeExecutor(LazyExtension executor, Tuple executorUriExtensionTuple, RunContext runContext, IFrameworkHandle frameworkHandle) + { + executor?.Value.RunTests(this.executorUriVsSourceList[executorUriExtensionTuple], runContext, frameworkHandle); + } + + /// + /// Returns executor Vs sources list + /// + private Dictionary, IEnumerable> GetExecutorVsSourcesList(IMessageLogger logger) + { + var result = new Dictionary, IEnumerable>(); + + var verifiedExtensionSourceMap = new Dictionary>(); + + // Validate the sources + foreach (var kvp in this.adapterSourceMap) + { + var verifiedSources = DiscoveryManager.GetValidSources(kvp.Value, logger); + if (verifiedSources.Any()) + { + verifiedExtensionSourceMap.Add(kvp.Key, kvp.Value); + } + } + + // No valid source is found => no executor vs source map + if (!verifiedExtensionSourceMap.Any()) + { + return result; + } + + foreach (var kvp in verifiedExtensionSourceMap) + { + Dictionary, IEnumerable> + discovererToSourcesMap = DiscovererEnumerator.GetDiscovererToSourcesMap( + kvp.Key, + kvp.Value, + logger); + + // Warning is logged by the inner layer + if (discovererToSourcesMap == null || discovererToSourcesMap.Count == 0) + { + continue; + } + + foreach (var discoverer in discovererToSourcesMap.Keys) + { + var executorUri = discoverer.Metadata.DefaultExecutorUri; + if (executorUri == null) + { + string errorMessage = string.Format( + CultureInfo.CurrentUICulture, + CrossPlatEngine.Resources.IgnoringExecutorAsNoDefaultExecutorUriAttribute, + discoverer.Value); + logger.SendMessage(TestMessageLevel.Warning, errorMessage); + continue; + } + + var executorUriExtensionTuple = new Tuple(executorUri, kvp.Key); + + if (!result.ContainsKey(executorUriExtensionTuple)) + { + result.Add(executorUriExtensionTuple, discovererToSourcesMap[discoverer]); + } + else + { + string errorMessage = string.Format( + CultureInfo.CurrentUICulture, + CrossPlatEngine.Resources.DuplicateAdaptersFound, + executorUri, + discoverer.Value); + logger.SendMessage(TestMessageLevel.Warning, errorMessage); + } + } + } + + return result; + } + } +} diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Execution/RunTestsWithTests.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Execution/RunTestsWithTests.cs new file mode 100644 index 0000000000..203f6d17cf --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Execution/RunTestsWithTests.cs @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Execution +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.Utilities; + using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Adapter; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.ClientProtocol; + using ObjectModel; + using ObjectModel.Client; + + internal class RunTestsWithTests : BaseRunTests + { + private IEnumerable testCases; + + private Dictionary, List> executorUriVsTestList; + + public RunTestsWithTests(IEnumerable testCases, ITestRunCache testRunCache, string runSettings, TestExecutionContext testExecutionContext, ITestCaseEventsHandler testCaseEventsHandler, ITestRunEventsHandler testRunEventsHandler) + : this(testCases, testRunCache, runSettings, testExecutionContext, testCaseEventsHandler, testRunEventsHandler, null) + { + } + + /// + /// Used for unit testing only. + /// + /// + /// + /// + /// + /// + /// + /// + internal RunTestsWithTests(IEnumerable testCases, ITestRunCache testRunCache, string runSettings, TestExecutionContext testExecutionContext, ITestCaseEventsHandler testCaseEventsHandler, ITestRunEventsHandler testRunEventsHandler, Dictionary, List> executorUriVsTestList) + : base(testRunCache, runSettings, testExecutionContext, testCaseEventsHandler, testRunEventsHandler) + { + this.testCases = testCases; + this.executorUriVsTestList = executorUriVsTestList; + } + + protected override void BeforeRaisingTestRunComplete(bool exceptionsHitDuringRunTests) + { + // Do Nothing. + } + + protected override IEnumerable> GetExecutorUriExtensionMap(IFrameworkHandle testExecutorFrameworkHandle, RunContext runContext) + { + this.executorUriVsTestList = this.GetExecutorVsTestCaseList(this.testCases); + + Debug.Assert(this.TestExecutionContext.TestCaseFilter == null, "TestCaseFilter should be null for specific tests."); + runContext.FilterExpressionWrapper = null; + + return this.executorUriVsTestList.Keys; + } + + protected override void InvokeExecutor(LazyExtension executor, Tuple executorUri, RunContext runContext, IFrameworkHandle frameworkHandle) + { + executor?.Value.RunTests(this.executorUriVsTestList[executorUri], runContext, frameworkHandle); + } + + /// + /// Returns the executor Vs TestCase list + /// + private Dictionary, List> GetExecutorVsTestCaseList(IEnumerable tests) + { + var result = new Dictionary, List>(); + foreach (var test in tests) + { + List testList; + + // Todo aajohn Fill this in with the right extension value. + var executorUriExtensionTuple = new Tuple( + test.ExecutorUri, + ObjectModel.Constants.UnspecifiedAdapterPath); + + if (result.TryGetValue(executorUriExtensionTuple, out testList)) + { + testList.Add(test); + } + else + { + testList = new List(); + testList.Add(test); + result.Add(executorUriExtensionTuple, testList); + } + } + + return result; + } + } +} diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Execution/TestRunCache.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Execution/TestRunCache.cs new file mode 100644 index 0000000000..e7400aa6b8 --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Execution/TestRunCache.cs @@ -0,0 +1,402 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Execution +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Diagnostics; + using System.Linq; + using System.Threading; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using ObjectModel.Client; + + /// + /// Maintains a cache of last 'n' test results and maintains stats for the complete run. + /// + internal class TestRunCache : ITestRunCache, IDisposable + { + #region Private members + + /// + /// Specifies whether the object is disposed or not. + /// + private bool isDisposed; + + /// + /// Test run stats + /// + private Dictionary runStats; + + /// + /// Total tests which have currently executed + /// + private long totalExecutedTests; + + /// + /// Callback used when cache is ready to report some test results/case. + /// + private OnCacheHit onCacheHit; + + /// + /// Max size of the test result buffer + /// + private long cacheSize; + + /// + /// Timeout that triggers sending results regardless of cache size. + /// + private TimeSpan cacheTimeout; + + /// + /// Timer for cache + /// + private Timer timer; + + /// + /// Last time results were sent. + /// + private DateTime lastUpdate; + + /// + /// The test case currently in progress. + /// + private ICollection inProgressTests; + + /// + /// Test results buffer + /// + private ICollection testResults; + + /// + /// Sync object + /// + private object syncObject; + + #endregion + + #region Constructor + + /// + /// Initializes a new instance of the class. + /// + /// The cache size. + /// The cache timeout. + /// The on cache hit. + internal TestRunCache(long cacheSize, TimeSpan cacheTimeout, OnCacheHit onCacheHit) + { + Debug.Assert(cacheSize > 0, "Buffer size cannot be less than zero"); + Debug.Assert(onCacheHit != null, "Callback which listens for cache size limit cannot be null."); + Debug.Assert(cacheTimeout > TimeSpan.MinValue, "The cache timeout must be greater than min value."); + + if (cacheTimeout.TotalMilliseconds > int.MaxValue) + { + cacheTimeout = TimeSpan.FromMilliseconds(int.MaxValue / 2); + } + + this.cacheSize = cacheSize; + this.onCacheHit = onCacheHit; + this.lastUpdate = DateTime.Now; + this.cacheTimeout = cacheTimeout; + this.inProgressTests = new Collection(); + this.testResults = new Collection(); + this.runStats = new Dictionary(); + this.syncObject = new object(); + + this.timer = new Timer(this.OnCacheTimeHit, this, cacheTimeout, cacheTimeout); + } + + #endregion + + #region Delegates + + /// + /// Called when the cache is ready to report on the current status. + /// + internal delegate void OnCacheHit(TestRunStatistics testRunStats, ICollection results, ICollection inProgressTests); + + #endregion + + #region Properties + + /// + /// Gets the test results present in the cache currently. + /// + public ICollection TestResults + { + get + { + lock (this.syncObject) + { + return this.testResults; + } + } + } + + /// + /// Gets the set of in-progress test cases present in the cache currently. + /// + public ICollection InProgressTests + { + get + { + lock (this.syncObject) + { + return this.inProgressTests; + } + } + } + + /// + /// Gets the total executed tests. + /// + public long TotalExecutedTests + { + get + { + lock (this.syncObject) + { + return this.totalExecutedTests; + } + } + } + + /// + /// Gets the test run stats + /// + public TestRunStatistics TestRunStatistics + { + get + { + lock (this.syncObject) + { + var stats = new TestRunStatistics(new Dictionary(this.runStats)); + stats.ExecutedTests = this.TotalExecutedTests; + + return stats; + } + } + } + + #endregion + + #region Public/internal methods + + /// + /// Disposes the cache + /// + public void Dispose() + { + this.Dispose(true); + + // Use SupressFinalize in case a subclass + // of this valueType implements a finalizer. + GC.SuppressFinalize(this); + } + + /// + /// Notifies the cache that a test is starting. + /// This notification comes from the adapter to the engine which then calls into the cache. + /// + /// The test Case. + public void OnTestStarted(TestCase testCase) + { + lock (this.syncObject) + { + this.inProgressTests.Add(testCase); + + this.CheckForCacheHit(); + } + } + + /// + /// Notifies the cache of a new test result from the adapter. + /// + /// The test result. + public void OnNewTestResult(TestResult testResult) + { + lock (this.syncObject) + { + this.totalExecutedTests++; + this.testResults.Add(testResult); + + long count; + if (this.runStats.TryGetValue(testResult.Outcome, out count)) + { + count++; + } + else + { + count = 1; + } + + this.runStats[testResult.Outcome] = count; + + this.RemoveInProgress(testResult); + + this.CheckForCacheHit(); + } + } + + /// + /// Notifies the cache of a test completion. + /// + /// + /// The completed Test. + /// + /// True if this test has been removed from the list of in progress tests. + public bool OnTestCompletion(TestCase completedTest) + { + lock (this.syncObject) + { + if (completedTest == null) + { + EqtTrace.Warning("TestRunCache: completedTest is null"); + return false; + } + + if (this.inProgressTests == null || this.inProgressTests.Count == 0) + { + EqtTrace.Warning("InProgressTests is null"); + return false; + } + + var removed = this.inProgressTests.Remove(completedTest); + if (removed) + { + return true; + } + + // Try finding/removing a matching test corresponding to the completed test + var inProgressTest = this.inProgressTests.Where(inProgress => inProgress.Id == completedTest.Id).FirstOrDefault(); + if (inProgressTest != null) + { + removed = this.inProgressTests.Remove(inProgressTest); + } + + return removed; + } + } + + /// + /// Returns the last chunk + /// + /// The set of test results remaining in the cache. + public ICollection GetLastChunk() + { + lock (this.syncObject) + { + var lastChunk = this.testResults; + + this.testResults = new Collection(); + + return lastChunk; + } + } + + /// + /// The dispose. + /// + /// Indicates if this needs to clean up managed resources. + /// + /// The dispose pattern is a best practice to differentiate between managed and native resources cleanup. + /// Even though this particular class does not have any native resources - honoring the pattern to be consistent throughout the code base. + /// + protected virtual void Dispose(bool disposing) + { + // If you need thread safety, use a lock around these + // operations, as well as in your methods that use the resource. + if (disposing && !this.isDisposed) + { + if (this.timer != null) + { + this.timer.Dispose(); + this.timer = null; + } + + // Indicate that the instance has been disposed. + this.isDisposed = true; + } + } + + #endregion + + #region Private methods. + + /// + /// Checks if the cache timeout/size has been met. + /// + private void CheckForCacheHit() + { + lock (this.syncObject) + { + // Send results when the specified cache size has been reached or + // after the specified cache timeout has been hit. + var timeDelta = DateTime.Now - this.lastUpdate; + + var inProgressTestsCount = this.inProgressTests.Count; + + if ((this.testResults.Count + inProgressTestsCount) >= this.cacheSize || (timeDelta >= this.cacheTimeout && inProgressTestsCount > 0)) + { + this.SendResults(); + } + } + } + + private void CheckForCacheHitOnTimer() + { + lock (this.syncObject) + { + if (this.testResults.Count > 0 || this.inProgressTests.Count > 0) + { + this.SendResults(); + } + } + } + + private void SendResults() + { + // Pass on the buffer to the listener and clear the old one + this.onCacheHit(this.TestRunStatistics, this.testResults, this.inProgressTests); + this.testResults = new Collection(); + this.inProgressTests = new Collection(); + this.lastUpdate = DateTime.Now; + + // Reset the timer + this.timer.Change(this.cacheTimeout, this.cacheTimeout); + + EqtTrace.Verbose("TestRunCache: OnNewTestResult: Notified the onCacheHit callback."); + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + private void OnCacheTimeHit(object state) + { + lock (this.syncObject) + { + try + { + this.CheckForCacheHitOnTimer(); + } + catch (Exception ex) + { + if (EqtTrace.IsErrorEnabled) + { + EqtTrace.Error("TestRunCache: OnCacheTimeHit: Exception occured while checking for cache hit. {0}", ex); + } + } + } + } + + private void RemoveInProgress(TestResult result) + { + var removed = this.OnTestCompletion(result.TestCase); + if (!removed) + { + EqtTrace.Warning("TestRunCache: No test found corresponding to testResult '{0}' in inProgress list.", result); + } + } + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Execution/TestRunStatistics.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Execution/TestRunStatistics.cs new file mode 100644 index 0000000000..8b1dc9dde2 --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Execution/TestRunStatistics.cs @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Client +{ + using System.Collections.Generic; + using System.Runtime.Serialization; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + + using Newtonsoft.Json; + + /// + /// Defines the test run stats header + /// + [DataContract] + public class TestRunStatistics : ITestRunStatistics + { + /// + /// Initializes a new instance of the class. + /// + /// The stats. + public TestRunStatistics(IDictionary stats) + { + this.Stats = stats; + } + + /// + /// Initializes a new instance of the class. + /// + /// The executed tests. + /// The stats. + /// This constructor is only needed to reconstruct the object during deserialization. + [JsonConstructor] + public TestRunStatistics(long executedTests, IDictionary stats) + { + this.ExecutedTests = executedTests; + this.Stats = stats; + } + + /// + /// Gets or sets the number of tests that have been run. + /// + [DataMember] + public long ExecutedTests { get; set; } + + /// + /// Gets the test stats which is the test outcome versus its state. + /// + [DataMember] + public IDictionary Stats { get; private set; } + + /// + /// Gets the number of tests with a specified outcome. + /// + /// The test outcome. + /// The number of tests with this outcome. + public long this[TestOutcome testOutcome] + { + get + { + long count; + if (this.Stats.TryGetValue(testOutcome, out count)) + { + return count; + } + + return 0; + } + } + } +} diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Friends.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Friends.cs new file mode 100644 index 0000000000..bb8ed573fc --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Friends.cs @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Microsoft.TestPlatform.CrossPlatEngine.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] +[assembly: InternalsVisibleTo("vstest.console.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] +[assembly: InternalsVisibleTo("datacollector.x86, PublicKey = 002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] +[assembly: InternalsVisibleTo("datacollector, PublicKey = 002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] + diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Helpers/Interfaces/IProcessHelper.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Helpers/Interfaces/IProcessHelper.cs new file mode 100644 index 0000000000..723407b155 --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Helpers/Interfaces/IProcessHelper.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.Interfaces +{ + using System.Diagnostics; + + /// + /// Interface for any process related functionality. This is needed for clean unit-testing. + /// + internal 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. + /// The process created. + Process LaunchProcess(string processPath, string arguments, string workingDirectory); + + /// + /// Gets the current process file path. + /// + /// The current process file path. + string GetCurrentProcessFileName(); + } +} diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Helpers/ProcessHelper.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Helpers/ProcessHelper.cs new file mode 100644 index 0000000000..91a1d0cf2f --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Helpers/ProcessHelper.cs @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers +{ + using System; + using System.Diagnostics; + + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + + /// + /// Helper class to deal with process related functionality. + /// + internal class ProcessHelper : IProcessHelper + { + /// + /// Launches the process with the arguments provided. + /// + /// The path to the process. + /// Process arguments. + /// Working directory of the process. + /// The process spawned. + /// Throws any exception that could result as part of the launch. + public Process LaunchProcess(string processPath, string arguments, string workingDirectory) + { + 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.EnableRaisingEvents = true; + + process.Start(); + } + catch (Exception exception) + { + process.Dispose(); + process = null; + + EqtTrace.Error("TestHost Process {0} failed to launch with the following exception: {1}", processPath, exception.Message); + + throw; + } + + return process; + } + + /// + /// Gets the current process file path. + /// + /// The current process file path. + public string GetCurrentProcessFileName() + { + return Process.GetCurrentProcess().MainModule.FileName; + } + } +} diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Hosting/DefaultTestHostManager.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Hosting/DefaultTestHostManager.cs new file mode 100644 index 0000000000..f4132e4df9 --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Hosting/DefaultTestHostManager.cs @@ -0,0 +1,164 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Hosting +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Reflection; + + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; + + /// + /// The default test host launcher for the engine. + /// This works for Desktop local scenarios + /// + public class DefaultTestHostManager : ITestHostManager + { + private const string X64TestHostProcessName = "testhost.exe"; + private const string X86TestHostProcessName = "testhost.x86.exe"; + private const string DotnetProcessName = "dotnet.exe"; + private const string DotnetProcessNameXPlat = "dotnet"; + + private Architecture architecture; + private ITestHostLauncher customTestHostLauncher; + + private Process testHostProcess; + private IProcessHelper processHelper; + + private EventHandler registeredExitHandler; + + /// + /// The constructor. + /// + /// + /// The architecture. + /// + public DefaultTestHostManager(Architecture architecture) + : this(architecture, new ProcessHelper()) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The architecture. + /// The process helper. + internal DefaultTestHostManager(Architecture architecture, IProcessHelper processHelper) + { + this.architecture = architecture; + this.processHelper = processHelper; + this.testHostProcess = null; + } + + /// + /// Gets the properties of the test executor launcher. These could be the targetID for emulator/phone specific scenarios. + /// + public IDictionary Properties + { + get + { + return new Dictionary(); + } + } + + /// + /// Sets a custom launcher + /// + /// Custom launcher to set + public void SetCustomLauncher(ITestHostLauncher customTestHostLauncher) + { + this.customTestHostLauncher = customTestHostLauncher; + } + + /// + /// Launches the test host for discovery/execution. + /// + /// Environment variables for the process. + /// The command line arguments to pass to the process. + /// ProcessId of launched Process. 0 means not launched. + public virtual int LaunchTestHost(IDictionary environmentVariables, IList commandLineArguments) + { + DeregisterForExitNotification(); + var testHostProcessInfo = GetTestHostProcessStartInfo(environmentVariables, commandLineArguments); + EqtTrace.Verbose("Launching default test Host Process {0} with arguments {1}", testHostProcessInfo.FileName, testHostProcessInfo.Arguments); + + if (this.customTestHostLauncher == null) + { + this.testHostProcess = this.processHelper.LaunchProcess(testHostProcessInfo.FileName, testHostProcessInfo.Arguments, testHostProcessInfo.WorkingDirectory); + } + else + { + int processId = this.customTestHostLauncher.LaunchTestHost(testHostProcessInfo); + this.testHostProcess = Process.GetProcessById(processId); + } + + return this.testHostProcess.Id; + } + + /// + /// Gives the ProcessStartInfo for the test host process + /// + /// + /// + /// ProcessStartInfo of the test host + public virtual TestProcessStartInfo GetTestHostProcessStartInfo(IDictionary environmentVariables, IList commandLineArguments) + { + var testHostProcessName = (architecture == Architecture.X86) ? X86TestHostProcessName : X64TestHostProcessName; + var currentWorkingDirectory = Path.GetDirectoryName(typeof(DefaultTestHostManager).GetTypeInfo().Assembly.Location); + string testhostProcessPath, processWorkingDirectory = null; + + // If we are running in the dotnet.exe context we do not want to launch testhost.exe but dotnet.exe with the testhost assembly. + // Since dotnet.exe is already built for multiple platforms this would avoid building testhost.exe also in multiple platforms. + var currentProcessFileName = this.processHelper.GetCurrentProcessFileName(); + if (currentProcessFileName.EndsWith(DotnetProcessName) || currentProcessFileName.EndsWith(DotnetProcessNameXPlat)) + { + testhostProcessPath = currentProcessFileName; + var testhostAssemblyPath = Path.Combine( + currentWorkingDirectory, + testHostProcessName.Replace("exe", "dll")); + commandLineArguments.Insert(0, testhostAssemblyPath); + processWorkingDirectory = Path.GetDirectoryName(currentProcessFileName); + } + else + { + testhostProcessPath = Path.Combine(currentWorkingDirectory, testHostProcessName); + // For IDEs and other scenario - Current directory should be the working directory - not the vstest.console.exe location + // For VS - this becomes the solution directory for example + // "TestResults" directory will be created at "current directory" of test host + processWorkingDirectory = Directory.GetCurrentDirectory(); + } + var argumentsString = string.Join(" ", commandLineArguments); + return new TestProcessStartInfo { FileName = testhostProcessPath, Arguments = argumentsString, EnvironmentVariables = environmentVariables, WorkingDirectory = processWorkingDirectory }; + } + + /// + /// Register for the exit event. + /// + /// The callback on exit. + public virtual void RegisterForExitNotification(Action abortCallback) + { + if (this.testHostProcess != null && abortCallback != null) + { + this.registeredExitHandler = new EventHandler((sender, args) => abortCallback()); + this.testHostProcess.Exited += this.registeredExitHandler; + } + } + + /// + /// Deregister for the exit event. + /// + public virtual void DeregisterForExitNotification() + { + if(this.testHostProcess != null && this.registeredExitHandler != null) + { + this.testHostProcess.Exited -= this.registeredExitHandler; + } + } + } +} diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Interfaces/ITestRunCache.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Interfaces/ITestRunCache.cs new file mode 100644 index 0000000000..665f4c03ff --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Interfaces/ITestRunCache.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Execution +{ + using System.Collections.Generic; + + using ObjectModel; + using ObjectModel.Client; + + /// + /// The cache for test execution information. + /// + internal interface ITestRunCache + { + #region Properties + + ICollection TestResults { get; } + + ICollection InProgressTests { get; } + + long TotalExecutedTests { get; } + + TestRunStatistics TestRunStatistics { get; } + + #endregion + + #region Methods + + void OnTestStarted(TestCase testCase); + + void OnNewTestResult(TestResult testResult); + + bool OnTestCompletion(TestCase completedTest); + + ICollection GetLastChunk(); + + #endregion + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Microsoft.TestPlatform.CrossPlatEngine.xproj b/src/Microsoft.TestPlatform.CrossPlatEngine/Microsoft.TestPlatform.CrossPlatEngine.xproj new file mode 100644 index 0000000000..4639db8e97 --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Microsoft.TestPlatform.CrossPlatEngine.xproj @@ -0,0 +1,19 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 40b59dda-aefd-45c9-be66-8d8684d88f2f + Microsoft.VisualStudio.TestPlatform.CrossPlatEngine + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\ + v4.5.2 + + + 2.0 + + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Properties/AssemblyInfo.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..e78300ff4f --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +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: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Microsoft.VisualStudio.TestPlatform.CrossPlatEngine")] +[assembly: AssemblyTrademark("")] + +// 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("dc31698e-13e9-41dd-8f32-3c4a7e4b147e")] diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources.Designer.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources.Designer.cs new file mode 100644 index 0000000000..c5666d76b6 --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources.Designer.cs @@ -0,0 +1,197 @@ +//------------------------------------------------------------------------------ +// +// 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 Microsoft.VisualStudio.TestPlatform.CrossPlatEngine { + using System; + using System.Reflection; + + + /// + /// 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.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + internal Resources() { + } + + /// + /// 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("Microsoft.TestPlatform.CrossPlatEngine.Resources", typeof(Resources).GetTypeInfo().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 Exception occurred while instantiating discoverer : {0}. + /// + public static string DiscovererInstantiationException { + get { + return ResourceManager.GetString("DiscovererInstantiationException", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Multiple test adapters with the same uri '{0}' were found. Ignoring adapter '{1}'. Please uninstall the conflicting adapter(s) to avoid this warning.. + /// + public static string DuplicateAdaptersFound { + get { + return ResourceManager.GetString("DuplicateAdaptersFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Ignoring the specified duplicate source '{0}'.. + /// + public static string DuplicateSource { + get { + return ResourceManager.GetString("DuplicateSource", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An exception occurred while test discoverer '{0}' was loading tests. Exception: {1}. + /// + public static string ExceptionFromLoadTests { + get { + return ResourceManager.GetString("ExceptionFromLoadTests", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An exception occurred while invoking executor '{0}': {1}. + /// + public static string ExceptionFromRunTests { + get { + return ResourceManager.GetString("ExceptionFromRunTests", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Could not find file {0}.. + /// + public static string FileNotFound { + get { + return ResourceManager.GetString("FileNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Ignoring the test executor corresponding to test discoverer {0} because the discoverer does not have the DefaultExecutorUri attribute . You might need to re-install the test adapter add-in.. + /// + public static string IgnoringExecutorAsNoDefaultExecutorUriAttribute { + get { + return ResourceManager.GetString("IgnoringExecutorAsNoDefaultExecutorUriAttribute", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Failed to initialize client proxy: could not connect to test process.. + /// + public static string InitializationFailed { + get { + return ResourceManager.GetString("InitializationFailed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to This operation is not allowed in the context of a non-debug run.. + /// + public static string LaunchDebugProcessNotAllowedForANonDebugRun { + get { + return ResourceManager.GetString("LaunchDebugProcessNotAllowedForANonDebugRun", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No test discoverer is registered to perform discovery of test cases. Register a test discoverer and try again.. + /// + public static string NoDiscovererRegistered { + get { + return ResourceManager.GetString("NoDiscovererRegistered", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Could not find test executor with URI '{0}'. Make sure that the test executor is installed and supports .net runtime version {1}.. + /// + public static string NoMatchingExecutor { + get { + return ResourceManager.GetString("NoMatchingExecutor", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to None of the specified source(s) '{0}' is valid. Fix the above errors/warnings and then try again. . + /// + public static string NoValidSourceFoundForDiscovery { + get { + return ResourceManager.GetString("NoValidSourceFoundForDiscovery", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to , . + /// + public static string StringSeperator { + get { + return ResourceManager.GetString("StringSeperator", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No test is available in {0}. Make sure that installed test discoverers & executors, platform & framework version settings are appropriate and try again.. + /// + public static string TestRunFailed_NoTestsAreAvailableInTheSources { + get { + return ResourceManager.GetString("TestRunFailed_NoTestsAreAvailableInTheSources", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No tests matched the filter because it contains one or more properties that are not valid ({0}). Specify filter expression containing valid properties ({1}) and try again.. + /// + public static string UnsupportedPropertiesInTestCaseFilter { + get { + return ResourceManager.GetString("UnsupportedPropertiesInTestCaseFilter", resourceCulture); + } + } + } +} diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources.resx b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources.resx new file mode 100644 index 0000000000..8faf831916 --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources.resx @@ -0,0 +1,167 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + Exception occurred while instantiating discoverer : {0} + + + Multiple test adapters with the same uri '{0}' were found. Ignoring adapter '{1}'. Please uninstall the conflicting adapter(s) to avoid this warning. + {0} is the unique identifier of test adapter. {1} is the name of the test adapter that shares a unique identifier with a previously loaded adapter. + + + Ignoring the specified duplicate source '{0}'. + + + An exception occurred while test discoverer '{0}' was loading tests. Exception: {1} + + + An exception occurred while invoking executor '{0}': {1} + + + Could not find file {0}. + + + Ignoring the test executor corresponding to test discoverer {0} because the discoverer does not have the DefaultExecutorUri attribute . You might need to re-install the test adapter add-in. + + + Failed to initialize client proxy: could not connect to test process. + + + This operation is not allowed in the context of a non-debug run. + + + No test discoverer is registered to perform discovery of test cases. Register a test discoverer and try again. + + + Could not find test executor with URI '{0}'. Make sure that the test executor is installed and supports .net runtime version {1}. + {0} - Executor uri String {1}- Version - .net Version Eg:-2.0.50727.00 + + + None of the specified source(s) '{0}' is valid. Fix the above errors/warnings and then try again. + + + , + + + No test is available in {0}. Make sure that installed test discoverers & executors, platform & framework version settings are appropriate and try again. + + + No tests matched the filter because it contains one or more properties that are not valid ({0}). Specify filter expression containing valid properties ({1}) and try again. + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/TestEngine.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/TestEngine.cs new file mode 100644 index 0000000000..070d73ae95 --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/TestEngine.cs @@ -0,0 +1,188 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine +{ + using System; + using System.Linq; + + using Microsoft.VisualStudio.TestPlatform.Common.Utilities; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.Parallel; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.DataCollection; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.DataCollection.Interfaces; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Hosting; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities; + + /// + /// Cross Platform test engine entry point for the client. + /// + public class TestEngine : ITestEngine + { + #region Private Fields + + private IProxyDiscoveryManager proxyDiscoveryManager; + private IProxyExecutionManager proxyExecutionManager; + private ITestExtensionManager testExtensionManager; + private IParallelProxyExecutionManager parallelProxyExecutionManager; + + #endregion + + #region ITestEngine implementation + + /// + /// Fetches the DiscoveryManager for this engine. This manager would provide all functionality required for discovery. + /// + /// ITestDiscoveryManager object that can do discovery + public IProxyDiscoveryManager GetDiscoveryManager() + { + return this.proxyDiscoveryManager ?? (this.proxyDiscoveryManager = new ProxyDiscoveryManager()); + } + + /// + /// Fetches the ExecutionManager for this engine. This manager would provide all functionality required for execution. + /// + /// + /// The test Run Criteria. + /// + /// + /// ITestExecutionManager object that can do execution + /// + public IProxyExecutionManager GetExecutionManager(TestRunCriteria testRunCriteria) + { + int parallelLevel = this.VerifyParallelSettingAndCalculateParallelLevel(testRunCriteria); + + var runconfiguration = XmlRunSettingsUtilities.GetRunConfigurationNode(testRunCriteria.TestRunSettings); + var architecture = runconfiguration.TargetPlatform; + var isDataCollectorEnabled = XmlRunSettingsUtilities.IsDataCollectionEnabled(testRunCriteria.TestRunSettings); + + // Initialize ProxyExecutionManager with data collection if data collectors are specififed in run settings. + Func proxyExecutionManagerCreator = () => isDataCollectorEnabled ? new ProxyExecutionManagerWithDataCollection(this.GetDataCollectionManager(architecture, testRunCriteria.TestRunSettings)) : new ProxyExecutionManager(); + + if (parallelLevel > 1) + { + if (parallelProxyExecutionManager == null) + { + parallelProxyExecutionManager = new ParallelProxyExecutionManager(proxyExecutionManagerCreator, parallelLevel); + } + else + { + parallelProxyExecutionManager.UpdateParallelLevel(parallelLevel); + } + + return parallelProxyExecutionManager; + } + else + { + return this.proxyExecutionManager ?? (this.proxyExecutionManager = proxyExecutionManagerCreator()); + } + } + + /// + /// Fetches the extension manager for this engine. This manager would provide extensibility features that this engine supports. + /// + /// ITestExtensionManager object that helps with extensibility + public ITestExtensionManager GetExtensionManager() + { + return this.testExtensionManager ?? (this.testExtensionManager = new TestExtensionManager()); + } + + /// + /// Retrieves the default test host manager for this engine. + /// + /// The architecture we want the test host manager for. + /// An instance of the test host manager. + public ITestHostManager GetDefaultTestHostManager(Architecture architecture) + { + // This is expected to be called once every run so returning a new instance every time. + return new DefaultTestHostManager(architecture); + } + + #endregion + + private static int GetDistinctNumberOfSources(TestRunCriteria testRunCriteria) + { + // No point in creating more processes if number of sources is less than what user configured for + int numSources = 1; + if (testRunCriteria.HasSpecificTests) + { + numSources = new System.Collections.Generic.HashSet( + testRunCriteria.Tests.Select((testCase) => testCase.Source)).Count; + } + else + { + numSources = testRunCriteria.Sources.Count(); + } + + return numSources; + } + + /// + /// Verifies Parallel Setting and returns parallel level to use based on the run criteria + /// + /// Test Run Criteria + /// Parallel Level to use + private int VerifyParallelSettingAndCalculateParallelLevel(TestRunCriteria testRunCriteria) + { + // Default is 1 + int parallelLevelToUse = 1; + try + { + // Check the User Parallel Setting + int userParallelSetting = RunSettingsUtilities.GetMaxCpuCount(testRunCriteria.TestRunSettings); + parallelLevelToUse = userParallelSetting == 0 ? Environment.ProcessorCount : userParallelSetting; + var enableParallel = parallelLevelToUse > 1; + + EqtTrace.Verbose("TestEngine: Initializing Parallel Execution as MaxCpuCount is set to: {0}", parallelLevelToUse); + + // Verify if the number of Sources is less than user setting of parallel + // we should use number of sources as the parallel level, if sources count is less than parallel level + if (enableParallel) + { + parallelLevelToUse = Math.Min(GetDistinctNumberOfSources(testRunCriteria), parallelLevelToUse); + + // If only one source, no need to use parallel service client + enableParallel = parallelLevelToUse > 1; + + if (EqtTrace.IsInfoEnabled) + { + EqtTrace.Verbose("TestEngine: ParallelExecution set to '{0}' as the parallel level is adjusted to '{1}' based on number of sources", enableParallel, parallelLevelToUse); + } + } + } + catch (Exception ex) + { + EqtTrace.Error("TestEngine: Error occured while initializing ParallelExecution: {0}", ex); + EqtTrace.Warning("TestEngine: Defaulting to Sequential Execution"); + + parallelLevelToUse = 1; + } + + return parallelLevelToUse; + } + + private IProxyDataCollectionManager GetDataCollectionManager(Architecture architecture, string settingsXml) + { + try + { + return new ProxyDataCollectionManager(architecture, settingsXml); + } + catch (Exception ex) + { + if (EqtTrace.IsErrorEnabled) + { + EqtTrace.Error("TestEngine: Error occured while initializing DataCollection Process: {0}", ex); + } + + if (EqtTrace.IsWarningEnabled) + { + EqtTrace.Warning("TestEngine: Skipping Data Collection"); + } + + return null; + } + } + } +} diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/TestExtensionManager.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/TestExtensionManager.cs new file mode 100644 index 0000000000..dfd1bdedb2 --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/TestExtensionManager.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine +{ + using System.Collections.Generic; + + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Hosting; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; + + /// + /// Orchestrates extensions for this engine. + /// + public class TestExtensionManager : ITestExtensionManager + { + public void UseAdditionalExtensions(IEnumerable pathToAdditionalExtensions, bool loadOnlyWellKnownExtensions) + { + TestPluginCache.Instance.UpdateAdditionalExtensions(pathToAdditionalExtensions, loadOnlyWellKnownExtensions); + } + } +} diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/TestHostManagerFactory.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/TestHostManagerFactory.cs new file mode 100644 index 0000000000..e69e50d65e --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/TestHostManagerFactory.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine +{ + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Discovery; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Execution; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.TesthostProtocol; + + /// + /// The factory that provides discovery and execution managers to the test host. + /// + public class TestHostManagerFactory : ITestHostManagerFactory + { + private IDiscoveryManager discoveryManager; + private IExecutionManager executionManager; + + /// + /// The discovery manager instance for any discovery related operations inside of the test host. + /// + /// The discovery manager. + public IDiscoveryManager GetDiscoveryManager() + { + if(this.discoveryManager == null) + { + this.discoveryManager = new DiscoveryManager(); + } + + return this.discoveryManager; + } + + /// + /// The execution manager instance for any discovery related operations inside of the test host. + /// + /// The execution manager. + public IExecutionManager GetExecutionManager() + { + if (this.executionManager == null) + { + this.executionManager = new ExecutionManager(); + } + + return this.executionManager; + } + } +} diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/project.json b/src/Microsoft.TestPlatform.CrossPlatEngine/project.json new file mode 100644 index 0000000000..ac845d8980 --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/project.json @@ -0,0 +1,30 @@ +{ + "version": "15.0.0-*", + "buildOptions": { + "delaySign": true, + "keyFile": "../../scripts/key.snk", + "warningsAsErrors": true + }, + + "dependencies": { + "Microsoft.TestPlatform.CommunicationUtilities": "15.0.0-*", + "Microsoft.TestPlatform.ObjectModel": "15.0.0-*", + "Microsoft.TestPlatform.Common": "15.0.0-*" + //"Microsoft.Extensions.PlatformAbstractions": "1.0.0-rc2-20896" + }, + + "frameworks": { + "netstandard1.5": { + "imports": [ + "dnxcore50", + "portable-net45+win8" + ], + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-24008", + "System.Diagnostics.Process": "4.1.0-rc2-23704", + "System.Threading": "4.0.11-rc2-24027" + } + }, + "net46": {} + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Friends.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Friends.cs new file mode 100644 index 0000000000..db63184055 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Friends.cs @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Runtime.CompilerServices; + +#region Test Assemblies + +[assembly: InternalsVisibleTo("Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] + +#endregion diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Interfaces/IDataAttachment.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Interfaces/IDataAttachment.cs new file mode 100644 index 0000000000..c7dd27d76a --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Interfaces/IDataAttachment.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft. All rights reserved. +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel +{ + /// + /// Interface used to define a data attachment. + /// + public interface IDataAttachment + { + /// + /// Gets the description for the attachment. + /// + string Description { get; } + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Interfaces/IXmlTestStore.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Interfaces/IXmlTestStore.cs new file mode 100644 index 0000000000..9d49b629a2 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Interfaces/IXmlTestStore.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel +{ + using System.Xml; + + /// + /// Implementing this interface indicates a custom persistence logic is provided. + /// The attribute based persistence is ignored in such a case. + /// + public interface IXmlTestStore + { + /// + /// Saves the class under the XmlElement. + /// + /// XmlElement element + /// XmlTestStoreParameters parameters + void Save(XmlElement element, XmlTestStoreParameters parameters); + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Interfaces/IXmlTestStoreCustom.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Interfaces/IXmlTestStoreCustom.cs new file mode 100644 index 0000000000..3aa979e231 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Interfaces/IXmlTestStoreCustom.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.Extensions.TrxLogger.XML +{ + using System.Diagnostics.CodeAnalysis; + + /// + /// Implementing this interface allows you to customize XmlStore persistence. + /// + public interface IXmlTestStoreCustom + { + /// + /// Gets the name of the tag to use to persist this object. + /// + string ElementName { get; } + + /// + /// Gets the xml namespace to use when creating the element + /// + [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "Reviewed. Suppression is OK here.")] + string NamespaceUri { get; } + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Interfaces/XmlTestStoreParameters.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Interfaces/XmlTestStoreParameters.cs new file mode 100644 index 0000000000..a51075f2e8 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Interfaces/XmlTestStoreParameters.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel +{ + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + + /// + /// Optional parameters to the persistence process. A class implementing IPersistable can + /// use the parameter values to alter its load/save behavior. + /// + /// + /// Example: a class has a summary and details fields. Details are large, so they're only + /// saved when 'MyClass.SaveDetails' parameter is set to 'true'. + /// + [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Reviewed. Suppression is OK here.")] + public sealed class XmlTestStoreParameters : Dictionary + { + private XmlTestStoreParameters() + { + } + + /// + /// To create XmlTestStoreParameters object + /// + /// + /// The . + /// + public static XmlTestStoreParameters GetParameters() + { + return new XmlTestStoreParameters(); + } + + /// + /// Check for the parameter + /// + /// + /// The parameter. + /// + /// + /// The . + /// + public bool Contains(string parameter) + { + return this.ContainsKey(parameter); + } + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Microsoft.TestPlatform.Extensions.TrxLogger.xproj b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Microsoft.TestPlatform.Extensions.TrxLogger.xproj new file mode 100644 index 0000000000..28255d60ac --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Microsoft.TestPlatform.Extensions.TrxLogger.xproj @@ -0,0 +1,21 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + 60d876ee-f278-4bf8-bc8a-15b356895c6f + Microsoft.TestPlatform.Extensions.TrxLogger + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\ + v4.5.2 + + + + 2.0 + + + diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/CollectorDataEntry.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/CollectorDataEntry.cs new file mode 100644 index 0000000000..ecafc08f8f --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/CollectorDataEntry.cs @@ -0,0 +1,239 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Xml; + + using Microsoft.TestPlatform.Extensions.TrxLogger.Utility; + using Microsoft.TestPlatform.Extensions.TrxLogger.XML; + + /// + /// Data entry from a collector along with information what this collector is and what agent it was collected on. + /// Instances are created by Agent and sent to Controller. Then controller puts agent names in. + /// The user is not supposed to create instances of this. + /// + internal class CollectorDataEntry : IXmlTestStore + { + #region Private Fields + + /// + /// List of data attachments. These attachments can be things such as files that the + /// collector wants to make available to the publishers. + /// + private readonly List attachments; + + /// + /// Name of the agent from which we received the data + /// + private string agentName; + + /// + /// Display name of the agent from which we received the data + /// + private string agentDisplayName; + + /// + /// Flag indicating whether this data is coming from a remote (not hosted) agent + /// + private bool isFromRemoteAgent; + + /// + /// URI of the collector. + /// + private Uri uri; + + /// + /// Name of the collector that should be displayed to the user. + /// + private string collectorDisplayName; + + #endregion + + #region Constructor + + /// + /// Used by the aggregator to put collector Uri, agentName, string agentDisplayName, whether it's remote data, and + /// Attachments. + /// + /// + /// The uri. + /// + /// + /// The collector Display Name. + /// + /// + /// The agent Name. + /// + /// + /// The agent Display Name. + /// + /// + /// Is From Remote Agent. + /// + /// + /// The attachments. + /// + public CollectorDataEntry(Uri uri, string collectorDisplayName, string agentName, string agentDisplayName, bool isFromRemoteAgent, IList attachments) + : this() + { + this.Initialize(uri, collectorDisplayName, agentName, agentDisplayName, isFromRemoteAgent, attachments); + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// For XML persistence + /// + internal CollectorDataEntry() + { + this.attachments = new List(); + } + + /// + /// Copies the specified collector data entry, making the paths in the data attachments absolute or relative, with + /// respect to the results directory + /// + /// The instance to copy from + /// The results directory to use to make paths in the data attachments relative or absolute + /// True to use absolute paths in this instance, false to use relative paths + private CollectorDataEntry(CollectorDataEntry other, string resultsDirectory, bool useAbsolutePaths) + { + Debug.Assert(other != null, "'other' is null"); + Debug.Assert(other.attachments != null, "'other.m_attachments' is null"); + Debug.Assert(!string.IsNullOrEmpty(resultsDirectory), "'resultsDirectory' is null or empty"); + + this.attachments = new List(other.attachments.Count); + this.Initialize(other.uri, other.collectorDisplayName, other.agentName, other.agentDisplayName, other.isFromRemoteAgent, null); + + // Clone the attachments + foreach (IDataAttachment attachment in other.attachments) + { + Debug.Assert(attachment != null, "'attachment' is null"); + + UriDataAttachment uriDataAttachment = attachment as UriDataAttachment; + if (uriDataAttachment != null) + { + this.attachments.Add(uriDataAttachment.Clone(resultsDirectory, useAbsolutePaths)); + } + else + { + this.attachments.Add(attachment); + } + } + } + + #endregion + + #region Public Properties + + /// + /// Gets the read-only list of data attachments + /// + public IList Attachments + { + get + { + return this.attachments.AsReadOnly(); + } + } + + #endregion + + #region IXmlTestStore Members + + /// + /// Saves the state to the XML element + /// + /// The XML element to save to + /// Parameters to customize the save behavior + public void Save(XmlElement element, XmlTestStoreParameters parameters) + { + EqtAssert.ParameterNotNull(element, "element"); + + XmlPersistence helper = new XmlPersistence(); + helper.SaveSimpleField(element, "@agentName", this.agentName, null); + helper.SaveSimpleField(element, "@agentDisplayName", this.agentDisplayName, this.agentName); + helper.SaveSimpleField(element, "@isFromRemoteAgent", this.isFromRemoteAgent, false); + helper.SaveSimpleField(element, "@uri", this.uri.AbsoluteUri, null); + helper.SaveSimpleField(element, "@collectorDisplayName", this.collectorDisplayName, string.Empty); + + IList uriAttachments = new List(); + foreach (IDataAttachment attachment in this.Attachments) + { + UriDataAttachment uriAtt = attachment as UriDataAttachment; + if (uriAtt != null) + { + uriAttachments.Add(uriAtt); + } + } + + helper.SaveIEnumerable(uriAttachments, element, "UriAttachments", "A", "UriAttachment", parameters); + } + + #endregion + + #region Internal Methods + + /// + /// Adds a data attachment to the list of data attachments + /// + /// The attachment to add + internal void AddAttachment(IDataAttachment attachment) + { + if (attachment == null) + { + throw new ArgumentNullException(nameof(attachment)); + } + + this.attachments.Add(attachment); + } + + /// + /// Clones the instance and attachments, with file paths in file attachments absolute or relative as specified + /// + /// The results directory to use to make paths in the data attachments relative or absolute + /// True to use absolute paths in this instance, false to use relative paths + /// A clone of the instance containing cloned attachments with file paths made absolute or relative + internal CollectorDataEntry Clone(string resultsDirectory, bool useAbsolutePaths) + { + Debug.Assert(!string.IsNullOrEmpty(resultsDirectory), "'resultsDirectory' is null or empty"); + Debug.Assert(resultsDirectory == resultsDirectory.Trim(), "'resultsDirectory' contains whitespace at the ends"); + + return new CollectorDataEntry(this, resultsDirectory, useAbsolutePaths); + } + + #endregion + + #region Private Methods + + private void Initialize(Uri uri, string collectorDisplayName, string agentName, string agentDisplayName, bool isFromRemoteAgent, IEnumerable attachments) + { + EqtAssert.ParameterNotNull(uri, "uri"); + EqtAssert.StringNotNullOrEmpty(collectorDisplayName, "collectorDisplayName"); + EqtAssert.StringNotNullOrEmpty(agentName, "agentName"); + EqtAssert.StringNotNullOrEmpty(agentDisplayName, "agentDisplayName"); + + if (null != attachments) + { + // Copy the attachments + foreach (IDataAttachment attachment in attachments) + { + this.AddAttachment(attachment); + } + } + + // Note that the data can be null. + this.uri = uri; + this.collectorDisplayName = collectorDisplayName; + this.agentName = agentName.Trim(); + this.agentDisplayName = agentDisplayName.Trim(); + this.isFromRemoteAgent = isFromRemoteAgent; + } + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/RunInfo.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/RunInfo.cs new file mode 100644 index 0000000000..67d70ad20b --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/RunInfo.cs @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel +{ + using System; + using System.Diagnostics; + using System.Xml; + + using Microsoft.TestPlatform.Extensions.TrxLogger.XML; + + /// + /// This is a record about one run-level event that happened at run execution or around it. + /// + internal sealed class RunInfo : IXmlTestStore + { + #region Fields + + [StoreXmlSimpleField("Text", "")] + private string text; + + private Exception exception; + + [StoreXmlSimpleField("@computerName", "")] + private string computer; + + [StoreXmlSimpleField("@outcome")] + private TestOutcome outcome; + + [StoreXmlSimpleField] + private DateTime timestamp; + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// + /// The text message. + /// + /// + /// The exception + /// + /// + /// The computer. + /// + /// + /// The outcome. + /// + public RunInfo(string textMessage, Exception ex, string computer, TestOutcome outcome) + { + Debug.Assert(computer != null, "computer is null"); + + this.text = textMessage; + this.exception = ex; + this.computer = computer; + this.outcome = outcome; + this.timestamp = DateTime.Now.ToUniversalTime(); + } + + #endregion + + #region IXmlTestStore Members + + /// + /// Saves the class under the XmlElement.. + /// + /// + /// The parent xml. + /// + /// + /// The parameters. + /// + public void Save(XmlElement element, XmlTestStoreParameters parameters) + { + XmlPersistence helper = new XmlPersistence(); + helper.SaveSingleFields(element, this, parameters); + helper.SaveSimpleField(element, "Exception", this.exception, null); + } + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestCategoryItems.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestCategoryItems.cs new file mode 100644 index 0000000000..b91d0c3a9b --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestCategoryItems.cs @@ -0,0 +1,267 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel +{ + using System; + using System.Diagnostics; + using System.Text; + + using Microsoft.TestPlatform.Extensions.TrxLogger.Utility; + using Microsoft.TestPlatform.Extensions.TrxLogger.XML; + + #region TestCategoryItem + /// + /// Stores a string which categorizes the Test + /// + public sealed class TestCategoryItem : IXmlTestStore + { + #region Fields + [StoreXmlSimpleField(Location = "@TestCategory", DefaultValue = "")] + private string category = string.Empty; + + #endregion + + #region Constructors + /// + /// Create a new item with the category set + /// + /// The category. + public TestCategoryItem(string category) + { + // Treat null as empty. + if (category == null) + { + category = String.Empty; + } + + + this.category = this.StripIllegalChars(category); + } + + #endregion + + #region Properties/Methods + /// + /// Gets the category for this TestCategory + /// + public string TestCategory + { + get + { + return this.category; + } + } + + private string StripIllegalChars(string category) + { + string ret = category.Trim(); + ret = ret.Replace("&", String.Empty); + ret = ret.Replace("|", String.Empty); + ret = ret.Replace("!", String.Empty); + ret = ret.Replace(",", String.Empty); + return ret; + } + + #endregion + + #region Methods - overrides + /// + /// Compare the values of the items + /// + /// Value being compared to. + /// True if the values are the same and false otherwise. + public override bool Equals(object other) + { + TestCategoryItem otherItem = other as TestCategoryItem; + if (otherItem == null) + { + return false; + } + Debug.Assert(this.category != null, "category is null"); + return String.Equals(this.category, otherItem.category, StringComparison.OrdinalIgnoreCase); + } + + /// + /// Convert the category name to a hashcode + /// + /// Hashcode of the cagegory. + public override int GetHashCode() + { + Debug.Assert(this.category != null, "category is null"); + return this.category.ToUpperInvariant().GetHashCode(); + } + + /// + /// Convert the category name to a string + /// + /// The category. + public override string ToString() + { + Debug.Assert(this.category != null, "category is null"); + return this.category; + } + #endregion + + #region IXmlTestStore Members + + /// + /// Saves the class under the XmlElement. + /// + /// XmlElement element + /// XmlTestStoreParameters parameters + public void Save(System.Xml.XmlElement element, XmlTestStoreParameters parameters) + { + new XmlPersistence().SaveSingleFields(element, this, parameters); + } + + #endregion + } + #endregion + + #region TestCategoryItemCollection + /// + /// A collection of strings which categorize the test. + /// + public sealed class TestCategoryItemCollection : EqtBaseCollection + { + #region Constructors + /// + /// Creates an empty TestCategoryItemCollection. + /// + public TestCategoryItemCollection() + { + } + + /// + /// Create a new TestCategoryItemCollection based on the string array. + /// + /// Add these items to the collection. + public TestCategoryItemCollection(string[] items) + { + EqtAssert.ParameterNotNull(items, "items"); + foreach (string s in items) + { + this.Add(s); + } + } + + #endregion + + #region Methods + + /// + /// Adds the category. + /// + /// Category to be added. + public void Add(string item) + { + this.Add(new TestCategoryItem(item)); + } + + /// + /// Adds the category. + /// + /// Category to be added. + public override void Add(TestCategoryItem item) + { + EqtAssert.ParameterNotNull(item, "item"); + + // Don't add empty items. + if (!String.IsNullOrEmpty(item.TestCategory)) + { + base.Add(item); + } + } + + /// + /// Convert the TestCategoryItemCollection to a string. + /// each item is surrounded by a comma (,) + /// + /// + public override string ToString() + { + StringBuilder returnString = new StringBuilder(); + if (this.Count > 0) + { + returnString.Append(","); + foreach (TestCategoryItem item in this) + { + returnString.Append(item.TestCategory); + returnString.Append(","); + } + } + + return returnString.ToString(); + } + + /// + /// Convert the TestCategoryItemCollection to an array of strings. + /// + /// Array of strings containing the test cagegories. + public string[] ToArray() + { + string[] result = new string[this.Count]; + + int i = 0; + foreach (TestCategoryItem item in this) + { + result[i++] = item.TestCategory; + } + + return result; + } + + /// + /// Compare the collection items + /// + /// other collection + /// true if the collections contain the same items + public override bool Equals(object obj) + { + TestCategoryItemCollection other = obj as TestCategoryItemCollection; + bool result = false; + + if (other == null) + { + // Other object is not a TestCategoryItemCollection. + result = false; + } + else if (Object.ReferenceEquals(this, other)) + { + // The other object is the same object as this one. + result = true; + } + else if (this.Count != other.Count) + { + // The count of categories in the other object does not + // match this one, so they are not equal. + result = false; + } + else + { + // Check each item and return on the first mismatch. + foreach (TestCategoryItem item in this) + { + if (!other.Contains(item)) + { + result = false; + break; + } + } + } + + return result; + } + + /// + /// Return the hash code of this collection + /// + /// The hashcode. + public override int GetHashCode() + { + return base.GetHashCode(); + } + #endregion + } + #endregion +} diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestEntry.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestEntry.cs new file mode 100644 index 0000000000..fae146e4a7 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestEntry.cs @@ -0,0 +1,134 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel +{ + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + + using Microsoft.TestPlatform.Extensions.TrxLogger.XML; + + /// + /// The test entry. + /// + internal sealed class TestEntry : IXmlTestStore + { + #region Fields + + private TestId testId; + private TestExecId execId; + private TestListCategoryId categoryId; + + #endregion + + #region Constructors + + /// + /// Constructor. + /// Note that using this constructor has different effect as setting CategoryId property. + /// When using this constructor, catId is used as specified, which CategoryId.set changes null to the root cat. + /// + /// Test Id. + /// Category Id. This gets into . + public TestEntry(TestId testId, TestListCategoryId catId) + { + Debug.Assert(testId != null, "testId is null"); + + // CatId can be null. + this.testId = testId; + this.categoryId = catId; + } + + #endregion + + #region Properties + + /// + /// Gets or sets the exec id. + /// + public TestExecId ExecId + { + get + { + return this.execId; + } + + set + { + Debug.Assert(value != null, "ExecId is null"); + this.execId = value; + } + } + + #endregion + + #region Overrides + + /// + /// Override function for Equals. + /// + /// + /// The object to compare. + /// + /// + /// The . + /// + [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1405:DebugAssertMustProvideMessageText", Justification = "Reviewed. Suppression is OK here.")] + public override bool Equals(object obj) + { + TestEntry e = obj as TestEntry; + + if (e == null) + { + return false; + } + + Debug.Assert(this.execId != null, "this.execId is null"); + Debug.Assert(e.execId != null, "e.execId is null"); + + if (!this.execId.Equals(e.execId)) + { + return false; + } + + Debug.Assert(object.Equals(this.testId, e.testId)); + Debug.Assert(object.Equals(this.categoryId, e.categoryId)); + return true; + } + + /// + /// Override function for GetHashCode. + /// + /// + /// The . + /// + public override int GetHashCode() + { + return this.execId.GetHashCode(); + } + + #endregion + + #region IXmlTestStore Members + + /// + /// Saves the class under the XmlElement.. + /// + /// + /// The parent xml. + /// + /// + /// The parameters. + /// + public void Save(System.Xml.XmlElement element, XmlTestStoreParameters parameters) + { + XmlPersistence helper = new XmlPersistence(); + helper.SaveSingleFields(element, this, parameters); + + helper.SaveObject(this.testId, element, null); + helper.SaveGuid(element, "@executionId", this.execId.Id); + helper.SaveGuid(element, "@testListId", this.categoryId.Id); + } + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestExecId.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestExecId.cs new file mode 100644 index 0000000000..686847f591 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestExecId.cs @@ -0,0 +1,97 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel +{ + using System; + using System.Globalization; + + /// + /// Class identifying test execution id. + /// Execution ID is assigned to test at run creation time and is guaranteed to be unique within that run. + /// + public sealed class TestExecId + { + private static TestExecId emptyId = new TestExecId(Guid.Empty); + + private Guid execId; + + /// + /// Initializes a new instance of the class. + /// + public TestExecId() + { + this.execId = Guid.NewGuid(); + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The id. + /// + public TestExecId(Guid id) + { + this.execId = id; + } + + /// + /// Gets an object of class which empty GUID + /// + public static TestExecId Empty + { + get { return emptyId; } + } + + /// + /// Gets the id. + /// + public Guid Id + { + get { return this.execId; } + } + + /// + /// Override function of Equals. + /// + /// + /// The object to compare. + /// + /// + /// The . + /// + public override bool Equals(object obj) + { + TestExecId id = obj as TestExecId; + + if (id == null) + { + return false; + } + + return this.execId.Equals(id.execId); + } + + /// + /// Override function of GetHashCode + /// + /// + /// The . + /// + public override int GetHashCode() + { + return this.execId.GetHashCode(); + } + + /// + /// Override function of ToString. + /// + /// + /// The . + /// + public override string ToString() + { + string s = this.execId.ToString("B"); + return string.Format(CultureInfo.InvariantCulture, s); + } + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestId.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestId.cs new file mode 100644 index 0000000000..4f71bd0a05 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestId.cs @@ -0,0 +1,275 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel +{ + using System; + using System.Diagnostics; + using System.Globalization; + using System.Xml; + + using Microsoft.TestPlatform.Extensions.TrxLogger.Utility; + using Microsoft.TestPlatform.Extensions.TrxLogger.XML; + + #region TestId + /// + /// Class that uniquely identifies a test. + /// + public sealed class TestId : IEquatable, IComparable, IComparable, IXmlTestStore + { + #region Constants + + /// + /// Key in for specifying the location where the test ID is stored, under an XML element + /// + internal static readonly string IdLocationKey = "IdLocation"; + + /// + /// Location where the test ID is stored, under an XML element + /// + private const string DefaultIdLocation = "@testId"; + + /// + /// Represents an empty test ID + /// + private static readonly TestId EmptyId = new TestId(Guid.Empty); + + #endregion + + #region Fields + + /// + /// The test ID + /// + private Guid id; + + #endregion + + #region Constructors + + /// + /// Generates a new test ID + /// + public TestId() + : this(Guid.NewGuid()) + { + } + + /// + /// Stores the specified ID + /// + /// GUID of the test + public TestId(Guid id) + { + this.id = id; + } + + #endregion + + #region Properties + + /// + /// Gets an empty test ID + /// + public static TestId Empty + { + get { return EmptyId; } + } + + /// + /// Gets test ID + /// + public Guid Id + { + get { return this.id; } + } + + #endregion + + #region IXmlTestStore Members + + /// + /// Saves the state to the XML element + /// + /// The XML element to save to + /// Parameters to customize the save behavior + void IXmlTestStore.Save(XmlElement element, XmlTestStoreParameters parameters) + { + Debug.Assert(element != null, "element is null"); + + string idLocation; + this.GetIdLocation(parameters, out idLocation); + + XmlPersistence helper = new XmlPersistence(); + helper.SaveGuid(element, idLocation, this.id); + } + + /// + /// Gets the location of the test ID under an XML element, based on the specified parameters. + /// This method is needed to parse the parameters sent by the caller of Save and Load. + /// We need to support different locations for saving the test ID, because previously, TestEntry and TestResult stored the test ID to @testId + /// (which is now the default location the TestId class' Save and Load methods), but TestElement was storing it to @id. + /// Since we can't change the location where the ID is stored in XML, we support custom locations in the TestId class. + /// + /// The parameters specifying the locations + /// The test ID location + private void GetIdLocation(XmlTestStoreParameters parameters, out string idLocation) + { + // Initialize to the default ID location + idLocation = DefaultIdLocation; + + // If any parameters are specified, see if we need to override the defaults + if (parameters != null) + { + object idLocationObj; + if (parameters.TryGetValue(IdLocationKey, out idLocationObj)) + { + idLocation = idLocationObj as string ?? idLocation; + } + } + } + + #endregion + + #region Equality + + #region IEquatable Members + + /// + /// Compares this instance with the other test ID for value equality + /// + /// The other test ID to compare with + /// True if the test IDs are equal in value, false otherwise + public bool Equals(TestId other) + { + // Check reference equality first, as it is faster than comparing value equality when the references are equal + return object.ReferenceEquals(this, other) || this.ValueEquals(other); + } + + /// + /// Compares this instance with the other test ID for value equality. This method does not check reference equality first. + /// + /// The other test ID to compare with + /// True if the test IDs are equal in value, false otherwise + private bool ValueEquals(TestId other) + { + // Avoid calling of "!= null", as the != operator has been overloaded. + return !object.ReferenceEquals(other, null) && this.id == other.id; + } + + #endregion + + #region Overrides + + /// + /// Compares this instance with the other test ID for value equality + /// + /// The other test ID to compare with + /// True if the test IDs are equal in value, false otherwise + public override bool Equals(object other) + { + return this.Equals(other as TestId); + } + + /// + /// Gets a hash code representing the state of the instance + /// + /// The hash code + public override int GetHashCode() + { + return this.id.GetHashCode(); + } + + #endregion + + #region Operators + + /// + /// Compares the two test IDs for value equality + /// + /// The test ID on the left of the operator + /// The test ID on the right of the operator + /// True if the test IDs are equal in value, false otherwise + public static bool operator ==(TestId left, TestId right) + { + return + object.ReferenceEquals(left, right) || + !object.ReferenceEquals(left, null) && left.ValueEquals(right); + } + + /// + /// Compares the two test IDs for value inequality + /// + /// The test ID on the left of the operator + /// The test ID on the right of the operator + /// True if the test IDs are unequal in value, false otherwise + public static bool operator !=(TestId left, TestId right) + { + return !(left == right); + } + + #endregion + + #endregion + + #region Comparison + + #region IComparable Members + + /// + /// Compares this instance with the other test ID + /// + /// The other test ID to compare with + /// + /// 0 if this instance is equal in value to the other test ID, < 0 if this instance is lesser than the other test ID, + /// or > 0 if this instance is greater than the other test ID + /// + public int CompareTo(TestId other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + return this.id.CompareTo(other.id); + } + + #endregion + + #region IComparable Members + + /// + /// Compares this instance with the other test ID + /// + /// The other test ID to compare with + /// + /// 0 if this instance is equal in value to the other test ID, < 0 if this instance is less than the other test ID, + /// or > 0 if this instance is greater than the other test ID + /// + public int CompareTo(object other) + { + return CompareTo(other as TestId); + } + + #endregion + + #endregion + + #region Overrides + + /// + /// Override ToString + /// + /// + /// The . + /// + public override string ToString() + { + // "B" adds curly braces around guid + string s = this.id.ToString("B"); + return string.Format(CultureInfo.InvariantCulture, s); + } + + #endregion + } + #endregion TestId +} diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestListCategory.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestListCategory.cs new file mode 100644 index 0000000000..30ebf5c507 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestListCategory.cs @@ -0,0 +1,200 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel +{ + using System; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + + using Microsoft.TestPlatform.Extensions.TrxLogger.Utility; + using Microsoft.TestPlatform.Extensions.TrxLogger.XML; + + /// + /// The test list category. + /// + public class TestListCategory : IXmlTestStore + { + #region Fields + + private static TestListCategory uncategorizedResults; + + private static TestListCategory allResults; + + private static object reservedCategoryLock = new object(); + + private TestListCategoryId id = new TestListCategoryId(); + + [StoreXmlSimpleField(DefaultValue = "")] + private string name = string.Empty; + + private TestListCategoryId parentCategoryId; + + #endregion + + /// + /// Constructor for TestListCategory . + /// + /// The name of new category. + /// Id of parent category. Use TestListCategoryId.Root for top level categories. + public TestListCategory(string name, TestListCategoryId parentCategoryId) + { + EqtAssert.StringNotNullOrEmpty(name, "name"); + EqtAssert.ParameterNotNull(parentCategoryId, "parentCategoryId"); + + this.name = name; + this.parentCategoryId = parentCategoryId; + } + + /// + /// Used internally for fake uncategorized category. + /// + /// + /// Category name. + /// + /// + /// Category id. + /// + /// + /// The parent Id. + /// + private TestListCategory(string name, TestListCategoryId id, TestListCategoryId parentId) : this(name, parentId) + { + EqtAssert.ParameterNotNull(id, "id"); + this.id = id; + } + + #region Properties + + /// + /// Gets the uncategorized results. + /// + public static TestListCategory UncategorizedResults + { + get + { + if (uncategorizedResults == null) + { + lock (reservedCategoryLock) + { + if (uncategorizedResults == null) + { + uncategorizedResults = new TestListCategory( + TrxResource.TS_UncategorizedResults, TestListCategoryId.Uncategorized, TestListCategoryId.Root); + } + } + } + + return uncategorizedResults; + } + } + + /// + /// Gets the all results. + /// + public static TestListCategory AllResults + { + get + { + if (allResults == null) + { + lock (reservedCategoryLock) + { + if (allResults == null) + { + allResults = new TestListCategory( + TrxResource.TS_AllResults, TestListCategoryId.AllItems, TestListCategoryId.Root); + } + } + } + + return allResults; + } + } + + /// + /// Gets the id. + /// + public TestListCategoryId Id + { + get { return this.id; } + } + + /// + /// Gets or sets id of parent category. Use TestCategoryId.Root for top level categories. + /// We do not keep category children in Object Model, only parent. + /// + public TestListCategoryId ParentCategoryId + { + get + { + return this.parentCategoryId; + } + + set + { + EqtAssert.ParameterNotNull(value, "ParentCategoryId.value"); + this.parentCategoryId = value; + } + } + + #endregion + + #region Overrides + + /// + /// Override function for Equals. + /// + /// + /// The object to compare. + /// + /// + /// The . + /// + public override bool Equals(object obj) + { + TestListCategory cat = obj as TestListCategory; + if (cat == null) + { + return false; + } + + Debug.Assert(this.id != null, "id is null"); + return this.id.Equals(cat.id); + } + + /// + /// Override function for GetHashCode. + /// + /// + /// The . + /// + public override int GetHashCode() + { + return this.id.GetHashCode(); + } + #endregion + + #region IXmlTestStore Members + + /// + /// Saves the class under the XmlElement.. + /// + /// + /// The parent xml. + /// + /// + /// The parameter + /// + public void Save(System.Xml.XmlElement element, XmlTestStoreParameters parameters) + { + XmlPersistence h = new XmlPersistence(); + + h.SaveSingleFields(element, this, parameters); + h.SaveGuid(element, "@id", this.Id.Id); + h.SaveGuid(element, "@parentListId", this.ParentCategoryId.Id); + } + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestListCategoryId.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestListCategoryId.cs new file mode 100644 index 0000000000..e5e449b931 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestListCategoryId.cs @@ -0,0 +1,133 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel +{ + using System; + using System.Globalization; + + /// + /// Class to categorize the tests. + /// + public sealed class TestListCategoryId + { + private static TestListCategoryId emptyId = new TestListCategoryId(Guid.Empty); + + private static TestListCategoryId uncategorizedId = new TestListCategoryId(new Guid("8C84FA94-04C1-424b-9868-57A2D4851A1D")); + + private static TestListCategoryId categoriesId = new TestListCategoryId(new Guid("8C43106B-9DC1-4907-A29F-AA66A61BF5B6")); + + private static TestListCategoryId all = new TestListCategoryId(new Guid("19431567-8539-422a-85D7-44EE4E166BDA")); + + private Guid id; + + /// + /// Initializes a new instance of the class. + /// + public TestListCategoryId() + { + this.id = Guid.NewGuid(); + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The id (GUID). + /// + public TestListCategoryId(Guid id) + { + this.id = id; + } + + + /// + /// Gets the Id of very root category - parent of all categories (fake, not real category). + /// + public static TestListCategoryId Root + { + get { return emptyId; } + } + + /// + /// Gets an object of class with empty GUID. + /// + public static TestListCategoryId Empty + { + get { return emptyId; } + } + + /// + /// Gets an object of class with GUID which represent uncategorized. + /// + public static TestListCategoryId Uncategorized + { + get { return uncategorizedId; } + } + + /// + /// Gets an object of class with GUID which represent categorize. + /// + public static TestListCategoryId Categories + { + get { return categoriesId; } + } + + /// + /// Gets the id. + /// + public Guid Id + { + get { return this.id; } + } + + public static TestListCategoryId AllItems + { + get { return all; } + } + + + /// + /// Override function for Equals. + /// + /// + /// The object to compare. + /// + /// + /// The . + /// + public override bool Equals(object other) + { + TestListCategoryId testListCategoryId = other as TestListCategoryId; + if (testListCategoryId == null) + { + return false; + } + + return this.id.Equals(testListCategoryId.id); + } + + /// + /// Override function for GetHashCode. + /// + /// + /// The . + /// + public override int GetHashCode() + { + return this.id.GetHashCode(); + } + + /// + /// Override function for ToString. + /// + /// + /// The . + /// + public override string ToString() + { + // "B" adds curly braces around guid + string s = this.id.ToString("B"); + return string.Format(CultureInfo.InvariantCulture, s); + } + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestMethod.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestMethod.cs new file mode 100644 index 0000000000..0c97687a03 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestMethod.cs @@ -0,0 +1,120 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel +{ + using System.Diagnostics; + using System.Xml; + + using Microsoft.TestPlatform.Extensions.TrxLogger.XML; + using System; + /// + /// TestMethod contains information about a unit test method that needs to be executed + /// + internal sealed class TestMethod : IXmlTestStore + { + private string className; + + private string name; // test method name + + + private bool isValid; + + public TestMethod(string name, string className) + { + Debug.Assert(!string.IsNullOrEmpty(name), "name is null"); + Debug.Assert(!string.IsNullOrEmpty(className), "className is null"); + this.name = name; + this.className = className; + } + + /// + /// Gets the name. + /// + public string Name + { + get + { + return this.name; + } + } + + /// + /// Gets the class name. + /// + public string ClassName + { + get + { + return this.className; + } + + } + + /// + /// Gets or sets a value indicating whether is valid. + /// + public bool IsValid + { + get + { + return this.isValid; + } + set + { + this.isValid = value; + } + } + + #region Override + + /// + /// Override function for Equals. + /// + /// + /// The object to compare. + /// + /// + /// The . + /// + public override bool Equals(object obj) + { + TestMethod otherTestMethod = obj as TestMethod; + return otherTestMethod != null && this.name == otherTestMethod.name + && this.className == otherTestMethod.className && this.isValid == otherTestMethod.isValid; + } + + /// + /// Override function for GetHashCode. + /// + /// + /// The . + /// + public override int GetHashCode() + { + return this.Name?.GetHashCode() ?? 0; + } + + #endregion Override + + #region IXmlTestStore Members + + /// + /// Saves the class under the XmlElement.. + /// + /// + /// The parent xml. + /// + /// + /// The parameter + /// + public void Save(XmlElement element, XmlTestStoreParameters parameters) + { + XmlPersistence helper = new XmlPersistence(); + helper.SaveSimpleField(element, "@className", this.className, string.Empty); + helper.SaveSimpleField(element, "@name", this.name, string.Empty); + helper.SaveSimpleField(element, "isValid", this.isValid, false); + } + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestOutcome.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestOutcome.cs new file mode 100644 index 0000000000..ce6a584e54 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestOutcome.cs @@ -0,0 +1,105 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel +{ + using System.Diagnostics.CodeAnalysis; + + /// + /// Outcome of a test or a run. + /// If a new successful state needs to be added you will need to modify + /// RunResultAndStatistics in TestRun.cs and TestOutcomeHelper below. + /// ---------------------------------------------------------------- + /// NOTE: the order is important and is used for computing outcome for aggregations. + /// More important outcomes come first. See TestOutcomeHelper.GetAggregationOutcome. + /// + public enum TestOutcome + { + /// + /// 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 was aborted. + /// This was not caused by a user gesture, but rather by a framework decision. + /// + Aborted, + + /// + /// Test has completed, but we can't say if it passed or failed. + /// May be used for aborted tests... + /// + Inconclusive, + + /// + /// Test was executed w/o any issues, but run was aborted. + /// + PassedButRunAborted, + + /// + /// Test had it chance for been executed but was not, as ITestElement.IsRunnable == false. + /// + NotRunnable, + + /// + /// Test was not executed. + /// This was caused by a user gesture - e.g. user hit stop button. + /// + NotExecuted, + + /// + /// Test run was disconnected before it finished running. + /// + Disconnected, + + /// + /// To be used by Run level results. + /// This is not a failure. + /// + Warning, + + /// + /// Test was executed w/o any issues. + /// + Passed, + + /// + /// Test has completed, but there is no qualitative measure of completeness. + /// + Completed, + + /// + /// Test is currently executing. + /// + InProgress, + + /// + /// Test is in the execution queue, was not started yet. + /// + Pending, + + /// + /// The min value of this enum + /// + [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Reviewed. Suppression is OK here.")] + Min = Error, + + /// + /// The max value of this enum + /// + [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", + Justification = "Reviewed. Suppression is OK here.")] + Max = Pending + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestResult.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestResult.cs new file mode 100644 index 0000000000..6f96d5e979 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestResult.cs @@ -0,0 +1,192 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel +{ + using System; + using System.Diagnostics; + using Microsoft.TestPlatform.Extensions.TrxLogger.XML; + + /// + /// Class to uniquely identify test results + /// + public sealed class TestResultId : IXmlTestStore + { + #region Fields + private Guid runId; + + // id of test within run + private TestExecId executionId; + + private TestId testId; + + #endregion + + #region Constructor + + /// + /// Initializes a new instance of the class. + /// + /// + /// The run id. + /// + /// + /// The execution id. + /// + /// + /// The parent execution id. + /// + /// + /// The test id. + /// + public TestResultId(Guid runId, TestExecId executionId, TestId testId) + { + this.runId = runId; + this.executionId = executionId; + this.testId = testId; + } + + #endregion + + #region properties + + /// + /// Gets the execution id. + /// + public TestExecId ExecutionId + { + get { return this.executionId; } + } + + #endregion + + #region Overrides + + /// + /// Override function for Equals + /// + /// + /// The object to compare + /// + /// + /// The . + /// + public override bool Equals(object obj) + { + TestResultId tmpId = obj as TestResultId; + if (tmpId == null) + { + return false; + } + + return this.runId.Equals(tmpId.runId) && this.executionId.Equals((object)tmpId.executionId); + } + + /// + /// Override function for GetHashCode. + /// + /// + /// The . + /// + public override int GetHashCode() + { + return this.runId.GetHashCode() ^ this.executionId.GetHashCode(); + } + + /// + /// Override function for ToString. + /// + /// + /// The . + /// + public override string ToString() + { + return this.executionId.Id.ToString("B"); + } + #endregion + + #region IXmlTestStore Members + + /// + /// Saves the class under the XmlElement.. + /// + /// + /// The parent xml. + /// + /// + /// The parameter + /// + public void Save(System.Xml.XmlElement element, XmlTestStoreParameters parameters) + { + XmlPersistence helper = new XmlPersistence(); + + if (this.executionId != null) + { + helper.SaveGuid(element, "@executionId", this.executionId.Id); + } + + helper.SaveObject(this.testId, element, null); + } + + #endregion + } + + /// + /// The test result error info class. + /// + internal sealed class TestResultErrorInfo : IXmlTestStore + { + [StoreXmlSimpleField("Message", "")] + private string message; + + [StoreXmlSimpleField("StackTrace", "")] + private string stackTrace; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The message. + /// + public TestResultErrorInfo(string message) + { + Debug.Assert(message != null, "message is null"); + this.message = message; + } + + /// + /// Gets or sets the message. + /// + public string Message + { + get { return this.message; } + set { this.message = value; } + } + + /// + /// Gets or sets the stack trace. + /// + public string StackTrace + { + get { return this.stackTrace; } + set { this.stackTrace = value; } + } + + #region IXmlTestStore Members + + /// + /// Saves the class under the XmlElement.. + /// + /// + /// The parent xml. + /// + /// + /// The parameter + /// + public void Save(System.Xml.XmlElement element, XmlTestStoreParameters parameters) + { + XmlPersistence.SaveUsingReflection(element, this, typeof(TestResultErrorInfo), parameters); + } + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestRun.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestRun.cs new file mode 100644 index 0000000000..4a3a46e0f5 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestRun.cs @@ -0,0 +1,205 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel +{ + using System; + using System.Diagnostics; + using System.Globalization; + using System.IO; + using System.Security.Principal; + + using Microsoft.TestPlatform.Extensions.TrxLogger.Utility; + using Microsoft.TestPlatform.Extensions.TrxLogger.XML; + + /// + /// Class having information about a test run. + /// + public sealed class TestRun + { + #region Fields + + #region Summary fields + + // These fields will be valid when the test run summary is loaded from a results file. + // The summary fields need to be first in the class so they get serialized first. When we + // read the summary we don't want to parse the XML tags for other fields because they can + // be quite large. + // + // When reading the results file, the summary is considered complete when all summary fields + // are non-null. Any new summary fields that are initialized in the constructor should be + // placed before the last non-initialized field. + // + // The summary parsing code is in XmlTestReader.ReadTestRunSummary. + [StoreXmlSimpleField] + private Guid id; + + [StoreXmlSimpleField] + private string name; + + [StoreXmlSimpleField("@runUser", "")] + private string runUser; + + private TestRunConfiguration runConfig; + + #endregion Summary fields + + #region Non-summary fields + [StoreXmlSimpleField("Times/@creation")] + private DateTime created; + + [StoreXmlSimpleField("Times/@queuing")] + private DateTime queued; + + [StoreXmlSimpleField("Times/@start")] + private DateTime started; + + [StoreXmlSimpleField("Times/@finish")] + private DateTime finished; + + #endregion + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// + /// The run id. + /// + internal TestRun(Guid runId) + { + this.Initialize(); + + EqtAssert.IsTrue(!Guid.Empty.Equals(runId), "Can't use Guid.Empty for run ID."); + this.id = runId; + } + + #endregion Constructors + + /// + /// Gets or sets the run configuration. + /// + internal TestRunConfiguration RunConfiguration + { + get + { + return this.runConfig; + } + + set + { + EqtAssert.ParameterNotNull(value, "RunConfiguration"); + this.runConfig = value; + } + } + + /// + /// Gets or sets the start time. + /// + internal DateTime Started + { + get + { + return this.started; + } + + set + { + this.started = value; + } + } + + /// + /// Gets or sets the finished time of Test run. + /// + internal DateTime Finished + { + get { return this.finished; } + set { this.finished = value; } + } + + /// + /// Gets or sets the name. + /// + internal string Name + { + get + { + return this.name; + } + + set + { + EqtAssert.StringNotNullOrEmpty(value, "Name"); + this.name = value; + } + } + + /// + /// Gets the id. + /// + internal Guid Id + { + get { return this.id; } + } + + /// + /// WARNING: do not use from inside Test Adapters, use from only on HA by UI etc. + /// Returns directory on HA for dependent files for TestResult. XmlPersistence method for UI. + /// Throws on error (e.g. if deployment directory was not set for test run). + /// + /// + /// Test Result to get dependent files directory for. + /// + /// + /// Result directory. + /// + internal string GetResultFilesDirectory(UnitTestResult result) + { + EqtAssert.ParameterNotNull(result, "result"); + return Path.Combine(this.GetResultsDirectory(), result.RelativeTestResultsDirectory); + } + + /// + /// Gets the results directory, which is the run deployment In directory + /// + /// The results directory + /// This method is called by public properties/methods, so it needs to throw on error + internal string GetResultsDirectory() + { + if (this.RunConfiguration == null) + { + Debug.Fail("'RunConfiguration' is null"); + throw new Exception(String.Format(CultureInfo.CurrentCulture, TrxResource.Common_MissingRunConfigInRun)); + } + + if (string.IsNullOrEmpty(this.RunConfiguration.RunDeploymentRootDirectory)) + { + Debug.Fail("'RunConfiguration.RunDeploymentRootDirectory' is null or empty"); + throw new Exception(String.Format(CultureInfo.CurrentCulture, TrxResource.Common_MissingRunDeploymentRootInRunConfig)); + } + + return this.RunConfiguration.RunDeploymentInDirectory; + } + + private static string FormatDateTimeForRunName(DateTime timeStamp) + { + // We use custom format string to make sure that runs are sorted in the same way on all intl machines. + // This is both for directory names and for Data Warehouse. + return timeStamp.ToString("yyyy-MM-dd HH:mm:ss", DateTimeFormatInfo.InvariantInfo); + } + + private void Initialize() + { + this.id = Guid.NewGuid(); + this.name = String.Format(CultureInfo.CurrentCulture, TrxResource.Common_TestRunName, Environment.GetEnvironmentVariable("UserName"), Environment.MachineName, FormatDateTimeForRunName(DateTime.Now)); + this.runUser = WindowsIdentity.GetCurrent().Name; + this.created = DateTime.Now.ToUniversalTime(); + this.queued = DateTime.Now.ToUniversalTime(); + this.started = DateTime.Now.ToUniversalTime(); + this.finished = DateTime.Now.ToUniversalTime(); + } + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestRunConfiguration.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestRunConfiguration.cs new file mode 100644 index 0000000000..5b0dee1245 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestRunConfiguration.cs @@ -0,0 +1,156 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel +{ + using System; + using System.Diagnostics; + using System.IO; + using System.Xml; + + using Microsoft.TestPlatform.Extensions.TrxLogger.Utility; + using Microsoft.TestPlatform.Extensions.TrxLogger.XML; + + /// + /// The test run configuration. + /// + internal class TestRunConfiguration : IXmlTestStore, IXmlTestStoreCustom + { + internal static readonly string DeploymentInDirectorySuffix = "In"; + + #region Fields + private TestRunConfigurationId id; + + [StoreXmlSimpleField(DefaultValue = "")] + private string name; + + private string runDeploymentRoot; + + #endregion + + /// + /// Initializes a new instance of the class. + /// + /// + /// The name of Run Configuration. + /// + public TestRunConfiguration(string name) + { + EqtAssert.ParameterNotNull(name, "name"); + + this.name = name; + + this.runDeploymentRoot = string.Empty; + this.id = new TestRunConfigurationId(); + } + + #region IXmlTestStoreCustom Members + + /// + /// Gets the element name. + /// + public string ElementName + { + get + { + return "TestSettings"; + } + } + + /// + /// Gets the namespace uri. + /// + public string NamespaceUri + { + get + { + return @"http://microsoft.com/schemas/VisualStudio/TeamTest/2010"; + } + } + + #endregion + + /// + /// Gets directory that receives reverse-deployed files from Controller. + /// + public string RunDeploymentInDirectory + { + get + { + Debug.Assert(this.runDeploymentRoot != null, "runDeploymentRoot is null"); + return Path.Combine(this.runDeploymentRoot, DeploymentInDirectorySuffix); + } + } + + /// + /// Gets or sets RunDeploymentRootDirectory + /// INTERNAL PROPERTY. DO NOT USE (except execution). + /// Run-level deployment root directory, already inside RunId directory, parent of In and Out directories. + /// + internal string RunDeploymentRootDirectory + { + get + { + return this.runDeploymentRoot; + } + + set + { + Debug.Assert(!string.IsNullOrEmpty(value), "RunDeploymentRootDirectory.value should not be null or empty."); + this.runDeploymentRoot = value; + } + } + + #region IXmlTestStore Members + + /// + /// Saves the class under the XmlElement.. + /// + /// + /// The parent xml. + /// + /// + /// The parameters. + /// + public void Save(XmlElement element, XmlTestStoreParameters parameters) + { + XmlPersistence helper = new XmlPersistence(); + + // Save all fields marked as StoreXmlSimpleField. + helper.SaveSingleFields(element, this, parameters); + + helper.SaveGuid(element, "@id", this.id.Id); + + // When saving and loading a TRX file, we want to use the run deployment root directory based on where the TRX file + // is being saved to or loaded from + object filePersistenceRootObjectType; + if (parameters.TryGetValue(XmlFilePersistence.RootObjectType, out filePersistenceRootObjectType) && + (Type)filePersistenceRootObjectType == typeof(TestRun)) + { + Debug.Assert( + parameters.ContainsKey(XmlFilePersistence.DirectoryPath), + "TestRun is the type of the root object being saved to a file, but the DirectoryPath was not specified in the XML test store parameters"); + + Debug.Assert( + !string.IsNullOrEmpty(this.runDeploymentRoot), + "TestRun is the type of the root object being saved to a file, but the run deployment root directory is null or empty"); + + // We are saving a TestRun object as the root element in a file (TRX file), so just save the test run directory + // name (last directory in the run deployment root), which is the relative path to the run deployment root + // directory from the directory where the TRX file exists + helper.SaveSimpleField( + element, + "Deployment/@runDeploymentRoot", + FileHelper.MakePathRelative(this.runDeploymentRoot, Path.GetDirectoryName(this.runDeploymentRoot)), + string.Empty); + } + else + { + // We are not saving a TestRun object as the root element in a file (i.e., we're not saving a TRX file), so just + // save the run deployment root directory as is + helper.SaveSimpleField(element, "Deployment/@runDeploymentRoot", this.runDeploymentRoot, string.Empty); + } + } + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestRunConfigurationId.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestRunConfigurationId.cs new file mode 100644 index 0000000000..0b2fdc6c3a --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestRunConfigurationId.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel +{ + using System; + + /// + /// The test run configuration id. + /// + internal sealed class TestRunConfigurationId + { + private Guid id; + + /// + /// Initializes a new instance of the class. + /// + public TestRunConfigurationId() + { + this.id = Guid.NewGuid(); + } + + /// + /// Gets the id. + /// + public Guid Id + { + get { return this.id; } + } + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestRunSummary.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestRunSummary.cs new file mode 100644 index 0000000000..5c5dd70bbe --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestRunSummary.cs @@ -0,0 +1,191 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel +{ + using System; + using System.Collections.Generic; + using System.Xml; + + using Microsoft.TestPlatform.Extensions.TrxLogger.XML; + + /// + /// XML object for saving test summary - Outcome and counts (passed, failed etc) + /// + internal class TestRunSummary : IXmlTestStore + { + #region Fields + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "Reviewed. Suppression is OK here.")] + [StoreXmlSimpleField("Counters/@total")] + private int totalTests; + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "Reviewed. Suppression is OK here.")] + [StoreXmlSimpleField("Counters/@executed")] + private int executedTests; + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "Reviewed. Suppression is OK here.")] + [StoreXmlSimpleField("Counters/@passed")] + private int passedTests; + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "Reviewed. Suppression is OK here.")] + [StoreXmlSimpleField("Counters/@failed")] + private int failedTests; + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "Reviewed. Suppression is OK here.")] + [StoreXmlSimpleField("Counters/@error")] + private int errorTests; + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "Reviewed. Suppression is OK here.")] + [StoreXmlSimpleField("Counters/@timeout")] + private int timeoutTests; + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "Reviewed. Suppression is OK here.")] + [StoreXmlSimpleField("Counters/@aborted")] + private int abortedTests; + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "Reviewed. Suppression is OK here.")] + [StoreXmlSimpleField("Counters/@inconclusive")] + private int inconclusiveTests; + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "Reviewed. Suppression is OK here.")] + [StoreXmlSimpleField("Counters/@passedButRunAborted")] + private int passedButRunAborted; + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "Reviewed. Suppression is OK here.")] + [StoreXmlSimpleField("Counters/@notRunnable")] + private int notRunnable; + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "Reviewed. Suppression is OK here.")] + [StoreXmlSimpleField("Counters/@notExecuted")] + private int notExecuted; + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "Reviewed. Suppression is OK here.")] + [StoreXmlSimpleField("Counters/@disconnected")] + private int disconnected; + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "Reviewed. Suppression is OK here.")] + [StoreXmlSimpleField("Counters/@warning")] + private int warning; + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "Reviewed. Suppression is OK here.")] + [StoreXmlSimpleField("Counters/@completed")] + private int completed; + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "Reviewed. Suppression is OK here.")] + [StoreXmlSimpleField("Counters/@inProgress")] + private int inProgress; + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "Reviewed. Suppression is OK here.")] + [StoreXmlSimpleField("Counters/@pending")] + private int pending; + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "Reviewed. Suppression is OK here.")] + [StoreXmlSimpleField] + private TestOutcome outcome = TestOutcome.Pending; + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "Reviewed. Suppression is OK here.")] + [StoreXmlSimpleField("Output/StdOut", "")] + private string stdOut = string.Empty; + + private List runLevelErrorsAndWarnings; + + private List collectorDataEntries; + + private IList resultFiles; + + #endregion + + #region constructor + + /// + /// Initializes a new instance of the class. + /// + /// + /// The total number of tests discover in this run. + /// + /// + /// The executed tests. + /// + /// + /// The pass tests. + /// + /// + /// The fail tests. + /// + /// + /// The outcome. + /// + /// + /// The run messages. + /// + /// + /// The standard out. + /// + /// + /// The result files. + /// + /// + /// The data collectors. + /// + public TestRunSummary( + int total, + int executed, + int pass, + int fail, + TestOutcome outcome, + List runMessages, + string stdOut, + IList resultFiles, + List dataCollectors) + { + this.totalTests = total; + this.executedTests = executed; + this.passedTests = pass; + this.failedTests = fail; + int countForNonExistingResults = 0; // if below values are assigned constants 0, compiler gives warning CS0414 + this.abortedTests = countForNonExistingResults; + this.errorTests = countForNonExistingResults; + this.timeoutTests = countForNonExistingResults; + this.inconclusiveTests = countForNonExistingResults; + this.passedButRunAborted = countForNonExistingResults; + this.notRunnable = countForNonExistingResults; + this.notExecuted = countForNonExistingResults; + this.disconnected = countForNonExistingResults; + this.warning = countForNonExistingResults; + this.completed = countForNonExistingResults; + this.inProgress = countForNonExistingResults; + this.pending = countForNonExistingResults; + + this.outcome = outcome; + this.stdOut = stdOut; + + this.runLevelErrorsAndWarnings = runMessages; + this.resultFiles = resultFiles; + this.collectorDataEntries = dataCollectors; + } + + #endregion + + #region IXmlTestStore Members + + /// + /// Saves the class under the XmlElement.. + /// + /// + /// The parent xml. + /// + /// + /// The parameter + /// + public void Save(XmlElement element, XmlTestStoreParameters parameters) + { + XmlPersistence helper = new XmlPersistence(); + helper.SaveSingleFields(element, this, parameters); + helper.SaveIEnumerable(this.runLevelErrorsAndWarnings, element, "RunInfos", ".", "RunInfo", parameters); + helper.SaveIEnumerable(this.resultFiles, element, "ResultFiles", "@path", "ResultFile", parameters); + helper.SaveIEnumerable(this.collectorDataEntries, element, "CollectorDataEntries", ".", "Collector", parameters); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestType.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestType.cs new file mode 100644 index 0000000000..85e0231eae --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestType.cs @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel +{ + using System; + + using Microsoft.TestPlatform.Extensions.TrxLogger.XML; + + /// + /// Class identifying test type. + /// + public sealed class TestType : IXmlTestStore + { + [StoreXmlSimpleField(".")] + private Guid typeId; + + public TestType(Guid id) + { + if (id == Guid.Empty) + { + throw new ArgumentNullException("id"); + } + + this.typeId = id; + } + + public Guid Id + { + get { return this.typeId; } + } + + public override bool Equals(object obj) + { + TestType tt = obj as TestType; + + if (tt == null) + { + return false; + } + + return this.typeId.Equals(tt.typeId); + } + + + public override int GetHashCode() + { + return this.typeId.GetHashCode(); + } + + #region IXmlTestStore Members + + /// + /// Saves the class under the XmlElement.. + /// + /// + /// The parent xml. + /// + /// + /// The parameter + /// + public void Save(System.Xml.XmlElement element, XmlTestStoreParameters parameters) + { + XmlPersistence.SaveUsingReflection(element, this, null, parameters); + } + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/UnitTestElement.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/UnitTestElement.cs new file mode 100644 index 0000000000..3f82cf6101 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/UnitTestElement.cs @@ -0,0 +1,366 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel +{ + using System; + using System.Diagnostics; + using System.Globalization; + + using Microsoft.TestPlatform.Extensions.TrxLogger.Utility; + using Microsoft.TestPlatform.Extensions.TrxLogger.XML; + + /// + /// Class for all tests + /// + internal class UnitTestElement : IXmlTestStore, IXmlTestStoreCustom + { + #region Constants + /// + /// Default priority for a test method that does not specify a priority + /// + internal const int DefaultPriority = int.MaxValue; + + /// + /// Timeout value indicating a not-set timeout + /// + internal const int NotSetTimeout = 0; + + private static readonly Guid TestTypeGuid = new Guid("13CDC9D9-DDB5-4fa4-A97D-D965CCFC6D4B"); + private static readonly TestType TestTypeInstance = new TestType(TestTypeGuid); + + #endregion + + #region Fields + + private TestId id; + + private string name; + + private string owner; + + private int priority; + + // Todo: Once the Bug 233635 is fixed, check it should populate + private TestCategoryItemCollection testCategories; + + private TestExecId executionId; + + private string storage; + + private string codeBase; + + // partial or fully qualified name of the adapter used to execute the test + private string executorUriOfAdapter; + + private TestMethod testMethod; + + private bool isRunnable; + + private TestListCategoryId catId; + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// + /// The id. + /// + /// + /// The name. + /// + /// + /// The adapter type name. + /// + /// + /// The test method. + /// + public UnitTestElement( + Guid id, + string name, + string executorUriOfAdapter, + TestMethod testMethod) + { + Debug.Assert(!string.IsNullOrEmpty(name), "name is null"); + Debug.Assert(!string.IsNullOrEmpty(executorUriOfAdapter), "executorUriOfAdapter is null"); + Debug.Assert(testMethod != null, "testMethod is null"); + + this.Initialize(); + + this.id = new TestId(id); + this.name = name; + this.executorUriOfAdapter = executorUriOfAdapter; + this.testMethod = testMethod; + Debug.Assert(this.testMethod.ClassName != null, "className is null"); + } + + #endregion + + #region IXmlTestStoreCustom + + string IXmlTestStoreCustom.ElementName + { + get { return "UnitTest"; } + } + + string IXmlTestStoreCustom.NamespaceUri + { + get { return null; } + } + + #endregion + + /// + /// Gets or sets the category id. + /// + /// + /// Instead of setting to null use TestListCategoryId.Uncategorized + /// + public TestListCategoryId CategoryId + { + get + { + return this.catId; + } + + set + { + EqtAssert.ParameterNotNull(value, "CategoryId"); + this.catId = value; + } + } + + /// + /// Gets the id. + /// + public TestId Id + { + get { return this.id; } + } + + /// + /// Gets or sets the execution id. + /// + public TestExecId ExecutionId + { + get { return this.executionId; } + set { this.executionId = value; } + } + + /// + /// Gets or sets the name. + /// + public string Name + { + get + { + return this.name; + } + + set + { + EqtAssert.ParameterNotNull(value, "Name"); + + this.name = value; + } + } + + /// + /// Gets or sets the storage. + /// + public string Storage + { + get + { + return this.storage; + } + + set + { + EqtAssert.StringNotNullOrEmpty(value, "Storage"); + this.storage = value.ToLowerInvariant(); + } + } + + /// + /// Gets or sets the priority. + /// + public int Priority + { + get + { + return this.priority; + } + + set + { + this.priority = value; + } + } + + /// + /// Gets or sets the owner. + /// + public string Owner + { + get + { + return this.owner; + } + + set + { + EqtAssert.ParameterNotNull(value, "Owner"); + this.owner = value; + } + } + + /// + /// Gets the test type. + /// + public TestType TestType + { + get { return TestTypeInstance; } + } + + /// + /// Gets or sets the test categories. + /// + public TestCategoryItemCollection TestCategories + { + get + { + return this.testCategories; + } + + set + { + EqtAssert.ParameterNotNull(value, "value"); + this.testCategories = value; + } + } + + /// + /// The assign code base. + /// + /// + /// The code base. + /// + public void AssignCodeBase(string cb) + { + EqtAssert.StringNotNullOrEmpty(cb, "codeBase"); + this.codeBase = cb; + } + + public bool IsRunnable + { + get { return this.isRunnable; } + } + + #region Overrides + + /// + /// Override for Tostring. + /// + /// + /// The . + /// + public override string ToString() + { + return string.Format( + CultureInfo.InvariantCulture, + "'{0}' {1}", + this.name != null ? this.name : TrxResource.Common_NullInMessages, + this.id != null ? this.id.ToString() : TrxResource.Common_NullInMessages); + } + + /// + /// Override for Equals. + /// + /// + /// The object to compare. + /// + /// + /// The . + /// + public override bool Equals(object other) + { + UnitTestElement otherTest = other as UnitTestElement; + if (otherTest == null) + { + return false; + } + + return this.id.Equals(otherTest.id); + } + + /// + /// Override for GetHashCode + /// + /// + /// The . + /// + public override int GetHashCode() + { + return this.id.GetHashCode(); + } + + #endregion + + #region IXmlTestStore Members + + /// + /// Saves the class under the XmlElement.. + /// + /// + /// The parent xml. + /// + /// + /// The parameter + /// + public void Save(System.Xml.XmlElement element, XmlTestStoreParameters parameters) + { + XmlPersistence h = new XmlPersistence(); + + h.SaveSimpleField(element, "@name", this.name, null); + h.SaveSimpleField(element, "@storage", this.storage, string.Empty); + h.SaveSimpleField(element, "@priority", this.priority, DefaultPriority); + h.SaveSimpleField(element, "Owners/Owner/@name", this.owner, string.Empty); + h.SaveObject(this.testCategories, element, "TestCategory", parameters); + + // Save the test ID. We exclude "test" from the default locations used by TestId, since this is already a test + // element. Ideally, we would let TestId save the IDs to the default locations, but the previous behavior of + // TestElement was to store the test ID at @testId, and since we can't change this, TestId supports custom + // locations for the IDs. See TestId.GetLocations for more info. + XmlTestStoreParameters testIdParameters = XmlTestStoreParameters.GetParameters(); + testIdParameters[TestId.IdLocationKey] = "@id"; + h.SaveObject(this.id, element, testIdParameters); + + if (this.executionId != null) + { + h.SaveGuid(element, "Execution/@id", this.executionId.Id); + } + + h.SaveSimpleField(element, "TestMethod/@codeBase", this.codeBase, string.Empty); + h.SaveSimpleField(element, "TestMethod/@executorUriOfAdapter", this.executorUriOfAdapter, string.Empty); + h.SaveObject(this.testMethod, element, "TestMethod", parameters); + } + + #endregion //IXmlTestStore + + private void Initialize() + { + this.id = TestId.Empty; + this.name = string.Empty; + this.owner = string.Empty; + this.priority = DefaultPriority; + this.executionId = TestExecId.Empty; + this.testCategories = new TestCategoryItemCollection(); + this.storage = string.Empty; + this.isRunnable = true; + this.catId = TestListCategoryId.Uncategorized; + } + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/UnitTestResult.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/UnitTestResult.cs new file mode 100644 index 0000000000..f4ca34b50e --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/UnitTestResult.cs @@ -0,0 +1,455 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.IO; + + using Microsoft.TestPlatform.Extensions.TrxLogger.Utility; + using Microsoft.TestPlatform.Extensions.TrxLogger.XML; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + + /// + /// Class for unit test result. + /// + internal class UnitTestResult: IXmlTestStore + { + #region Fields + // id of test within run + private TestResultId id; + + // name of test within run + private string testName; + + private string computerInfo; + + private TimeSpan duration; + + private DateTime startTime; + + private DateTime endTime; + + // type of test (Guid) + private TestType testType; + + /// + /// The outcome of the test result + /// + private TestOutcome outcome; + + /// + /// The test run in which the test was executed + /// + private TestRun testRun; + + private string stdOut; + + private string stdErr; + + private string debugTrace; + + private TestResultErrorInfo errorInfo; + + private TestListCategoryId categoryId; + + private ArrayList textMessages; + + /// + /// Directory containing the test result files, relative to the root test results directory + /// + private string relativeTestResultsDirectory; + + /// + /// Paths to test result files, relative to the test results folder, sorted in increasing order + /// + private SortedList resultFiles = new SortedList(StringComparer.OrdinalIgnoreCase); + + /// + /// Information provided by data collectors for the test case + /// + private List collectorDataEntries = new List(); + + #endregion + + #region Constructor + + /// + /// Initializes a new instance of the class. + /// + /// + /// The computer name. + /// + /// + /// The run id. + /// + /// + /// The test. + /// + /// + /// The outcome. + /// + public UnitTestResult(string computerName, Guid runId, UnitTestElement test, TestOutcome outcome) + { + Debug.Assert(computerName != null, "computername is null"); + Debug.Assert(test != null, "test is null"); + Debug.Assert(!Guid.Empty.Equals(test.ExecutionId.Id), "ExecutionId is empty"); + Debug.Assert(!Guid.Empty.Equals(test.Id.Id), "Id is empty"); + + this.Initialize(); + + this.id = new TestResultId(runId, test.ExecutionId, test.Id); + this.testName = test.Name; + this.testType = test.TestType; + this.computerInfo = computerName; + + this.outcome = outcome; + this.categoryId = test.CategoryId; + this.relativeTestResultsDirectory = TestRunDirectories.GetRelativeTestResultsDirectory(test.ExecutionId.Id); + } + + #endregion + + #region properties + + /// + /// Gets or sets the end time. + /// + public DateTime EndTime + { + get { return this.endTime; } + set { this.endTime = value; } + } + + /// + /// Gets or sets the start time. + /// + public DateTime StartTime + { + get { return this.startTime; } + set { this.startTime = value; } + } + + /// + /// Gets or sets the duration. + /// + public TimeSpan Duration + { + get + { + return this.duration; + } + + set + { + // On some hardware the Stopwatch.Elapsed can return a negative number. This tends + // to happen when the duration of the test is very short and it is hardware dependent + // (seems to happen most on virtual machines or machines with AMD processors). To prevent + // reporting a negative duration, use TimeSpan.Zero when the elapsed time is less than zero. + EqtTrace.WarningIf(value < TimeSpan.Zero, "TestResult.Duration: The duration is being set to {0}. Since the duration is negative the duration will be updated to zero.", value); + this.duration = value > TimeSpan.Zero ? value : TimeSpan.Zero; + } + } + + /// + /// Gets the computer name. + /// + public string ComputerName + { + get { return this.computerInfo; } + } + + /// + /// Gets or sets the outcome. + /// + public TestOutcome Outcome + { + get { return this.outcome; } + set { this.outcome = value; } + } + + + /// + /// Gets or sets the id. + /// + public TestResultId Id + { + get { return this.id; } + internal set { this.id = value; } + } + + /// + /// Gets or sets the error message. + /// + public string ErrorMessage + { + get + { + if (this.errorInfo == null) + { + return string.Empty; + } + + return this.errorInfo.Message; + } + + set + { + this.errorInfo = new TestResultErrorInfo(value); + } + } + + /// + /// Gets or sets the error stack trace. + /// + public string ErrorStackTrace + { + get + { + if (this.errorInfo == null) + { + return string.Empty; + } + + return this.errorInfo.StackTrace; + } + + set + { + Debug.Assert(this.errorInfo != null, "errorInfo is null"); + this.errorInfo.StackTrace = value; + } + } + + /// + /// Gets the text messages. + /// + /// + /// Additional information messages from TestTextResultMessage, e.g. generated by TestOutcome.WriteLine. + /// Avoid using this property in the following way: for (int i=0; i<prop.Length; i++) { ... prop[i] ...} + /// + public string[] TextMessages + { + get + { + return (string[])this.textMessages.ToArray(typeof(string)); + } + + internal set + { + if (value != null) + { + this.textMessages = new ArrayList(value); + } + else + { + this.textMessages.Clear(); + } + } + } + + /// + /// Gets or sets the standard out. + /// + public string StdOut + { + get { return this.stdOut ?? string.Empty; } + set { this.stdOut = value; } + } + + /// + /// Gets or sets the standard err. + /// + public string StdErr + { + get { return this.stdErr ?? string.Empty; } + set { this.stdErr = value; } + } + + /// + /// Gets or sets the debug trace. + /// + public string DebugTrace + { + get { return this.debugTrace ?? string.Empty; } + set { this.debugTrace = value; } + } + + /// + /// Gets the path to the test results directory + /// + public string TestResultsDirectory + { + get + { + if (this.testRun == null) + { + Debug.Fail("'m_testRun' is null"); + throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, TrxResource.Common_MissingRunInResult)); + } + + return this.testRun.GetResultFilesDirectory(this); + } + } + + /// + /// Gets the directory containing the test result files, relative to the root results directory + /// + internal string RelativeTestResultsDirectory + { + get + { + return this.relativeTestResultsDirectory; + } + } + + #endregion + + #region Overrides + public override bool Equals(object obj) + { + UnitTestResult trm = obj as UnitTestResult; + if (trm == null) + { + return false; + } + Debug.Assert(this.id != null, "id is null"); + Debug.Assert(trm.id != null, "test result message id is null"); + return this.id.Equals(trm.id); + } + + public override int GetHashCode() + { + Debug.Assert(this.id != null, "id is null"); + return this.id.GetHashCode(); + } + + #endregion + + /// + /// Helper function to add a text message info to the test result + /// + /// Message to be added + public void AddTextMessage(string text) + { + EqtAssert.ParameterNotNull(text, "text"); + this.textMessages.Add(text); + } + + /// + /// Sets the test run the test was executed in + /// + /// The test run the test was executed in + internal virtual void SetTestRun(TestRun testRun) + { + Debug.Assert(testRun != null, "'testRun' is null"); + this.testRun = testRun; + } + + /// + /// Adds result files to the collection + /// + /// Paths to the result files + internal void AddResultFiles(IEnumerable resultFileList) + { + Debug.Assert(resultFileList != null, "'resultFileList' is null"); + + string testResultsDirectory = this.TestResultsDirectory; + foreach (string resultFile in resultFileList) + { + Debug.Assert(!string.IsNullOrEmpty(resultFile), "'resultFile' is null or empty"); + Debug.Assert(resultFile.Trim() == resultFile, "'resultFile' has whitespace at the ends"); + Debug.Assert(Path.IsPathRooted(resultFile), "'resultFile' is a relative path"); + + this.resultFiles[FileHelper.MakePathRelative(resultFile, testResultsDirectory)] = null; + } + } + + /// + /// Adds collector data entries to the collection + /// + /// The collector data entry to add + internal void AddCollectorDataEntries(IEnumerable collectorDataEntryList) + { + Debug.Assert(collectorDataEntryList != null, "'collectorDataEntryList' is null"); + + string testResultsDirectory = this.TestResultsDirectory; + foreach (CollectorDataEntry collectorDataEntry in collectorDataEntryList) + { + Debug.Assert(collectorDataEntry != null, "'collectorDataEntry' is null"); + Debug.Assert(!this.collectorDataEntries.Contains(collectorDataEntry), "The collector data entry already exists in the collection"); +#if DEBUG + // Verify that any URI data attachments in the entry have relative paths + foreach (IDataAttachment attachment in collectorDataEntry.Attachments) + { + UriDataAttachment uriDataAttachment = attachment as UriDataAttachment; + if (uriDataAttachment != null) + { + Debug.Assert(uriDataAttachment.Uri.IsAbsoluteUri, "'collectorDataEntry' contains a URI data attachment with a relative URI"); + } + } +#endif + + this.collectorDataEntries.Add(collectorDataEntry.Clone(testResultsDirectory, false)); + } + } + + + #region IXmlTestStore Members + + /// + /// Saves the class under the XmlElement.. + /// + /// + /// The parent xml. + /// + /// + /// The parameter + /// + public void Save(System.Xml.XmlElement element, XmlTestStoreParameters parameters) + { + XmlPersistence helper = new XmlPersistence(); + + helper.SaveObject(this.id, element, ".", parameters); + helper.SaveSimpleField(element, "@testName", this.testName, string.Empty); + helper.SaveSimpleField(element, "@computerName", this.computerInfo, string.Empty); + helper.SaveSimpleField(element, "@duration", this.duration, default(TimeSpan)); + helper.SaveSimpleField(element, "@startTime", this.startTime, default(DateTime)); + helper.SaveSimpleField(element, "@endTime", this.endTime, default(DateTime)); + helper.SaveGuid(element, "@testType", this.testType.Id); + + if (this.stdOut != null) + { + this.stdOut = this.stdOut.Trim(); + } + + if (this.stdErr != null) + { + this.stdErr = this.stdErr.Trim(); + } + + helper.SaveSimpleField(element, "@outcome", this.outcome, default(TestOutcome)); + helper.SaveSimpleField(element, "Output/StdOut", this.stdOut, string.Empty); + helper.SaveSimpleField(element, "Output/StdErr", this.stdErr, string.Empty); + helper.SaveSimpleField(element, "Output/DebugTrace", this.debugTrace, string.Empty); + helper.SaveObject(this.errorInfo, element, "Output/ErrorInfo", parameters); + + helper.SaveGuid(element, "@testListId", this.categoryId.Id); + + helper.SaveIEnumerable(this.textMessages, element, "Output/TextMessages", ".", "Message", parameters); + helper.SaveSimpleField(element, "@relativeResultsDirectory", this.relativeTestResultsDirectory, null); + helper.SaveIEnumerable(this.resultFiles.Keys, element, "ResultFiles", "@path", "ResultFile", parameters); + helper.SaveIEnumerable(this.collectorDataEntries, element, "CollectorDataEntries", ".", "Collector", parameters); + } + + #endregion + + private void Initialize() + { + this.textMessages = new ArrayList(); + } + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/UriDataAttachment.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/UriDataAttachment.cs new file mode 100644 index 0000000000..809be67566 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/UriDataAttachment.cs @@ -0,0 +1,149 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel +{ + using System; + using System.Diagnostics; + using System.IO; + using System.Xml; + + using Microsoft.TestPlatform.Extensions.TrxLogger.Utility; + using Microsoft.TestPlatform.Extensions.TrxLogger.XML; + + /// + /// Class that provides a basic implementation of IUriAttachment, which can be used by plugin + /// writers to send any resource accessible by a URI as an attachment. + /// + public class UriDataAttachment : IDataAttachment, IXmlTestStore + { + #region Private fields + + /// + /// The name for the attachment + /// + private string description; + + /// + /// The URI pointing to the resource that forms the data for this attachment + /// + private Uri uri; + + #endregion + + /// + /// Initializes the URI data attachment + /// + /// Short description for the attachment + /// The URI pointing to the resource + /// 'name' is null or empty + /// 'uri' is null + public UriDataAttachment(string description, Uri uri) + { + this.Initialize(description, uri); + } + + #region IDataAttachment Members + + /// + /// Gets short description for the attachment. + /// + public string Description + { + get + { + return this.description; + } + } + + /// + /// Gets the URI that can be used to obtain the data of this attachment + /// + public Uri Uri + { + get + { + return this.uri; + } + } + + #endregion + + #region IXmlTestStore Members + + /// + /// Saves the class under the XmlElement. + /// + /// + /// The parent xml. + /// + /// + /// The parameter + /// + public void Save(XmlElement element, XmlTestStoreParameters parameters) + { + EqtAssert.ParameterNotNull(element, "element"); + + XmlPersistence helper = new XmlPersistence(); + helper.SaveSimpleField(element, ".", this.description, null); + + // The URI is not a true URI, it must always be a local path represented as a URI. Also, the URI can be absolute or + // relative. We use OriginalString because: + // - ToString gets a string in the form "file://..." for an absolute URI and what was passed in for a relative URI + // - AbsoluteUri only works for an absolute URI naturally + // - LocalPath only works for an absolute URI + // Due to the above assumption, that it is always an absolute or relative local path to a file, it's simplest and + // safest to treat the URI as a string and just use OriginalString. + helper.SaveSimpleField(element, "@href", this.uri.OriginalString, null); + } + + #endregion + + #region Internal Methods + + /// + /// Clones the instance and makes the URI in the clone absolute using the specified base directory + /// + /// The base directory to use to make the URI absolute + /// True to use an absolute URI in the clone, false to use a relative URI + /// A clone of the instance, with the URI made absolute + internal UriDataAttachment Clone(string baseDirectory, bool useAbsoluteUri) + { + Debug.Assert(!string.IsNullOrEmpty(baseDirectory), "'baseDirectory' is null or empty"); + Debug.Assert(baseDirectory == baseDirectory.Trim(), "'baseDirectory' contains whitespace at the ends"); + Debug.Assert(Path.IsPathRooted(baseDirectory), "'baseDirectory' is not a rooted path"); + + if (useAbsoluteUri != this.uri.IsAbsoluteUri) + { + Uri uriToUse; + if (useAbsoluteUri) + { + uriToUse = new Uri(Path.Combine(baseDirectory, this.uri.OriginalString), UriKind.Absolute); + } + else + { + uriToUse = new Uri(FileHelper.MakePathRelative(this.uri.OriginalString, baseDirectory), UriKind.Relative); + } + + return new UriDataAttachment(this.description, uriToUse); + } + + // The URI in this instance is already how we want it, and since this class is immutable, no need to clone + return this; + } + + #endregion + + #region Private Methods + + private void Initialize(string desc, Uri uri) + { + EqtAssert.ParameterNotNull(desc, "desc"); + EqtAssert.ParameterNotNull(uri, "uri"); + + this.description = desc; + this.uri = uri; + } + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Properties/AssemblyInfo.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..2c9d5c120e --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +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: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Microsoft.TestPlatform.Extensions.TrxLogger")] +[assembly: AssemblyTrademark("")] + +// 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("60d876ee-f278-4bf8-bc8a-15b356895c6f")] diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/TrxLogger.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/TrxLogger.cs new file mode 100644 index 0000000000..33631a8607 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/TrxLogger.cs @@ -0,0 +1,432 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Extensions.TrxLogger +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + using System.IO; + using System.Text; + using System.Xml; + + using Microsoft.TestPlatform.Extensions.TrxLogger; + using Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel; + using Microsoft.TestPlatform.Extensions.TrxLogger.Utility; + using Microsoft.TestPlatform.Extensions.TrxLogger.XML; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + + using ObjectModel.Logging; + + using TrxLoggerObjectModel = Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel; + + /// + /// Logger for Generating TRX + /// + [FriendlyName(TrxLogger.FriendlyName)] + [ExtensionUri(TrxLogger.ExtensionUri)] + internal class TrxLogger : ITestLogger + { + #region Constants + + /// + /// Uri used to uniquely identify the TRX logger. + /// + public const string ExtensionUri = "logger://Microsoft/TestPlatform/TrxLogger/v2"; + + /// + /// Alternate user friendly string to uniquely identify the console logger. + /// + public const string FriendlyName = "Trx"; + + /// + /// Prefix of the data collector + /// + public const string DataCollectorUriPrefix = "dataCollector://"; + + #endregion + + #region Fields + + /// + /// Cache the TRX filename + /// + private static string trxFileName; + + private TrxLoggerObjectModel.TestRun testRun; + private List results; + private List testElements; + private List entries; + + /// + /// Specifies the run level "out" messages + /// + private StringBuilder runLevelStdOut; + + // List of run level errors and warnings generated. These are logged in the Trx in the Results Summary. + private List runLevelErrorsAndWarnings; + + private TrxLoggerObjectModel.TestOutcome testRunOutcome = TrxLoggerObjectModel.TestOutcome.Passed; + + private int totalTests, passTests, failTests; + + #endregion + + /// + /// Gets the directory under which trx file should be saved. + /// + [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Reviewed. Suppression is OK here.")] + public static string TrxFileDirectory + { + get; + internal set; + } + + #region ITestLogger + + /// + /// Initializes the Test Logger. + /// + /// Events that can be registered for. + /// Test Run Directory + public void Initialize(TestLoggerEvents events, string testRunDirectory) + { + if (events == null) + { + throw new ArgumentNullException(nameof(events)); + } + + if (string.IsNullOrEmpty(testRunDirectory)) + { + throw new ArgumentNullException(nameof(testRunDirectory)); + } + + // Register for the events. + events.TestRunMessage += this.TestMessageHandler; + events.TestResult += this.TestResultHandler; + events.TestRunComplete += this.TestRunCompleteHandler; + + // ToDo: + // currently we are getting null in testRunDirectory because reading run setting work has to be done + TrxFileDirectory = testRunDirectory; + + this.InitializeInternal(); + } + + #endregion + + #region ForTesting + + internal string GetRunLevelInformationalMessage() + { + return this.runLevelStdOut.ToString(); + } + + internal List GetRunLevelErrorsAndWarnings() + { + return this.runLevelErrorsAndWarnings; + } + + internal TestRun LoggerTestRun + { + get { return this.testRun; } + } + + internal int TotalTestCount + { + get { return totalTests; } + } + + internal int PassedTestCount + { + get { return passTests; } + } + + internal int FailedTestCount + { + get { return failTests; } + } + + internal int TestResultCount + { + get { return this.results.Count; } + } + + internal int UnitTestElementCount + { + get { return this.testElements.Count; } + } + + internal int TestEntryCount + { + get { return this.entries.Count; } + } + + internal TrxLoggerObjectModel.TestOutcome TestResultOutcome + { + get { return this.testRunOutcome; } + } + + #endregion + + #region Event Handlers + + /// + /// Called when a test message is received. + /// + /// + /// The sender. + /// + /// + /// Event args + /// + internal void TestMessageHandler(object sender, TestRunMessageEventArgs e) + { + ValidateArg.NotNull(sender, "sender"); + ValidateArg.NotNull(e, "e"); + + TrxLoggerObjectModel.RunInfo runMessage; + switch (e.Level) + { + case TestMessageLevel.Informational: + this.AddRunLevelInformationalMessage(e.Message); + break; + case TestMessageLevel.Warning: + runMessage = new TrxLoggerObjectModel.RunInfo(e.Message, null, Environment.MachineName, TrxLoggerObjectModel.TestOutcome.Warning); + this.runLevelErrorsAndWarnings.Add(runMessage); + break; + case TestMessageLevel.Error: + this.testRunOutcome = TrxLoggerObjectModel.TestOutcome.Failed; + runMessage = new TrxLoggerObjectModel.RunInfo(e.Message, null, Environment.MachineName, TrxLoggerObjectModel.TestOutcome.Error); + this.runLevelErrorsAndWarnings.Add(runMessage); + break; + default: + Debug.Fail("TrxLogger.TestMessageHandler: The test message level is unrecognized: {0}", e.Level.ToString()); + break; + } + } + + /// + /// Called when a test result is received. + /// + /// + /// The sender. + /// + /// + /// The eventArgs. + /// + internal void TestResultHandler(object sender, ObjectModel.Logging.TestResultEventArgs e) + { + if (this.testRun == null) + { + Guid runId = Guid.NewGuid(); + + this.testRun = new TestRun(runId); + this.testRun.Started = e.Result.StartTime.UtcDateTime; + + // Save default test settings + string runDeploymentRoot = FileHelper.ReplaceInvalidFileNameChars(this.testRun.Name); + TestRunConfiguration testrunConfig = new TestRunConfiguration("default"); + + testrunConfig.RunDeploymentRootDirectory = runDeploymentRoot; + + this.testRun.RunConfiguration = testrunConfig; + } + + // Convert skipped test to a log entry as that is the behaviour of mstest. + if (e.Result.Outcome == ObjectModel.TestOutcome.Skipped) + { + this.HandleSkippedTest(e.Result); + } + + // Create MSTest test element from rocksteady test case + UnitTestElement testElement = Converter.ToUnitTestElement(e.Result); + + // Conver the rocksteady result to MSTest result + TrxLoggerObjectModel.TestOutcome testOutcome = Converter.ToOutcome(e.Result.Outcome); + TrxLoggerObjectModel.UnitTestResult testResult = Converter.ToUnitTestResult(e.Result, testElement, testOutcome, this.testRun, TrxFileDirectory); + + // Set various counts (passtests, failed tests, total tests) + this.totalTests++; + if (testResult.Outcome == TrxLoggerObjectModel.TestOutcome.Failed) + { + this.testRunOutcome = TrxLoggerObjectModel.TestOutcome.Failed; + this.failTests++; + } + else if (testResult.Outcome == TrxLoggerObjectModel.TestOutcome.Passed) + { + this.passTests++; + } + + // Add results to in-memory lists that are saved to the xml at completion. + this.results.Add(testResult); + + if (!this.testElements.Contains(testElement)) + { + this.testElements.Add(testElement); + } + + // create a test entry + TestEntry te = new TestEntry(testElement.Id, TestListCategory.UncategorizedResults.Id); + te.ExecId = testElement.ExecutionId; + this.entries.Add(te); + } + + /// + /// Called when a test run is completed. + /// + /// + /// The sender. + /// + /// + /// Test run complete events arguments. + /// + internal void TestRunCompleteHandler(object sender, TestRunCompleteEventArgs e) + { + if (this.testRun != null) + { + XmlPersistence helper = new XmlPersistence(); + XmlTestStoreParameters parameters = XmlTestStoreParameters.GetParameters(); + XmlElement rootElement = helper.CreateRootElement("TestRun"); + + // Save runId/username/creation time etc. + this.testRun.Finished = DateTime.Now; + helper.SaveSingleFields(rootElement, this.testRun, parameters); + + // Save test settings + helper.SaveObject(this.testRun.RunConfiguration, rootElement, "TestSettings", parameters); + + // Save test results + helper.SaveIEnumerable(this.results, rootElement, "Results", ".", null, parameters); + + // Save test definitions + helper.SaveIEnumerable(this.testElements, rootElement, "TestDefinitions", ".", null, parameters); + + // Save test entries + helper.SaveIEnumerable(this.entries, rootElement, "TestEntries", ".", "TestEntry", parameters); + + // Save default categories + List categories = new List(); + categories.Add(TestListCategory.UncategorizedResults); + categories.Add(TestListCategory.AllResults); + helper.SaveList(categories, rootElement, "TestLists", ".", "TestList", parameters); + + // Save summary + if (this.testRunOutcome == TrxLoggerObjectModel.TestOutcome.Passed) + { + this.testRunOutcome = TrxLoggerObjectModel.TestOutcome.Completed; + } + + List errorMessages = new List(); + List collectorEntries = Converter.ToCollectionEntries(e.AttachmentSets, this.testRun, TrxFileDirectory); + IList resultFiles = Converter.ToResultFiles(e.AttachmentSets, this.testRun, TrxFileDirectory, errorMessages); + + if (errorMessages.Count > 0) + { + // Got some errors while attaching files, report them and set the outcome of testrun to be Error... + this.testRunOutcome = TrxLoggerObjectModel.TestOutcome.Error; + foreach (string msg in errorMessages) + { + RunInfo runMessage = new RunInfo(msg, null, Environment.MachineName, TrxLoggerObjectModel.TestOutcome.Error); + this.runLevelErrorsAndWarnings.Add(runMessage); + } + } + + TestRunSummary runSummary = new TestRunSummary( + this.totalTests, + this.passTests + this.failTests, + this.passTests, + this.failTests, + this.testRunOutcome, + this.runLevelErrorsAndWarnings, + this.runLevelStdOut.ToString(), + resultFiles, + collectorEntries); + + helper.SaveObject(runSummary, rootElement, "ResultSummary", parameters); + + if (Directory.Exists(TrxFileDirectory) == false) + { + Directory.CreateDirectory(TrxFileDirectory); + } + + if (string.IsNullOrEmpty(trxFileName)) + { + // save the xml to file in testResultsFolder + trxFileName = this.GetTrxFileName(TrxFileDirectory, this.testRun.RunConfiguration.RunDeploymentRootDirectory); + } + + try + { + FileStream fs = File.OpenWrite(trxFileName); + rootElement.OwnerDocument.Save(fs); + String resultsFileMessage = String.Format(CultureInfo.CurrentCulture, TrxResource.TrxLoggerResultsFile, trxFileName); + Console.WriteLine(resultsFileMessage); + } + catch (System.UnauthorizedAccessException fileWriteException) + { + Console.WriteLine(fileWriteException.Message); + } + } + } + + /// + /// Get full path to trx file + /// + /// + /// The base Directory. + /// + /// + /// The trx File Name. + /// + /// + /// trx file name. + /// + [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Reviewed. Suppression is OK here.")] + private string GetTrxFileName(string baseDirectory, string trxFileName) + { + return FileHelper.GetNextIterationFileName(baseDirectory, trxFileName + ".trx", false); + } + + // Initializes trx logger cache. + private void InitializeInternal() + { + this.results = new List(); + this.testElements = new List(); + this.entries = new List(); + this.runLevelErrorsAndWarnings = new List(); + this.testRun = null; + this.totalTests = 0; + this.passTests = 0; + this.failTests = 0; + this.runLevelStdOut = new StringBuilder(); + } + + /// + /// Add run level informational message + /// + /// + /// The message. + /// + private void AddRunLevelInformationalMessage(string message) + { + this.runLevelStdOut.Append(message); + } + + // Handle the skipped test result + [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Reviewed. Suppression is OK here.")] + private void HandleSkippedTest(ObjectModel.TestResult rsTestResult) + { + Debug.Assert(rsTestResult.Outcome == ObjectModel.TestOutcome.Skipped, "Test Result should be skipped but it is " + rsTestResult.Outcome); + + ObjectModel.TestCase testCase = rsTestResult.TestCase; + string testCaseName = !string.IsNullOrEmpty(testCase.DisplayName) ? testCase.DisplayName : testCase.FullyQualifiedName; + string message = String.Format(CultureInfo.CurrentCulture, TrxResource.MessageForSkippedTests, testCaseName); + this.AddRunLevelInformationalMessage(message); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/TrxResource.Designer.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/TrxResource.Designer.cs new file mode 100644 index 0000000000..83a47f553c --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/TrxResource.Designer.cs @@ -0,0 +1,333 @@ +//------------------------------------------------------------------------------ +// +// 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 Microsoft.TestPlatform.Extensions.TrxLogger { + using System; + using System.Reflection; + + + /// + /// 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.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class TrxResource { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + internal TrxResource() { + } + + /// + /// 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("Microsoft.TestPlatform.Extensions.TrxLogger.TrxResource", typeof(TrxResource).GetTypeInfo().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 The parameter cannot be less than 0.. + /// + public static string Common_CannotBeLessThanZero { + get { + return ResourceManager.GetString("Common_CannotBeLessThanZero", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The parameter cannot be null or empty.. + /// + public static string Common_CannotBeNullOrEmpty { + get { + return ResourceManager.GetString("Common_CannotBeNullOrEmpty", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot get free name for {0}(1),(2),... in directory {1}. Please clean up this directory.. + /// + public static string Common_CannotGetNextIterationName { + get { + return ResourceManager.GetString("Common_CannotGetNextIterationName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to deployment item '{0}'. + /// + public static string Common_DeploymentItem { + get { + return ResourceManager.GetString("Common_DeploymentItem", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to deployment item '{0}' (output directory '{1}'). + /// + public static string Common_DeploymentItemWithOutputDirectory { + get { + return ResourceManager.GetString("Common_DeploymentItemWithOutputDirectory", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Test Settings are not specified.. + /// + public static string Common_MissingRunConfigInRun { + get { + return ResourceManager.GetString("Common_MissingRunConfigInRun", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The active Test Settings do not define the Run Deployment Directory.. + /// + public static string Common_MissingRunDeploymentRootInRunConfig { + get { + return ResourceManager.GetString("Common_MissingRunDeploymentRootInRunConfig", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The result is not associated with a test run. Use a result that was obtained from an in-progress or completed test run.. + /// + public static string Common_MissingRunInResult { + get { + return ResourceManager.GetString("Common_MissingRunInResult", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The specified file/directory name '{0}' is not valid.. + /// + public static string Common_NothingLeftAfterReplaciingBadCharsInName { + get { + return ResourceManager.GetString("Common_NothingLeftAfterReplaciingBadCharsInName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to (null). + /// + public static string Common_NullInMessages { + get { + return ResourceManager.GetString("Common_NullInMessages", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0}@{1} {2}. + /// + public static string Common_TestRunName { + get { + return ResourceManager.GetString("Common_TestRunName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Failed to attach files from: {0} + ///Error Details: {1}:{2}. + /// + public static string FailureToAttach { + get { + return ResourceManager.GetString("FailureToAttach", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Test '{0}' was skipped in the test run.. + /// + public static string MessageForSkippedTests { + get { + return ResourceManager.GetString("MessageForSkippedTests", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Aborted. + /// + public static string TestOutcomeAborted { + get { + return ResourceManager.GetString("TestOutcomeAborted", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Completed. + /// + public static string TestOutcomeCompleted { + get { + return ResourceManager.GetString("TestOutcomeCompleted", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Disconnected. + /// + public static string TestOutcomeDisconnected { + get { + return ResourceManager.GetString("TestOutcomeDisconnected", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Error. + /// + public static string TestOutcomeError { + get { + return ResourceManager.GetString("TestOutcomeError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Failed. + /// + public static string TestOutcomeFailed { + get { + return ResourceManager.GetString("TestOutcomeFailed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Inconclusive. + /// + public static string TestOutcomeInconclusive { + get { + return ResourceManager.GetString("TestOutcomeInconclusive", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to In Progress. + /// + public static string TestOutcomeInProgress { + get { + return ResourceManager.GetString("TestOutcomeInProgress", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Not Executed. + /// + public static string TestOutcomeNotExecuted { + get { + return ResourceManager.GetString("TestOutcomeNotExecuted", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Not Runnable. + /// + public static string TestOutcomeNotRunnable { + get { + return ResourceManager.GetString("TestOutcomeNotRunnable", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Passed. + /// + public static string TestOutcomePassed { + get { + return ResourceManager.GetString("TestOutcomePassed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Passed (run aborted). + /// + public static string TestOutcomePassedButRunAborted { + get { + return ResourceManager.GetString("TestOutcomePassedButRunAborted", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Pending. + /// + public static string TestOutcomePending { + get { + return ResourceManager.GetString("TestOutcomePending", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Timeout. + /// + public static string TestOutcomeTimeout { + get { + return ResourceManager.GetString("TestOutcomeTimeout", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Warning. + /// + public static string TestOutcomeWarning { + get { + return ResourceManager.GetString("TestOutcomeWarning", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Results File: {0}. + /// + public static string TrxLoggerResultsFile { + get { + return ResourceManager.GetString("TrxLoggerResultsFile", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to All Loaded Results. + /// + public static string TS_AllResults { + get { + return ResourceManager.GetString("TS_AllResults", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Results Not in a List. + /// + public static string TS_UncategorizedResults { + get { + return ResourceManager.GetString("TS_UncategorizedResults", resourceCulture); + } + } + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/TrxResource.resx b/src/Microsoft.TestPlatform.Extensions.TrxLogger/TrxResource.resx new file mode 100644 index 0000000000..41612403b4 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/TrxResource.resx @@ -0,0 +1,211 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + Aborted + + + Completed + + + Disconnected + + + Error + + + Failed + + + Inconclusive + + + In Progress + + + Not Executed + + + Not Runnable + + + Passed + + + Passed (run aborted) + + + Pending + + + Timeout + + + Warning + + + The parameter cannot be less than 0. + + + The parameter cannot be null or empty. + + + Cannot get free name for {0}(1),(2),... in directory {1}. Please clean up this directory. + + + Test Settings are not specified. + + + The active Test Settings do not define the Run Deployment Directory. + + + The result is not associated with a test run. Use a result that was obtained from an in-progress or completed test run. + + + The specified file/directory name '{0}' is not valid. + + + {0}@{1} {2} + + + Failed to attach files from: {0} +Error Details: {1}:{2} + + + Test '{0}' was skipped in the test run. + + + Results File: {0} + + + All Loaded Results + + + Results Not in a List + + + deployment item '{0}' + + + deployment item '{0}' (output directory '{1}') + + + (null) + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/Collection.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/Collection.cs new file mode 100644 index 0000000000..436da79322 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/Collection.cs @@ -0,0 +1,205 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.Extensions.TrxLogger.Utility +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + + using Microsoft.TestPlatform.Extensions.TrxLogger.XML; + using Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel; + /// + /// Base class for Eqt Collections. + /// Fast collection, default implementations (Add/Remove/etc) do not allow null items and ignore duplicates. + /// + public class EqtBaseCollection : ICollection, IXmlTestStore + { + #region private classes + /// + /// Wraps non-generic enumerator. + /// + /// + private sealed class EqtBaseCollectionEnumerator : IEnumerator + { + private IEnumerator enumerator; + + internal EqtBaseCollectionEnumerator(IEnumerator e) + { + Debug.Assert(e != null, "e is null"); + this.enumerator = e; + } + + public TemplateType Current + { + get { return (TemplateType)this.enumerator.Current; } + } + + object IEnumerator.Current + { + get { return this.enumerator.Current; } + } + + public bool MoveNext() + { + return this.enumerator.MoveNext(); + } + + public void Reset() + { + this.enumerator.Reset(); + } + + public void Dispose() + { + } + } + #endregion + + #region Fields + protected Hashtable container; + + private string childElementName; + #endregion + + #region Constructors + protected EqtBaseCollection() + { + this.container = new Hashtable(); + } + + /// + /// Constructor. + /// + /// For case insensitive comparison use StringComparer.InvariantCultureIgnoreCase. + protected EqtBaseCollection(IEqualityComparer comparer) + { + this.container = new Hashtable(0, comparer); // Ad default Hashtable() constructor creates table with 0 items. + } + + /// + /// Copy constructor. Shallow copy. + /// + /// The object to copy items from. + protected EqtBaseCollection(EqtBaseCollection other) + { + EqtAssert.ParameterNotNull(other, "other"); + this.container = new Hashtable(other.container); + } + #endregion + + #region Methods: ICollection + // TODO: Consider putting check for null to derived classes. + public virtual void Add(T item) + { + EqtAssert.ParameterNotNull(item, "item"); + + if (!this.container.Contains(item)) + { + this.container.Add(item, null); // Do not want to xml-persist the value. + } + } + + public virtual bool Contains(T item) + { + if (item == null) + { + return false; + } + + return this.container.Contains(item); + } + + /// + /// Removes specified item from collection. + /// + /// The item to remove. + /// True if collection contained the item, otherwise false. + public virtual bool Remove(T item) + { + EqtAssert.ParameterNotNull(item, "item"); // This is to be consistent with Add... + + if (this.container.Contains(item)) + { + this.container.Remove(item); + return true; + } + return false; + } + + public virtual void Clear() + { + this.container.Clear(); + } + + /// + /// Shallow copy. Assumes that items are immutable. Override if your items are mutable. + /// + public virtual object Clone() + { + return new EqtBaseCollection(this); + } + + public virtual int Count + { + get { return this.container.Count; } + } + + /// + /// Copies all items to the array. + /// As FxCop recommends, this is an explicit implementation and derived classes need to define strongly typed CopyTo. + /// + public virtual void CopyTo(T[] array, int index) + { + EqtAssert.ParameterNotNull(array, "array"); + this.container.Keys.CopyTo(array, index); + } + + public bool IsReadOnly + { + get { return false; } + } + #endregion + + #region IEnumerable + public virtual IEnumerator GetEnumerator() + { + return this.container.Keys.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return new EqtBaseCollectionEnumerator(this.GetEnumerator()); + } + #endregion + + #region IXmlTestStore Members + + /// + /// Default behavior is to create child elements with name same as name of type T. + /// Does not respect IXmlTestStoreCustom. + /// + public virtual void Save(System.Xml.XmlElement element, XmlTestStoreParameters parameters) + { + XmlPersistence h = new XmlPersistence(); + h.SaveHashtable(this.container, element, ".", ".", null, ChildElementName, parameters); + } + #endregion + + #region Private + private string ChildElementName + { + get + { + if (this.childElementName == null) + { + // All we can do here is to delegate to T. Cannot cast T to IXmlTestStoreCustom as T is a type, not an instance. + this.childElementName = typeof(T).Name; + } + return this.childElementName; + } + } + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/Converter.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/Converter.cs new file mode 100644 index 0000000000..4ba252b7ac --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/Converter.cs @@ -0,0 +1,566 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.Extensions.TrxLogger.Utility +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Text; + + using Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.Extensions.TrxLogger; + using TrxObjectModel = Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel; + using ObjectModel = Microsoft.VisualStudio.TestPlatform.ObjectModel; + + /// + /// The converter class. + /// + internal class Converter + { + /// + /// Property Id storing the TMITestId. + /// + private const string TmiTestIdPropertyIdentifier = "MSTestDiscoverer.TmiTestId"; + + /// + /// Converts the parameter rockSteady test case to test element + /// + /// + /// The rockSteady Test Result. + /// + /// + /// The . + /// + internal static TrxObjectModel.UnitTestElement ToUnitTestElement(ObjectModel.TestResult rockSteadyTestResult) + { + return GetQToolsTestElementFromTestCase(rockSteadyTestResult); + } + + /// + /// Returns QToolsCommon.TestElement from rockSteady TestCase. + /// + /// + /// The rockSteady Test Result. + /// + /// + /// The . + /// + internal static TrxObjectModel.UnitTestElement GetQToolsTestElementFromTestCase(ObjectModel.TestResult rockSteadyTestResult) + { + ObjectModel.TestCase rockSteadyTestCase = rockSteadyTestResult.TestCase; + + // Fix for bug# 868033 + // Use TMI Test id when available. This is needed to ensure that test id in trx files is same as specified in + // .vsmdi files. + // (This is required for test explorer: It removes all test nodes where test id is not in expected test id when merging + // trx files from different batches). + Guid testId = GetTmiTestId(rockSteadyTestCase); + +#if NET46 + if (Guid.Empty.Equals(testId)) + { + testId = rockSteadyTestCase.Id; + } +#else + testId = Guid.NewGuid(); +#endif + + string testDisplayName = rockSteadyTestCase.DisplayName; + + // If it is an inner test case name + if (!string.IsNullOrEmpty(rockSteadyTestResult.DisplayName)) + { + testId = Guid.NewGuid(); // Changing of guid is done so that VS can load trx otherwise it fails with duplicate id error. + testDisplayName = rockSteadyTestResult.DisplayName; + } + + TrxObjectModel.TestMethod testMethod = GetTestMethod(testDisplayName, rockSteadyTestCase); + + // convert the rocksteady tests to TestElement. + TrxObjectModel.UnitTestElement testElement = new TrxObjectModel.UnitTestElement(testId, testDisplayName, rockSteadyTestCase.ExecutorUri.ToString(), testMethod); + testElement.ExecutionId = new TrxObjectModel.TestExecId(Guid.NewGuid()); + testElement.AssignCodeBase(rockSteadyTestCase.Source); + testElement.Storage = rockSteadyTestCase.Source; + + if (rockSteadyTestCase.Traits != null) + { + ObjectModel.Trait priorityTrait = rockSteadyTestCase.Traits.FirstOrDefault(t => t.Name.Equals("Priority")); + if (priorityTrait != null) + { + int priorityValue; + if (Int32.TryParse(priorityTrait.Value, out priorityValue)) + { + testElement.Priority = priorityValue; + } + } + + ObjectModel.Trait ownerTrait = rockSteadyTestCase.Traits.FirstOrDefault(t => t.Name.Equals("Owner")); + if (ownerTrait != null) + { + testElement.Owner = ownerTrait.Value; + } + } + + // reading TestCategories from the testcase + var testCategories = GetCustomPropertyValueFromTestCase(rockSteadyTestCase, "MSTestDiscoverer.TestCategory"); + foreach (string testCategory in testCategories) + { + testElement.TestCategories.Add(testCategory); + } + + return testElement; + } + + /// + /// converts ObjectModel.TestOutcome type to TrxLogger.TestOutcome type + /// + /// + /// The rockSteady Outcome. + /// + /// + /// The . + /// + [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Reviewed. Suppression is OK here.")] + internal static TrxObjectModel.TestOutcome ToOutcome(ObjectModel.TestOutcome rockSteadyOutcome) + { + TrxObjectModel.TestOutcome outcome = TrxObjectModel.TestOutcome.Failed; + + switch (rockSteadyOutcome) + { + case ObjectModel.TestOutcome.Failed: + outcome = TrxObjectModel.TestOutcome.Failed; + break; + case ObjectModel.TestOutcome.Passed: + outcome = TrxObjectModel.TestOutcome.Passed; + break; + case ObjectModel.TestOutcome.Skipped: + outcome = TrxObjectModel.TestOutcome.NotExecuted; + break; + case ObjectModel.TestOutcome.None: + outcome = TrxObjectModel.TestOutcome.Failed; + break; + case ObjectModel.TestOutcome.NotFound: + outcome = TrxObjectModel.TestOutcome.Failed; + break; + default: + Debug.Fail("Unexpected Outcome."); + break; + } + + return outcome; + } + + /// + /// Converts the rockSteady result to unit test result + /// + /// rock steady test result + /// testElement of that test + /// Test outcome + /// test run object + /// TRX file directory + /// TestResult object + internal static TrxObjectModel.UnitTestResult ToUnitTestResult( + ObjectModel.TestResult rockSteadyTestResult, + TrxObjectModel.UnitTestElement testElement, + TrxObjectModel.TestOutcome testOutcome, + TrxObjectModel.TestRun testRun, + string trxFileDirectory) + { + TrxObjectModel.UnitTestResult qtoolsResult = GetQToolsTestResultFromTestResult(rockSteadyTestResult, testElement, testOutcome, testRun); + + // Clear exsting messages and store rocksteady result messages. + qtoolsResult.TextMessages = null; + UpdateResultMessages(qtoolsResult, rockSteadyTestResult); + + // Save result attachments to target location. + UpdateTestResultAttachments(rockSteadyTestResult, qtoolsResult, testRun, trxFileDirectory, true); + + return qtoolsResult; + } + + internal static List ToCollectionEntries(IEnumerable attachmentSets, TestRun testRun, string trxFileDirectory) + { + List collectorEntries = new List(); + if (attachmentSets == null) + { + return collectorEntries; + } + + foreach (var attachmentSet in attachmentSets) + { + if (attachmentSet.Uri.AbsoluteUri.StartsWith(TrxLogger.DataCollectorUriPrefix, StringComparison.OrdinalIgnoreCase)) + { + CollectorDataEntry collectorEntry = ToCollectorEntry(attachmentSet, Guid.Empty, testRun, trxFileDirectory); + collectorEntries.Add(collectorEntry); + } + } + + return collectorEntries; + } + + internal static IList ToResultFiles(IEnumerable attachmentSets, TestRun testRun, string trxFileDirectory, List errorMessages) + { + List resultFiles = new List(); + if (attachmentSets == null) + { + return resultFiles; + } + + foreach (var attachmentSet in attachmentSets) + { + if (!attachmentSet.Uri.AbsoluteUri.StartsWith(TrxLogger.DataCollectorUriPrefix, StringComparison.OrdinalIgnoreCase)) + { + try + { + IList testResultFiles = ToResultFiles(attachmentSet, Guid.Empty, testRun, trxFileDirectory); + resultFiles.AddRange(testResultFiles); + } + catch (Exception e) + { + string errorMsg = string.Format( + CultureInfo.CurrentCulture, + TrxResource.FailureToAttach, + attachmentSet.DisplayName, + e.GetType().ToString(), + e.Message); + errorMessages.Add(errorMsg); + } + } + } + return resultFiles; + } + + /// + /// Returns the QToolsCommon.TestResult object created from rockSteady TestResult. + /// + /// rock steady test result + /// testElement of that test + /// Test outcome + /// test run object + /// TestResult object + private static TrxObjectModel.UnitTestResult GetQToolsTestResultFromTestResult( + ObjectModel.TestResult rockSteadyTestResult, + TrxObjectModel.UnitTestElement testElement, + TrxObjectModel.TestOutcome testOutcome, + TrxObjectModel.TestRun testRun) + { + UnitTestResult testResult = new UnitTestResult(Environment.MachineName, testRun.Id, testElement, testOutcome); + if (rockSteadyTestResult.ErrorMessage != null) + { + testResult.ErrorMessage = rockSteadyTestResult.ErrorMessage; + } + + if (rockSteadyTestResult.ErrorStackTrace != null) + { + testResult.ErrorStackTrace = rockSteadyTestResult.ErrorStackTrace; + } + + // set start and end times + if (rockSteadyTestResult.EndTime != null) + { + testResult.EndTime = rockSteadyTestResult.EndTime.UtcDateTime; + } + if (rockSteadyTestResult.StartTime != null) + { + testResult.StartTime = rockSteadyTestResult.StartTime.UtcDateTime; + } + + if (rockSteadyTestResult.Duration != null) + { + testResult.Duration = rockSteadyTestResult.Duration; + } + + return testResult; + } + + /// + /// Copies the result messages to unitTestResult + /// + /// TRX TestResult + /// rock steady test result + private static void UpdateResultMessages(TrxObjectModel.UnitTestResult unitTestResult, ObjectModel.TestResult testResult) + { + StringBuilder debugTrace = new StringBuilder(); + StringBuilder stdErr = new StringBuilder(); + StringBuilder stdOut = new StringBuilder(); + + foreach (Microsoft.VisualStudio.TestPlatform.ObjectModel.TestResultMessage message in testResult.Messages) + { + if (ObjectModel.TestResultMessage.AdditionalInfoCategory.Equals(message.Category, StringComparison.OrdinalIgnoreCase)) + { + unitTestResult.AddTextMessage(message.Text); + } + else if (ObjectModel.TestResultMessage.DebugTraceCategory.Equals(message.Category, StringComparison.OrdinalIgnoreCase)) + { + debugTrace.AppendLine(message.Text); + } + else if (ObjectModel.TestResultMessage.StandardErrorCategory.Equals(message.Category, StringComparison.OrdinalIgnoreCase)) + { + stdErr.AppendLine(message.Text); + } + else if (ObjectModel.TestResultMessage.StandardOutCategory.Equals(message.Category, StringComparison.OrdinalIgnoreCase)) + { + stdOut.AppendLine(message.Text); + } + else + { + ObjectModel.EqtTrace.Warning("The message category " + message.Category + " does not match any predefined category."); + } + } + + unitTestResult.DebugTrace = debugTrace.ToString(); + unitTestResult.StdErr = stdErr.ToString(); + unitTestResult.StdOut = stdOut.ToString(); + } + + /// + /// Get Custom property values from test cases. + /// + /// TestCase object extracted from the TestResult + /// Property Name from the list of properties in TestCase + /// list of properties + internal static List GetCustomPropertyValueFromTestCase(ObjectModel.TestCase testCase, string categoryID) + { + var customProperty = testCase.Properties.FirstOrDefault(t => t.Id.Equals(categoryID)); + + if (customProperty != null) + { + var cateogryValues = (string[])testCase.GetPropertyValue(customProperty); + if (cateogryValues != null) + { + return cateogryValues.ToList(); + } + else + { + return Enumerable.Empty().ToList(); + } + } + + return Enumerable.Empty().ToList(); + } + + /// + /// Return TMI Test id when available for TestPlatform TestCase. + /// + /// + /// The rock Steady Test Case. + /// + /// + /// The . + /// + private static Guid GetTmiTestId(ObjectModel.TestCase rockSteadyTestCase) + { + Guid tmiTestId = Guid.Empty; + ObjectModel.TestProperty tmiTestIdProperty = rockSteadyTestCase.Properties.FirstOrDefault(property => property.Id.Equals(TmiTestIdPropertyIdentifier)); + if (null != tmiTestIdProperty) + { + tmiTestId = rockSteadyTestCase.GetPropertyValue(tmiTestIdProperty, Guid.Empty); + } + return tmiTestId; + } + + /// + /// Returns TestMethod for given testCase name and its class name. + /// + /// test case display name + /// rockSteady Test Case + /// The + private static TrxObjectModel.TestMethod GetTestMethod(string testDisplayName, ObjectModel.TestCase rockSteadyTestCase) + { + string className = "DefaultClassName"; + string testCaseName = rockSteadyTestCase.FullyQualifiedName; + if (testCaseName.Contains(".")) + { + className = testCaseName.Substring(0, testCaseName.LastIndexOf('.')); + } + else if (testCaseName.Contains("::")) + { + // if this is a C++ test case then we would have a "::" instaed of a '.' + className = testCaseName.Substring(0, testCaseName.LastIndexOf("::")); + + // rename for a consistent behaviour for all tests. + className = className.Replace("::", "."); + } + + return new TrxObjectModel.TestMethod(testDisplayName, className); + } + + private static void UpdateTestResultAttachments(ObjectModel.TestResult rockSteadyTestResult, TrxObjectModel.UnitTestResult testResult, TestRun testRun, string trxFileDirectory, bool addAttachments) + { + if (rockSteadyTestResult.Attachments == null || rockSteadyTestResult.Attachments.Count == 0) + { + return; + } + + // the testResult needs to have the testRun property set. Otherwise Data Collector entries can't be added. + testResult.SetTestRun(testRun); + + // result files + List resultFiles = new List(); + + // data collection files + List collectorEntries = new List(); + + foreach (ObjectModel.AttachmentSet attachmentSet in rockSteadyTestResult.Attachments) + { + try + { + // If the attachement is from data collector + if (attachmentSet.Uri.AbsoluteUri.StartsWith(TrxLogger.DataCollectorUriPrefix, StringComparison.OrdinalIgnoreCase)) + { + CollectorDataEntry collectorEntry = ToCollectorEntry(attachmentSet, testResult.Id.ExecutionId.Id, testRun, trxFileDirectory); + collectorEntries.Add(collectorEntry); + } + else + { + IList testResultFiles = ToResultFiles(attachmentSet, testResult.Id.ExecutionId.Id, testRun, trxFileDirectory); + resultFiles.AddRange(testResultFiles); + } + } + catch (Exception e) + { + string errorMsg = string.Format( + CultureInfo.CurrentCulture, + TrxResource.FailureToAttach, + attachmentSet.DisplayName, + e.GetType().ToString(), + e.Message); + + StringBuilder stdErr = new StringBuilder(testResult.StdErr); + stdErr.AppendLine(errorMsg); + + testResult.StdErr = stdErr.ToString(); + testResult.Outcome = TrxObjectModel.TestOutcome.Error; + } + } + + if (addAttachments) + { + if (resultFiles.Count > 0) + { + testResult.AddResultFiles(resultFiles); + } + + if (collectorEntries.Count > 0) + { + testResult.AddCollectorDataEntries(collectorEntries); + } + } + } + + // Returns a list of collector entry + private static CollectorDataEntry ToCollectorEntry(ObjectModel.AttachmentSet attachmentSet, Guid testResultExecutionId, TestRun testRun, string trxFileDirectory) + { + string runDirectoryName = Path.Combine(trxFileDirectory, testRun.RunConfiguration.RunDeploymentRootDirectory); + string inDirectory = Path.Combine(runDirectoryName, "In"); + + string targetDirectory = inDirectory; + if (!testResultExecutionId.Equals(Guid.Empty)) + { + targetDirectory = Path.Combine(inDirectory, testResultExecutionId.ToString()); + } + + targetDirectory = Path.Combine(targetDirectory, Environment.MachineName); + + if (!Directory.Exists(targetDirectory)) + { + Directory.CreateDirectory(targetDirectory); + } + + List uriDataAttachments = new List(); + foreach (ObjectModel.UriDataAttachment uriDataAttachment in attachmentSet.Attachments) + { + if (ObjectModel.EqtTrace.IsVerboseEnabled) + { + ObjectModel.EqtTrace.Verbose("TrxLogger: ToCollectorEntry: Got attachment " + uriDataAttachment.Uri + " with description " + uriDataAttachment.Description); + } + + string sourceFile = uriDataAttachment.Uri.LocalPath; + Debug.Assert(Path.IsPathRooted(sourceFile), "Source file is not rooted"); + + // copy the source file to the target location + string targetFileName = FileHelper.GetNextIterationFileName(targetDirectory, Path.GetFileName(sourceFile), false); + CopyFile(sourceFile, targetFileName); + + // Add the source file name to the collector files list. + // (Trx viewer automatically adds In\ to the collected file. + string fileName = Path.Combine(Environment.MachineName, Path.GetFileName(sourceFile)); + Uri sourceFileUri = new Uri(fileName, UriKind.Relative); + UriDataAttachment dataAttachment = new UriDataAttachment(uriDataAttachment.Description, sourceFileUri); + + uriDataAttachments.Add(dataAttachment); + } + + return new CollectorDataEntry( + attachmentSet.Uri, + attachmentSet.DisplayName, + Environment.MachineName, + Environment.MachineName, + false, + uriDataAttachments); + } + + // Get the path to the result files + private static IList ToResultFiles(ObjectModel.AttachmentSet attachmentSet, Guid testResultExecutionId, TestRun testRun, string trxFileDirectory) + { + string runDirectoryName = Path.Combine(trxFileDirectory, testRun.RunConfiguration.RunDeploymentRootDirectory); + string testResultDirectory = Path.Combine(runDirectoryName, "In"); + + if (!Guid.Equals(testResultExecutionId, Guid.Empty)) + { + testResultDirectory = Path.Combine(testResultDirectory, testResultExecutionId.ToString()); + } + + testResultDirectory = Path.Combine(testResultDirectory, Environment.MachineName); + + if (!Directory.Exists(testResultDirectory)) + { + Directory.CreateDirectory(testResultDirectory); + } + + List resultFiles = new List(); + foreach (ObjectModel.UriDataAttachment uriDataAttachment in attachmentSet.Attachments) + { + if (ObjectModel.EqtTrace.IsVerboseEnabled) + { + ObjectModel.EqtTrace.Verbose("TrxLogger: ToResultFiles: Got attachment " + uriDataAttachment.Uri + " with local path " + uriDataAttachment.Uri.LocalPath); + } + + string sourceFile = uriDataAttachment.Uri.LocalPath; + Debug.Assert(Path.IsPathRooted(sourceFile), "Source file is not rooted"); + + // copy the source file to the target location + string targetFileName = FileHelper.GetNextIterationFileName(testResultDirectory, Path.GetFileName(sourceFile), false); + CopyFile(sourceFile, targetFileName); + + // Add the source file name to the result files list. + // (Trx viewer automatically adds In\ to the result file. + string fileName = Path.Combine(Environment.MachineName, Path.GetFileName(targetFileName)); + resultFiles.Add(fileName); + } + + return resultFiles; + } + + private static void CopyFile(string sourceFile, string targetFile) + { + try + { + File.Copy(sourceFile, targetFile, true); + } + catch (Exception ex) + { + if (ObjectModel.EqtTrace.IsErrorEnabled) + { + ObjectModel.EqtTrace.Error("Trxlogger: Failed to copy file {0} to {1}. Reason:{2}", sourceFile, targetFile, ex); + } + + throw; + } + } + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/EqtAssert.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/EqtAssert.cs new file mode 100644 index 0000000000..976b598a66 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/EqtAssert.cs @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.Extensions.TrxLogger.Utility +{ + using System; + using System.Diagnostics; + using System.Globalization; + + /// + /// Class to be used for parameter verification. + /// + internal sealed class EqtAssert + { + /// + /// Do not instantiate. + /// + private EqtAssert() + { + } + + /// + /// Verifies that the specified parameter is not null, Debug.Asserts and throws. + /// + /// Expression to check + /// Comment to write + public static void IsTrue(bool expression, string comment) + { + Debug.Assert(expression, comment); + if (!expression) + { + throw new Exception(comment); + } + } + + /// + /// Verifies that the specified parameter is not null, Debug.Asserts and throws. + /// + /// Parameter to check + /// String - parameter name + public static void ParameterNotNull(object parameter, string parameterName) + { + AssertParameterNameNotNullOrEmpty(parameterName); + Debug.Assert(parameter != null, string.Format(CultureInfo.InvariantCulture, "'{0}' is null", parameterName)); + if (parameter == null) + { + throw new ArgumentNullException(parameterName); + } + } + + /// + /// Verifies that the specified string parameter is neither null nor empty, Debug.Asserts and throws. + /// + /// Parameter to check + /// String - parameter name + public static void StringNotNullOrEmpty(string parameter, string parameterName) + { + AssertParameterNameNotNullOrEmpty(parameterName); + Debug.Assert(!string.IsNullOrEmpty(parameter), string.Format(CultureInfo.InvariantCulture, "'{0}' is null or empty", parameterName)); + if (string.IsNullOrEmpty(parameter)) + { + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, TrxResource.Common_CannotBeNullOrEmpty)); + } + } + + /// + /// Asserts that the parameter name is not null or empty + /// + /// The parameter name to verify + [Conditional("DEBUG")] + private static void AssertParameterNameNotNullOrEmpty(string parameterName) + { + Debug.Assert(!string.IsNullOrEmpty(parameterName), "'parameterName' is null or empty"); + } + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/FilterHelper.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/FilterHelper.cs new file mode 100644 index 0000000000..267e752c19 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/FilterHelper.cs @@ -0,0 +1,357 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.Extensions.TrxLogger.Utility +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + using System.IO; + using System.Text; + using System.Text.RegularExpressions; + + /// + /// Helper function to deal with file name. + /// + internal static class FileHelper + { + private const string RelativeDirectorySeparator = ".."; + + private static readonly Dictionary InvalidFileNameChars; + private static readonly Dictionary AdditionalInvalidFileNameChars; + private static readonly Regex ReservedFileNamesRegex = new Regex(@"(?i:^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9]|CLOCK\$)(\..*)?)$"); + + #region Constructors + [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification = "Reviewed. Suppression is OK here.")] + + // Have to init InvalidFileNameChars dynamically. + static FileHelper() + { + // Create a hash table of invalid chars. + char[] invalidCharsArray = Path.GetInvalidFileNameChars(); + InvalidFileNameChars = new Dictionary(invalidCharsArray.Length); + foreach (char c in invalidCharsArray) + { + InvalidFileNameChars.Add(c, null); + } + + // Needed becuase when kicking off qtsetup.bat cmd.exe is used. '@' is a special character + // for cmd so must be removed from the path to the bat file + AdditionalInvalidFileNameChars = new Dictionary(4); + AdditionalInvalidFileNameChars.Add('@', null); + AdditionalInvalidFileNameChars.Add('(', null); + AdditionalInvalidFileNameChars.Add(')', null); + AdditionalInvalidFileNameChars.Add('^', null); + } + + #endregion + + /// + /// Replaces invalid file name chars in the specified string and changes it if it is a reserved file name. + /// + /// the name of the file + /// Replaced string. + public static string ReplaceInvalidFileNameChars(string fileName) + { + EqtAssert.StringNotNullOrEmpty(fileName, "fileName"); + + // Replace bad chars by this. + char replacementChar = '_'; + StringBuilder result = new StringBuilder(fileName.Length); + result.Length = fileName.Length; + + // Replace each invalid char with replacement char. + for (int i = 0; i < fileName.Length; ++i) + { + if (InvalidFileNameChars.ContainsKey(fileName[i]) || + AdditionalInvalidFileNameChars.ContainsKey(fileName[i])) + { + result[i] = replacementChar; + } + else + { + result[i] = fileName[i]; + } + } + + // We trim spaces in the end because CreateFile/Dir trim those. + string replaced = result.ToString().TrimEnd(); + if (replaced.Length == 0) + { + Debug.Fail(string.Format(CultureInfo.InvariantCulture, "After replacing invalid chars in file '{0}' there's nothing left...", fileName)); + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, TrxResource.Common_NothingLeftAfterReplaciingBadCharsInName, fileName)); + } + + if (IsReservedFileName(replaced)) + { + replaced = replacementChar + replaced; // Cannot add to the end because it can have extensions. + } + + return replaced; + } + + /// + /// Checks whether file with specified name exists in the specified directory. + /// If it exits, adds (1),(2)... to the file name and checks again. + /// Returns full file name (with path) of the iteration when the file does not exist. + /// + /// + /// The directory where to check. + /// + /// + /// The original file (that we would add (1),(2),.. in the end of if needed) name to check. + /// + /// + /// If true, and directory with filename without extension exists, try next iteration. + /// + /// + /// The . + /// + public static string GetNextIterationFileName(string parentDirectoryName, string originalFileName, bool checkMatchingDirectory) + { + EqtAssert.StringNotNullOrEmpty(parentDirectoryName, "parentDirectoryName"); + EqtAssert.StringNotNullOrEmpty(originalFileName, "originalFileName"); + return GetNextIterationNameHelper(parentDirectoryName, originalFileName, new FileIterationHelper(checkMatchingDirectory)); + } + + public static string MakePathRelative(string path, string basePath) + { + EqtAssert.StringNotNullOrEmpty(path, "path"); + + // Can't be relative to nothing + if (string.IsNullOrEmpty(basePath)) + { + return path; + } + + // Canonicalize those paths: + + if (!Path.IsPathRooted(path)) + { + //If path is relative, we combine it with base path before canonicalizing. + //Else Path.GetFullPath is going to use the process worker directory (e.g. e:\binariesy.x86\bin\i386). + path = Path.Combine(basePath, path); + } + path = Path.GetFullPath(path); + basePath = Path.GetFullPath(basePath); + + char[] delimiters = new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }; + + basePath = basePath.TrimEnd(delimiters); + path = path.TrimEnd(delimiters); + + string[] pathTokens = path.Split(delimiters); + string[] basePathTokens = basePath.Split(delimiters); + + Debug.Assert(pathTokens.Length > 0 && basePathTokens.Length > 0); + int max = Math.Min(pathTokens.Length, basePathTokens.Length); + + // Skip all of the empty tokens that result from things like "\dir1" + // and "\\dir1". We need to compare the first non-null token + // to know if we've got differences. + int i = 0; + for (i = 0; i < max && pathTokens[i].Length == 0 && basePathTokens[i].Length == 0; i++) ; + + if (i >= max) + { + // At least one of these strings is too short to work with + return path; + } + + if (!pathTokens[i].Equals(basePathTokens[i], StringComparison.OrdinalIgnoreCase)) + { + // These differ from the very start - just return the original path + return path; + } + + for (++i; i < max; i++) + { + if (!pathTokens[i].Equals(basePathTokens[i], StringComparison.OrdinalIgnoreCase)) + { + // We've found a non-matching token + break; + } + } + + // i should point to first non-matching token. + + StringBuilder newPath = new StringBuilder(); + + // ok, for each remaining token in the base path, + // add ..\ to the string. + for (int j = i; j < basePathTokens.Length; j++) + { + if (newPath.Length > 0) + { + newPath.Append(Path.DirectorySeparatorChar); + } + newPath.Append(RelativeDirectorySeparator); + } + + // And now, for every remaining token in the path, + // add it to the string, separated by the directory + // separator. + + for (int j = i; j < pathTokens.Length; j++) + { + if (newPath.Length > 0) + { + newPath.Append(Path.DirectorySeparatorChar); + } + newPath.Append(pathTokens[j]); + } + + return newPath.ToString(); + } + + /// + /// Returns true if the file name specified is Windows reserved file name. + /// + /// + /// The name of the file. Note: only a file name, does not expect to contain directory separators. + /// + /// + /// The True if yes else False. + /// + private static bool IsReservedFileName(string fileName) + { + Debug.Assert(!string.IsNullOrEmpty(fileName), "FileHelper.IsReservedFileName: the argument is null or empty string!"); + if (string.IsNullOrEmpty(fileName)) + { + return false; + } + + // CreateFile: + // The following reserved device names cannot be used as the name of a file: + // CON, PRN, AUX, NUL, COM1, COM2, COM3, COM4, COM5, COM6, COM7, COM8, COM9, + // LPT1, LPT2, LPT3, LPT4, LPT5, LPT6, LPT7, LPT8, and LPT9. + // Also avoid these names followed by an extension, for example, NUL.tx7. + // Windows NT: CLOCK$ is also a reserved device name. + return ReservedFileNamesRegex.Match(fileName).Success; + } + + /// + /// Helper to get next iteration (1),(2),.. names. + /// Note that we don't check for security permissions: + /// If the file exists and you have access to the dir you will get File.Exist = true + /// If the file exists and you don't have access to the dir, you will not be able to create the file anyway. + /// Result.trx -> Result(1).trx, Result(2).trx, etc. + /// + /// + /// Base directory to try iteration in. + /// + /// + /// The name to start the iterations from. + /// + /// + /// An instance of IterationHelper. + /// + /// + /// Next valid iteration name. + /// + [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Reviewed. Suppression is OK here.")] + private static string GetNextIterationNameHelper( + string baseDirectoryName, + string originalName, + IterationHelper helper) + { + Debug.Assert(!string.IsNullOrEmpty(baseDirectoryName), "baseDirectoryname is null"); + Debug.Assert(!string.IsNullOrEmpty(originalName), "originalName is Null"); + Debug.Assert(helper != null, "helper is null"); + + uint iteration = 0; + do + { + var tryMe = iteration == 0 ? originalName : helper.NextIteration(originalName, iteration); + + string tryMePath = Path.Combine(baseDirectoryName, tryMe); + if (helper.IsValidIteration(tryMePath)) + { + return tryMePath; + } + + ++iteration; + } + while (iteration != uint.MaxValue); + + throw new Exception(string.Format(CultureInfo.CurrentCulture, TrxResource.Common_CannotGetNextIterationName, originalName, baseDirectoryName)); + } + + private abstract class IterationHelper + { + /// + /// Formats iteration like baseName[1]. + /// + /// + /// Base name for the iteration. + /// + /// + /// The iteration number + /// + /// + /// The formatted string. + /// + internal static string FormatIteration(string baseName, uint iteration) + { + Debug.Assert(!string.IsNullOrEmpty(baseName), "basename is null"); + + var tryMe = string.Format( + CultureInfo.InvariantCulture, + "{0}[{1}]", + baseName, + iteration.ToString(CultureInfo.InvariantCulture)); + return tryMe; + } + + internal abstract string NextIteration(string baseName, uint iteration); + + internal abstract bool IsValidIteration(string name); + } + + private class FileIterationHelper : IterationHelper + { + private readonly bool checkMatchingDirectory; + + /// + /// Constructor for class checkMatchingDirectory. + /// + /// If true, and directory with filename without extension exists, try next iteration. + internal FileIterationHelper(bool checkMatchingDirectory) + { + this.checkMatchingDirectory = checkMatchingDirectory; + } + + internal override string NextIteration(string baseName, uint iteration) + { + Debug.Assert(!string.IsNullOrEmpty(baseName), "baseName is null"); + + string withoutExtensionName = Path.GetFileNameWithoutExtension(baseName); + string tryMe = FormatIteration(withoutExtensionName, iteration); + if (Path.HasExtension(baseName)) + { + tryMe += Path.GetExtension(baseName); // Path.GetExtension already returns the leading ".". + } + + return tryMe; + } + + internal override bool IsValidIteration(string path) + { + Debug.Assert(!string.IsNullOrEmpty(path), "path is null"); + if (File.Exists(path) || Directory.Exists(path)) + { + return false; + } + + // Path.ChangeExtension for "" returns trailing dot but Directory.Exists works the same for dir with and without trailing dot. + if (this.checkMatchingDirectory && Path.HasExtension(path) && Directory.Exists(Path.ChangeExtension(path, string.Empty))) + { + return false; + } + + return true; + } + } + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/TestRunDirectories.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/TestRunDirectories.cs new file mode 100644 index 0000000000..85d698aac8 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/TestRunDirectories.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.Extensions.TrxLogger.Utility +{ + using System; + + /// + /// Class to deal with directories. + /// + internal sealed class TestRunDirectories + { + /// + /// Computes the test results directory, relative to the root results directory (whatever that may be) + /// + /// The test's execution ID + /// + /// The test results directory (<testExecutionId>), under which test-specific result files should be stored + /// + public static string GetRelativeTestResultsDirectory(Guid testExecutionId) + { + return testExecutionId.ToString(); + } + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/XML/Attributes.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/XML/Attributes.cs new file mode 100644 index 0000000000..89c756f3d1 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/XML/Attributes.cs @@ -0,0 +1,141 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.Extensions.TrxLogger.XML +{ + using System; + using System.Diagnostics.CodeAnalysis; + + /// + /// Additional data needed to describe a field for automatic xml storage + /// + [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] + internal abstract class StoreXmlAttribute : Attribute + { + /// + /// simple xpath location. only element and attribute names can be used. + /// + [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:AccessibleFieldsMustBeginWithUpperCaseLetter", Justification = "Reviewed. Suppression is OK here.")] + [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate", Justification = "Reviewed. Suppression is OK here.")] + public string Location; + + /// + /// Initializes a new instance of the class. + /// + public StoreXmlAttribute() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The location. + /// + public StoreXmlAttribute(string location) + { + this.Location = location; + } + } + + /// + /// Additional info for storing simple fields with default value + /// + [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] + internal sealed class StoreXmlSimpleFieldAttribute : StoreXmlAttribute + { + [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:ElementsMustBeDocumented", Justification = "Reviewed. Suppression is OK here.")] + [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate", Justification = "Reviewed. Suppression is OK here.")] + public object DefaultValue; + + /// + /// Initializes a new instance of the class. + /// + public StoreXmlSimpleFieldAttribute() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The location. + /// + public StoreXmlSimpleFieldAttribute(string location) : base(location) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The default value. + /// + public StoreXmlSimpleFieldAttribute(object defaultValue) : this(null, defaultValue) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The location. + /// + /// + /// The default value. + /// + public StoreXmlSimpleFieldAttribute(string location, object defaultValue) + : base(location) + { + this.DefaultValue = defaultValue; + } + } + + /// + /// Storing of fields that support IXmlTestStore + /// + [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] + internal sealed class StoreXmlFieldAttribute : StoreXmlAttribute + { + /// + /// If there's no xml for the field a default instance is created or not. + /// + [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate", Justification = "Reviewed. Suppression is OK here.")] + public bool CreateDefaultInstance = CreateDefaultInstanceDefault; + + /// + /// Default value + /// + internal static readonly bool CreateDefaultInstanceDefault = true; + + /// + /// Initializes a new instance of the class. + /// + public StoreXmlFieldAttribute() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The location. + /// + /// + /// The create default instance. + /// + public StoreXmlFieldAttribute(string location, bool createDefaultInstance) : base(location) + { + this.CreateDefaultInstance = createDefaultInstance; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The location. + /// + public StoreXmlFieldAttribute(string location) : this(location, true) + { + } + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/XML/XmlFilePersistence.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/XML/XmlFilePersistence.cs new file mode 100644 index 0000000000..53ae039dd6 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/XML/XmlFilePersistence.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft. All rights reserved. + + +namespace Microsoft.TestPlatform.Extensions.TrxLogger.XML +{ + internal class XmlFilePersistence: XmlPersistence + { + #region Constants + + /// + /// Type of the object that is persisted to the file, the object that represents the root element + /// + public const string RootObjectType = "RootObjectType"; + + /// + /// The directory to where the file is being saved, or from where the file is being loaded + /// + public const string DirectoryPath = "DirectoryPath"; + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/XML/XmlPersistence.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/XML/XmlPersistence.cs new file mode 100644 index 0000000000..800ca80f7e --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/XML/XmlPersistence.cs @@ -0,0 +1,892 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.Extensions.TrxLogger.XML +{ + using ObjectModel; + using System; + using System.Collections; + using System.Collections.Generic; + using System.Collections.Specialized; + using System.ComponentModel; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + using System.Linq; + using System.Reflection; + using System.Xml; + + using VisualStudio.TestPlatform.ObjectModel; + + using TrxObjectModel = Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel; + + /// + /// The xml persistence class. + /// + internal class XmlPersistence + { + #region Types + + /// + /// The exception type that is thrown when a duplicate key is added to a hashtable or + /// dictionary + /// + public class DuplicateKeyLoadException : Exception + { + #region Constructors + + /// + /// Initializes the instance + /// + /// Key that was a duplicate + /// The duplicate-key exception message + public DuplicateKeyLoadException(object key, string message) + : this(key, message, null) + { + } + + /// + /// Initializes the instance + /// + /// Key that was a duplicate + /// The duplicate-key exception message + /// The inner exception + public DuplicateKeyLoadException(object key, string message, Exception innerException) + : base(message, innerException) + { + this.Key = key; + } + + #endregion + + #region Properties + + /// + /// Gets the key that was a duplicate + /// + public object Key + { + get; + private set; + } + + #endregion + } + + #endregion + + #region Fields + + /// This is how we persist date time except DateTime.MinValue. + private const string DateTimePersistenceFormat = "yyyy'-'MM'-'ddTHH':'mm':'ss'.'fffffffzzz"; + + /// Special case to persist DateTime.MinValue. + private const string DateTimeUtcPersistenceFormat = "u"; + + private const string DefaultNamespacePrefixEquivalent = "dpe"; + + private static readonly string EmptyGuidString = Guid.Empty.ToString(); + + private static readonly Type BoolType = typeof(bool); + private static readonly Type ByteArrayType = typeof(byte[]); + private static readonly Type DateTimeType = typeof(DateTime); + private static readonly Type TimeSpanType = typeof(TimeSpan); + private static readonly Type UriType = typeof(Uri); + private static readonly Type GuidType = typeof(Guid); + private static readonly Type TestElementType = typeof(TrxObjectModel.UnitTestElement); + private static readonly Type TestResultType = typeof(TrxObjectModel.UnitTestResult); + + /// + /// this is the top level cache: Type->field information + /// + private static Dictionary> typeToPersistenceInfoCache = + new Dictionary>(); + + /// + /// cache for type->persistence string mapping + /// + private static Dictionary typeToPersistenceString = new Dictionary(); + + + /// + /// Optimization: avoid reparsing same query multiple times + /// + private static Dictionary queryCache = new Dictionary(); + + private string prefix; + private string namespaceUri; + private XmlNamespaceManager xmlNamespaceManager = new XmlNamespaceManager(new NameTable()); + + #endregion Fields + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + public XmlPersistence() + { + this.prefix = string.Empty; + this.namespaceUri = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010"; + + if (!string.IsNullOrEmpty(this.prefix)) + { + // Register the specified prefix with the specified namespace + this.xmlNamespaceManager.AddNamespace(this.prefix, this.namespaceUri); + } + + if (!string.IsNullOrEmpty(this.namespaceUri)) + { + // Register a prefix for the namespace. This is needed for XPath queries, since an element in an XPath + // expression without a prefix is assumed to be in the empty namespace, and NOT in the provided XML namespace + // manager's default namespace (for some reason). So, we need to register a prefix that we will prepend to + // elements that are in this namespace (if it is not already the empty namespace) in XPath queries so that we + // will be able to find elements, and so that callers will not need to provide the appropriate prefixes. + // + // See the documentation for the 'XmlNamespaceManager.AddNamespace' method, specifically the Note for the + // 'prefix' parameter. + this.xmlNamespaceManager.AddNamespace(DefaultNamespacePrefixEquivalent, this.namespaceUri); + } + } + + #endregion Constructors + + /// + /// Create root element. + /// + /// + /// Name of the root element + /// + /// + /// The . + /// + public XmlElement CreateRootElement(string name) + { + return this.CreateRootElement(name, this.namespaceUri); + } + + private XmlElement CreateRootElement(string name, string namespaceUri) + { + if (namespaceUri == null) + { + namespaceUri = this.namespaceUri; + } + + XmlDocument dom = new XmlDocument(); + dom.AppendChild(dom.CreateXmlDeclaration("1.0", "UTF-8", null)); + return (XmlElement)dom.AppendChild(dom.CreateElement(this.prefix, name, namespaceUri)); + } + + #region PublicSaveDataInTrx + + /// + /// Save single fields. + /// + /// + /// The parent xml. + /// + /// + /// The instance. + /// + /// + /// The parameters. + /// + public void SaveSingleFields(XmlElement parentXml, object instance, XmlTestStoreParameters parameters) + { + this.SaveSingleFields(parentXml, instance, null, parameters); + } + + /// + /// Based on the StoreXml* attributes saves simple fields + /// + /// + /// Parent xml + /// + /// + /// object to save + /// + /// + /// The requested Type. + /// + /// + /// The parameters. + /// + public void SaveSingleFields(XmlElement parentXml, object instance, Type requestedType, XmlTestStoreParameters parameters) + { + if (instance == null) + { + return; // nothing to do + } + + Type type = requestedType ?? instance.GetType(); + + foreach (FieldPersistenceInfo info in GetFieldInfos(type)) + { + object fieldValue = info.FieldInfo.GetValue(instance); + if (fieldValue != null) + { + if (info.FieldAttribute != null) + { + this.SaveObject(fieldValue, parentXml, info.Location, parameters); + } + else if (info.SimpleFieldAttribute != null) + { + this.SaveSimpleField(parentXml, info.Location, fieldValue, info.SimpleFieldAttribute.DefaultValue); + } + } + } + } + + /// + /// Based on the StoreXml* attributes saves simple fields + /// + /// + /// The object to save. + /// + /// + /// The parent xml. + /// + /// + /// The location. + /// + /// + /// The parameters. + /// + public void SaveObject(object objectToSave, XmlElement parentXml, string location, XmlTestStoreParameters parameters) + { + if (objectToSave != null && location != null) + { + string nameSpaceUri = this.namespaceUri; + IXmlTestStoreCustom customStore = objectToSave as IXmlTestStoreCustom; + if (customStore != null) + { + nameSpaceUri = customStore.NamespaceUri; + } + + XmlNode xmlNode = this.EnsureLocationExists(parentXml, location, nameSpaceUri); + this.SaveObject(objectToSave, xmlNode, parameters); + + XmlElement element = xmlNode as XmlElement; + if (element != null && + !element.HasAttributes && + !element.HasChildNodes && + string.IsNullOrEmpty(element.InnerText)) + { + element.ParentNode.RemoveChild(element); // get rid of empty elements to keep the xml clean + } + } + } + + /// + /// Save object. + /// + /// + /// The object to save. + /// + /// + /// The node to save at. + /// + /// + /// The parameters. + /// + public void SaveObject(object objectToSave, XmlNode nodeToSaveAt, XmlTestStoreParameters parameters) + { + this.SaveObject(objectToSave, nodeToSaveAt, parameters, null); + } + + /// + /// Save the object. + /// + /// + /// The object to save. + /// + /// + /// The node to save at. + /// + /// + /// The parameters. + /// + /// + /// The default value. + /// + public void SaveObject(object objectToSave, XmlNode nodeToSaveAt, XmlTestStoreParameters parameters, object defaultValue) + { + if (objectToSave != null) + { + IXmlTestStore persistable = objectToSave as IXmlTestStore; + if (persistable != null) + { + persistable.Save((XmlElement)nodeToSaveAt, parameters); + } + else + { + this.SaveSimpleData(objectToSave, nodeToSaveAt, defaultValue); + } + } + } + + /// + /// Save simple field. + /// + /// + /// The xml. + /// + /// + /// The location. + /// + /// + /// The value. + /// + /// + /// The default value. + /// + public void SaveSimpleField(XmlElement xml, string location, object value, object defaultValue) + { + if (value == null || value.Equals(defaultValue)) + { + return; + } + + XmlNode saveTarget = this.EnsureLocationExists(xml, location); + this.SaveSimpleData(value, saveTarget, defaultValue); + } + + /// + /// Save GUID. + /// + /// + /// The xml. + /// + /// + /// The location. + /// + /// + /// The GUID. + /// + public void SaveGuid(XmlElement xml, string location, Guid guid) + { + this.SaveSimpleField(xml, location, guid.ToString(), EmptyGuidString); + } + + public void SaveHashtable(Hashtable ht, XmlElement element, string location, string keyLocation, string valueLocation, string itemElementName, XmlTestStoreParameters parameters) + { + if (ht != null && ht.Count > 0) + { + XmlElement dictionaryElement = (XmlElement)this.EnsureLocationExists(element, location); + foreach (DictionaryEntry de in ht) + { + XmlElement itemXml = this.CreateElement(dictionaryElement, itemElementName); + + this.SaveObject(de.Key, itemXml, keyLocation, parameters); + this.SaveObject(de.Value, itemXml, valueLocation, parameters); + } + } + } + + public void SaveStringDictionary(StringDictionary dict, XmlElement element, string location, string keyLocation, string valueLocation, string itemElementName, XmlTestStoreParameters parameters) + { + if (dict != null && dict.Count > 0) + { + XmlElement dictionaryElement = (XmlElement)EnsureLocationExists(element, location); + foreach (DictionaryEntry de in dict) + { + XmlElement itemXml = this.CreateElement(dictionaryElement, itemElementName); + + this.SaveObject(de.Key, itemXml, keyLocation, parameters); + this.SaveObject(de.Value, itemXml, valueLocation, parameters); + } + } + } + + #region Lists + /// + /// Save list of object . + /// + /// + /// The list. + /// + /// + /// The parent element. + /// + /// + /// The list xml element. + /// + /// + /// The item location. + /// + /// + /// The item element name. + /// + /// + /// The parameters. + /// + public void SaveIEnumerable(IEnumerable list, XmlElement element, string listXmlElement, string itemLocation, string itemElementName, XmlTestStoreParameters parameters) + { + if (list != null && list.GetEnumerator().MoveNext()) + { + XmlElement listElement = (XmlElement)this.EnsureLocationExists(element, listXmlElement); + foreach (object item in list) + { + XmlElement itemXml = this.CreateElement(listElement, itemElementName, item); + this.SaveObject(item, itemXml, itemLocation, parameters); + } + } + } + + /// + /// Save list. + /// + /// + /// The list. + /// + /// + /// The element. + /// + /// + /// The list xml element. + /// + /// + /// The item location. + /// + /// + /// The item element name. + /// + /// + /// The parameters. + /// + /// Generic parameter + /// + public void SaveList(IList list, XmlElement element, string listXmlElement, string itemLocation, string itemElementName, XmlTestStoreParameters parameters) + { + if (list != null && list.Count > 0) + { + XmlElement listElement = (XmlElement)this.EnsureLocationExists(element, listXmlElement); + foreach (V item in list) + { + XmlElement itemXml = this.CreateElement(listElement, itemElementName, item); + this.SaveObject(item, itemXml, itemLocation, parameters); + } + } + } + + #region Counters + + [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")] + public void SaveCounters(XmlElement xml, string location, int[] counters) + { + xml = (XmlElement)LocationToXmlNode(xml, location); + + for (int i = 0; i < counters.Length; i++) + { + TrxObjectModel.TestOutcome outcome = (TrxObjectModel.TestOutcome)i; + string attributeName = outcome.ToString(); + attributeName = attributeName.Substring(0, 1).ToLowerInvariant() + attributeName.Substring(1); + + xml.SetAttribute(attributeName, counters[i].ToString(CultureInfo.InvariantCulture)); + } + } + + #endregion Counters + + #endregion List + + internal static void SaveUsingReflection(XmlElement element, object instance, Type requestedType, XmlTestStoreParameters parameters) + { + XmlPersistence helper = new XmlPersistence(); + helper.SaveSingleFields(element, instance, requestedType, parameters); + } + + #endregion PublicSaveDataInTrx + + #region Utilities + + #region Optimization: Reflection caching + + /// + /// Updates the cache if needed and gets the field info collection + /// + /// + /// The type. + /// + /// + /// The . + /// + private static IEnumerable GetFieldInfos(Type type) + { + IEnumerable toReturn; + if (!typeToPersistenceInfoCache.TryGetValue(type, out toReturn)) + { + toReturn = ReflectFields(type); + lock (typeToPersistenceInfoCache) + { + IEnumerable checkCache; + if (!typeToPersistenceInfoCache.TryGetValue(type, out checkCache)) + { + typeToPersistenceInfoCache.Add(type, toReturn); + } + } + } + + return toReturn; + } + + /// + /// EXPENSIVE! Uses reflection to build a cache item of information about the type and its fields + /// + /// the type to reflect on + /// collection of field information + private static IEnumerable ReflectFields(Type type) + { + List toReturn = new List(); + + foreach ( + FieldInfo reflectedFieldInfo in + type.GetTypeInfo().GetFields( + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly)) + { + FieldPersistenceInfo info = new FieldPersistenceInfo(reflectedFieldInfo); + + // only fields with known location need to be persisted + if (!string.IsNullOrEmpty(info.Location)) + { + toReturn.Add(info); + } + } + + return toReturn; + } + + #endregion Optimization: Reflection caching + + /// + /// Convert dateTime to string. + /// + /// + /// The date time. + /// + /// + /// The . + /// + private static string DateTimeToString(DateTime dateTime) + { + if (dateTime == DateTime.MinValue) + { + // DateTime.MinValue has Kind.Unspecified and thus it does not make any sense to convert it to local/universal. + // Also note that we use format w/o time zones to persist DateTime.MinValue. + return dateTime.ToString(DateTimeUtcPersistenceFormat, CultureInfo.InvariantCulture.DateTimeFormat); + } + else + { + // Ensure that the datetime value is in local time.. + // This is needed as the persistenceformat we use needs the datetime to be in local time.. + DateTime localDateTime = dateTime.ToLocalTime(); + return localDateTime.ToString(DateTimePersistenceFormat, CultureInfo.InvariantCulture.DateTimeFormat); + } + } + + private static string GetFieldLocation(FieldInfo fieldInfo) + { + string location = null; + + StoreXmlAttribute locationAttribute = GetAttribute(fieldInfo); + if (locationAttribute != null) + { + location = locationAttribute.Location; + if (location == null) + { + location = GetDefaultFieldLocation(fieldInfo); + } + } + + return location; + } + + private static string GetDefaultFieldLocation(FieldInfo fieldInfo) + { + string fieldName = fieldInfo.Name; + string defaultFieldLocation = fieldName.StartsWith("m_", StringComparison.Ordinal) ? fieldName.Substring(2, fieldName.Length - 2) : fieldName; + + if (!ImplementsIXmlTestStore(fieldInfo.FieldType)) + { + defaultFieldLocation = '@' + defaultFieldLocation; + } + + return defaultFieldLocation; + } + + private static bool ImplementsIXmlTestStore(Type type) + { + return type.GetTypeInfo().GetInterface(typeof(IXmlTestStore).Name) != null; + } + + private static T GetAttribute(FieldInfo fieldInfo) where T : Attribute + { + var attributes = fieldInfo.GetCustomAttributes(typeof(T), false).ToArray(); + + if (attributes.Length > 0) + { + return (T)attributes[0]; + } + + return default(T); + } + + [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "Reviewed. Suppression is OK here.")] + private void SaveSimpleData(object value, XmlNode nodeToSaveAt, object defaultValue) + { + if (value == null || value.Equals(defaultValue)) + { + return; + } + + Type valueType = value.GetType(); + + string valueToSave; + if (valueType == BoolType) + { + valueToSave = value.ToString().ToLowerInvariant(); + } + else if (valueType == ByteArrayType) + { + // Use only for Arrays, Collections and Lists. E.g. string is also IEnumerable. + valueToSave = Convert.ToBase64String(value as byte[]); + } + else if (valueType == DateTimeType) + { + // always save as a sortable date time with fractional seconds. + valueToSave = DateTimeToString((DateTime)value); + } + else + { + // use the appropriate type converter to get a culture invariant string. + try + { + TypeConverter convert = TypeDescriptor.GetConverter(valueType); + valueToSave = convert.ConvertToInvariantString(value); + } + catch (NotSupportedException nosupportEx) + { + EqtTrace.Info("TypeConverter not supported for {0} : NotSupportedException Exception Message {1}", value.ToString(), nosupportEx.Message); + valueToSave = value.ToString(); + } + } + + XmlElement elementToSaveAt = nodeToSaveAt as XmlElement; + if (elementToSaveAt != null) + { + elementToSaveAt.InnerText = valueToSave; + } + else + { + nodeToSaveAt.Value = valueToSave; + } + } + + public XmlNode EnsureLocationExists(XmlElement xml, string location) + { + return this.EnsureLocationExists(xml, location, this.namespaceUri); + } + + private XmlNode EnsureLocationExists(XmlElement xml, string location, string nameSpaceUri) + { + XmlNode node = this.LocationToXmlNode(xml, location); + if (node != null) + { + return node; + } + + if (location.StartsWith("@", StringComparison.Ordinal)) + { + string attributeName = location.Substring(1, location.Length - 1); + if (xml.HasAttribute(attributeName)) + { + return xml.GetAttributeNode(attributeName); + } + else + { + return xml.Attributes.Append(xml.OwnerDocument.CreateAttribute(attributeName)); + } + } + else + { + string[] parts = location.Split(new char[] { '/' }, 2); + string firstPart = parts[0]; + + XmlNode firstChild = this.LocationToXmlNode(xml, firstPart); + if (firstChild == null) + { + firstChild = this.CreateElement(xml, firstPart, this.GetNamespaceUriOrDefault(nameSpaceUri)); + } + + if (parts.Length > 1) + { + return this.EnsureLocationExists((XmlElement)firstChild, parts[1]); + } + else + { + return firstChild; + } + } + } + + private string GetNamespaceUriOrDefault(string nameSpaceUri) + { + return nameSpaceUri != null ? nameSpaceUri : this.namespaceUri; + } + + + /// + /// Creates a new element with the given name in the current (of this instance of XmlPersistence namespace) + /// + /// parent xml + /// element name + /// a new XmlElement attached to the parent + private XmlElement CreateElement(XmlElement xml, string name) + { + return this.CreateElement(xml, name, this.namespaceUri); + } + + private XmlElement CreateElement(XmlElement xml, string name, string elementNamespaceUri) + { + return (XmlElement)xml.AppendChild(xml.OwnerDocument.CreateElement(this.prefix, name, elementNamespaceUri)); + } + + /// + /// Accepts name == null + /// + /// parent xml + /// The local name of the new element. + /// the object for which element has to create + /// a new XmlElement attached to the parent + private XmlElement CreateElement(XmlElement parent, string name, object instance) + { + if (name != null) + { + return this.CreateElement(parent, name); + } + else + { + NewElementCreateData createData = this.GetElementCreateData(instance); + return this.CreateElement(parent, createData.ElementName, createData.NamespaceUri); + } + } + + private NewElementCreateData GetElementCreateData(object persistee) + { + Debug.Assert(persistee != null, "persistee is null"); + + IXmlTestStoreCustom custom = persistee as IXmlTestStoreCustom; + + NewElementCreateData toReturn = new NewElementCreateData(); + if (custom != null) + { + toReturn.ElementName = custom.ElementName; + toReturn.NamespaceUri = custom.NamespaceUri; + } + + if (toReturn.ElementName == null) + { + toReturn.ElementName = persistee.GetType().Name; + } + + if (toReturn.NamespaceUri == null) + { + toReturn.NamespaceUri = this.namespaceUri; + } + + return toReturn; + } + + private XmlNode LocationToXmlNode(XmlElement element, string location) + { + location = this.ProcessXPathQuery(location); + + try + { + return element.SelectSingleNode(location, this.xmlNamespaceManager); + } + catch (System.Xml.XPath.XPathException e) + { + throw new Exception("The persistance location is invalid. Element: '" + element.Name + "', location: '" + location + "'", e); + } + } + + private string ProcessXPathQuery(string queryIn) + { + // If we are working with the empty namespace, there is no need to decorate elements in the XPath expression, since + // elements in the XPath expression that don't have a prefix will be searched for in the empty namespace. + if (string.IsNullOrEmpty(this.namespaceUri)) + { + return queryIn; + } + + if (queryCache.ContainsKey(queryIn)) + { + return queryCache[queryIn]; + } + + // fix the empty namespaces to a temp prefix, so xpath query can understand them + string[] parts = queryIn.Split(new char[] { '/' }, StringSplitOptions.None); + + string query = string.Empty; + + foreach (string part in parts) + { + if (query.Length > 0 || queryIn.StartsWith("/", StringComparison.Ordinal)) + { + query += '/'; + } + + if (part != "." && part != ".." && !part.Contains(":") && (part.Length > 0) && (!part.StartsWith("@", StringComparison.Ordinal))) + { + query += DefaultNamespacePrefixEquivalent + ":"; + } + + query += part; + } + + queryCache[queryIn] = query; + + return query; + } + + #endregion Utilities + + #region Types + + private class NewElementCreateData + { + public string NamespaceUri { get; set; } + + public string ElementName { get; set; } + } + + /// + /// caches information about a field + /// + [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate", Justification = "Reviewed. Suppression is OK here.")] + private class FieldPersistenceInfo + { + internal FieldInfo FieldInfo; + + internal string Location; + + internal StoreXmlAttribute Attribute; + + internal StoreXmlSimpleFieldAttribute SimpleFieldAttribute; + + internal StoreXmlFieldAttribute FieldAttribute; + + internal FieldPersistenceInfo(FieldInfo fieldInfo) + { + this.FieldInfo = fieldInfo; + this.Location = GetFieldLocation(fieldInfo); + + this.Attribute = GetAttribute(fieldInfo); + this.SimpleFieldAttribute = this.Attribute as StoreXmlSimpleFieldAttribute; + this.FieldAttribute = this.Attribute as StoreXmlFieldAttribute; + } + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/project.json b/src/Microsoft.TestPlatform.Extensions.TrxLogger/project.json new file mode 100644 index 0000000000..15e32a27b2 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/project.json @@ -0,0 +1,31 @@ +{ + "version": "15.0.0-*", + "buildOptions": { + "outputName": "Microsoft.VisualStudio.TestPlatform.Extensions.TrxLogger", + "delaySign": true, + "keyFile": "../../scripts/key.snk", + "warningsAsErrors": true + }, + + "dependencies": { + + "Microsoft.TestPlatform.ObjectModel": "15.0.0-*", + "Microsoft.TestPlatform.CoreUtilities": "15.0.0-*" + }, + + "frameworks": { + "netstandard1.5": { + "imports": [ + "dnxcore50", + "portable-net45+win8" + ], + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-24008", + "System.Collections.NonGeneric": "4.0.1-rc2-24027", + "System.Security.Principal.Windows": "4.0.0-rc2-24027", + "System.Collections.Specialized": "4.0.1-rc2-24027" + } + }, + "net46": { } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Adapter/Interfaces/IDiscoveryContext.cs b/src/Microsoft.TestPlatform.ObjectModel/Adapter/Interfaces/IDiscoveryContext.cs new file mode 100644 index 0000000000..9d0c5324d2 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Adapter/Interfaces/IDiscoveryContext.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter +{ + /// + /// Specifies the user specified RunSettings and framework provided context of the discovery. + /// + public interface IDiscoveryContext + { + /// + /// Runsettings specified for this request. + /// + IRunSettings RunSettings { get; } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Adapter/Interfaces/IFrameworkHandle.cs b/src/Microsoft.TestPlatform.ObjectModel/Adapter/Interfaces/IFrameworkHandle.cs new file mode 100644 index 0000000000..d8b1d12cb2 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Adapter/Interfaces/IFrameworkHandle.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter +{ + using System.Collections.Generic; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + + /// + /// Handle to the framework which is passed to the test executors. + /// + public interface IFrameworkHandle : ITestExecutionRecorder, IMessageLogger + { + /// + /// Gets or sets a value indicating whether the execution framework enables the shutdown of execution process after the test run is complete. This should be used only in out of process test runs when IRunContext.KeepAlive is true + /// and should be used only when absolutely required as using it degrades the performance of the subsequent run. + /// It throws InvalidOperationException when it is attempted to be enabled when keepAlive is false. + /// + bool EnableShutdownAfterTestRun { get; set; } + + /// + /// Launch the specified process with the debugger attached. + /// + /// File path to the exe to launch. + /// Working directory that process should use. + /// Command line arguments the process should be launched with. + /// Environment variables to be set in target process + /// Process ID of the started process. + int LaunchProcessWithDebuggerAttached(string filePath, string workingDirectory, string arguments, IDictionary environmentVariables); + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.ObjectModel/Adapter/Interfaces/IRunContext.cs b/src/Microsoft.TestPlatform.ObjectModel/Adapter/Interfaces/IRunContext.cs new file mode 100644 index 0000000000..92bac52e8b --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Adapter/Interfaces/IRunContext.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter +{ + using System; + using System.Collections.Generic; + + /// + /// It provides user specified runSettings and framework provided context of the run. + /// + public interface IRunContext : IDiscoveryContext + { + /// + /// Whether the execution process should be kept alive after the run is finished or not. + /// + bool KeepAlive { get; } + + /// + /// Whether the execution is happening in InProc or outOfProc + /// + bool InIsolation { get; } + + /// + /// Whether the data collection is enabled or not + /// + bool IsDataCollectionEnabled { get; } + + /// + /// Whether the test is being debugged or not. + /// + bool IsBeingDebugged { get; } + + /// + /// Test case filter for user specified criteria which has been validated for 'supportedProperties'. + /// It is used only with sources. With specific test cases it will always be null. + /// If there is a parsing error or filter expression has unsupported properties, TestPlatformFormatException() is thrown. + /// + ITestCaseFilterExpression GetTestCaseFilter(IEnumerable supportedProperties, Func propertyProvider); + + /// + /// Directory which should be used for storing result files/deployment files etc. + /// + string TestRunDirectory { get; } + + /// + /// Solution Directory. + /// + string SolutionDirectory { get; } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Adapter/Interfaces/IRunSettings.cs b/src/Microsoft.TestPlatform.ObjectModel/Adapter/Interfaces/IRunSettings.cs new file mode 100644 index 0000000000..53c7f85391 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Adapter/Interfaces/IRunSettings.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter +{ + /// + /// Used for loading settings for a run. + /// + public interface IRunSettings + { + /// + /// Get the settings for the provided settings name. + /// + /// Name of the settings section to get. + /// The settings provider for the settings or null if one was not found. + ISettingsProvider GetSettings(string settingsName); + + /// + /// Settings used for this run. + /// + string SettingsXml { get; } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Adapter/Interfaces/ISettingsProvider.cs b/src/Microsoft.TestPlatform.ObjectModel/Adapter/Interfaces/ISettingsProvider.cs new file mode 100644 index 0000000000..e5faeb7ddf --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Adapter/Interfaces/ISettingsProvider.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter +{ + using System.Xml; + + /// + /// Interface implemented to provide a section in the run settings. A class that + /// implements this interface will be available for use if it exports its type via + /// MEF, and if its containing assembly is placed in the Extensions folder. + /// + public interface ISettingsProvider + { + /// + /// Load the settings from the reader. + /// + /// Reader to load the settings from. + void Load(XmlReader reader); + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Adapter/Interfaces/ITestCaseDiscoverySink.cs b/src/Microsoft.TestPlatform.ObjectModel/Adapter/Interfaces/ITestCaseDiscoverySink.cs new file mode 100644 index 0000000000..b36affbb61 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Adapter/Interfaces/ITestCaseDiscoverySink.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter +{ + /// + /// TestCaseDiscovery sink is used by discovery extensions to communicate test cases as they are being discovered, + /// and various discovery related events. + /// + public interface ITestCaseDiscoverySink + { + /// + /// Callback used by discovery extensions to send back testcases as they are being discovered. + /// + /// New test discovered since last invocation. + void SendTestCase(TestCase discoveredTest); + + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Adapter/Interfaces/ITestCaseFilterExpression.cs b/src/Microsoft.TestPlatform.ObjectModel/Adapter/Interfaces/ITestCaseFilterExpression.cs new file mode 100644 index 0000000000..0db69fba22 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Adapter/Interfaces/ITestCaseFilterExpression.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter +{ + using System; + + /// + /// It represents expression for filtering test cases. + /// + public interface ITestCaseFilterExpression + { + /// + /// Gets original string for test case filter. + /// + string TestCaseFilterValue { get; } + + /// + /// Matched test case with test case filtering criteria. + /// + bool MatchTestCase(TestCase testCase, Func propertyValueProvider); + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.ObjectModel/Adapter/Interfaces/ITestDiscoverer.cs b/src/Microsoft.TestPlatform.ObjectModel/Adapter/Interfaces/ITestDiscoverer.cs new file mode 100644 index 0000000000..04aa45e4d7 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Adapter/Interfaces/ITestDiscoverer.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter +{ + using System.Collections.Generic; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + + /// + /// Interface implemented to provide tests to the test platform. A class that + // implements this interface will be available for use if its containing + // assembly is either placed in the Extensions folder or is marked as a 'UnitTestExtension' type + // in the vsix package. + /// + public interface ITestDiscoverer + { + /// + /// Discovers the tests available from the provided source. + /// + /// Collection of test containers. + /// Context in which discovery is being performed. + /// Logger used to log messages. + /// Used to send testcases and discovery related events back to Discoverer manager. + void DiscoverTests(IEnumerable sources, IDiscoveryContext discoveryContext, IMessageLogger logger, ITestCaseDiscoverySink discoverySink); + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Adapter/Interfaces/ITestExecutor.cs b/src/Microsoft.TestPlatform.ObjectModel/Adapter/Interfaces/ITestExecutor.cs new file mode 100644 index 0000000000..c10740100c --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Adapter/Interfaces/ITestExecutor.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter +{ + using System.Collections.Generic; + + /// + /// Defines the test executor which provides capability to run tests. + /// + /// A class that implements this interface will be available for use if its containing + // assembly is either placed in the Extensions folder or is marked as a 'UnitTestExtension' type + // in the vsix package. + /// + public interface ITestExecutor + { + /// + /// Runs only the tests specified by parameter 'tests'. + /// + /// Tests to be run. + /// Context to use when executing the tests. + /// Handle to the framework to record results and to do framework operations. + void RunTests(IEnumerable tests, IRunContext runContext, IFrameworkHandle frameworkHandle); + + /// + /// Runs 'all' the tests present in the specified 'sources'. + /// + /// Path to test container files to look for tests in. + /// Context to use when executing the tests. + /// Handle to the framework to record results and to do framework operations. + void RunTests(IEnumerable sources, IRunContext runContext, IFrameworkHandle frameworkHandle); + + /// + /// Cancel the execution of the tests. + /// + void Cancel(); + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Adapter/Interfaces/ITestLog.cs b/src/Microsoft.TestPlatform.ObjectModel/Adapter/Interfaces/ITestLog.cs new file mode 100644 index 0000000000..07703816e7 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Adapter/Interfaces/ITestLog.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter +{ + using System.Collections.Generic; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + + /// + /// Used for recording test results and test messages. + /// + public interface ITestExecutionRecorder : IMessageLogger + { + /// + /// Notify the framework about the test result. + /// + /// Test Result to be sent to the framework. + /// Exception thrown by the framework when an executor attempts to send + /// test result to the framework when the test(s) is canceled. + void RecordResult(TestResult testResult); + + + /// + /// Notify the framework about starting of the test case. + /// Framework sends this event to data collectors enabled in the run. If no data collector is enabled, then the event is ignored. + /// + /// testcase which will be started. + void RecordStart(TestCase testCase); + + /// + /// Notify the framework about completion of the test case. + /// Framework sends this event to data collectors enabled in the run. If no data collector is enabled, then the event is ignored. + /// + /// testcase which has completed. + /// outcome of the test case. + void RecordEnd(TestCase testCase, TestOutcome outcome); + + + /// + /// Notify the framework about run level attachments. + /// + /// attachments produced in this run. + void RecordAttachments(IList attachmentSets); + + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Adapter/TestPlatformFormatException.cs b/src/Microsoft.TestPlatform.ObjectModel/Adapter/TestPlatformFormatException.cs new file mode 100644 index 0000000000..690dc8b5fe --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Adapter/TestPlatformFormatException.cs @@ -0,0 +1,104 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter +{ + using System; + using System.Runtime.Serialization; +#if NET46 +using System.Security.Permissions; +#endif + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + + /// + /// Exception thrown on parsing error in user provided filter expression. + /// This can happen when filter has invalid format or has unsupported properties. + /// +#if NET46 + [Serializable] +#endif + public class TestPlatformFormatException : Exception + { + #region Constructors + + /// + /// Creates a new TestPlatformFormatException + /// + public TestPlatformFormatException() + : base() + { + } + + /// + /// Initializes with the message. + /// + /// Message for the exception. + public TestPlatformFormatException(string message) + : base(message) + { + } + + /// + /// Initializes with the message and filter string. + /// + /// Message for the exception. + /// Filter expression. + public TestPlatformFormatException(string message, string filterValue) + : base(message) + { + FilterValue = filterValue; + } + + /// + /// Initializes with message and inner exception. + /// + /// Message for the exception. + /// The inner exception. + public TestPlatformFormatException(string message, Exception innerException) + : base(message, innerException) + { + } + +#if NET46 + /// + /// Seralization constructor. + /// + protected TestPlatformFormatException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + ValidateArg.NotNull(info, "info"); + // Save the basic properties. + this.FilterValue = info.GetString("FilterValue"); + } + +#endif + #endregion + + /// + /// Filter expression. + /// + public string FilterValue + { + get; + private set; + } + +#if NET46 + /// + /// Serialization helper. + /// + /// Serialization info to add to + /// not used + [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)] + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (info == null) + { + throw new ArgumentNullException("info"); + } + + base.GetObjectData(info, context); + info.AddValue("FilterValue", this.FilterValue); + } +#endif + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Adapter/TestsCanceledException.cs b/src/Microsoft.TestPlatform.ObjectModel/Adapter/TestsCanceledException.cs new file mode 100644 index 0000000000..386055df48 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Adapter/TestsCanceledException.cs @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter +{ + using System; + +#if NET46 + using System.Runtime.Serialization; +#endif + + /// + /// Exception thrown by the framework when an executor attempts to send + /// test result to the framework when the test is canceled. + /// +#if NET46 + [Serializable] +#endif + public class TestCanceledException : Exception + { +#region Constructors + + /// + /// Creates a new TestCanceledException + /// + public TestCanceledException() + { + } + + /// + /// Initializes with the message. + /// + /// Message for the exception. + public TestCanceledException(string message) + : base(message) + { + } + + /// + /// Initializes with message and inner exception. + /// + /// Message for the exception. + /// The inner exception. + public TestCanceledException(string message, Exception innerException) + : base(message, innerException) + { + } + +#if NET46 + /// + /// Seralization constructor. + /// + protected TestCanceledException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + +#endif +#endregion + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Architecture.cs b/src/Microsoft.TestPlatform.ObjectModel/Architecture.cs new file mode 100644 index 0000000000..0be1016b9b --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Architecture.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel +{ + public enum Architecture + { + Default, + X86, + X64, + ARM, + AnyCPU + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/AttachmentSet.cs b/src/Microsoft.TestPlatform.ObjectModel/AttachmentSet.cs new file mode 100644 index 0000000000..307dddb23d --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/AttachmentSet.cs @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Runtime.Serialization; + + /// + /// Represents a set of attachments. + /// + [DataContract] + public class AttachmentSet + { + /// + /// URI of the sender. + /// + /// If a data-collector is sending this set, then it should be uri of the data collector. Also if an + /// executor is sending this attachment, then it should be uri of executor. + /// + /// + [DataMember] + public Uri Uri {get; private set;} + + /// + /// Name of the sender. + /// + [DataMember] + public string DisplayName {get; private set;} + + /// + /// List of data attachments. + /// + /// These attachments can be things such as files that the collector/adapter wants to make available to the publishers. + /// + [DataMember] + public IList Attachments {get; private set;} + + public AttachmentSet(Uri uri, string displayName) + { + Uri = uri; + DisplayName = displayName; + Attachments = new List(); + } + } + + + /// + /// Defines the data attachment. + /// Dev10 equivalent is UriDataAttachment. + /// + [DataContract] + public class UriDataAttachment + { + /// + /// Description of the attachment. + /// + [DataMember] + public string Description { get; private set; } + + /// + /// Uri of the attchment. + /// + [DataMember] + public Uri Uri { get; private set; } + + public UriDataAttachment(Uri uri, string description) + { + Uri = uri; + Description = description; + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Client/DiscoveryCriteria.cs b/src/Microsoft.TestPlatform.ObjectModel/Client/DiscoveryCriteria.cs new file mode 100644 index 0000000000..0a0b648ec6 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Client/DiscoveryCriteria.cs @@ -0,0 +1,123 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Client +{ + using System; + using System.Collections.Generic; + using System.Linq; + + using Newtonsoft.Json; + + /// + /// Defines the discovery criterion + /// + public class DiscoveryCriteria + { + /// + /// Criteria used for test discovery + /// + /// Sources from which the tests should be discovered + /// Frequency of discovered test event + /// Run Settings for the discovery. + public DiscoveryCriteria(IEnumerable sources, long frequencyOfDiscoveredTestsEvent, string testSettings) + : this(sources, frequencyOfDiscoveredTestsEvent, TimeSpan.MaxValue, testSettings) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The sources. + /// The frequency of discovered tests event. + /// The discovered test event timeout. + /// The run settings. + /// + /// + [JsonConstructor] + public DiscoveryCriteria(Dictionary> adapterSourceMap, long frequencyOfDiscoveredTestsEvent, TimeSpan discoveredTestEventTimeout, string runSettings) + { + ValidateArg.NotNullOrEmpty(adapterSourceMap, "adapterSourceMap"); + if (frequencyOfDiscoveredTestsEvent <= 0) throw new ArgumentOutOfRangeException("frequencyOfDiscoveredTestsEvent", Resources.NotificationFrequencyIsNotPositive); + if (discoveredTestEventTimeout <= TimeSpan.MinValue) throw new ArgumentOutOfRangeException("discoveredTestEventTimeout", Resources.NotificationTimeoutIsZero); + + this.AdapterSourceMap = adapterSourceMap; + this.FrequencyOfDiscoveredTestsEvent = frequencyOfDiscoveredTestsEvent; + this.DiscoveredTestEventTimeout = discoveredTestEventTimeout; + + this.RunSettings = runSettings; + } + + /// + /// Criteria used for test discovery + /// + /// Sources from which the tests should be discovered + /// Frequency of discovered test event + /// Timeout that triggers the discovered test event regardless of cache size. + /// Run Settings for the discovery. + public DiscoveryCriteria(IEnumerable sources, long frequencyOfDiscoveredTestsEvent, TimeSpan discoveredTestEventTimeout, string runSettings) + { + ValidateArg.NotNullOrEmpty(sources, "sources"); + if (frequencyOfDiscoveredTestsEvent <= 0) + { + throw new ArgumentOutOfRangeException( + "frequencyOfDiscoveredTestsEvent", + Resources.NotificationFrequencyIsNotPositive); + } + + if (discoveredTestEventTimeout <= TimeSpan.MinValue) + { + throw new ArgumentOutOfRangeException("discoveredTestEventTimeout", Resources.NotificationTimeoutIsZero); + } + + this.AdapterSourceMap = new Dictionary>(); + this.AdapterSourceMap.Add(Constants.UnspecifiedAdapterPath, sources); + this.FrequencyOfDiscoveredTestsEvent = frequencyOfDiscoveredTestsEvent; + this.DiscoveredTestEventTimeout = discoveredTestEventTimeout; + + this.RunSettings = runSettings; + } + + /// + /// Test Containers (e.g. DLL/EXE/artifacts to scan) + /// + [JsonIgnore] + public IEnumerable Sources + { + get + { + IEnumerable sources = new List(); + return this.AdapterSourceMap.Values.Aggregate(sources, (current, enumerable) => current.Concat(enumerable)); + } + } + + /// + /// The test adapter and source map which would look like below: + /// { C:\temp\testAdapter1.dll : [ source1.dll, source2.dll ], C:\temp\testadapter2.dll : [ source3.dll, source2.dll ] + /// + public Dictionary> AdapterSourceMap + { + get; private set; + } + + /// + /// Defines the frequency of discovered test event. + /// + /// + /// Discovered test event will be raised after discovering these number of tests. + /// Note that this event is raised asynchronously and the underlying discovery process is not + /// paused during the listener invocation. So if the event handler, you try to query the + /// next set of tests, you may get more than 'FrequencyOfDiscoveredTestsEvent'. + /// + public long FrequencyOfDiscoveredTestsEvent { get; private set; } + + /// + /// Timeout that triggers the discovered test event regardless of cache size. + /// + public TimeSpan DiscoveredTestEventTimeout { get; private set; } + + /// + /// Settings used for the discovery request. + /// + public string RunSettings { get; private set; } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Client/Events/DiscoveredTestsEventArgs.cs b/src/Microsoft.TestPlatform.ObjectModel/Client/Events/DiscoveredTestsEventArgs.cs new file mode 100644 index 0000000000..478cc5a961 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Client/Events/DiscoveredTestsEventArgs.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Client +{ + using System; + using System.Collections.Generic; + + /// + /// Event arguments used to notify the availability of new tests + /// + public partial class DiscoveredTestsEventArgs : EventArgs + { + public DiscoveredTestsEventArgs(IEnumerable discoveredTestCases) + { + DiscoveredTestCases = discoveredTestCases; + } + /// + /// Tests discovered in this discovery request + /// + public IEnumerable DiscoveredTestCases { get; private set; } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Client/Events/DiscoveryCompleteEventArgs.cs b/src/Microsoft.TestPlatform.ObjectModel/Client/Events/DiscoveryCompleteEventArgs.cs new file mode 100644 index 0000000000..fd40e05be6 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Client/Events/DiscoveryCompleteEventArgs.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Client +{ + using System; + using System.Diagnostics; + + /// + /// Event arguments used on completion of discovery + /// + public class DiscoveryCompleteEventArgs : EventArgs + { + /// + /// Constructor for creating event args object + /// + /// Total tests which got discovered + /// Specifies if discovery has been aborted. + public DiscoveryCompleteEventArgs(long totalTests, bool isAborted) + { + Debug.Assert((isAborted ? -1 == totalTests : true), "If discovery request is aborted totalTest should be -1."); + this.TotalCount = totalTests; + this.IsAborted = isAborted; + } + + /// + /// Indicates the total tests which got discovered in this request. + /// + public long TotalCount { get; private set; } + + /// + /// Specifies if discovery has been aborted. If true TotalCount is also set to -1. + /// + public bool IsAborted { get; private set; } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Client/Events/TestRunChangedEventArgs.cs b/src/Microsoft.TestPlatform.ObjectModel/Client/Events/TestRunChangedEventArgs.cs new file mode 100644 index 0000000000..072d47d695 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Client/Events/TestRunChangedEventArgs.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Client +{ + using System; + using System.Collections.Generic; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + + /// + /// The test run changed event args that provides the test results available. + /// + public class TestRunChangedEventArgs : EventArgs + { + /// + /// Initializes a new instance of the class. + /// + /// The stats. + /// The new test results. + /// The active tests. + public TestRunChangedEventArgs(ITestRunStatistics stats, IEnumerable newTestResults, IEnumerable activeTests) + { + this.TestRunStatistics = stats; + this.NewTestResults = newTestResults; + this.ActiveTests = activeTests; + } + + /// + /// Gets the new test results. + /// + public IEnumerable NewTestResults { get; private set; } + + /// + /// Gets the test run statistics. + /// + public ITestRunStatistics TestRunStatistics { get; private set; } + + /// + /// Gets the active tests. + /// + public IEnumerable ActiveTests { get; private set; } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Client/Events/TestRunCompleteEventArgs.cs b/src/Microsoft.TestPlatform.ObjectModel/Client/Events/TestRunCompleteEventArgs.cs new file mode 100644 index 0000000000..20e3c6246c --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Client/Events/TestRunCompleteEventArgs.cs @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Client +{ + using System; + using System.Collections.ObjectModel; + using System.Runtime.Serialization; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + + /// + /// Event arguments used when a test run has completed. + /// + public class TestRunCompleteEventArgs : EventArgs + { + /// + /// Default constructor. + /// + /// The final stats for the test run. This parameter is only set for communications between the test host and the clients (like VS) + /// Specifies whether the test run is canceled. + /// Specifies whether the test run is aborted. + /// Specifies the error encountered during the execution of the test run. + /// Attachment sets associated with the run. + /// Time elapsed in just running tests + public TestRunCompleteEventArgs(ITestRunStatistics stats, bool isCanceled, bool isAborted, Exception error, Collection attachmentSets, TimeSpan elapsedTime) + { + this.TestRunStatistics = stats; + this.IsCanceled = isCanceled; + this.IsAborted = isAborted; + this.Error = error; + this.AttachmentSets = attachmentSets; + this.ElapsedTimeInRunningTests = elapsedTime; + } + + /// + /// Gets the statistics on the state of the test run. + /// + public ITestRunStatistics TestRunStatistics { get; private set; } + + /// + /// Gets a value indicating whether the test run is canceled or not. + /// + public bool IsCanceled { get; private set; } + + /// + /// Gets a value indicating whether the test run is aborted. + /// + public bool IsAborted { get; private set; } + + /// + /// Gets the error encountered during the execution of the test run. Null if there is no error. + /// + public Exception Error { get; private set; } + + /// + /// Gets the attachment sets associated with the test run. + /// + public Collection AttachmentSets { get; private set; } + + /// + /// Gets the time elapsed in just running the tests. + /// Value is set to TimeSpan.Zero incase of any error. + /// + public TimeSpan ElapsedTimeInRunningTests { get; private set; } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/IDiscoveryRequest.cs b/src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/IDiscoveryRequest.cs new file mode 100644 index 0000000000..8819bcbfb6 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/IDiscoveryRequest.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Client +{ + using System; + using System.Collections.Generic; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + + /// + /// IDiscoverTestsRequest returned after calling GetDiscoveredTestsAsync + /// + public interface IDiscoveryRequest: IRequest + { + /// + /// Starts tests discovery async. + /// + void DiscoverAsync(); + + /// + /// Aborts the discovery request + /// + void Abort(); + + /// + /// + /// Handler for notifying discovery process is complete + /// + event EventHandler OnDiscoveryComplete; + + /// + /// Handler for notifying when newly found tests are available for UI to fetch. + /// + event EventHandler OnDiscoveredTests; + + /// + /// Handler for receiving error during fetching/execution. This is used for when abnormal error + /// occurs; equivalent of IRunMessageLogger in the current RockSteady core + /// + event EventHandler OnDiscoveryMessage; + + /// + /// Specifies the discovery criterion + /// + DiscoveryCriteria DiscoveryCriteria + { + get; + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/IRequest.cs b/src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/IRequest.cs new file mode 100644 index 0000000000..c240888b7c --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/IRequest.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Client +{ + using System; + using System.Threading; + + public interface IRequest : IDisposable + { + /// + /// Handler for receiving raw messages directly from host without any deserialization or morphing + /// This is required if one wants to re-direct the message over the process boundary without any processing overhead + /// All events should come as raw messages as well as actual serialized events + /// + event EventHandler OnRawMessageReceived; + + /// + /// Waits for the request to complete + /// + /// Timeout + /// True if the request timeouts + bool WaitForCompletion(int timeout); + } + + public static class RequestExtensions + { + public static void WaitForCompletion(this IRequest request) + { + request.WaitForCompletion(Timeout.Infinite); + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestDiscoveryEventsHandler.cs b/src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestDiscoveryEventsHandler.cs new file mode 100644 index 0000000000..47fe6ac6c5 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestDiscoveryEventsHandler.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Client +{ + using System.Collections.Generic; + + /// + /// Interface contract for handling discovery events during test discovery operation + /// + public interface ITestDiscoveryEventsHandler : ITestMessageEventHandler + { + /// + /// Dispatch DiscoveryComplete event to listeners. + /// + void HandleDiscoveryComplete(long totalTests, IEnumerable lastChunk, bool isAborted); + + + /// + /// Dispatch DiscoveredTest event to listeners. + /// + /// Discovered test cases. + void HandleDiscoveredTests(IEnumerable discoveredTestCases); + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestHostLauncher.cs b/src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestHostLauncher.cs new file mode 100644 index 0000000000..39c7769fb7 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestHostLauncher.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + + /// + /// Interface defining contract for custom test host implementations + /// + public interface ITestHostLauncher + { + /// + /// Is Debug Launcher + /// + bool IsDebug { get; } + + /// + /// Launches custom test host using the default test process start info + /// + /// Architecture for the test host + /// Default TestHost Process Info + /// Process id of the launched test host + int LaunchTestHost(TestProcessStartInfo defaultTestHostStartInfo); + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestPlatform.cs b/src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestPlatform.cs new file mode 100644 index 0000000000..b4da447ef6 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestPlatform.cs @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Client +{ + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces; + using System; + using System.Collections.Generic; + + public interface ITestPlatform : IDisposable + { + /// + /// Initialize the test platform with the path to additional unit test extensions. + /// If no additional extension is available, then specify null or empty list. + /// + /// Specifies the path to unit test extensions. + /// Specifies whether only well known extensions should be loaded. + /// Forces test discovery in x86 Discoverer process. + void Initialize(IEnumerable pathToAdditionalExtensions, bool loadOnlyWellKnownExtensions, bool forceX86Discoverer); + + /// + /// Update the extensions to be used by the test service + /// + /// + /// Specifies the path to unit test extensions. + /// If no additional extension is available, then specify null or empty list. + /// + /// Specifies whether only well known extensions should be loaded. + void UpdateExtensions(IEnumerable pathToAdditionalExtensions, bool loadOnlyWellKnownExtensions); + + /// + /// Creates a discovery request + /// + /// Specifies the discovery parameters + /// DiscoveryRequest object + IDiscoveryRequest CreateDiscoveryRequest(DiscoveryCriteria discoveryCriteria); + + /// + /// Creates a test run request. + /// + /// Specifies the test run criteria + /// RunRequest object + ITestRunRequest CreateTestRunRequest(TestRunCriteria testRunCriteria); + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestPlatformCapabilities.cs b/src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestPlatformCapabilities.cs new file mode 100644 index 0000000000..f5329346c8 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestPlatformCapabilities.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Client +{ + using System.Diagnostics.CodeAnalysis; + + /// + /// Basic metadata for ITestPlaform. + /// + /// + /// This interface is only public due to limitations in MEF which require metadata interfaces + /// to be public. This interface is not intended for external consumption. + /// + [SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes", Justification = "This interface is only public due to limitations in MEF which require metadata interfaces to be public.")] + public interface ITestPlatformCapabilities + { + /// + /// Type of testPlatform + /// + TestPlatformType TestPlatformType { get; } + } + + public enum TestPlatformType + { + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + InProc, + + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + OutOfProc + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestRunConfiguration.cs b/src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestRunConfiguration.cs new file mode 100644 index 0000000000..360ced302d --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestRunConfiguration.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Client +{ + using System.Collections.Generic; + + /// + /// Defines common test run configuration APIs + /// + public interface ITestRunConfiguration + { + /// + /// Defines the frequency of run stats test event. + /// + /// + /// Run stats change event will be raised after completion of these number of tests. + /// + /// Note that this event is raised asynchronously and the underlying execution process is not + /// paused during the listener invocation. So if the event handler, you try to query the + /// next set of results, you may get more than 'FrequencyOfRunStatsChangeEvent'. + /// + long FrequencyOfRunStatsChangeEvent { get; } + + /// + /// Returns whether the run is configured to run specific tests + /// + bool HasSpecificTests { get; } + + /// + /// Returns whether the run is configured to run specific sources + /// + bool HasSpecificSources { get; } + + /// + /// The specific tests for this test run if any. + /// + IEnumerable Tests { get; } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestRunEventsHandler.cs b/src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestRunEventsHandler.cs new file mode 100644 index 0000000000..e4b4adcde3 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestRunEventsHandler.cs @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Client +{ + using System.Collections.Generic; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + + /// + /// Interface contract for handling test run events during run operation + /// + public interface ITestRunEventsHandler : ITestMessageEventHandler + { + /// + /// Handle the TestRunCompletion event from a test engine + /// + /// TestRunCompletion Data + /// Last set of test results + /// Attachments of the test run + /// ExecutorURIs of the adapters involved in test run + void HandleTestRunComplete(TestRunCompleteEventArgs testRunCompleteArgs, TestRunChangedEventArgs lastChunkArgs, ICollection runContextAttachments, ICollection executorUris); + + /// + /// Handle a change in TestRun i.e. new testresults and stats + /// + /// TestRunChanged Data + void HandleTestRunStatsChange(TestRunChangedEventArgs testRunChangedArgs); + + /// + /// Launches a process with a given process info under debugger + /// Adapter get to call into this to launch any additional processes under debugger + /// + /// Process start info + /// ProcessId of the launched process + int LaunchProcessWithDebuggerAttached(TestProcessStartInfo testProcessStartInfo); + } + + /// + /// Interface for handling generic message events during test discovery or execution + /// + public interface ITestMessageEventHandler + { + /// + /// Raw Message from the host directly + /// + /// raw message args from host + void HandleRawMessage(string rawMessage); + + /// + /// Handle a IMessageLogger message event from Adapter + /// Whenever adapters call IMessageLogger.SendMessage, TestEngine notifies client with this event + /// + /// Message Level + /// string message + void HandleLogMessage(TestMessageLevel level, string message); + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestRunRequest.cs b/src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestRunRequest.cs new file mode 100644 index 0000000000..46c8b259f7 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestRunRequest.cs @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Client +{ + using System; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + + /// + /// The request that a RunTests API returns. + /// + public interface ITestRunRequest : IRequest + { + /// + /// Start the current RunTestAsync API call. + /// + /// Id of the executor process + int ExecuteAsync(); + + /// + /// Cancel the current RunTestsAsync API call. This can be used when making async RunTestsAsync call. + /// + void CancelAsync(); + + /// + /// Aborts the test run execution process. + /// + void Abort(); + + /// + /// Specifies the test run criteria + /// + ITestRunConfiguration TestRunConfiguration + { + get; + } + + /// + /// State of the test run + /// + TestRunState State { get; } + + /// + /// Handler for notifying when test results came back from the agent! + /// + event EventHandler OnRunStatsChange; + + /// + /// Handler for notifying test run is complete + /// + event EventHandler OnRunCompletion; + + /// + /// Handler for receiving error during fetching/execution. This is used for when abnormal error + /// occurs; equivalent of IRunMessageLogger in the current RockSteady core + /// + event EventHandler TestRunMessage; + + /// + /// Handler for receiving data collection messages. + /// + event EventHandler DataCollectionMessage; + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestRunStatistics.cs b/src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestRunStatistics.cs new file mode 100644 index 0000000000..e5f9fad14b --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestRunStatistics.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Client +{ + using System.Collections.Generic; + + /// + /// Stats on the test run state + /// + public interface ITestRunStatistics + { + /// + /// The number of tests that have the specified value of TestOutcome + /// + /// + /// + long this[TestOutcome testOutcome] { get; } + + /// + /// TestOutcome - Test count map + /// + IDictionary Stats { get; } + + /// + /// Number of tests that have been run. + /// + long ExecutedTests { get; } + } + +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Client/TestRunCriteria.cs b/src/Microsoft.TestPlatform.ObjectModel/Client/TestRunCriteria.cs new file mode 100644 index 0000000000..f58f7255aa --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Client/TestRunCriteria.cs @@ -0,0 +1,362 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Client +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Text; + using System.Linq; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces; + using Newtonsoft.Json; + + /// + /// Defines the testRun criterion + /// + public class TestRunCriteria : BaseTestRunCriteria, ITestRunConfiguration + { + #region Constructors that take list of source strings + + /// + /// Create the TestRunCriteria for a test run + /// + /// Sources which contains tests that should be executed + /// Frequency of run stats event + public TestRunCriteria(IEnumerable sources, long frequencyOfRunStatsChangeEvent) + :this(sources, frequencyOfRunStatsChangeEvent, true) + { + } + + /// + /// Create the TestRunCriteria for a test run + /// + /// Sources which contains tests that should be executed + /// Frequency of run stats event + /// Whether the execution process should be kept alive after the run is finished or not. + public TestRunCriteria(IEnumerable sources, long frequencyOfRunStatsChangeEvent, bool keepAlive) + : this(sources, frequencyOfRunStatsChangeEvent, keepAlive, string.Empty) + { + } + + /// + /// Create the TestRunCriteria for a test run + /// + /// Sources which contains tests that should be executed + /// Frequency of run stats event + /// Whether the execution process should be kept alive after the run is finished or not. + /// Settings used for this run. + public TestRunCriteria(IEnumerable sources, long frequencyOfRunStatsChangeEvent, bool keepAlive, string testSettings) + : this(sources, frequencyOfRunStatsChangeEvent, keepAlive, testSettings, TimeSpan.MaxValue) + { + } + + /// + /// Create the TestRunCriteria for a test run + /// + /// Sources which contains tests that should be executed + /// Frequency of run stats event + /// List of TestRunSettings for all providers as applicable for current run. + /// Whether the execution process should be kept alive after the run is finished or not. + /// Timeout that triggers sending results regardless of cache size. + public TestRunCriteria(IEnumerable sources, long frequencyOfRunStatsChangeEvent, bool keepAlive, string testSettings, TimeSpan runStatsChangeEventTimeout) + : this(sources, frequencyOfRunStatsChangeEvent, keepAlive, testSettings, runStatsChangeEventTimeout, null) + { + } + + /// + /// Create the TestRunCriteria for a test run + /// + /// Sources which contains tests that should be executed + /// The BaseTestRunCriteria + public TestRunCriteria(IEnumerable sources, BaseTestRunCriteria baseTestRunCriteria) + : base(baseTestRunCriteria) + { + ValidateArg.NotNullOrEmpty(sources, "sources"); + + this.AdapterSourceMap = new Dictionary>(); + this.AdapterSourceMap.Add(Constants.UnspecifiedAdapterPath, sources); + } + + /// + /// Create the TestRunCriteria for a test run + /// + /// Sources which contains tests that should be executed + /// Frequency of run stats event + /// Settings used for this run. + /// Whether the execution process should be kept alive after the run is finished or not. + /// Timeout that triggers sending results regardless of cache size. + /// Test host launcher. If null then default will be used. + public TestRunCriteria(IEnumerable sources, + long frequencyOfRunStatsChangeEvent, bool keepAlive, string testSettings, + TimeSpan runStatsChangeEventTimeout, ITestHostLauncher testHostLauncher) + : base (frequencyOfRunStatsChangeEvent, keepAlive, testSettings, runStatsChangeEventTimeout, testHostLauncher) + { + ValidateArg.NotNullOrEmpty(sources, "sources"); + + this.AdapterSourceMap = new Dictionary>(); + this.AdapterSourceMap.Add(Constants.UnspecifiedAdapterPath, sources); + } + + /// + /// Create the TestRunCriteria for a test run + /// + /// Sources which contains tests that should be executed + /// Frequency of run stats event + /// Settings used for this run. + /// Whether the execution process should be kept alive after the run is finished or not. + /// Timeout that triggers sending results regardless of cache size. + /// Test host launcher. If null then default will be used. + public TestRunCriteria(Dictionary> adapterSourceMap, long frequencyOfRunStatsChangeEvent, bool keepAlive, + string testSettings, TimeSpan runStatsChangeEventTimeout, ITestHostLauncher testHostLauncher) + : base(frequencyOfRunStatsChangeEvent, keepAlive, testSettings, runStatsChangeEventTimeout, testHostLauncher) + { + ValidateArg.NotNullOrEmpty(adapterSourceMap, "adapterSourceMap"); + + this.AdapterSourceMap = adapterSourceMap; + } + + #endregion + + #region Constructors that take list of test cases + + /// + /// Create the TestRunCriteria for a test run + /// + /// Tests which should be executed + /// Frequency of run stats event + public TestRunCriteria(IEnumerable tests, long frequencyOfRunStatsChangeEvent) + :this(tests, frequencyOfRunStatsChangeEvent, false) + { + } + + /// + /// Create the TestRunCriteria for a test run + /// + /// Tests which should be executed + /// Frequency of run stats event + /// Whether or not to keep the test executor process alive after run completion + public TestRunCriteria(IEnumerable tests, long frequencyOfRunStatsChangeEvent, bool keepAlive) + : this(tests, frequencyOfRunStatsChangeEvent, keepAlive, string.Empty) + { + } + + /// + /// Create the TestRunCriteria for a test run + /// + /// Tests which should be executed + /// Frequency of run stats event + /// Whether or not to keep the test executor process alive after run completion + /// Settings used for this run. + public TestRunCriteria(IEnumerable tests, long frequencyOfRunStatsChangeEvent, bool keepAlive, string testSettings) + : this(tests, frequencyOfRunStatsChangeEvent, keepAlive, testSettings, TimeSpan.MaxValue) + { + } + + /// + /// Create the TestRunCriteria for a test run + /// + /// Tests which should be executed + /// Frequency of run stats event + /// Whether or not to keep the test executor process alive after run completion + /// Settings used for this run. + /// Timeout that triggers sending results regardless of cache size. + public TestRunCriteria(IEnumerable tests, long frequencyOfRunStatsChangeEvent, bool keepAlive, string testSettings, TimeSpan runStatsChangeEventTimeout) + : this(tests, frequencyOfRunStatsChangeEvent, keepAlive, testSettings, runStatsChangeEventTimeout, null) + { + } + + /// + /// Create the TestRunCriteria for a test run + /// + /// Tests which should be executed + /// The BaseTestRunCriteria + public TestRunCriteria(IEnumerable tests, BaseTestRunCriteria baseTestRunCriteria) + : base(baseTestRunCriteria) + { + ValidateArg.NotNullOrEmpty(tests, "tests"); + this.Tests = tests; + } + + /// + /// Create the TestRunCriteria for a test run + /// + /// Sources which contains tests that should be executed + /// Frequency of run stats event + /// Whether or not to keep the test executor process alive after run completion + /// Settings used for this run. + /// Timeout that triggers sending results regardless of cache size. + /// Test host launcher. If null then default will be used. + public TestRunCriteria(IEnumerable tests, long frequencyOfRunStatsChangeEvent, bool keepAlive, string testSettings, TimeSpan runStatsChangeEventTimeout, ITestHostLauncher testHostLauncher) + : base(frequencyOfRunStatsChangeEvent, keepAlive, testSettings, runStatsChangeEventTimeout, testHostLauncher) + { + ValidateArg.NotNullOrEmpty(tests, "tests"); + + this.Tests = tests; + } + + #endregion + + + /// + /// Test Containers (e.g. DLL/EXE/artifacts to scan) + /// + [JsonIgnore] + public IEnumerable Sources + { + get + { + IEnumerable sources = new List(); + return this.AdapterSourceMap?.Values.Aggregate(sources, (current, enumerable) => current.Concat(enumerable)); + } + } + + /// + /// The test adapter and source map which would look like below: + /// { C:\temp\testAdapter1.dll : [ source1.dll, source2.dll ], C:\temp\testadapter2.dll : [ source3.dll, source2.dll ] + /// + public Dictionary> AdapterSourceMap + { + get; private set; + } + + /// + /// Tests that need to executed in this test run. + /// + /// This will be null if test run is created with specific test containers + /// + public IEnumerable Tests { get; private set; } + + private string testCaseFilter; + + /// + /// Criteria for filtering test cases. This is only for with sources. + /// + public string TestCaseFilter + { + get + { + return this.testCaseFilter; + } + + set + { + if (value != null && !this.HasSpecificSources) + { + throw new InvalidOperationException(Resources.NoTestCaseFilterForSpecificTests); + } + + this.testCaseFilter = value; + } + } + + /// + /// Returns whether run criteria is based on specific tests + /// + public bool HasSpecificTests + { + get { return Tests != null; } + } + + /// + /// Returns whether run criteria is based on specific sources + /// + public bool HasSpecificSources + { + get { return Sources != null; } + } + + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.AppendLine(string.Format(CultureInfo.CurrentCulture, "TestRunCriteria:")); + sb.AppendLine(string.Format(CultureInfo.CurrentCulture, " KeepAlive={0},FrequencyOfRunStatsChangeEvent={1},RunStatsChangeEventTimeout={2},TestCaseFilter={3},TestExecutorLauncher={4}", + KeepAlive, FrequencyOfRunStatsChangeEvent, RunStatsChangeEventTimeout, TestCaseFilter, TestHostLauncher)); + sb.AppendLine(string.Format(CultureInfo.CurrentCulture, " Settingsxml={0}", TestRunSettings)); + + return sb.ToString(); + } + } + + + /// + /// Defines the base testRun criterion + /// + public class BaseTestRunCriteria + { + public BaseTestRunCriteria(BaseTestRunCriteria runCriteria) + { + ValidateArg.NotNull(runCriteria, "runCriteria"); + + this.FrequencyOfRunStatsChangeEvent = runCriteria.FrequencyOfRunStatsChangeEvent; + this.KeepAlive = runCriteria.KeepAlive; + this.TestRunSettings = runCriteria.TestRunSettings; + this.RunStatsChangeEventTimeout = runCriteria.RunStatsChangeEventTimeout; + this.TestHostLauncher = runCriteria.TestHostLauncher; + } + + public BaseTestRunCriteria(long frequencyOfRunStatsChangeEvent) + : this(frequencyOfRunStatsChangeEvent, true) + { + } + + public BaseTestRunCriteria(long frequencyOfRunStatsChangeEvent, bool keepAlive) + : this(frequencyOfRunStatsChangeEvent, keepAlive, string.Empty) + { + } + + public BaseTestRunCriteria(long frequencyOfRunStatsChangeEvent, bool keepAlive, string testSettings) + : this(frequencyOfRunStatsChangeEvent, keepAlive, testSettings, TimeSpan.MaxValue) + { + } + + public BaseTestRunCriteria(long frequencyOfRunStatsChangeEvent, bool keepAlive, string testSettings, TimeSpan runStatsChangeEventTimeout) + : this(frequencyOfRunStatsChangeEvent, keepAlive, testSettings, runStatsChangeEventTimeout, null) + { + } + + + public BaseTestRunCriteria(long frequencyOfRunStatsChangeEvent, bool keepAlive, string testSettings, TimeSpan runStatsChangeEventTimeout, ITestHostLauncher testHostLauncher) + { + if (frequencyOfRunStatsChangeEvent <= 0) throw new ArgumentOutOfRangeException("frequencyOfRunStatsChangeEvent", Resources.NotificationFrequencyIsNotPositive); + if (runStatsChangeEventTimeout <= TimeSpan.MinValue) throw new ArgumentOutOfRangeException("runStatsChangeEventTimeout", Resources.NotificationTimeoutIsZero); + + this.FrequencyOfRunStatsChangeEvent = frequencyOfRunStatsChangeEvent; + this.KeepAlive = keepAlive; + this.TestRunSettings = testSettings; + this.RunStatsChangeEventTimeout = runStatsChangeEventTimeout; + this.TestHostLauncher = testHostLauncher; + } + + /// + /// Whether or not to keep the test executor process alive after run completion. + /// + public bool KeepAlive { get; private set; } + + /// + /// Settings used for this run. + /// + public string TestRunSettings { get; private set; } + + /// + /// Custom launcher for test executor. + /// + public ITestHostLauncher TestHostLauncher { get; private set; } + + /// + /// Defines the frequency of run stats test event. + /// + /// + /// Run stats change event will be raised after completion of these number of tests. + /// Note that this event is raised asynchronously and the underlying execution process is not + /// paused during the listener invocation. So if the event handler, you try to query the + /// next set of results, you may get more than 'FrequencyOfRunStatsChangeEvent'. + /// + public long FrequencyOfRunStatsChangeEvent { get; private set; } + + /// + /// Timeout that triggers sending results regardless of cache size. + /// + public TimeSpan RunStatsChangeEventTimeout { get; private set; } + } + +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Client/TestRunState.cs b/src/Microsoft.TestPlatform.ObjectModel/Client/TestRunState.cs new file mode 100644 index 0000000000..a1e671a450 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Client/TestRunState.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Client +{ + /// + /// States of the TestRun + /// + public enum TestRunState + { + /// + /// Status is not known + /// + None = 0, + + /// + /// The run is still being created. No tests have started yet. + /// + Pending = 1, + + /// + /// Tests are running. + /// + InProgress = 2, + + /// + /// All tests have completed or been skipped. + /// + Completed = 3, + + /// + /// Run is canceled and remaing tests have been aborted + /// + Canceled = 4, + + /// + /// Run is aborted + /// + Aborted = 5 + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/CommonResources.Designer.cs b/src/Microsoft.TestPlatform.ObjectModel/CommonResources.Designer.cs new file mode 100644 index 0000000000..269ec30bbf --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/CommonResources.Designer.cs @@ -0,0 +1,89 @@ +//------------------------------------------------------------------------------ +// +// 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 Microsoft.VisualStudio.TestPlatform.ObjectModel { + using System; + using System.Reflection; + + + /// + /// 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.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class CommonResources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + internal CommonResources() { + } + + /// + /// 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("Microsoft.TestPlatform.ObjectModel.CommonResources", typeof(CommonResources).GetTypeInfo().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 The parameter cannot be null or empty.. + /// + public static string CannotBeNullOrEmpty { + get { + return ResourceManager.GetString("CannotBeNullOrEmpty", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Test run will use DLL(s) built for framework {0} and platform {1}. Following DLL(s) will not be part of run: {2} Go to {3} for more details on managing these settings.. + /// + public static string DisplayChosenSettings { + get { + return ResourceManager.GetString("DisplayChosenSettings", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to None of the provided test containers match the Platform Architecture and .Net Framework settings for the test run. Platform: {0} .Net Framework: {1}. Go to http://go.microsoft.com/fwlink/?LinkID=330428 for more details on managing these settings.. + /// + public static string NoMatchingSourcesFound { + get { + return ResourceManager.GetString("NoMatchingSourcesFound", resourceCulture); + } + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/CommonResources.resx b/src/Microsoft.TestPlatform.ObjectModel/CommonResources.resx new file mode 100644 index 0000000000..4e40f26901 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/CommonResources.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + The parameter cannot be null or empty. + + + Test run will use DLL(s) built for framework {0} and platform {1}. Following DLL(s) will not be part of run: {2} Go to {3} for more details on managing these settings. + + + None of the provided test containers match the Platform Architecture and .Net Framework settings for the test run. Platform: {0} .Net Framework: {1}. Go to http://go.microsoft.com/fwlink/?LinkID=330428 for more details on managing these settings. + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.ObjectModel/CommonResources.tt b/src/Microsoft.TestPlatform.ObjectModel/CommonResources.tt new file mode 100644 index 0000000000..6a133f74e0 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/CommonResources.tt @@ -0,0 +1 @@ +<#@ include file = "..\..\Templates\CommonResources.tt" #> \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.ObjectModel/Constants.cs b/src/Microsoft.TestPlatform.ObjectModel/Constants.cs new file mode 100644 index 0000000000..d5d15aa24c --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Constants.cs @@ -0,0 +1,115 @@ +// Copyright(c) Microsoft.All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel +{ + using System.IO; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities; + + /// + /// Defines the defaults/constants used across different components. + /// + public static class Constants + { + /// + /// The in process data collection run settings name. + /// + public const string InProcDataCollectionRunSettingsName = "InProcDataCollectionRunSettings"; + + /// + /// The in process data collector setting name. + /// + public const string InProcDataCollectorSettingName = "InProcDataCollector"; + + /// + /// The in process data collectors setting name. + /// + public const string InProcDataCollectorsSettingName = "InProcDataCollectors"; + + /// + /// Name of data collection settigns node in RunSettings. + /// + public const string DataCollectionRunSettingsName = "DataCollectionRunSettings"; + + /// + /// Name of RunConfiguration settings node in RunSettings. + /// + public const string RunConfigurationSettingsName = "RunConfiguration"; + + /// + /// Default testrunner if testrunner is not specified + /// + public const string UnspecifiedAdapterPath = "_none_"; + + public const string DataCollectorsSettingName = "DataCollectors"; + + public const string RunSettingsName = "RunSettings"; + + public const string DataCollectorSettingName = "DataCollector"; + + public const string TestRunParametersName = "TestRunParameters"; + + /// + /// Type of the unit test extension. (Extension author will use this name while authoring their Vsix) + /// + public const string UnitTestExtensionType = "UnitTestExtension"; + + /// + /// Maximum size of the trace log file (in kilobytes). + /// + public const string TraceLogMaxFileSizeInKB = "TraceLogMaxFileSizeInKb"; + + public const string EmptyRunSettings = @""; + + + public static readonly Architecture DefaultPlatform = XmlRunSettingsUtilities.OSArchitecture == Architecture.ARM ? Architecture.ARM : Architecture.X86; + + public const FrameworkVersion DefaultFramework = FrameworkVersion.Framework45; + + /// + /// Default option for parallel execution + /// + public const int DefaultCpuCount = 1; + + /// + /// Name of the results directory + /// + public const string ResultsDirectoryName = "TestResults"; + + /// + /// Default results directory. + /// + public static readonly string DefaultResultsDirectory = Path.Combine("%Temp%", ResultsDirectoryName); + + /// + /// Default treatment of error from test adapters. + /// + public const bool DefaultTreatTestAdapterErrorsAsWarnings = false; + + /// + /// Contants for detecting .net framework. + /// + public const string TargetFrameworkAttributeFullName = "System.Runtime.Versioning.TargetFrameworkAttribute"; + + public const string DotNetFrameWorkStringPrefix = ".NETFramework,Version="; + + public const string DotNetFramework40 = ".NETFramework,Version=v4.0"; + + public const string DotNetFramework45 = ".NETFramework,Version=v4.5"; + + public const string DotNetFramework46 = ".NETFramework,Version=v4.6"; + + public const string TargetFrameworkName = "TargetFrameworkName"; + } + + /// + /// Default parameters to be passed onto all loggers. + /// + public static class DefaultLoggerParameterNames + { + // Denotes target location for test run resutls + // For ex. TrxLogger saves test run results at this target + public const string TestRunDirectory = "TestRunDirectory"; + } + +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/DataCollector/Common/DataCollectorMessageLevel.cs b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/Common/DataCollectorMessageLevel.cs new file mode 100644 index 0000000000..5052661627 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/Common/DataCollectorMessageLevel.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection +{ + /// + /// Severity levels at which a DataCollectionTextMessage can be logged. + /// + public enum DataCollectorMessageLevel + { + Error = 0, + Warning = 1, + Info = 2, + Data = 3, + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/DataCollector/Common/FileHelper.cs b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/Common/FileHelper.cs new file mode 100644 index 0000000000..470390e6a8 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/Common/FileHelper.cs @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.IO; + using System.Text.RegularExpressions; + + internal sealed class FileHelper + { + private static Dictionary invalidFileNameChars; + private static Regex ReservedFileNamesRegex = new Regex(@"(?i:^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9]|CLOCK\$)(\..*)?)$"); + + #region Constructors + [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")] // Have to init invalidFileNameChars dynamically. + static FileHelper() + { + // Create a hash table of invalid chars. + char[] invalidCharsArray = Path.GetInvalidFileNameChars(); + invalidFileNameChars = new Dictionary(invalidCharsArray.Length); + foreach (char c in invalidCharsArray) + { + invalidFileNameChars.Add(c, null); + } + } + + private FileHelper() + { + } + #endregion + + #region Fields + /// + /// Determines if a file name has invalid characters. + /// + /// File name to check. + /// Invalid characters which were found in the file name. + /// True if the file name is valid and false if the filename contains invalid characters. + public static bool IsValidFileName(string fileName, out string invalidCharacters) + { + bool result = true; + //EqtAssert.StringNotNullOrEmpty(fileName, "fileName"); + + // Find all of the invalid characters in the file name. + invalidCharacters = null; + for (int i = 0; i < fileName.Length; i++) + { + if (invalidFileNameChars.ContainsKey(fileName[i])) + { + invalidCharacters = String.Concat(invalidCharacters, fileName[i]); + result = false; + } + } + + return result; + } + + /// + /// Returns true if the file name specified is Windows reserved file name. + /// + /// The name of the file. Note: only a file name, does not expect to contain dir separators. + internal static bool IsReservedFileName(string fileName) + { + Debug.Assert(!string.IsNullOrEmpty(fileName), "FileHelper.IsReservedFileName: the argument is null or empty string!"); + if (string.IsNullOrEmpty(fileName)) + { + return false; + } + + // CreateFile: + // The following reserved device names cannot be used as the name of a file: + // CON, PRN, AUX, NUL, COM1, COM2, COM3, COM4, COM5, COM6, COM7, COM8, COM9, + // LPT1, LPT2, LPT3, LPT4, LPT5, LPT6, LPT7, LPT8, and LPT9. + // Also avoid these names followed by an extension, for example, NUL.tx7. + // Windows NT: CLOCK$ is also a reserved device name. + return ReservedFileNamesRegex.Match(fileName).Success; + } + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/DataCollector/Common/RequestId.cs b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/Common/RequestId.cs new file mode 100644 index 0000000000..fce76eb99e --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/Common/RequestId.cs @@ -0,0 +1,206 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection +{ + using System; + using System.Diagnostics.CodeAnalysis; + + /// + /// Wrapper class for a request ID that can be used for messages or events for identification + /// purposes + /// +#if NET451 + [Serializable] +#endif + [SuppressMessage("Microsoft.Design", "CA1036:OverrideMethodsOnComparableTypes", + Justification = "Guid does not define < and > operators")] + public sealed class RequestId : IEquatable, IComparable, IComparable + { + #region Constants + + /// + /// A request ID with an empty GUID + /// + [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", + Justification = "RequestId is immutable")] + public static readonly RequestId Empty = new RequestId(Guid.Empty); + + #endregion + + #region Constructors + + /// + /// Initializes the instance by creating a new GUID + /// + internal RequestId() + { + Id = Guid.NewGuid(); + } + + /// + /// Initializes the instance with the provided GUID + /// + /// The GUID to use as the underlying ID + internal RequestId(Guid id) + { + Id = id; + } + + #endregion + + #region Overrides + + /// + /// Compares this instance with the provided object for value equality + /// + /// The object to compare to + /// True if equal, false otherwise + public override bool Equals(object obj) + { + if (obj == null) + { + return false; + } + + if (object.ReferenceEquals(this, obj)) + { + return true; + } + + RequestId other = obj as RequestId; + if (other == null) + { + return false; + } + + return Id == other.Id; + } + + /// + /// Gets a hash code for this instance + /// + /// The underlying GUID's hash code + public override int GetHashCode() + { + return Id.GetHashCode(); + } + + /// + /// Converts the instance to a string in lower-case registry format + /// + /// A lower-case string in registry format representing the underlying GUID + public override string ToString() + { + return Id.ToString("B"); + } + + #endregion + + #region Interface implementations + + #region IEquatable Members + + /// + /// Compares this instance with the provided request ID for value equality + /// + /// The request ID to compare to + /// True if equal, false otherwise + public bool Equals(RequestId other) + { + return + other != null && ( + object.ReferenceEquals(this, other) || + Id == other.Id + ); + } + + #endregion + + #region IComparable Members + + /// + /// Compares this instance with the provided request ID + /// + /// The request ID to compare to + /// An indication of the two request IDs' relative values + public int CompareTo(RequestId other) + { + return other == null ? 1 : Id.CompareTo(other.Id); + } + + #endregion + + #region IComparable Members + + /// + /// Compares this instance with the provided object + /// + /// The object to compare to + /// An indication of the two objects' relative values + /// + /// 'obj' is not null and not an instance of + /// + public int CompareTo(object obj) + { + if (obj == null) + { + return 1; + } + + RequestId other = obj as RequestId; + if (other == null) + { + throw new ArgumentException(string.Format(Resources.Common_ObjectMustBeOfType, new object[] { typeof(RequestId).Name }), "obj"); + } + + return Id.CompareTo(other.Id); + } + + #endregion + + #endregion + + #region Operators + + /// + /// Compares the two request IDs for value equality + /// + /// The left-hand request ID + /// The right-hand request ID + /// True if equal, false otherwise + public static bool operator ==(RequestId left, RequestId right) + { + return + object.ReferenceEquals(left, right) || + !object.ReferenceEquals(left, null) && + !object.ReferenceEquals(right, null) && + left.Id == right.Id; + } + + /// + /// Compares two request IDs for value inequality + /// + /// The left-hand request ID + /// The right-hand request ID + /// True if unequal, false otherwise + public static bool operator !=(RequestId left, RequestId right) + { + return !(left == right); + } + + #endregion + + #region Properties + + /// + /// Gets the underlying GUID that represents the request ID + /// + public Guid Id + { + get; + private set; + } + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/DataCollector/Common/Session.cs b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/Common/Session.cs new file mode 100644 index 0000000000..e7f8eb90da --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/Common/Session.cs @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection +{ + using System; + + /// + /// Class identifying a session. + /// +#if NET451 + [Serializable] +#endif + public sealed class SessionId + { + private Guid sessionId; + + private static SessionId empty = new SessionId(Guid.Empty); + + public SessionId() + { + sessionId = Guid.NewGuid(); + } + + public SessionId(Guid id) + { + sessionId = id; + } + + public static SessionId Empty + { + get { return empty; } + } + + public Guid Id + { + get { return sessionId; } + } + + public override bool Equals(object obj) + { + SessionId id = obj as SessionId; + + if (id == null) + { + return false; + } + + return sessionId.Equals(id.sessionId); + } + + public override int GetHashCode() + { + return sessionId.GetHashCode(); + } + + public override string ToString() + { + return sessionId.ToString("B"); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.ObjectModel/DataCollector/Common/TestCaseFailureType.cs b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/Common/TestCaseFailureType.cs new file mode 100644 index 0000000000..7025e65a20 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/Common/TestCaseFailureType.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection +{ + /// + /// Type of test case failure which occured. + /// + public enum TestCaseFailureType + { + None = 0, + Assertion = 1, + UnhandledException = 2, + UnexpectedException = 3, + MissingException = 4, + Other = 5, + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/DataCollector/Common/TestExecId.cs b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/Common/TestExecId.cs new file mode 100644 index 0000000000..ee499ebf00 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/Common/TestExecId.cs @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection +{ + using System; + + /// + /// Class identifying test execution id. + /// Execution ID is assigned to test at run creation time and is guaranteed to be unique within that run. + /// +#if NET451 + [Serializable] +#endif + public sealed class TestExecId + { + private Guid execId; + + private static TestExecId empty = new TestExecId(Guid.Empty); + + public TestExecId() + { + execId = Guid.NewGuid(); + } + + public TestExecId(Guid id) + { + execId = id; + } + + public static TestExecId Empty + { + get { return empty; } + } + + public Guid Id + { + get { return execId; } + } + + public override bool Equals(object obj) + { + TestExecId id = obj as TestExecId; + + if (id == null) + { + return false; + } + + return execId.Equals(id.execId); + } + + public override int GetHashCode() + { + return execId.GetHashCode(); + } + + public override string ToString() + { + return execId.ToString("B"); + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/DataCollector/DataCollectionContext.cs b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/DataCollectionContext.cs new file mode 100644 index 0000000000..c66cb0e6af --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/DataCollectionContext.cs @@ -0,0 +1,154 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection +{ + /// + /// Class representing the context in which data collection occurs. + /// +#if NET451 + [Serializable] +#endif + public class DataCollectionContext + { + #region Constructors + + // NOTE: These constructors are protected internal to allow 3rd parties to + // do unit testing of their data collectors. + // + // We do not want to make the constructors of this class public as it + // would lead to a great deal of user error when they start creating + // their own data collection context instances to log errors/warnings + // or send files with. The potential for this type of error still + // exists by having the protected constructor, but it is less likely + // and we have added safeguards in our DataCollectinLogger and + // DataCollectionDataSink to safeguard against derived types being + // passed to us. + // + // In order to create mock instances of the DataCollectionContext for + // unit testing purposes, 3rd parties can derive from this class and + // have public constructors. This will allow them to instantiate their + // class and pass to us for creating data collection events. + + /// + /// Constructs a DataCollectionContext indicating that there is a session, + /// but no executing test, in context. + /// + /// The session under which the data collection occurs. Cannot be null. + protected internal DataCollectionContext(SessionId sessionId) + : this(sessionId, null) + { + } + + /// + /// Constructs a DataCollectionContext indicating that there is a session and an executing test, + /// but no test step, in context. + /// + /// The session under which the data collection occurs. Cannot be null. + /// The test execution under which the data collection occurs, + /// or null if no executing test case is in context + protected internal DataCollectionContext(SessionId sessionId, TestExecId testExecId) + { + //todo + //EqtAssert.ParameterNotNull(sessionId, "sessionId"); + + this.sessionId = sessionId; + this.testExecId = testExecId; + this.hashCode = ComputeHashCode(); + } + + #endregion + + #region Properties + + /// + /// Identifies the session under which the data collection occurs. Will not be null. + /// + public SessionId SessionId + { + get + { + return sessionId; + } + } + + /// + /// Identifies the test execution under which the data collection occurs, + /// or null if no such test exists. + /// + public TestExecId TestExecId + { + get + { + return testExecId; + } + } + + /// + /// Returns true if there is an executing test case associated with this context. + /// + public bool HasTestCase + { + get { return testExecId != null; } + } + + #endregion + + #region Equals and Hashcode + + public static bool operator ==(DataCollectionContext context1, DataCollectionContext context2) + { + return object.Equals(context1, context2); + } + + public static bool operator !=(DataCollectionContext context1, DataCollectionContext context2) + { + return !(context1 == context2); + } + + public override bool Equals(object obj) + { + DataCollectionContext other = obj as DataCollectionContext; + + if (other == null) + { + return false; + } + + return sessionId.Equals(other.sessionId) + && (testExecId == null ? other.testExecId == null : testExecId.Equals(other.testExecId)); + } + + public override int GetHashCode() + { + return hashCode; + } + + #endregion + + #region Private Methods + + private int ComputeHashCode() + { + int hashCode = 17; + + hashCode = 31 * hashCode + sessionId.GetHashCode(); + + if (testExecId != null) + { + hashCode = 31 * hashCode + testExecId.GetHashCode(); + } + + return hashCode; + } + + #endregion + + #region Private Fields + + private readonly SessionId sessionId; + private readonly TestExecId testExecId; + private readonly int hashCode; + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/DataCollector/DataCollectionEnvironmentContext.cs b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/DataCollectionEnvironmentContext.cs new file mode 100644 index 0000000000..95e65ba934 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/DataCollectionEnvironmentContext.cs @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection +{ + /// + /// Encapsulates the context of the environment a data collector is being hosted in. + /// +#if NET451 + [Serializable] +#endif + public sealed class DataCollectionEnvironmentContext + { +#region Fields + /// + /// DataCollectionContext for the session. + /// + private DataCollectionContext sessionDataCollectionContext; + +#endregion + +#region Constructors and initialization + + /// + /// Default Constructor + /// + internal DataCollectionEnvironmentContext() + : this(null) + { + } + + /// + /// Initializes with the DataCollectionContext + /// + public DataCollectionEnvironmentContext(DataCollectionContext sessionDataCollectionContext) + { + SessionDataCollectionContext = sessionDataCollectionContext; + } + + + /// + /// Creates an environment context for a local (hosted) agent and controller + /// + /// An environment context for a local (hosted) agent and controller + public static DataCollectionEnvironmentContext CreateForLocalEnvironment() + { + return CreateForLocalEnvironment(null); + } + + /// + /// Creates an environment context for a local (hosted) agent and controller + /// + /// Session level data collection context. + /// An environment context for a local (hosted) agent and controller + public static DataCollectionEnvironmentContext CreateForLocalEnvironment(DataCollectionContext sessionDataCollectionContext) + { + var dataCollectionEnvironmentContext = new DataCollectionEnvironmentContext(); + dataCollectionEnvironmentContext.SessionDataCollectionContext = sessionDataCollectionContext; + + return dataCollectionEnvironmentContext; + } + +#endregion + +#region Properties + + /// + /// DataCollectionContext for the session. + /// + public DataCollectionContext SessionDataCollectionContext + { + get + { + return sessionDataCollectionContext; + } + internal set + { + sessionDataCollectionContext = value; + } + } + +#endregion + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/DataCollector/DataCollectionLogger.cs b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/DataCollectionLogger.cs new file mode 100644 index 0000000000..77ba748086 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/DataCollectionLogger.cs @@ -0,0 +1,108 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection +{ + using System; + + /// + /// Class used by data collectors to send messages to the client (e.g. Manual Test Runner, Visual Studio IDE, MSTest). + /// + public abstract class DataCollectionLogger + { + /// + /// Constructs a DataCollectionLogger + /// + protected DataCollectionLogger() + { + } + + #region Public Members + + /// + /// Logs an error message. + /// + /// The context in which the message is being sent. + /// The error text. Cannot be null. + /// + /// When a Data Collector invokes this method, Client would get called on OnCollectionError( ) with a CollectionErrorMessageEventArgs. + /// + public abstract void LogError(DataCollectionContext context, string text); + + /// + /// Logs an error message for an exception. + /// + /// The context in which the message is being sent. + /// The exception. Cannot be null. + /// + /// When a Data Collector invokes this method, Client would get called on OnCollectionError( ) with a CollectionErrorMessageEventArgs. + /// + public void LogError(DataCollectionContext context, Exception exception) + { + LogError(context, string.Empty, exception); + } + + /// + /// Logs an error message for an exception. + /// + /// The context in which the message is being sent. + /// Text explaining the exception. Cannot be null. + /// The exception. Cannot be null. + /// + /// When a Data Collector invokes this method, Client would get called on OnCollectionError( ) with a CollectionErrorMessageEventArgs. + /// + public abstract void LogError(DataCollectionContext context, string text, Exception exception); + + /// + /// Logs a warning. + /// + /// The context in which the message is being sent. + /// The warning text. Cannot be null. + /// + /// When a Data Collector invokes this method, Client would get called on OnCollectionWarning( ) with a CollectionWarningMessageEventArgs. + /// + public abstract void LogWarning(DataCollectionContext context, string text); + + /// + /// Logs and given exception to the client. + /// + /// + /// The exception to be logged + /// Is the exception at warning level or error level. + /// + /// When a Data Collector invokes this method, Client would get called on OnCollectionException( ) with a CollectionExceptionMessageEventArgs. + /// + public virtual void LogException(DataCollectionContext context, Exception ex, DataCollectorMessageLevel level) + { + } + + /// + /// Sends custom data from the collector + /// + /// Context under which the data collection is happening + /// Custom notification arguments to which the data is being sent in respose to. + /// Custom data to be sent + /// + /// When a Data Collector invokes this method, Client would get called on OnCollectionData( ) with a CollectionDataMessageEventArgs. + /// The datacollectioncontext of the matchingeventargs will be used for sending the data. + /// + public virtual void SendData(CustomNotificationEventArgs matchingEventArgs, CustomCollectorData data) + { + // Default no-op. + } + + /// + /// Sends custom data from the collector + /// + /// Context under which the data collection is happening + /// Custom data to be sent + /// + /// When a Data Collector invokes this method, Client would get called on OnCollectionException( ) with a CollectionExceptionMessageEventArgs. + /// + public virtual void SendData(DataCollectionContext context, CustomCollectorData data) + { + // Default no-op. + } + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/DataCollector/DataCollectionRunSettings.cs b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/DataCollectionRunSettings.cs new file mode 100644 index 0000000000..a1a646c52c --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/DataCollectionRunSettings.cs @@ -0,0 +1,229 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel +{ + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Globalization; + using System.Linq; + using System.Xml; + + /// + /// The in procedure data collection run settings. + /// + public class DataCollectionRunSettings : TestRunSettings + { + private string dataCollectionRootName = string.Empty; + + private string dataCollectionsName = string.Empty; + + private string dataCollectorName = string.Empty; + + /// + /// Initializes a new instance of the class. + /// + public DataCollectionRunSettings() : base(Constants.DataCollectionRunSettingsName) + { + this.DataCollectorSettingsList = new Collection(); + this.dataCollectionRootName = Constants.DataCollectionRunSettingsName; + this.dataCollectionsName = Constants.DataCollectorsSettingName; + this.dataCollectorName = Constants.DataCollectorSettingName; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The data collection root name. + /// + /// + /// The data collections name. + /// + /// + /// The data collector name. + /// + public DataCollectionRunSettings( + string dataCollectionRootName, + string dataCollectionsName, + string dataCollectorName) + : base(dataCollectionRootName) + { + this.dataCollectionRootName = dataCollectionRootName; + this.dataCollectionsName = dataCollectionsName; + this.dataCollectorName = dataCollectorName; + this.DataCollectorSettingsList = new Collection(); + } + + /// + /// Gets the in procedure data collector settings list. + /// + public Collection DataCollectorSettingsList + { + get; + private set; + } + + /// + /// Gets a value indicating whether is in procedure data collection enabled. + /// + public bool IsCollectionEnabled + { + get + { + return this.DataCollectorSettingsList.Any(setting => setting.IsEnabled); + } + } + + /// + /// The to xml. + /// + /// + /// The . + /// + public override XmlElement ToXml() + { + XmlDocument doc = new XmlDocument(); + XmlElement root = doc.CreateElement(this.dataCollectionRootName); + XmlElement subRoot = doc.CreateElement(this.dataCollectionsName); + root.AppendChild(subRoot); + + foreach (var collectorSettings in this.DataCollectorSettingsList) + { + XmlNode child = doc.ImportNode(collectorSettings.ToXml(this.dataCollectorName), true); + subRoot.AppendChild(child); + } + + return root; + } + + /// + /// The from xml. + /// + /// + /// The reader. + /// + /// + /// The . + /// + /// + /// Settings exception + /// + public static DataCollectionRunSettings FromXml(XmlReader reader) + { + return CreateDataCollectionRunSettings( + reader, + Constants.DataCollectionRunSettingsName, + Constants.DataCollectorsSettingName, + Constants.DataCollectorSettingName); + } + + public static DataCollectionRunSettings FromXml(XmlReader reader, string dataCollectionName, string dataCollectorsName, string dataCollectorName) + { + return CreateDataCollectionRunSettings(reader, dataCollectionName, dataCollectorsName, dataCollectorName); + } + + public static DataCollectionRunSettings CreateDataCollectionRunSettings( + XmlReader reader, string dataCollectionName, + string dataCollectorsName, string dataCollectorName) + { + ValidateArg.NotNull(reader, "reader"); + ValidateArg.NotNull(dataCollectorsName, "dataCollectorsName"); + ValidateArg.NotNull(dataCollectorName, "dataCollectorName"); + + DataCollectionRunSettings settings = new DataCollectionRunSettings(dataCollectionName, dataCollectorsName, dataCollectorName); + bool empty = reader.IsEmptyElement; + if (reader.HasAttributes) + { + reader.MoveToNextAttribute(); + throw new SettingsException( + string.Format( + CultureInfo.CurrentCulture, + Resources.InvalidSettingsXmlAttribute, + dataCollectorsName, + reader.Name)); + } + + // Process the fields in Xml elements + reader.Read(); + if (!empty) + { + while (reader.NodeType == XmlNodeType.Element) + { + if (reader.Name.Equals(dataCollectorsName)) + { + var items = ReadListElementFromXml(reader, dataCollectorName); + foreach (var item in items) + { + settings.DataCollectorSettingsList.Add(item); + } + } + else + { + throw new SettingsException( + string.Format( + CultureInfo.CurrentCulture, + Resources.InvalidSettingsXmlElement, + dataCollectorsName, + reader.Name)); + } + } + + reader.ReadEndElement(); + } + + return settings; + } + + /// + /// The read list element from xml. + /// + /// + /// The reader. + /// + /// + /// The . + /// + /// + /// + internal static List ReadListElementFromXml(XmlReader reader, string dataCollectorsName) + { + List settings = new List(); + bool empty = reader.IsEmptyElement; + if (reader.HasAttributes) + { + reader.MoveToNextAttribute(); + throw new SettingsException( + string.Format( + CultureInfo.CurrentCulture, + Resources.InvalidSettingsXmlAttribute, + dataCollectorsName, + reader.Name)); + } + + reader.Read(); + if (!empty) + { + while (reader.NodeType == XmlNodeType.Element) + { + if (reader.Name.Equals(dataCollectorsName)) + { + settings.Add(DataCollectorSettings.FromXml(reader)); + } + else + { + throw new SettingsException( + string.Format( + CultureInfo.CurrentCulture, + Resources.InvalidSettingsXmlElement, + dataCollectorsName, + reader.Name)); + } + } + + reader.ReadEndElement(); + } + + return settings; + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/DataCollector/DataCollectionSink.cs b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/DataCollectionSink.cs new file mode 100644 index 0000000000..15686accf2 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/DataCollectionSink.cs @@ -0,0 +1,114 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection +{ + using System; + using System.ComponentModel; + using System.IO; + + /// + /// Class used by data collectors to send data to up-stream components + /// (agent, controller, client, etc). + /// + public abstract class DataCollectionSink + { + #region Constructor + + /// + /// Creates a DataCollectionSink + /// + protected DataCollectionSink() + { + } + + #endregion + + #region Events + + /// + /// Called when sending of a file has completed. + /// + public abstract event AsyncCompletedEventHandler SendFileCompleted; + + /// + /// Called when sending of a stream has completed. + /// + public abstract event AsyncCompletedEventHandler SendStreamCompleted; + + #endregion + + #region SendFileAsync + + /// + /// Sends a file to up-stream components. + /// + /// The context in which the file is being sent. Cannot be null. + /// the path to the file on the local file system + /// True to automatically have the file removed after sending it. + public void SendFileAsync(DataCollectionContext context, string path, bool deleteFile) + { + SendFileAsync(context, path, String.Empty, deleteFile); + } + + /// + /// Sends a file to up-stream components. + /// + /// The context in which the file is being sent. Cannot be null. + /// the path to the file on the local file system + /// A short description of the data being sent. + /// True to automatically have the file removed after sending it. + public void SendFileAsync(DataCollectionContext context, string path, string description, bool deleteFile) + { + var fileInfo = new FileTransferInformation(context, path, deleteFile); + fileInfo.Description = description; + + SendFileAsync(fileInfo); + } + + /// + /// Sends a file to up-stream components + /// + /// Information about the file to be transferred. + public abstract void SendFileAsync(FileTransferInformation fileTransferInformation); + + #endregion + + #region SendStreamAsync + + /// + /// Sends a stream to up-stream components. + /// + /// The context in which the stream is being sent. Cannot be null. + /// Stream to send. + /// File name to use for the data on the client. + /// True to automatically have the stream closed when sending of the contents has completed. + public void SendStreamAsync(DataCollectionContext context, Stream stream, string fileName, bool closeStream) + { + SendStreamAsync(context, stream, fileName, String.Empty, closeStream); + } + + /// + /// Sends a stream to up-stream components. + /// + /// The context in which the stream is being sent. Cannot be null. + /// Stream to send. + /// File name to use for the data on the client. + /// A short description of the data being sent. + /// True to automatically have the stream closed when sending of the contents has completed. + public void SendStreamAsync(DataCollectionContext context, Stream stream, string fileName, string description, bool closeStream) + { + var streamInfo = new StreamTransferInformation(context, stream, fileName, closeStream); + streamInfo.Description = description; + + SendStreamAsync(streamInfo); + } + + /// + /// Sends a stream to up-stream components. + /// + /// Information about the stream being transferred. + public abstract void SendStreamAsync(StreamTransferInformation streamTransferInformation); + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/DataCollector/DataCollector.cs b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/DataCollector.cs new file mode 100644 index 0000000000..23cfeca484 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/DataCollector.cs @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection +{ + using System; + using System.Diagnostics.CodeAnalysis; + using System.Xml; + + /// + /// Interface for data collector add-ins + /// + public abstract class DataCollector : IDisposable + { + #region Methods + + /// + /// Initializes the data collector + /// + /// + /// The XML element containing configuration information for the data collector. Can be + /// null if the add-in does not have any configuration information. + /// + /// + /// Object containing the execution events the data collector can register for + /// + /// The sink used by the data collector to send its data + /// + /// Used by the data collector to send warnings, errors, or other messages + /// + /// Provides contextual information about the agent environment + [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes", MessageId = "System.Xml.XmlNode")] + public abstract void Initialize( + XmlElement configurationElement, + DataCollectionEvents events, + DataCollectionSink dataSink, + DataCollectionLogger logger, + DataCollectionEnvironmentContext environmentContext + ); + + /// + /// Disposes the data collector. + /// + public void Dispose() + { + Dispose(true); + + // Suppress Finalize in case a subclass implements a finalizer. + GC.SuppressFinalize(this); + } + + /// + /// Called to perform cleanup when the instance is being disposed. + /// + /// True when being called from the Dispose method and false when being called during finalization. + protected virtual void Dispose(bool disposing) + { + } + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/DataCollector/DataCollectorSettings.cs b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/DataCollectorSettings.cs new file mode 100644 index 0000000000..c475d1a144 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/DataCollectorSettings.cs @@ -0,0 +1,219 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + using System.Threading.Tasks; + using System.Xml; + + /// + /// The in procedure data collector settings. + /// + public class DataCollectorSettings + { + /// + /// Gets or sets the uri. + /// + public Uri Uri + { + get; + set; + } + + /// + /// Gets or sets the assembly qualified name. + /// + public string AssemblyQualifiedName + { + get; + set; + } + + /// + /// Gets or sets the friendly name. + /// + public string FriendlyName + { + get; + set; + } + + /// + /// Gets or sets a value indicating whether is enabled. + /// + public bool IsEnabled + { + get; + set; + } + + /// + /// Gets or sets value CodeBase of collector DLL. The syntax is same as Code Base in AssemblyName class. + /// + public string CodeBase + { + get; + set; + } + + /// + /// Gets or sets the configuration. + /// + public XmlElement Configuration + { + get; + set; + } + + /// + /// The to xml. + /// + /// + /// The . + /// + public XmlElement ToXml() + { + XmlDocument doc = new XmlDocument(); + XmlElement root = doc.CreateElement(Constants.DataCollectorSettingName); + AppendAttribute(doc, root, "uri", this.Uri.ToString()); + AppendAttribute(doc, root, "assemblyQualifiedName", this.AssemblyQualifiedName); + AppendAttribute(doc, root, "friendlyName", this.FriendlyName); + + root.AppendChild(doc.ImportNode(this.Configuration, true)); + + return root; + } + + /// + /// The to xml. + /// + /// + /// The data collector name. + /// + /// + /// The . + /// + public XmlElement ToXml(string dataCollectorName) + { + XmlDocument doc = new XmlDocument(); + XmlElement root = doc.CreateElement(dataCollectorName); + AppendAttribute(doc, root, "uri", this.Uri.ToString()); + AppendAttribute(doc, root, "assemblyQualifiedName", this.AssemblyQualifiedName); + AppendAttribute(doc, root, "friendlyName", this.FriendlyName); + + root.AppendChild(doc.ImportNode(this.Configuration, true)); + + return root; + } + + /// + /// The from xml. + /// + /// + /// The reader. + /// + /// + /// The . + /// + /// + /// Settings exception + /// + internal static DataCollectorSettings FromXml(XmlReader reader) + { + DataCollectorSettings settings = new DataCollectorSettings(); + settings.IsEnabled = true; + bool empty = reader.IsEmptyElement; + if (reader.HasAttributes) + { + while (reader.MoveToNextAttribute()) + { + switch (reader.Name) + { + case "uri": + ValidateArg.NotNullOrEmpty(reader.Value, "uri"); + settings.Uri = new Uri(reader.Value); + break; + + case "assemblyQualifiedName": + ValidateArg.NotNullOrEmpty(reader.Value, "assemblyQualifiedName"); + settings.AssemblyQualifiedName = reader.Value; + break; + + case "friendlyName": + ValidateArg.NotNullOrEmpty(reader.Value, "FriendlyName"); + settings.FriendlyName = reader.Value; + break; + + case "enabled": + settings.IsEnabled = bool.Parse(reader.Value); + break; + + case "codebase": + settings.CodeBase = reader.Value; // Optional. + break; + + default: + throw new SettingsException( + String.Format( + CultureInfo.CurrentCulture, + Resources.InvalidSettingsXmlAttribute, + Constants.DataCollectionRunSettingsName, + reader.Name)); + } + } + + } + + if (settings.Uri == null) + { + throw new SettingsException( + String.Format(CultureInfo.CurrentCulture, Resources.MissingDataCollectorAttributes, "Uri")); + } + if (settings.AssemblyQualifiedName == null) + { + throw new SettingsException( + String.Format( + CultureInfo.CurrentCulture, + Resources.MissingDataCollectorAttributes, + "AssemblyQualifiedName")); + } + + reader.Read(); + if (!empty) + { + while (reader.NodeType == XmlNodeType.Element) + { + switch (reader.Name) + { + case "Configuration": + XmlDocument doc = new XmlDocument(); + XmlElement element = doc.CreateElement("Configuration"); + element.InnerXml = reader.ReadInnerXml(); + settings.Configuration = element; + break; + + default: + throw new SettingsException( + String.Format( + CultureInfo.CurrentCulture, + Resources.InvalidSettingsXmlElement, + Constants.DataCollectionRunSettingsName, + reader.Name)); + } + } + reader.ReadEndElement(); + } + return settings; + } + + private static void AppendAttribute(XmlDocument doc, XmlElement owner, string attributeName, string attributeValue) + { + XmlAttribute attribute = doc.CreateAttribute(attributeName); + attribute.Value = attributeValue; + owner.Attributes.Append(attribute); + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/DataCollector/Events/CustomNotificationEventArgs.cs b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/Events/CustomNotificationEventArgs.cs new file mode 100644 index 0000000000..551e8fd43c --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/Events/CustomNotificationEventArgs.cs @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection +{ + using System; + + /// + /// Base class for all data collector custom notification event arguments + /// +#if NET451 + [Serializable] +#endif + public abstract class CustomNotificationEventArgs : DataCollectionEventArgs + { + /// + /// Initializes for sending a session level custom notification. + /// + protected CustomNotificationEventArgs() : + this(new DataCollectionContext(SessionId.Empty, null), null) + { + } + + /// + /// Initializes for sending a test case level custom notification + /// + /// ID of the test case to send the event against. + protected CustomNotificationEventArgs(TestExecId testExecId) : + base(new DataCollectionContext(SessionId.Empty, testExecId)) + { + + } + + /// + /// Initializes for sending a custom notification against the provided data collection context. + /// + /// Data Collection Context that the event is being sent against. + internal CustomNotificationEventArgs(DataCollectionContext context) : + this(context, null) + { + } + + /// + /// Initializes for sending a custom notification against the provided data collection context. + /// + /// Data Collection Context that the event is being sent against. + /// Uri of the targetd collector + internal CustomNotificationEventArgs(DataCollectionContext context, Uri targetDataCollectorUri) : + base(context, targetDataCollectorUri) + { + this.NotificationIdentifier = Guid.NewGuid(); + } + + /// + /// Identifier for this custom notification + /// + internal Guid NotificationIdentifier + { + get; + private set; + } + } + + /// + /// Custom data send from the collector + /// +#if NET451 + [Serializable] +#endif + public abstract class CustomCollectorData + { + } + +#if NET451 + [Serializable] +#endif + public class CustomCollectorGenericErrorData : CustomCollectorData + { + public string Message + { + get; + set; + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/DataCollector/Events/DataCollectionEventArgs.cs b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/Events/DataCollectionEventArgs.cs new file mode 100644 index 0000000000..68137c235a --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/Events/DataCollectionEventArgs.cs @@ -0,0 +1,79 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection +{ + using System; + using System.Diagnostics; + + /// + /// Base class for all execution event arguments + /// +#if NET451 + [Serializable] +#endif + public abstract class DataCollectionEventArgs : EventArgs + { + #region Constructor + + /// + /// Initializes the instance by storing the given information + /// + /// Context information for the event + protected DataCollectionEventArgs(DataCollectionContext context) : + this(context, null) + { + } + + protected DataCollectionEventArgs(DataCollectionContext context, Uri targetDataCollectorUri) + { + //EqtTrace.FailIf(context == null, "Context should not be null."); + + Context = context; + TargetDataCollectorUri = targetDataCollectorUri; + } + + #endregion + + #region Public properties + + /// + /// Gets the context information for the event + /// + public DataCollectionContext Context + { + get; + private set; + } + + /// + /// Gets or sets Data collector Uri this notification is targeted for + /// + public Uri TargetDataCollectorUri + { + get; + set; + } + + #endregion + + #region Private Methods + + /// + /// Updates the data collection context stored by this instance. + /// + /// Context to update with. + /// + /// Generally the data collection context is known in advance, however there + /// are cases around custom notifications where it is not necessiarly known + /// until the event is being sent. This is used for updating the context when + /// sending the event. + /// + internal void UpdateDataCollectionContext(DataCollectionContext context) + { + Debug.Assert(context != null, "'context' cannot be null."); + Context = context; + } + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/DataCollector/Events/DataCollectionEvents.cs b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/Events/DataCollectionEvents.cs new file mode 100644 index 0000000000..ec68e4f2d2 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/Events/DataCollectionEvents.cs @@ -0,0 +1,104 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection +{ + using System; + + /// + /// Class defining execution events that will be registered for by collectors + /// + public abstract class DataCollectionEvents + { + #region Constructor + + /// + /// Default constructor. + /// + protected DataCollectionEvents() + { + } + + #endregion + + #region Events + + #region Session events + + /// + /// Raised when a session is starting + /// + public abstract event EventHandler SessionStart; + + /// + /// Raised when a session is ending + /// + public abstract event EventHandler SessionEnd; + + /// + /// Raised when a session is paused + /// + public abstract event EventHandler SessionPause; + + /// + /// Raised when a session is resuming + /// + public abstract event EventHandler SessionResume; + #endregion + + #region Test case events + + /// + /// Raised when a test case is starting + /// + public abstract event EventHandler TestCaseStart; + + /// + /// Raised when a test case is ending + /// + public abstract event EventHandler TestCaseEnd; + + /// + /// Raised when a test case is pausing + /// + public abstract event EventHandler TestCasePause; + + /// + /// Raised when a test case is resuming + /// + public abstract event EventHandler TestCaseResume; + + /// + /// Raised when a test case is reset + /// + public abstract event EventHandler TestCaseReset; + + /// + /// Raised when a test case has failed. + /// + /// + /// This event is only raised for test types which send test failure notifications. + /// + public abstract event EventHandler TestCaseFailed; + + #endregion + + #region Other events + + /// + /// Raised when intermediate data is requested. Can be a test case-specific event, or just + /// a session event. When sent with a test case-specific context, intermediate data for the + /// test case is requested, and when sent with only a session-specific context, + /// intermediate data for a session is requested. + /// + public abstract event EventHandler DataRequest; + + /// + /// Raised on a custom notification + /// + public abstract event EventHandler CustomNotification; + + #endregion + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/DataCollector/Events/DataRequestEventArgs.cs b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/Events/DataRequestEventArgs.cs new file mode 100644 index 0000000000..b1aa3b1c56 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/Events/DataRequestEventArgs.cs @@ -0,0 +1,160 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection +{ + using System; + using System.Diagnostics; + + /// + /// Data Request event arguments + /// +#if NET451 + [Serializable] +#endif + public sealed class DataRequestEventArgs : TestCaseEventArgs + { + #region Constants + + /// + /// Default test case ID used when sending the event for a session data request + /// + private static readonly Guid DefaultTestCaseId = Guid.Empty; + + /// + /// Default test case name used when sending the event for a session data request + /// + private static readonly string DefaultTestCaseName = string.Empty; + + /// + /// Default value for flag indicating whether this is a child test case + /// + private const bool DefaultIsChildTestCase = false; + + #endregion + + #region Constructors + + /// + /// Initializes the instance by storing the given information + /// + /// Context information for the test case + /// How much of the previously collected data the requestor is interested in. + internal DataRequestEventArgs(DataCollectionContext context, TimeSpan requestedDuration) + : this(context, DefaultTestCaseId, DefaultTestCaseName, DefaultIsChildTestCase, requestedDuration) + { + Debug.Assert( + !context.HasTestCase, + "This constructor overload is to be used only for a session data request" + ); + } + + /// + /// Initializes the instance by storing the given information + /// + /// Context information for the test case + /// The test case ID + /// + /// Information used to obtain further data about the test from the Test Case Management (TCM) server, + /// or null if the test did not originate from TCM. + /// + /// The test case name + /// + /// True if this is a child test case, false if this is a top-level test case + /// + /// How much of the previously collected data the requestor is interested in. + internal DataRequestEventArgs( + DataCollectionContext context, + Guid testCaseId, + //TcmInformation tcmInformation, + string testCaseName, + bool isChildTestCase, + TimeSpan requestedDuration) + : base(context, testCaseId, testCaseName, isChildTestCase) + { + RequestId = new RequestId(); + RequestedDuration = requestedDuration; + } + + /// + /// Initializes the instance by storing the given information. + /// + /// Context information for the test case + /// The test element of the test that this event is for. + /// + /// Information used to obtain further data about the test from the Test Case Management (TCM) server, + /// or null if the test did not originate from TCM. + /// + /// How much of the previously collected data the requestor is interested in. + public DataRequestEventArgs( + DataCollectionContext context, + TestCase testElement, + //TcmInformation tcmInformation, + TimeSpan requestedDuration) + : base(context, testElement) + { + // NOTE: ONLY USE FOR UNIT TESTING! + // This overload is only here for 3rd parties to use for unit testing + // their data collectors. Internally we should not be passing the test element + // around in the events as this is extra information that needs to be seralized + // and the Execution Plugin Manager will fill this in for us before the event + // is sent to the data collector when running in a production environment. + + RequestId = new RequestId(); + RequestedDuration = requestedDuration; + } + + #endregion + + #region Properties + + /// + /// Gets the request ID that uniquely identifies this request context + /// + public RequestId RequestId + { + get; + private set; + } + + /// + /// How much of the previously collected data the requestor is interested in. A value + /// of is used to request all data. + /// + /// + /// It is up to each individual data collector to respect this value when returning data. + /// Some collectors may not be able to break up their data and will return the full + /// set of data instead of just the requested portion. + /// + public TimeSpan RequestedDuration + { + get; + private set; + } + + #endregion + } + + /// + /// Event arguments for the event that tells the plugin manager to flush any remaining data + /// collectors have sent into the result sink, and then append a token at the end, to + /// indicate that all the data has been flushed into the result sink. + /// +#if NET451 + [Serializable] +#endif + internal sealed class FlushDataEventArgs : DataCollectionEventArgs + { + #region Constructor + + /// + /// Initializes the instance by storing the given information + /// + /// Context information for the test case + internal FlushDataEventArgs(DataCollectionContext context) + : base(context) + { + } + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/DataCollector/Events/SessionEvents.cs b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/Events/SessionEvents.cs new file mode 100644 index 0000000000..a4a8d954c0 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/Events/SessionEvents.cs @@ -0,0 +1,97 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection +{ + using System.Diagnostics; + + /// + /// Session Start event arguments + /// +#if NET451 + [Serializable] +#endif + public sealed class SessionStartEventArgs : DataCollectionEventArgs + { + #region Constructor + + /// + /// Initializes the instance by storing the given information + /// + /// Context information for the session + public SessionStartEventArgs(DataCollectionContext context) + : base(context) + { + Debug.Assert(!context.HasTestCase, "Session event has test a case context"); + } + + #endregion + } + + /// + /// Session End event arguments + /// +#if NET451 + [Serializable] +#endif + public sealed class SessionEndEventArgs : DataCollectionEventArgs + { + #region Constructor + + /// + /// Initializes the instance by storing the given information + /// + /// Context information for the session + public SessionEndEventArgs(DataCollectionContext context) + : base(context) + { + Debug.Assert(!context.HasTestCase, "Session event has test a case context"); + } + + #endregion + } + /// + /// Session Pause event arguments + /// +#if NET451 + [Serializable] +#endif + public sealed class SessionPauseEventArgs : DataCollectionEventArgs + { + #region Constructor + + /// + /// Initializes the instance by storing the given information + /// + /// Context information for the session + public SessionPauseEventArgs(DataCollectionContext context) + : base(context) + { + Debug.Assert(!context.HasTestCase, "Session event has test a case context"); + } + + #endregion + } + + /// + /// Session Resume event arguments + /// +#if NET451 + [Serializable] +#endif + public sealed class SessionResumeEventArgs : DataCollectionEventArgs + { + #region Constructor + + /// + /// Initializes the instance by storing the given information + /// + /// Context information for the session + public SessionResumeEventArgs(DataCollectionContext context) + : base(context) + { + Debug.Assert(!context.HasTestCase, "Session event has test a case context"); + } + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/DataCollector/Events/TestCaseEvents.cs b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/Events/TestCaseEvents.cs new file mode 100644 index 0000000000..af833d3838 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/Events/TestCaseEvents.cs @@ -0,0 +1,554 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection +{ + using System; + using System.Diagnostics; + + /// + /// Base class for all test case event arguments. + /// +#if NET451 + [Serializable] +#endif + public abstract class TestCaseEventArgs : DataCollectionEventArgs + { + #region Constructor + + /// + /// Initializes the instance by storing the given information. + /// + /// Context information for the test case + /// The test case ID + /// + /// Information used to obtain further data about the test from the Test Case Management (TCM) server, + /// or null if the test did not originate from TCM. + /// + /// The test case name + /// + /// True if this is a child test case, false if this is a top-level test case. + /// + protected TestCaseEventArgs( + DataCollectionContext context, + Guid testCaseId, + //TcmInformation tcmInformation, + string testCaseName, + bool isChildTestCase) + : base(context) + { + TestCaseId = testCaseId; + //TcmInformation = tcmInformation; + TestCaseName = testCaseName == null ? string.Empty : testCaseName; + IsChildTestCase = isChildTestCase; + } + + /// + /// Initializes the instance by storing the given information. + /// + /// Context information for the test case + /// The test element of the test that this event is for. + /// + /// Information used to obtain further data about the test from the Test Case Management (TCM) server, + /// or null if the test did not originate from TCM. + /// + protected TestCaseEventArgs( + DataCollectionContext context, + TestCase testElement + //TcmInformation tcmInformation + ) + : this(context, Guid.Empty, null, false) + { + // NOTE: ONLY USE FOR UNIT TESTING! + // This overload is only here for 3rd parties to use for unit testing + // their data collectors. Internally we should not be passing the test element + // around in the events as this is extra information that needs to be seralized + // and the Execution Plugin Manager will fill this in for us before the event + // is sent to the data collector when running in a production environment. + + //todo + //EqtAssert.ParameterNotNull(testElement, "testElement"); + + TestElement = testElement; + TestCaseId = testElement.Id; + TestCaseName = testElement.DisplayName; + //IsChildTestCase = testElement != null && + // !testElement.ParentExecId.Equals(TestExecId.Empty); + } + + #endregion + + #region Public properties + + /// + /// Gets the test case ID + /// + public Guid TestCaseId + { + get; + private set; + } + + /// + /// Information used to obtain further data about the test from the Test Case Management (TCM) server, + /// or null if the test did not originate from TCM. + /// + //public TcmInformation TcmInformation + //{ + // get; + // private set; + //} + + /// + /// Gets the test case name + /// + public string TestCaseName + { + get; + private set; + } + + /// + /// True if this is a child test case, false if this is a top-level test case + /// + public bool IsChildTestCase + { + get; + private set; + } + + /// + /// Test element of the test this event is for. + /// + public TestCase TestElement + { + get; + internal set; + } + + #endregion + } + + /// + /// Test Case Start event arguments. + /// +#if NET451 + [Serializable] +#endif + public sealed class TestCaseStartEventArgs : TestCaseEventArgs + { + #region Constructor + + /// + /// Initializes the instance by storing the given information + /// + /// Context information for the test case + /// The test case ID + /// + /// Information used to obtain further data about the test from the Test Case Management (TCM) server, + /// or null if the test did not originate from TCM. + /// + /// The test case name + /// + /// True if this is a child test case, false if this is a top-level test case + /// + internal TestCaseStartEventArgs( + DataCollectionContext context, + Guid testCaseId, + //TcmInformation tcmInformation, + string testCaseName, + bool isChildTestCase) + : base(context, testCaseId, testCaseName, isChildTestCase) + { + Debug.Assert(context.HasTestCase, "Context is not for a test case"); + } + + /// + /// Initializes the instance by storing the given information. + /// + /// Context information for the test case + /// The test element of the test that this event is for. + /// + /// Information used to obtain further data about the test from the Test Case Management (TCM) server, + /// or null if the test did not originate from TCM. + /// + public TestCaseStartEventArgs( + DataCollectionContext context, + TestCase testElement) + //TcmInformation tcmInformation) + : base(context, testElement) + { + // NOTE: ONLY USE FOR UNIT TESTING! + // This overload is only here for 3rd parties to use for unit testing + // their data collectors. Internally we should not be passing the test element + // around in the events as this is extra information that needs to be seralized + // and the Execution Plugin Manager will fill this in for us before the event + // is sent to the data collector when running in a production environment. + } + + #endregion + } + + /// + /// Test Case End event arguments. + /// +#if NET451 + [Serializable] +#endif + public sealed class TestCaseEndEventArgs : TestCaseEventArgs + { + #region Constructor + + /// + /// Initializes the instance by storing the given information + /// + /// Context information for the test case + /// The test case ID + /// + /// Information used to obtain further data about the test from the Test Case Management (TCM) server, + /// or null if the test did not originate from TCM. + /// + /// The test case name + /// + /// True if this is a child test case, false if this is a top-level test case + /// + internal TestCaseEndEventArgs( + DataCollectionContext context, + Guid testCaseId, + //TcmInformation tcmInformation, + string testCaseName, + bool isChildTestCase) + : this(context, testCaseId, testCaseName, isChildTestCase, TestOutcome.Failed) + { + } + + /// + /// Initializes the instance by storing the given information + /// + /// Context information for the test case + /// The test case ID + /// + /// Information used to obtain further data about the test from the Test Case Management (TCM) server, + /// or null if the test did not originate from TCM. + /// + /// The test case name + /// + /// True if this is a child test case, false if this is a top-level test case + /// + /// The outcome of the test case. + internal TestCaseEndEventArgs( + DataCollectionContext context, + Guid testCaseId, + //TcmInformation tcmInformation, + string testCaseName, + bool isChildTestCase, + TestOutcome testOutcome) + : base(context, testCaseId, testCaseName, isChildTestCase) + { + Debug.Assert(context.HasTestCase, "Context is not for a test case"); + this.TestOutcome = testOutcome; + } + + /// + /// Initializes the instance by storing the given information. + /// + /// Context information for the test case + /// The test element of the test that this event is for. + /// + /// Information used to obtain further data about the test from the Test Case Management (TCM) server, + /// or null if the test did not originate from TCM. + /// + /// The outcome of the test case. + public TestCaseEndEventArgs( + DataCollectionContext context, + TestCase testElement, + //TcmInformation tcmInformation, + TestOutcome testOutcome) + : base(context, testElement) + { + // NOTE: ONLY USE FOR UNIT TESTING! + // This overload is only here for 3rd parties to use for unit testing + // their data collectors. Internally we should not be passing the test element + // around in the events as this is extra information that needs to be seralized + // and the Execution Plugin Manager will fill this in for us before the event + // is sent to the data collector when running in a production environment. + + this.TestOutcome = testOutcome; + } + + #endregion + + #region Properties + + /// + /// The outcome of the test. + /// + public TestOutcome TestOutcome + { + get; + private set; + } + #endregion + } + + /// + /// Test Case Pause Event arguments. + /// +#if NET451 + [Serializable] +#endif + public sealed class TestCasePauseEventArgs : TestCaseEventArgs + { + #region Constructor + + /// + /// Initializes the instance by storing the given information + /// + /// Context information for the test case + /// The test case ID + /// + /// Information used to obtain further data about the test from the Test Case Management (TCM) server, + /// or null if the test did not originate from TCM. + /// + /// The test case name + /// + /// True if this is a child test case, false if this is a top-level test case + /// + internal TestCasePauseEventArgs( + DataCollectionContext context, + Guid testCaseId, + //TcmInformation tcmInformation, + string testCaseName, + bool isChildTestCase) + : base(context, testCaseId, testCaseName, isChildTestCase) + { + Debug.Assert(context.HasTestCase, "Context is not for a test case"); + } + + /// + /// Initializes the instance by storing the given information. + /// + /// Context information for the test case + /// The test element of the test that this event is for. + /// + /// Information used to obtain further data about the test from the Test Case Management (TCM) server, + /// or null if the test did not originate from TCM. + /// + public TestCasePauseEventArgs( + DataCollectionContext context, + TestCase testElement) + //TcmInformation tcmInformation) + : base(context, testElement) + { + // NOTE: ONLY USE FOR UNIT TESTING! + // This overload is only here for 3rd parties to use for unit testing + // their data collectors. Internally we should not be passing the test element + // around in the events as this is extra information that needs to be seralized + // and the Execution Plugin Manager will fill this in for us before the event + // is sent to the data collector when running in a production environment. + } + + #endregion + } + + /// + /// Test Case Resume Event arguments. + /// +#if NET451 + [Serializable] +#endif + public sealed class TestCaseResumeEventArgs : TestCaseEventArgs + { + #region Constructor + + /// + /// Initializes the instance by storing the given information + /// + /// Context information for the test case + /// The test case ID + /// + /// Information used to obtain further data about the test from the Test Case Management (TCM) server, + /// or null if the test did not originate from TCM. + /// + /// The test case name + /// + /// True if this is a child test case, false if this is a top-level test case + /// + internal TestCaseResumeEventArgs( + DataCollectionContext context, + Guid testCaseId, + //TcmInformation tcmInformation, + string testCaseName, + bool isChildTestCase) + : base(context, testCaseId, testCaseName, isChildTestCase) + { + Debug.Assert(context.HasTestCase, "Context is not for a test case"); + } + + /// + /// Initializes the instance by storing the given information. + /// + /// Context information for the test case + /// The test element of the test that this event is for. + /// + /// Information used to obtain further data about the test from the Test Case Management (TCM) server, + /// or null if the test did not originate from TCM. + /// + public TestCaseResumeEventArgs( + DataCollectionContext context, + TestCase testElement) + //TcmInformation tcmInformation) + : base(context, testElement) + { + // NOTE: ONLY USE FOR UNIT TESTING! + // This overload is only here for 3rd parties to use for unit testing + // their data collectors. Internally we should not be passing the test element + // around in the events as this is extra information that needs to be seralized + // and the Execution Plugin Manager will fill this in for us before the event + // is sent to the data collector when running in a production environment. + } + + #endregion + } + + /// + /// Test Case Reset Event arguments. + /// +#if NET451 + [Serializable] +#endif + public sealed class TestCaseResetEventArgs : TestCaseEventArgs + { + #region Constructor + + /// + /// Initializes the instance by storing the given information + /// + /// Context information for the test case + /// The test case ID + /// + /// Information used to obtain further data about the test from the Test Case Management (TCM) server, + /// or null if the test did not originate from TCM. + /// + /// The test case name + /// + /// True if this is a child test case, false if this is a top-level test case + /// + internal TestCaseResetEventArgs( + DataCollectionContext context, + Guid testCaseId, + //TcmInformation tcmInformation, + string testCaseName, + bool isChildTestCase) + : base(context, testCaseId, testCaseName, isChildTestCase) + { + Debug.Assert(context.HasTestCase, "Context is not for a test case"); + } + + /// + /// Initializes the instance by storing the given information. + /// + /// Context information for the test case + /// The test element of the test that this event is for. + /// + /// Information used to obtain further data about the test from the Test Case Management (TCM) server, + /// or null if the test did not originate from TCM. + /// + public TestCaseResetEventArgs( + DataCollectionContext context, + TestCase testElement) + //TcmInformation tcmInformation) + : base(context, testElement) + { + // NOTE: ONLY USE FOR UNIT TESTING! + // This overload is only here for 3rd parties to use for unit testing + // their data collectors. Internally we should not be passing the test element + // around in the events as this is extra information that needs to be seralized + // and the Execution Plugin Manager will fill this in for us before the event + // is sent to the data collector when running in a production environment. + } + + #endregion + } + + /// + /// Test Case Failed Event arguments. + /// +#if NET451 + [Serializable] +#endif + public sealed class TestCaseFailedEventArgs : TestCaseEventArgs + { + #region Constructor + + /// + /// Initializes the instance by storing the given information + /// + /// Context information for the test case + /// The test case ID + /// + /// Information used to obtain further data about the test from the Test Case Management (TCM) server, + /// or null if the test did not originate from TCM. + /// + /// The test case name + /// + /// True if this is a child test case, false if this is a top-level test case + /// + /// The type of failure which has occured. + internal TestCaseFailedEventArgs( + DataCollectionContext context, + Guid testCaseId, + //TcmInformation tcmInformation, + string testCaseName, + bool isChildTestCase, + TestCaseFailureType failureType) + : base(context, testCaseId, testCaseName, isChildTestCase) + { + Debug.Assert(context.HasTestCase, "Context is not for a test case"); + + if (failureType < TestCaseFailureType.None || failureType > TestCaseFailureType.Other) + { + throw new ArgumentOutOfRangeException("failureType"); + } + + FailureType = failureType; + } + + /// + /// Initializes the instance by storing the given information. + /// + /// Context information for the test case + /// The test element of the test that this event is for. + /// + /// Information used to obtain further data about the test from the Test Case Management (TCM) server, + /// or null if the test did not originate from TCM. + /// + /// The type of failure which has occured. + public TestCaseFailedEventArgs( + DataCollectionContext context, + TestCase testElement, + //TcmInformation tcmInformation, + TestCaseFailureType failureType) + : base(context, testElement) + { + // NOTE: ONLY USE FOR UNIT TESTING! + // This overload is only here for 3rd parties to use for unit testing + // their data collectors. Internally we should not be passing the test element + // around in the events as this is extra information that needs to be seralized + // and the Execution Plugin Manager will fill this in for us before the event + // is sent to the data collector when running in a production environment. + + FailureType = failureType; + } + + #endregion + + #region Properties + + /// + /// The type of failure which occured. + /// + public TestCaseFailureType FailureType { get; private set; } + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/DataCollector/InProcDataCollector/InProcDataCollectionArgs.cs b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/InProcDataCollector/InProcDataCollectionArgs.cs new file mode 100644 index 0000000000..dc7333d444 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/InProcDataCollector/InProcDataCollectionArgs.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollector.InProcDataCollector +{ + /// + /// The InProcDataCollectionArgs interface. + /// + public interface InProcDataCollectionArgs + { + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/DataCollector/InProcDataCollector/InProcDataCollector.cs b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/InProcDataCollector/InProcDataCollector.cs new file mode 100644 index 0000000000..a4b66d7ce3 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/InProcDataCollector/InProcDataCollector.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.InProcDataCollector +{ + using System; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollector.InProcDataCollector; + + /// + /// Listener interface for external exe from test host + /// + public interface InProcDataCollection + { + /// + /// Called when test session starts + /// + /// + /// The test Session Start Args. + /// + void TestSessionStart(TestSessionStartArgs testSessionStartArgs); + + /// + /// Called when test case starts + /// + /// + /// Test Case start args + /// + void TestCaseStart(TestCaseStartArgs testCaseStartArgs); + + /// + /// Called when test case end + /// + /// + /// The test Case End Args. + /// + void TestCaseEnd(TestCaseEndArgs testCaseEndArgs); + + /// + /// Called when test session end + /// + /// + /// The test Session End Args. + /// + void TestSessionEnd(TestSessionEndArgs testSessionEndArgs); + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/DataCollector/InProcDataCollector/TestCaseEndArgs.cs b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/InProcDataCollector/TestCaseEndArgs.cs new file mode 100644 index 0000000000..a55f95fbf1 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/InProcDataCollector/TestCaseEndArgs.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollector.InProcDataCollector +{ + /// + /// The test case end args. + /// + public class TestCaseEndArgs : InProcDataCollectionArgs + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The test case. + /// + /// + /// The outcome. + /// + public TestCaseEndArgs(TestCase testCase, TestOutcome outcome) + { + this.TestCase = testCase; + this.TestOutcome = outcome; + } + + /// + /// Gets the test case. + /// + public TestCase TestCase { get; private set; } + + /// + /// Gets the outcome. + /// + public TestOutcome TestOutcome { get; private set; } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/DataCollector/InProcDataCollector/TestCaseStartArgs.cs b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/InProcDataCollector/TestCaseStartArgs.cs new file mode 100644 index 0000000000..bdfda67f91 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/InProcDataCollector/TestCaseStartArgs.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollector.InProcDataCollector +{ + /// + /// The test case start args. + /// + public class TestCaseStartArgs : InProcDataCollectionArgs + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The test case. + /// + public TestCaseStartArgs(TestCase testCase) + { + this.TestCase = testCase; + } + + /// + /// Gets the test case. + /// + public TestCase TestCase { get; private set; } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/DataCollector/InProcDataCollector/TestSessionEndArgs.cs b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/InProcDataCollector/TestSessionEndArgs.cs new file mode 100644 index 0000000000..a6f1c081e1 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/InProcDataCollector/TestSessionEndArgs.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollector.InProcDataCollector +{ + /// + /// The test session end args. + /// + public class TestSessionEndArgs : InProcDataCollectionArgs + { + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/DataCollector/InProcDataCollector/TestSessionStartArgs.cs b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/InProcDataCollector/TestSessionStartArgs.cs new file mode 100644 index 0000000000..f912303f71 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/InProcDataCollector/TestSessionStartArgs.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollector.InProcDataCollector +{ + using System; + + /// + /// The test session start args. + /// + public class TestSessionStartArgs : InProcDataCollectionArgs + { + /// + /// Initializes a new instance of the class. + /// + public TestSessionStartArgs() + { + this.Configuration = String.Empty; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The configuration. + /// + public TestSessionStartArgs(string configuration) + { + this.Configuration = configuration; + } + + /// + /// Gets or sets the configuration. + /// + public string Configuration { get; set; } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/DataCollector/TransferInformation/BaseTransferInformation.cs b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/TransferInformation/BaseTransferInformation.cs new file mode 100644 index 0000000000..c56a24d9b2 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/TransferInformation/BaseTransferInformation.cs @@ -0,0 +1,102 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection +{ + using System; + + /// + /// Collects the required and optional information needed for requesting a file transfer from a data collector. + /// + public abstract class BasicTransferInformation + { + #region Fields + + private string description; + + #endregion + + #region Constructor + + /// + /// Initializes with the data collection context for the transfer. + /// + /// The data collection context for the transfer. + protected BasicTransferInformation(DataCollectionContext context) + { + //EqtAssert.ParameterNotNull(context, "context"); + + Context = context; + Description = String.Empty; + } + + #endregion + + #region Required Parameters. + + /// + /// The data collection context the transfer will be associated with. + /// + public DataCollectionContext Context { get; private set; } + + #endregion + + #region Optional Parameters. + + /// + /// A short description of the data being sent. + /// + public string Description + { + get + { + return description; + } + set + { + // If we don't have a description, use an empty string. + if (value == null) + { + description = String.Empty; + } + else + { + description = value; + } + } + } + + /// + /// Token which will be included with the callback to identify this file transfer. + /// + public object UserToken { get; set; } + + /// + /// The ID of the request that this file should be associated with. This is used + /// for sending transient data which will be associated only with this + /// data request and not the session or test cases that are currently running. + /// + public RequestId RequestId { get; set; } + + /// + /// Indicates if cleanup should be performed after transferring the resource. This + /// can be known by different names in the derived classes so it is protected internal + /// so that we can refer to it in a consistent way. + /// + protected internal abstract bool PerformCleanup + { + get; + } + + /// + /// The name of the file to use on the client machine. This + /// can be known by different names in the derived classes so it is protected internal + /// so that we can refer to it in a consistent way. + /// + protected internal abstract string ClientFileName + { + get; + } + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/DataCollector/TransferInformation/FileTransferInformation.cs b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/TransferInformation/FileTransferInformation.cs new file mode 100644 index 0000000000..be6b8682a0 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/TransferInformation/FileTransferInformation.cs @@ -0,0 +1,66 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection +{ + using System; + using System.IO; + + /// + /// Represents required and optional information needed for requesting a file transfer. + /// + public class FileTransferInformation : BasicTransferInformation + { + #region Constructor + + /// + /// Initializes with the information required for sending the contents of a file. + /// + /// The context in which the file is being sent. Cannot be null. + /// The path to the file on the local file system + /// True to automatically have the file removed after sending it. + public FileTransferInformation(DataCollectionContext context, string path, bool deleteFile) + : base(context) + { + //EqtAssert.StringNotNullOrEmpty(path, "path"); + + // Expand environment variables in the path + path = Environment.ExpandEnvironmentVariables(path); + + // Make sure the file exists. + if (!File.Exists(path)) + { + throw new FileNotFoundException(string.Format(Resources.Common_FileNotExist, new object[] { path }), path); + } + + // Make sure the path we have is a full path (not relative). + Path = System.IO.Path.GetFullPath(path); + + PerformCleanup = deleteFile; + } + + #endregion + + #region Required Parameters. + + /// + /// The path to the file on the local file system. + /// + public string Path { get; private set; } + + + /// + /// Indicates if cleanup should be performed after transferring the resource. + /// + protected internal override bool PerformCleanup { get; } + + /// + /// The name of the file to use on the client machine. + /// + protected internal override string ClientFileName + { + get { return Path; } + } + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/DataCollector/TransferInformation/StreamTransferInformation.cs b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/TransferInformation/StreamTransferInformation.cs new file mode 100644 index 0000000000..65d26f6b12 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/TransferInformation/StreamTransferInformation.cs @@ -0,0 +1,89 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection +{ + using System; + using System.IO; + + /// + /// Represents required and optional information needed for requesting a stream transfer. + /// + public class StreamTransferInformation : BasicTransferInformation + { + #region Constructor + + /// + /// Initializes with the with required information for sending the contents of a stream. + /// + /// The context in which the file is being sent. Cannot be null. + /// Stream to send. + /// File name to use for the data on the client. + /// True to automatically have the stream closed when sending of the contents has completed. + public StreamTransferInformation(DataCollectionContext context, Stream stream, string fileName, bool closeStream) + : base(context) + { + //todo + //EqtAssert.ParameterNotNull(stream, "stream"); + + // Make sure the trimmed filename is not empty. + if ((fileName == null) || + (fileName = fileName.Trim()).Length == 0) + { + throw new ArgumentException(Resources.Common_CannotBeNullOrEmpty, "fileName"); + } + + // Make sure the filename provided is not a reserved filename. + if (FileHelper.IsReservedFileName(fileName)) + { + throw new ArgumentException(string.Format(Resources.DataCollectionSink_ReservedFilenameUsed, new object[] { fileName }), "fileName"); + } + + // Make sure just the filename was provided. + string invalidCharacters; + if (!FileHelper.IsValidFileName(fileName, out invalidCharacters)) + { + throw new ArgumentException(string.Format(Resources.DataCollectionSink_InvalidFileNameCharacters, new object[] { fileName, invalidCharacters }), "fileName"); + } + + // If we can not read the stream, throw. + if (!stream.CanRead) + { + throw new InvalidOperationException(Resources.DataCollectionSink_CanNotReadStream); + } + + Stream = stream; + FileName = fileName; + PerformCleanup = closeStream; + } + + #endregion + + #region Required Parameters. + + /// + /// Stream to send. + /// + public Stream Stream { get; private set; } + + /// + /// File name to use for the data on the client. + /// + public string FileName { get; private set; } + + + /// + /// Indicates if cleanup should be performed after transferring the resource. + /// + protected internal override bool PerformCleanup { get; } + + /// + /// The name of the file to use on the client machine. + /// + protected internal override string ClientFileName + { + get { return FileName; } + } + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/DefaultExecutorUriAttribute.cs b/src/Microsoft.TestPlatform.ObjectModel/DefaultExecutorUriAttribute.cs new file mode 100644 index 0000000000..9c2040a37c --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/DefaultExecutorUriAttribute.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel +{ + using System; + + /// + /// This attribute is applied on the discoverers to inform the framework about their default executor. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public sealed class DefaultExecutorUriAttribute : Attribute + { + #region Constructor + + /// + /// Initializes with the Uri of the executor. + /// + /// The Uri of the executor + public DefaultExecutorUriAttribute(string executorUri) + { + if (string.IsNullOrWhiteSpace(executorUri)) + { + throw new ArgumentException(CommonResources.CannotBeNullOrEmpty, "executorUri"); + } + + ExecutorUri = executorUri; + } + + #endregion + + #region Properties + + /// + /// The Uri of the Test Executor. + /// + public string ExecutorUri { get; private set; } + + #endregion + + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/ExceptionConverter.cs b/src/Microsoft.TestPlatform.ObjectModel/ExceptionConverter.cs new file mode 100644 index 0000000000..6c6404961f --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/ExceptionConverter.cs @@ -0,0 +1,107 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel +{ + using System; + using System.Diagnostics.CodeAnalysis; + + /// + /// Base exception for all Rocksteady service exceptions + /// +#if NET46 + [Serializable] +#endif + [SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors")] + public class TestPlatformException : Exception + { + public TestPlatformException(String message) + : base(message) + { + } + + public TestPlatformException(string message, Exception innerException) + : base(message, innerException) + { + } + } + +#if FullCLR + /// + /// This class converts WCF fault exception to a strongly-typed exception + /// + public static class ExceptionConverter + { + /// + /// This method converts WCF fault exception to a strongly-typed exception + /// + /// FaultException + /// strongly typed excetption that is wrapped in Fault Exception + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0")] + public static Exception ConvertException(FaultException faultEx) + { + ValidateArg.NotNull(faultEx, "faultEx"); + if (faultEx.Code == null || faultEx.Code.Name == null) + { + return new TestPlatformException(faultEx.Message, faultEx); + } + return ConvertException(faultEx.Code.Name, faultEx.Message, faultEx); + } + + /// + /// Creates a strontly-typed exception that is represented by the exception name + /// passed as parameter + /// + /// Exception type classname + /// message of exception + /// actual exception that is to be wrapped + /// actual exception that is represented by the exception name + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Instantiating an instance of Exception class")] + private static Exception ConvertException(String exceptionType, String message, Exception innerException) + { + try + { + string className = rocksteadyExceptionNameSpace + "." + exceptionType; + Type t = typeof(Microsoft.VisualStudio.TestPlatform.Common.TestRunner.TestPlatformException); + string assembly = Assembly.GetAssembly(t).FullName; + + System.Runtime.Remoting.ObjectHandle handle = Activator.CreateInstance(assembly, className, true, + 0, null, new object[] { message, innerException }, CultureInfo.InvariantCulture, null, null); + return ((TestPlatformException)handle.Unwrap()); + } + catch (Exception) + { + // Ignore it, and try to get System.Exception + } + + try + { + string className = "System" + "." + exceptionType; + Type t = typeof(System.Exception); + string assembly = Assembly.GetAssembly(t).FullName; + + System.Runtime.Remoting.ObjectHandle handle = Activator.CreateInstance(assembly, className, true, + 0, null, new object[] { message, innerException }, CultureInfo.InvariantCulture, null, null); + Exception tempEx = (Exception)handle.Unwrap(); + return tempEx; + } + catch (Exception) + { + // Neither System nor TestPlatformException, but still pass it as TestPlatformException + return new TestPlatformException(message, innerException); + } + } + + private const string rocksteadyExceptionNameSpace = "Microsoft.VisualStudio.TestPlatform.Core.TestRunner"; + } +#endif + +#if NET46 + [Serializable] +#endif + [SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors")] + public class ProcessExitedException : TestPlatformException + { + public ProcessExitedException(string message) : base(message) { } + public ProcessExitedException(string message, Exception inner) : base(message, inner) { } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/ExtensionUriAttribute.cs b/src/Microsoft.TestPlatform.ObjectModel/ExtensionUriAttribute.cs new file mode 100644 index 0000000000..9605eb552d --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/ExtensionUriAttribute.cs @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel +{ + using System; + + /// + /// This attribute is applied to extensions so they can be uniquely identified. + /// It indicates the Uri which uniquely identifies the extension. If this attribute + /// is not provided on the extensions such as the Test Executor or Test Logger, then + /// the extensions will not be used. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public sealed class ExtensionUriAttribute : Attribute + { + #region Constructor + + /// + /// Initializes with the Uri of the extension. + /// + /// The Uri of the extension + public ExtensionUriAttribute(string extensionUri) + { + if (string.IsNullOrWhiteSpace(extensionUri)) + { + throw new ArgumentException(CommonResources.CannotBeNullOrEmpty, "extensionUri"); + } + + ExtensionUri = extensionUri; + } + + #endregion + + #region Properties + + /// + /// The Uri of the Test Executor. + /// + public string ExtensionUri { get; private set; } + + #endregion + + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/FileExtensionAttribute.cs b/src/Microsoft.TestPlatform.ObjectModel/FileExtensionAttribute.cs new file mode 100644 index 0000000000..60b0de07bf --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/FileExtensionAttribute.cs @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel +{ + using System; + + /// + /// This attribute is applied to ITestDiscoverers. It indicates + /// which file extensions the test discoverer knows how to process. + /// If this attribute is not provided on the test discoverer it will be + /// called for all file types. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + public sealed class FileExtensionAttribute : Attribute + { + #region Constructor + + /// + /// Initializes with a file extension that the test discoverer can process tests from. + /// For example ".dll" or ".exe". + /// + /// The file extensions that the test discoverer can process tests from. + public FileExtensionAttribute(string fileExtension) + { + if (string.IsNullOrWhiteSpace(fileExtension)) + { + throw new ArgumentException(CommonResources.CannotBeNullOrEmpty, "fileExtension"); + } + + FileExtension = fileExtension; + } + + #endregion + + #region Properties + + /// + /// A file extensions that the test discoverer can process tests from. For example ".dll" or ".exe". + /// + public string FileExtension { get; private set; } + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/FrameworkVersion.cs b/src/Microsoft.TestPlatform.ObjectModel/FrameworkVersion.cs new file mode 100644 index 0000000000..2c6d4d9212 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/FrameworkVersion.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel +{ + /// + /// This holds the major desktop framework versions. This is just to maintain compatibility with older runsettings files. + /// + public enum FrameworkVersion + { + None, + Framework35, + Framework40, + Framework45 + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/FriendlyNameAttribute.cs b/src/Microsoft.TestPlatform.ObjectModel/FriendlyNameAttribute.cs new file mode 100644 index 0000000000..782df7e060 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/FriendlyNameAttribute.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel +{ + using System; + + /// + /// This attribute is applied to Loggers so they can be uniquely identified. + /// It indicates the Friendly Name which uniquely identifies the extension. + /// This attribute is optional. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public sealed class FriendlyNameAttribute : Attribute + { + #region Constructor + + /// + /// Initializes with the Friendly Name of the logger. + /// + /// The friendly name of the Logger + public FriendlyNameAttribute(string friendlyName) + { + if (string.IsNullOrWhiteSpace(friendlyName)) + { + throw new ArgumentException(CommonResources.CannotBeNullOrEmpty, "friendlyName"); + } + + FriendlyName = friendlyName; + } + + #endregion + + #region Properties + + /// + /// The friendly Name of the Test Logger. + /// + public string FriendlyName { get; private set; } + + #endregion + + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Friends.cs b/src/Microsoft.TestPlatform.ObjectModel/Friends.cs new file mode 100644 index 0000000000..9bb9221087 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Friends.cs @@ -0,0 +1,5 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Microsoft.VisualStudio.TestPlatform.Extensions.VSTestIntegration, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] +[assembly: InternalsVisibleTo("Microsoft.VisualStudio.TestPlatform.Extensions.MSAppContainerAdapter, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] +[assembly: InternalsVisibleTo("Microsoft.VisualStudio.TestPlatform.Extensions.MSPhoneAdapter, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] diff --git a/src/Microsoft.TestPlatform.ObjectModel/LazyPropertyValue.cs b/src/Microsoft.TestPlatform.ObjectModel/LazyPropertyValue.cs new file mode 100644 index 0000000000..d9f5c1bb24 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/LazyPropertyValue.cs @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel +{ + using System; + + /// + /// Represents a lazy value calculation for a TestObject + /// + internal interface ILazyPropertyValue + { + /// + /// Forces calculation of the value + /// + object Value { get; } + } + + /// + /// Represents a lazy value calculation for a TestObject + /// + /// The type of the value to be calculated + public sealed class LazyPropertyValue : ILazyPropertyValue + { + private T value; + private Func getValue; + private bool isValueCreated; + + public LazyPropertyValue(Func getValue) + { + this.isValueCreated = false; + this.value = default(T); + this.getValue = getValue; + } + + /// + /// Forces calculation of the value + /// + public T Value + { + get + { + if (!isValueCreated) + { + this.value = this.getValue(); + isValueCreated = true; + } + + return this.value; + } + } + + object ILazyPropertyValue.Value + { + get + { + return this.Value; + } + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Logger/ITestLogger.cs b/src/Microsoft.TestPlatform.ObjectModel/Logger/ITestLogger.cs new file mode 100644 index 0000000000..2d6794e84f --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Logger/ITestLogger.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Client +{ + using System; + + /// + /// Interface implemented to log messages and results from tests. A class that + /// implements this interface will be available for use if it exports its type via + /// MEF, and if its containing assembly is placed in the Extensions folder. + /// + public interface ITestLogger + { + /// + /// Initializes the Test Logger. + /// + /// Events that can be registered for. + /// Test Run Directory + void Initialize(TestLoggerEvents events, string testRunDirectory); + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Logger/ITestLoggerWithParams.cs b/src/Microsoft.TestPlatform.ObjectModel/Logger/ITestLoggerWithParams.cs new file mode 100644 index 0000000000..674e4fa340 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Logger/ITestLoggerWithParams.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Client +{ + using System.Collections.Generic; + + /// + /// This Interface extends ITestLogger and adds capability to pass + /// parameters to loggers such as TfsPublisher. + /// Currently it is marked for internal consumption (ex: TfsPublisher) + /// + public interface ITestLoggerWithParameters : ITestLogger + { + /// + /// Initializes the Test Logger with given parameters. + /// + /// Events that can be registered for. + /// Collection of parameters + void Initialize(TestLoggerEvents events, Dictionary parameters); + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.ObjectModel/Logger/TestLoggerEvents.cs b/src/Microsoft.TestPlatform.ObjectModel/Logger/TestLoggerEvents.cs new file mode 100644 index 0000000000..e9aae36caf --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Logger/TestLoggerEvents.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Client +{ + using System; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + + /// + /// Exposes events that Test Loggers can register for. + /// + public abstract class TestLoggerEvents + { + #region Constructor + + /// + /// Default constructor. + /// + protected TestLoggerEvents() + { + } + + #endregion + + #region Events + + /// + /// Raised when a test message is received. + /// + public abstract event EventHandler TestRunMessage; + + /// + /// Raised when a test result is received. + /// + public abstract event EventHandler TestResult; + + /// + /// Raised when a test run is complete. + /// + public abstract event EventHandler TestRunComplete; + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Logging/Events/DataCollectionMessageEventArgs.cs b/src/Microsoft.TestPlatform.ObjectModel/Logging/Events/DataCollectionMessageEventArgs.cs new file mode 100644 index 0000000000..dde15bdca9 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Logging/Events/DataCollectionMessageEventArgs.cs @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging +{ + using System; + using System.Runtime.Serialization; + + /// + /// EventArg used for raising data collector message event. + /// + [DataContract] + public class DataCollectionMessageEventArgs : TestRunMessageEventArgs + { + #region constructor + /// + /// Constructor + /// + /// Level at which message should be logged. + /// Text message. + public DataCollectionMessageEventArgs(TestMessageLevel level, string message) : base(level, message) + { + } + #endregion + + #region properties + + /// + /// Friendly name of collector + /// + [DataMember] + public string FriendlyName + { + get; + set; + } + + + /// + /// Uri of collector. + /// + [DataMember] + public Uri Uri + { + get; + set; + } + + /// + /// Test case id if the message is generated by test case collection. + /// + [DataMember] + public Guid TestCaseId + { + get; + set; + } + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Logging/Events/TestResultEventArgs.cs b/src/Microsoft.TestPlatform.ObjectModel/Logging/Events/TestResultEventArgs.cs new file mode 100644 index 0000000000..a0b8d97c62 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Logging/Events/TestResultEventArgs.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging +{ + using System; + + /// + /// Event arguments used for raising Test Result events. + /// + public class TestResultEventArgs : EventArgs + { + #region Constructor + + /// + /// Initializes with the test result for the event. + /// + /// Test Result for the event. + public TestResultEventArgs(TestResult result) + { + if (result == null) + { + throw new ArgumentNullException("result"); + } + + Result = result; + } + + #endregion + + #region Properties + + /// + /// Test Result. + /// + public TestResult Result { get; private set; } + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Logging/Events/TestRunMessageEventArgs.cs b/src/Microsoft.TestPlatform.ObjectModel/Logging/Events/TestRunMessageEventArgs.cs new file mode 100644 index 0000000000..ce7928c70d --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Logging/Events/TestRunMessageEventArgs.cs @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging +{ + using System; + using System.Runtime.Serialization; + + /// + /// Event arguments used for raising Test Run Message events. + /// + [DataContract] + public class TestRunMessageEventArgs : EventArgs + { + #region Constructor + + /// + /// Initializes with the level and the message for the event. + /// + /// Level of the message. + /// The message. + public TestRunMessageEventArgs(TestMessageLevel level, string message) + { + if (string.IsNullOrWhiteSpace(message)) + { + throw new ArgumentException(CommonResources.CannotBeNullOrEmpty, "message"); + } + + if (level < TestMessageLevel.Informational || level > TestMessageLevel.Error) + { + throw new ArgumentOutOfRangeException("level"); + } + + Level = level; + Message = message; + } + + #endregion + + #region Properties + + /// + /// The message. + /// + [DataMember] + public string Message { get; set; } + + /// + /// Level of the message. + /// + [DataMember] + public TestMessageLevel Level { get; set; } + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Logging/Events/TestRunStartedEventArgs.cs b/src/Microsoft.TestPlatform.ObjectModel/Logging/Events/TestRunStartedEventArgs.cs new file mode 100644 index 0000000000..ae2a2c0c2d --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Logging/Events/TestRunStartedEventArgs.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging +{ + using System; + + /// + /// Event arguments used for raising TestRunStarted events. + /// Mainly contains the process Id of the test execution process running the tests. + /// + public class TestRunStartedEventArgs : EventArgs + { + public int ProcessId { get; private set; } + + /// The process Id of the test execution process running the tests. + public TestRunStartedEventArgs(int processId) + { + ProcessId = processId; + } + + public override string ToString() + { + return "ProcessId = " + ProcessId; + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Logging/Interfaces/IMessageLogger.cs b/src/Microsoft.TestPlatform.ObjectModel/Logging/Interfaces/IMessageLogger.cs new file mode 100644 index 0000000000..7aac2e4169 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Logging/Interfaces/IMessageLogger.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging +{ + using System; + + /// + /// Used for logging error warning and informational messages. + /// + public interface IMessageLogger + { + /// + /// Sends a message to the enabled loggers. + /// + /// Level of the message. + /// The message to be sent. + void SendMessage(TestMessageLevel testMessageLevel, string message); + + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Logging/TestMessageLevel.cs b/src/Microsoft.TestPlatform.ObjectModel/Logging/TestMessageLevel.cs new file mode 100644 index 0000000000..2e8c23b290 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Logging/TestMessageLevel.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging +{ + using System; + + /// + /// Levels for test messages. + /// + public enum TestMessageLevel + { + /// + /// Informational message. + /// + Informational = 0, + + /// + /// Warning message. + /// + Warning = 1, + + /// + /// Error message. + /// + Error = 2 + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Microsoft.TestPlatform.ObjectModel.xproj b/src/Microsoft.TestPlatform.ObjectModel/Microsoft.TestPlatform.ObjectModel.xproj new file mode 100644 index 0000000000..2bc9a17166 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Microsoft.TestPlatform.ObjectModel.xproj @@ -0,0 +1,22 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 7df58ef4-20be-4a7f-af21-881639e7cd5e + Microsoft.VisualStudio.TestPlatform.ObjectModel + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\ + v4.5.2 + + + 2.0 + + + True + + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.ObjectModel/Navigation/DiaNavigationData.cs b/src/Microsoft.TestPlatform.ObjectModel/Navigation/DiaNavigationData.cs new file mode 100644 index 0000000000..36568f544d --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Navigation/DiaNavigationData.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel +{ +#if NET46 + + using System.Diagnostics.CodeAnalysis; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Navigation; + + /// + /// 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; + } + } + +#endif +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.ObjectModel/Navigation/DiaSession.cs b/src/Microsoft.TestPlatform.ObjectModel/Navigation/DiaSession.cs new file mode 100644 index 0000000000..81f99b8a43 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Navigation/DiaSession.cs @@ -0,0 +1,458 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel +{ +#if NET46 + + using System; + using System.Collections.Generic; + using System.Runtime.InteropServices; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + + using Dia; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Navigation; + + /// + /// 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 + { + private IDiaDataSource source; + private IDiaSession session; + private bool isDisposed; + + /// + /// 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) + /// + private Dictionary> methodSymbols = new Dictionary>(); + + public DiaSession(string binaryPath) : this(binaryPath, null) + { + } + + public DiaSession(string binaryPath, string searchPath) + { + ValidateArg.NotNullOrEmpty(binaryPath, "binaryPath"); + + try + { + this.source = new DiaSource(); + this.source.loadDataForExe(binaryPath, searchPath, null); + this.source.openSession(out this.session); + PopulateCacheForTypeAndMethodSymbols(); + } + catch (COMException) + { + Dispose(); + throw; + } + } + + public void Dispose() + { + this.Dispose(true); + + // Use SupressFinalize in case a subclass + // of this type implements a finalizer. + GC.SuppressFinalize(this); + } + + /// + /// 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(s_testNameStripChars); + + DiaNavigationData navigationData = null; + + IDiaSymbol methodSymbol = null; + + + IDiaSymbol typeSymbol = GetTypeSymbol(declaringTypeName, SymTagEnum.SymTagCompiland); + if (typeSymbol != null) + { + methodSymbol = GetMethodSymbol(typeSymbol, methodName); + } + else + { + // May be a managed C++ test assembly... + string fullMethodName = declaringTypeName.Replace(".", "::"); + fullMethodName = fullMethodName + "::" + methodName; + + methodSymbol = GetTypeSymbol(fullMethodName, SymTagEnum.SymTagFunction); + } + + if (methodSymbol != null) + { + navigationData = GetSymbolNavigationData(methodSymbol); + } + + return navigationData; + } + + private void Dispose(bool disposing) + { + if (!isDisposed) + { + if (disposing) + { + foreach (Dictionary methodSymbolsForType in methodSymbols.Values) + { + foreach (IDiaSymbol methodSymbol in methodSymbolsForType.Values) + { + IDiaSymbol symToRelease = methodSymbol; + ReleaseComObject(ref symToRelease); + } + methodSymbolsForType.Clear(); + } + methodSymbols.Clear(); + methodSymbols = null; + foreach (IDiaSymbol typeSymbol in typeSymbols.Values) + { + IDiaSymbol symToRelease = typeSymbol; + ReleaseComObject(ref symToRelease); + } + typeSymbols.Clear(); + typeSymbols = null; + ReleaseComObject(ref this.session); + ReleaseComObject(ref this.source); + } + + isDisposed = true; + } + } + + private static void ReleaseComObject(ref T obj) + where T : class + { + if (obj != null) + { + Marshal.FinalReleaseComObject(obj); + obj = null; + } + } + + /// + /// Characters that should be stripped off the end of test names. + /// + private static readonly char[] s_testNameStripChars = { '(', ')', ' ' }; + + private DiaNavigationData GetSymbolNavigationData(IDiaSymbol symbol) + { + ValidateArg.NotNull(symbol, "symbol"); + + DiaNavigationData navigationData = new DiaNavigationData(null, int.MaxValue, int.MinValue); + + IDiaEnumLineNumbers lines = null; + + try + { + this.session.findLinesByAddr(symbol.addressSection, symbol.addressOffset, (uint)symbol.length, out lines); + + uint celt; + IDiaLineNumber lineNumber; + + while (true) + { + lines.Next(1, out lineNumber, out celt); + + if (celt != 1) + { + break; + } + + IDiaSourceFile sourceFile = null; + try + { + sourceFile = lineNumber.sourceFile; + + //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 (lineNumber.lineNumber >= Magic || lineNumber.lineNumberEnd >= Magic) + { + continue; + } + + navigationData.FileName = sourceFile.fileName; + navigationData.MinLineNumber = Math.Min(navigationData.MinLineNumber, (int)lineNumber.lineNumber); + navigationData.MaxLineNumber = Math.Max(navigationData.MaxLineNumber, (int)lineNumber.lineNumberEnd); + } + finally + { + ReleaseComObject(ref sourceFile); + ReleaseComObject(ref lineNumber); + } + } + } + finally + { + ReleaseComObject(ref lines); + } + + return navigationData; + } + + + /// + /// Create a cache for type symbols and method symbols contained in the type symbol. + /// + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Failure to build cache is not fatal exception, ignore it and continue.")] + private void PopulateCacheForTypeAndMethodSymbols() + { + IDiaEnumSymbols enumTypeSymbols = null; + IDiaSymbol global = null; + try + { + global = this.session.globalScope; + global.findChildren(SymTagEnum.SymTagCompiland, null, 0, out enumTypeSymbols); + uint celtTypeSymbol; + IDiaSymbol typeSymbol = null; + // 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.Next(1, out typeSymbol, out celtTypeSymbol); + while (celtTypeSymbol == 1 && null != typeSymbol) + { + typeSymbols[typeSymbol.name] = typeSymbol; + + IDiaEnumSymbols enumMethodSymbols = null; + try + { + Dictionary methodSymbolsForType = new Dictionary(); + typeSymbol.findChildren(SymTagEnum.SymTagFunction, null, 0, out enumMethodSymbols); + + uint celtMethodSymbol; + IDiaSymbol methodSymbol = null; + + enumMethodSymbols.Next(1, out methodSymbol, out celtMethodSymbol); + while (celtMethodSymbol == 1 && null != methodSymbol) + { + UpdateMethodSymbolCache(methodSymbol.name, methodSymbol, methodSymbolsForType); + enumMethodSymbols.Next(1, out methodSymbol, out celtMethodSymbol); + } + methodSymbols[typeSymbol.name] = methodSymbolsForType; + } + catch (Exception ex) + { + if (EqtTrace.IsErrorEnabled) + { + EqtTrace.Error("Ignoring the exception while iterating method symbols:{0} for type:{1}", ex, typeSymbol.name); + } + } + finally + { + ReleaseComObject(ref enumMethodSymbols); + } + enumTypeSymbols.Next(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; + + uint celt; + + try + { + typeName = typeName.Replace('+', '.'); + if (typeSymbols.ContainsKey(typeName)) + { + return typeSymbols[typeName]; + } + global = this.session.globalScope; + global.findChildren(symTag, typeName, 0, out enumSymbols); + + enumSymbols.Next(1, out typeSymbol, out celt); + +#if DEBUG + if (typeSymbol == null) + { + IDiaEnumSymbols enumAllSymbols = null; + try + { + global.findChildren(symTag, null, 0, out enumAllSymbols); + List children = new List(); + + IDiaSymbol childSymbol = null; + uint fetchedCount = 0; + while (true) + { + enumAllSymbols.Next(1, out childSymbol, out fetchedCount); + if (fetchedCount == 0 || childSymbol == null) + { + break; + } + + children.Add(childSymbol.name); + ReleaseComObject(ref childSymbol); + } + Debug.Assert(children.Count > 0); + } + finally + { + ReleaseComObject(ref enumAllSymbols); + } + } +#endif + } + finally + { + ReleaseComObject(ref enumSymbols); + ReleaseComObject(ref global); + } + if (null != typeSymbol) + { + 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 + { + + if (methodSymbols.ContainsKey(typeSymbol.name)) + { + methodSymbolsForType = methodSymbols[typeSymbol.name]; + if (methodSymbolsForType.ContainsKey(methodName)) + { + return methodSymbolsForType[methodName]; + } + + } + else + { + methodSymbolsForType = new Dictionary(); + methodSymbols[typeSymbol.name] = methodSymbolsForType; + } + + typeSymbol.findChildren(SymTagEnum.SymTagFunction, methodName, 0, out enumSymbols); + + uint celtFetched; + enumSymbols.Next(1, out methodSymbol, out celtFetched); + +#if DEBUG + if (methodSymbol == null) + { + IDiaEnumSymbols enumAllSymbols = null; + try + { + typeSymbol.findChildren(SymTagEnum.SymTagFunction, null, 0, out enumAllSymbols); + List children = new List(); + + IDiaSymbol childSymbol = null; + uint fetchedCount = 0; + while (true) + { + enumAllSymbols.Next(1, out childSymbol, out fetchedCount); + if (fetchedCount == 0 || childSymbol == null) + { + break; + } + + children.Add(childSymbol.name); + ReleaseComObject(ref childSymbol); + } + + Debug.Assert(children.Count > 0); + } + finally + { + ReleaseComObject(ref enumAllSymbols); + } + } +#endif + } + finally + { + ReleaseComObject(ref enumSymbols); + } + if (null != methodSymbol) + { + 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. + // + IDiaSymbol oldSymbol; + if (methodSymbolCache.TryGetValue(methodName, out oldSymbol)) + { + ReleaseComObject(ref oldSymbol); + } + + methodSymbolCache[methodName] = methodSymbol; + } + } + +#endif +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Navigation/INavigationData.cs b/src/Microsoft.TestPlatform.ObjectModel/Navigation/INavigationData.cs new file mode 100644 index 0000000000..8242975556 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Navigation/INavigationData.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft. All rights reserved. + +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/src/Microsoft.TestPlatform.ObjectModel/Navigation/INavigationSession.cs b/src/Microsoft.TestPlatform.ObjectModel/Navigation/INavigationSession.cs new file mode 100644 index 0000000000..8d69327af2 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Navigation/INavigationSession.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft. All rights reserved. + +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/src/Microsoft.TestPlatform.ObjectModel/Properties/AssemblyInfo.cs b/src/Microsoft.TestPlatform.ObjectModel/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..8e6e964ef6 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Properties/AssemblyInfo.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using Microsoft.VisualStudio.TestPlatform.ObjectModel; + +// 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: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Microsoft.TestPlatfrom.ObjectModel")] +[assembly: AssemblyTrademark("")] + +// 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("8a200cda-4813-43a1-aa18-9faedc31d2af")] + + +// Type forwarding utility classes defined earlier in object model to a core utilities assembly. +[assembly: TypeForwardedTo(typeof(EqtTrace))] +[assembly: TypeForwardedTo(typeof(ValidateArg))] \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.ObjectModel/Resources.Designer.cs b/src/Microsoft.TestPlatform.ObjectModel/Resources.Designer.cs new file mode 100644 index 0000000000..71641eed47 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Resources.Designer.cs @@ -0,0 +1,623 @@ +//------------------------------------------------------------------------------ +// +// 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 Microsoft.VisualStudio.TestPlatform.ObjectModel { + using System; + using System.Reflection; + + + /// + /// 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.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + internal Resources() { + } + + /// + /// 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("Microsoft.TestPlatform.ObjectModel.Resources", typeof(Resources).GetTypeInfo().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 {0,-10} {1}. + /// + public static string BasicTestResultFormat { + get { + return ResourceManager.GetString("BasicTestResultFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The parameter cannot be null or empty.. + /// + public static string Common_CannotBeNullOrEmpty { + get { + return ResourceManager.GetString("Common_CannotBeNullOrEmpty", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to File {0} does not exist.. + /// + public static string Common_FileNotExist { + get { + return ResourceManager.GetString("Common_FileNotExist", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Object must be of type {0}.. + /// + public static string Common_ObjectMustBeOfType { + get { + return ResourceManager.GetString("Common_ObjectMustBeOfType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Aborted. + /// + public static string Common_TestOutcomeAborted { + get { + return ResourceManager.GetString("Common_TestOutcomeAborted", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Completed. + /// + public static string Common_TestOutcomeCompleted { + get { + return ResourceManager.GetString("Common_TestOutcomeCompleted", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Disconnected. + /// + public static string Common_TestOutcomeDisconnected { + get { + return ResourceManager.GetString("Common_TestOutcomeDisconnected", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Error. + /// + public static string Common_TestOutcomeError { + get { + return ResourceManager.GetString("Common_TestOutcomeError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Failed. + /// + public static string Common_TestOutcomeFailed { + get { + return ResourceManager.GetString("Common_TestOutcomeFailed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Inconclusive. + /// + public static string Common_TestOutcomeInconclusive { + get { + return ResourceManager.GetString("Common_TestOutcomeInconclusive", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to In Progress. + /// + public static string Common_TestOutcomeInProgress { + get { + return ResourceManager.GetString("Common_TestOutcomeInProgress", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Not Executed. + /// + public static string Common_TestOutcomeNotExecuted { + get { + return ResourceManager.GetString("Common_TestOutcomeNotExecuted", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Not Runnable. + /// + public static string Common_TestOutcomeNotRunnable { + get { + return ResourceManager.GetString("Common_TestOutcomeNotRunnable", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Passed. + /// + public static string Common_TestOutcomePassed { + get { + return ResourceManager.GetString("Common_TestOutcomePassed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Passed (run aborted). + /// + public static string Common_TestOutcomePassedButRunAborted { + get { + return ResourceManager.GetString("Common_TestOutcomePassedButRunAborted", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Pending. + /// + public static string Common_TestOutcomePending { + get { + return ResourceManager.GetString("Common_TestOutcomePending", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Timeout. + /// + public static string Common_TestOutcomeTimeout { + get { + return ResourceManager.GetString("Common_TestOutcomeTimeout", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Warning. + /// + public static string Common_TestOutcomeWarning { + get { + return ResourceManager.GetString("Common_TestOutcomeWarning", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot find TypeConverter for type {0}.. + /// + public static string ConverterNotSupported { + get { + return ResourceManager.GetString("ConverterNotSupported", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Could not find '{0}' node. + /// + public static string CouldNotFindXmlNode { + get { + return ResourceManager.GetString("CouldNotFindXmlNode", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unable to read from the provided stream. Data from a stream cannot be sent unless the stream supports reading.. + /// + public static string DataCollectionSink_CanNotReadStream { + get { + return ResourceManager.GetString("DataCollectionSink_CanNotReadStream", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The provided file name '{0}' contains the following invalid characters: '{1}'.. + /// + public static string DataCollectionSink_InvalidFileNameCharacters { + get { + return ResourceManager.GetString("DataCollectionSink_InvalidFileNameCharacters", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The provided file name '{0}' is reserved.. + /// + public static string DataCollectionSink_ReservedFilenameUsed { + get { + return ResourceManager.GetString("DataCollectionSink_ReservedFilenameUsed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot register property '{0}' as value type '{1}' because it was already registered as '{2}'.. + /// + public static string Exception_RegisteredTestPropertyHasDifferentValueType { + get { + return ResourceManager.GetString("Exception_RegisteredTestPropertyHasDifferentValueType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The root node of the run settings must be named 'RunSettings'.. + /// + public static string InvalidRunSettingsRootNode { + get { + return ResourceManager.GetString("InvalidRunSettingsRootNode", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid settings '{0}'. Invalid value '{1}' specified for '{2}'.. + /// + public static string InvalidSettingsIncorrectValue { + get { + return ResourceManager.GetString("InvalidSettingsIncorrectValue", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid settings '{0}'. Unexpected XmlAttribute: '{1}'.. + /// + public static string InvalidSettingsXmlAttribute { + get { + return ResourceManager.GetString("InvalidSettingsXmlAttribute", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid settings '{0}'. Unexpected XmlElement: '{1}'.. + /// + public static string InvalidSettingsXmlElement { + get { + return ResourceManager.GetString("InvalidSettingsXmlElement", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid data collector settings. Expected attribute '{0}' is missing. A typical data collector setting would look like <DataCollector uri="dataCollector://Samples/SampleCollector/1.0" assemblyQualifiedName="Samples.SampleCollector.SampleDataCollector, SampleCollectors, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1111111111111111" friendlyName="sampleCollector">.. + /// + public static string MissingDataCollectorAttributes { + get { + return ResourceManager.GetString("MissingDataCollectorAttributes", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot specify TestCaseFilter for specific tests run. FilterCriteria is only for run with sources.. + /// + public static string NoTestCaseFilterForSpecificTests { + get { + return ResourceManager.GetString("NoTestCaseFilterForSpecificTests", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Notification frequency need to be a positive value.. + /// + public static string NotificationFrequencyIsNotPositive { + get { + return ResourceManager.GetString("NotificationFrequencyIsNotPositive", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Notification timeout must be greater than zero.. + /// + public static string NotificationTimeoutIsZero { + get { + return ResourceManager.GetString("NotificationTimeoutIsZero", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to (null). + /// + public static string NullString { + get { + return ResourceManager.GetString("NullString", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Solution directory '{0}' does not exists. Please make sure solution directory specified in runsettings exists and have read permissions for directory.. + /// + public static string SolutionDirectoryNotExists { + get { + return ResourceManager.GetString("SolutionDirectoryNotExists", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Column Number. + /// + public static string TestCasePropertyColumnNumberLabel { + get { + return ResourceManager.GetString("TestCasePropertyColumnNumberLabel", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Executor Uri. + /// + public static string TestCasePropertyExecutorUriLabel { + get { + return ResourceManager.GetString("TestCasePropertyExecutorUriLabel", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to File Path. + /// + public static string TestCasePropertyFilePathLabel { + get { + return ResourceManager.GetString("TestCasePropertyFilePathLabel", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to FullyQualifiedName. + /// + public static string TestCasePropertyFullyQualifiedNameLabel { + get { + return ResourceManager.GetString("TestCasePropertyFullyQualifiedNameLabel", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Id. + /// + public static string TestCasePropertyIdLabel { + get { + return ResourceManager.GetString("TestCasePropertyIdLabel", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Line Number. + /// + public static string TestCasePropertyLineNumberLabel { + get { + return ResourceManager.GetString("TestCasePropertyLineNumberLabel", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Name. + /// + public static string TestCasePropertyNameLabel { + get { + return ResourceManager.GetString("TestCasePropertyNameLabel", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Source. + /// + public static string TestCasePropertySourceLabel { + get { + return ResourceManager.GetString("TestCasePropertySourceLabel", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Traits. + /// + public static string TestCasePropertyTraitsLabel { + get { + return ResourceManager.GetString("TestCasePropertyTraitsLabel", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Message: {0}. + /// + public static string TestFailureMessageFormat { + get { + return ResourceManager.GetString("TestFailureMessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to StackTrace: + ///{0}. + /// + public static string TestFailureStackTraceFormat { + get { + return ResourceManager.GetString("TestFailureStackTraceFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Failed. + /// + public static string TestOutcomeFailed { + get { + return ResourceManager.GetString("TestOutcomeFailed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to None. + /// + public static string TestOutcomeNone { + get { + return ResourceManager.GetString("TestOutcomeNone", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to NotFound. + /// + public static string TestOutcomeNotFound { + get { + return ResourceManager.GetString("TestOutcomeNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Passed. + /// + public static string TestOutcomePassed { + get { + return ResourceManager.GetString("TestOutcomePassed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Skipped. + /// + public static string TestOutcomeSkipped { + get { + return ResourceManager.GetString("TestOutcomeSkipped", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0}: + ///{1}. + /// + public static string TestResultMessageFormat { + get { + return ResourceManager.GetString("TestResultMessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Computer Name. + /// + public static string TestResultPropertyComputerNameLabel { + get { + return ResourceManager.GetString("TestResultPropertyComputerNameLabel", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to TestResult Display Name. + /// + public static string TestResultPropertyDisplayNameLabel { + get { + return ResourceManager.GetString("TestResultPropertyDisplayNameLabel", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Duration. + /// + public static string TestResultPropertyDurationLabel { + get { + return ResourceManager.GetString("TestResultPropertyDurationLabel", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to End Time. + /// + public static string TestResultPropertyEndTimeLabel { + get { + return ResourceManager.GetString("TestResultPropertyEndTimeLabel", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Error Column Number. + /// + public static string TestResultPropertyErrorColumnNumberLabel { + get { + return ResourceManager.GetString("TestResultPropertyErrorColumnNumberLabel", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Error Message. + /// + public static string TestResultPropertyErrorMessageLabel { + get { + return ResourceManager.GetString("TestResultPropertyErrorMessageLabel", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Error Stack Trace. + /// + public static string TestResultPropertyErrorStackTraceLabel { + get { + return ResourceManager.GetString("TestResultPropertyErrorStackTraceLabel", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Outcome. + /// + public static string TestResultPropertyOutcomeLabel { + get { + return ResourceManager.GetString("TestResultPropertyOutcomeLabel", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Start Time. + /// + public static string TestResultPropertyStartTimeLabel { + get { + return ResourceManager.GetString("TestResultPropertyStartTimeLabel", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Test Messages: + ///{0}. + /// + public static string TestResultTextMessagesFormat { + get { + return ResourceManager.GetString("TestResultTextMessagesFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The test property type '{0}' of property '{1}' is not supported. Use one of the supported property type (primitive types, uri, string, string[]) and try again. . + /// + public static string UnexpectedTypeOfProperty { + get { + return ResourceManager.GetString("UnexpectedTypeOfProperty", resourceCulture); + } + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Resources.resx b/src/Microsoft.TestPlatform.ObjectModel/Resources.resx new file mode 100644 index 0000000000..5e1c587e88 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Resources.resx @@ -0,0 +1,332 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + Failed + Test result for failed test + + + None + + + Passed + Test result for passed test + + + {0,-10} {1} + + + {0}: +{1} + + + Test Messages: +{0} + + + Message: {0} + + + StackTrace: +{0} + + + (null) + + + Cannot register property '{0}' as value type '{1}' because it was already registered as '{2}'. + + + Column Number + The label of the test property ColumnNumber for test case. + + + Executor Uri + The label of the test property ExecutorUri for test case. + + + Source + The label of the test property Source for test case. + + + Line Number + The label of the test property LineNumber for test case. + + + Name + The label of the test property Name for test case. + + + Computer Name + The label of the test property ComputerName for test result. + + + TestResult Display Name + The label of TestResult.DisplayName, mainly used for parameterized data tests + + + Duration + The label of the test property Duration for test result. + + + End Time + The label of the test property EndTime for test result. + + + Error Column Number + The label of the test property ErrorColumnNumber for test result. + + + Error Message + The label of the test property ErrorMessage for test result. + + + Error Stack Trace + The label of the test property ErrorStackTrace for test result. + + + Outcome + The label of the test property Outcome for test result. + + + Start Time + The label of the test property StartTime for test result. + + + Cannot find TypeConverter for type {0}. + + + File Path + The label of the test property FilePath for test case. + + + Notification frequency need to be a positive value. + frequency for DiscoveryCriteria/TestRunCriteria needs to be a positive value + + + Id + + + Invalid settings '{0}'. Unexpected XmlAttribute: '{1}'. + + + Invalid settings '{0}'. Unexpected XmlElement: '{1}'. + + + Invalid data collector settings. Expected attribute '{0}' is missing. A typical data collector setting would look like <DataCollector uri="dataCollector://Samples/SampleCollector/1.0" assemblyQualifiedName="Samples.SampleCollector.SampleDataCollector, SampleCollectors, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1111111111111111" friendlyName="sampleCollector">. + + + NotFound + Test result for not found test + + + Skipped + Test result for skipped test + + + Notification timeout must be greater than zero. + Timeout used during test discovery and execution must be greater than zero. + + + Invalid settings '{0}'. Invalid value '{1}' specified for '{2}'. + + + The root node of the run settings must be named 'RunSettings'. + + + Cannot specify TestCaseFilter for specific tests run. FilterCriteria is only for run with sources. + + + The test property type '{0}' of property '{1}' is not supported. Use one of the supported property type (primitive types, uri, string, string[]) and try again. + + + Could not find '{0}' node + Error message when the specified {0} xml node can not be found in the run settings xml document + + + FullyQualifiedName + + + Traits + The label of the test property Traits for test case. + + + Solution directory '{0}' does not exists. Please make sure solution directory specified in runsettings exists and have read permissions for directory. + + + The parameter cannot be null or empty. + + + File {0} does not exist. + + + Object must be of type {0}. + + + Aborted + + + Completed + + + Disconnected + + + Error + + + Failed + + + Inconclusive + + + In Progress + + + Not Executed + + + Not Runnable + + + Passed + + + Passed (run aborted) + + + Pending + + + Timeout + + + Warning + + + Unable to read from the provided stream. Data from a stream cannot be sent unless the stream supports reading. + + + The provided file name '{0}' contains the following invalid characters: '{1}'. + + + The provided file name '{0}' is reserved. + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.ObjectModel/RunSettings/RunConfiguration.cs b/src/Microsoft.TestPlatform.ObjectModel/RunSettings/RunConfiguration.cs new file mode 100644 index 0000000000..bb6975b86a --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/RunSettings/RunConfiguration.cs @@ -0,0 +1,488 @@ +// Copyright(c) Microsoft.All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel +{ + using System; + using System.Globalization; + using System.IO; + using System.Xml; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities; + + /// + /// Stores information about a test settings. + /// + public class RunConfiguration : TestRunSettings + { + #region private fields + + /// + /// Platform architecture which rocksteady should use for discovery/execution + /// + private Architecture platform; + + /// + /// Maximum number of cores that the engine can use to run tests in parallel + /// + private int maxCpuCount; + + /// + /// .Net framework which rocksteady should use for discovery/execution + /// + private FrameworkVersion framework; + + /// + /// Directory in which rocksteady/adapter should keep their run specific data. + /// + private string resultsDirectory; + + /// + /// Paths at which rocksteady should look for test adapters + /// + private string testAdaptersPaths; + + /// + /// Inidication to adapters to disable app domain. + /// + private bool disableAppDomain; + + #endregion + + #region Constructor + + /// + /// Initializes with the name of the test case. + /// + /// The name of the test case. + /// The Uri of the executor to use for running this test. + public RunConfiguration() + : base(Constants.RunConfigurationSettingsName) + { + // set defaults for target platform, framework version type and results directory. + this.platform = Constants.DefaultPlatform; + this.framework = Constants.DefaultFramework; + this.resultsDirectory = Constants.DefaultResultsDirectory; + this.SolutionDirectory = null; + this.TreatTestAdapterErrorsAsWarnings = Constants.DefaultTreatTestAdapterErrorsAsWarnings; + this.BinariesRoot = null; + this.testAdaptersPaths = null; + this.maxCpuCount = Constants.DefaultCpuCount; + this.disableAppDomain = false; + } + + #endregion + + #region Properties + + /// + /// Gets or sets the solution directory. + /// + public string SolutionDirectory + { + get; + set; + } + + /// + /// Gets or sets the results directory. + /// + public string ResultsDirectory + { + get + { + return this.resultsDirectory; + } + + set + { + this.resultsDirectory = value; + this.ResultsDirectorySet = true; + } + } + + /// + /// Gets or sets the Parallel execution option. Should be non-negative integer. + /// + public int MaxCpuCount + { + get + { + return this.maxCpuCount; + } + set + { + this.maxCpuCount = value; + this.MaxCpuCountSet = true; + } + } + + /// + /// Disable App domain creation. + /// + public bool DisableAppDomain + { + get + { + return this.disableAppDomain; + } + set + { + this.disableAppDomain = value; + this.DisableAppDomainSet = true; + } + } + + /// + /// Gets or sets the Target platform this run is targeting. Possible values are x86|x64|arm|anycpu + /// + public Architecture TargetPlatform + { + get + { + return this.platform; + } + set + { + this.platform = value; + this.TargetPlatformSet = true; + } + } + + /// + /// Gets or sets the target Framework this run is targeting. Possible values are Framework3.5|Framework4.0|Framework4.5 + /// + public FrameworkVersion TargetFrameworkVersion + { + get + { + return this.framework; + } + + set + { + this.framework = value; + this.TargetFrameworkSet = true; + } + } + + /// + /// Gets or sets the paths at which rocksteady should look for test adapters + /// + public string TestAdaptersPaths + { + get + { + return this.testAdaptersPaths; + } + + set + { + this.testAdaptersPaths = value; + + if (this.testAdaptersPaths != null) + { + this.TestAdaptersPathsSet = true; + } + } + } + + /// + /// Gets or sets a value indicating whether to treat the errors from test adapters as warnings. + /// + public bool TreatTestAdapterErrorsAsWarnings + { + get; + set; + } + + /// + /// Gets a value indicating whether target platform set. + /// + public bool TargetPlatformSet + { + get; + private set; + } + + /// + /// Gets a value indicating whether max cpu count set. + /// + public bool MaxCpuCountSet + { + get; + private set; + } + + /// + /// Gets a value indicating whether app domain needs to be disabled by the adapters. + /// + public bool DisableAppDomainSet + { + get; + private set; + } + + /// + /// Gets a value indicating whether target framework set. + /// + public bool TargetFrameworkSet + { + get; + private set; + } + + /// + /// Gets a value indicating whether test adapters paths set. + /// + public bool TestAdaptersPathsSet + { + get; + private set; + } + + /// + /// Gets a value indicating whether results directory is set. + /// + public bool ResultsDirectorySet + { + get; + private set; + } + + /// + /// Gets the binaries root. + /// + public string BinariesRoot { get; private set; } + + #endregion + + public override XmlElement ToXml() + { + XmlDocument doc = new XmlDocument(); + + XmlElement root = doc.CreateElement(Constants.RunConfigurationSettingsName); + + XmlElement resultDirectory = doc.CreateElement("ResultsDirectory"); + resultDirectory.InnerXml = this.ResultsDirectory; + root.AppendChild(resultDirectory); + + XmlElement targetPlatform = doc.CreateElement("TargetPlatform"); + targetPlatform.InnerXml = this.TargetPlatform.ToString(); + root.AppendChild(targetPlatform); + + XmlElement maxCpuCount = doc.CreateElement("MaxCpuCount"); + maxCpuCount.InnerXml = this.MaxCpuCount.ToString(); + root.AppendChild(maxCpuCount); + + XmlElement disableAppDomain = doc.CreateElement("DisableAppDomain"); + disableAppDomain.InnerXml = this.DisableAppDomain.ToString(); + root.AppendChild(disableAppDomain); + + XmlElement targetFrameworkVersion = doc.CreateElement("TargetFrameworkVersion"); + targetFrameworkVersion.InnerXml = this.TargetFrameworkVersion.ToString(); + root.AppendChild(targetFrameworkVersion); + + if (this.TestAdaptersPaths != null) + { + XmlElement testAdaptersPaths = doc.CreateElement("TestAdaptersPaths"); + testAdaptersPaths.InnerXml = this.TestAdaptersPaths; + root.AppendChild(testAdaptersPaths); + } + + XmlElement treatTestAdapterErrorsAsWarnings = doc.CreateElement("TreatTestAdapterErrorsAsWarnings"); + treatTestAdapterErrorsAsWarnings.InnerXml = this.TreatTestAdapterErrorsAsWarnings.ToString(); + root.AppendChild(treatTestAdapterErrorsAsWarnings); + + if (this.BinariesRoot != null) + { + XmlElement binariesRoot = doc.CreateElement("BinariesRoot"); + binariesRoot.InnerXml = this.BinariesRoot; + root.AppendChild(binariesRoot); + } + + return root; + } + + /// + /// Loads RunConfiguration from XmlReader. + /// + /// XmlReader having run configuration node. + /// + public static RunConfiguration FromXml(XmlReader reader) + { + ValidateArg.NotNull(reader, "reader"); + var runConfiguration = new RunConfiguration(); + var empty = reader.IsEmptyElement; + + XmlRunSettingsUtilities.ThrowOnHasAttributes(reader); + + // Process the fields in Xml elements + reader.Read(); + if (!empty) + { + while (reader.NodeType == XmlNodeType.Element) + { + string elementName = reader.Name; + switch (elementName) + { + case "ResultsDirectory": + XmlRunSettingsUtilities.ThrowOnHasAttributes(reader); + runConfiguration.ResultsDirectory = reader.ReadElementContentAsString(); + break; + + case "MaxCpuCount": + XmlRunSettingsUtilities.ThrowOnHasAttributes(reader); + + string cpuCount = reader.ReadElementContentAsString(); + int count; + if (!int.TryParse(cpuCount, out count) || count < 0) + { + throw new SettingsException( + string.Format( + CultureInfo.CurrentCulture, + Resources.InvalidSettingsIncorrectValue, + Constants.RunConfigurationSettingsName, + cpuCount, + elementName)); + } + + runConfiguration.MaxCpuCount = count; + break; + + case "DisableAppDomain": + XmlRunSettingsUtilities.ThrowOnHasAttributes(reader); + + string appContainerCheck = reader.ReadElementContentAsString(); + bool disableAppDomainCheck; + if (!bool.TryParse(appContainerCheck, out disableAppDomainCheck)) + { + throw new SettingsException(String.Format(CultureInfo.CurrentCulture, + Resources.InvalidSettingsIncorrectValue, Constants.RunConfigurationSettingsName, appContainerCheck, elementName)); + } + runConfiguration.DisableAppDomain = disableAppDomainCheck; + break; + + case "TargetPlatform": + XmlRunSettingsUtilities.ThrowOnHasAttributes(reader); + Architecture archType; + string value = reader.ReadElementContentAsString(); + try + { + archType = (Architecture)Enum.Parse(typeof(Architecture), value, true); + if (archType != Architecture.X64 && archType != Architecture.X86 && archType != Architecture.ARM) + { + throw new SettingsException( + string.Format( + CultureInfo.CurrentCulture, + Resources.InvalidSettingsIncorrectValue, + Constants.RunConfigurationSettingsName, + value, + elementName)); + } + } + catch (ArgumentException) + { + throw new SettingsException(string.Format(CultureInfo.CurrentCulture, + Resources.InvalidSettingsIncorrectValue, Constants.RunConfigurationSettingsName, value, elementName)); + } + + runConfiguration.TargetPlatform = archType; + break; + + case "TargetFrameworkVersion": + XmlRunSettingsUtilities.ThrowOnHasAttributes(reader); + FrameworkVersion frameworkType; + value = reader.ReadElementContentAsString(); + try + { + frameworkType = (FrameworkVersion)Enum.Parse(typeof(FrameworkVersion), value, true); + if (frameworkType != FrameworkVersion.Framework35 && frameworkType != FrameworkVersion.Framework40 && + frameworkType != FrameworkVersion.Framework45) + { + throw new SettingsException( + string.Format( + CultureInfo.CurrentCulture, + Resources.InvalidSettingsIncorrectValue, + Constants.RunConfigurationSettingsName, + value, + elementName)); + } + } + catch (ArgumentException) + { + throw new SettingsException(string.Format(CultureInfo.CurrentCulture, + Resources.InvalidSettingsIncorrectValue, Constants.RunConfigurationSettingsName, value, elementName)); + } + + runConfiguration.TargetFrameworkVersion = frameworkType; + break; + + case "TestAdaptersPaths": + XmlRunSettingsUtilities.ThrowOnHasAttributes(reader); + runConfiguration.TestAdaptersPaths = reader.ReadElementContentAsString(); + break; + + case "TreatTestAdapterErrorsAsWarnings": + XmlRunSettingsUtilities.ThrowOnHasAttributes(reader); + bool treatTestAdapterErrorsAsWarnings = false; + + value = reader.ReadElementContentAsString(); + + try + { + treatTestAdapterErrorsAsWarnings = bool.Parse(value); + } + catch (ArgumentException) + { + throw new SettingsException(string.Format(CultureInfo.CurrentCulture, + Resources.InvalidSettingsIncorrectValue, Constants.RunConfigurationSettingsName, value, elementName)); + } + catch (FormatException) + { + throw new SettingsException(string.Format(CultureInfo.CurrentCulture, + Resources.InvalidSettingsIncorrectValue, Constants.RunConfigurationSettingsName, value, elementName)); + } + + runConfiguration.TreatTestAdapterErrorsAsWarnings = treatTestAdapterErrorsAsWarnings; + break; + + case "SolutionDirectory": + XmlRunSettingsUtilities.ThrowOnHasAttributes(reader); + string solutionDirectory = reader.ReadElementContentAsString(); + solutionDirectory = Environment.ExpandEnvironmentVariables(solutionDirectory); + if (string.IsNullOrEmpty(solutionDirectory) || !Directory.Exists(solutionDirectory)) + { + if (EqtTrace.IsErrorEnabled) + { + EqtTrace.Error(string.Format(CultureInfo.CurrentCulture, Resources.SolutionDirectoryNotExists, solutionDirectory)); + } + + solutionDirectory = null; + } + + runConfiguration.SolutionDirectory = solutionDirectory; + + break; + + case "BinariesRoot": + XmlRunSettingsUtilities.ThrowOnHasAttributes(reader); + runConfiguration.BinariesRoot = reader.ReadElementContentAsString(); + break; + + default: + throw new SettingsException( + string.Format( + CultureInfo.CurrentCulture, + Resources.InvalidSettingsXmlElement, + Constants.RunConfigurationSettingsName, + reader.Name)); + } + } + + reader.ReadEndElement(); + } + + return runConfiguration; + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/RunSettings/SettingsException.cs b/src/Microsoft.TestPlatform.ObjectModel/RunSettings/SettingsException.cs new file mode 100644 index 0000000000..a5050a306b --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/RunSettings/SettingsException.cs @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel +{ + using System; + using System.Runtime.Serialization; + + /// + /// Exception thrown by Run Settings when an error with a settings provider + /// is encountered. + /// +#if NET46 + [Serializable] +#endif + public class SettingsException : Exception + { + #region Constructors + + /// + /// Default Constructor + /// + public SettingsException() : base() + { + } + + /// + /// Initializes with the message. + /// + /// Message for the exception. + public SettingsException(string message) + : base(message) + { + } + + /// + /// Initializes with message and inner exception. + /// + /// Message for the exception. + /// The inner exception. + public SettingsException(string message, Exception innerException) + : base(message, innerException) + { + } + +#if NET46 + /// + /// Seralization constructor. + /// + protected SettingsException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + +#endif + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/RunSettings/SettingsNameAttribute.cs b/src/Microsoft.TestPlatform.ObjectModel/RunSettings/SettingsNameAttribute.cs new file mode 100644 index 0000000000..75093b885d --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/RunSettings/SettingsNameAttribute.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel +{ + using System; + + /// + /// Attribute applied to ISettingsProviders to associate it with a settings + /// name. This name will be used to request the settings from the RunSettings. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public sealed class SettingsNameAttribute : Attribute + { + #region Constructor + + /// + /// Initializes with the name of the settings. + /// + /// Name of the settings + public SettingsNameAttribute(string settingsName) + { + if (string.IsNullOrWhiteSpace(settingsName)) + { + throw new ArgumentException(CommonResources.CannotBeNullOrEmpty, "settingsName"); + } + + SettingsName = settingsName; + } + + #endregion + + #region Properties + + /// + /// The name of the settings. + /// + public string SettingsName { get; private set; } + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/RunSettings/TestRunParameters.cs b/src/Microsoft.TestPlatform.ObjectModel/RunSettings/TestRunParameters.cs new file mode 100644 index 0000000000..b330c3d2a3 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/RunSettings/TestRunParameters.cs @@ -0,0 +1,75 @@ +// Copyright(c) Microsoft.All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Xml; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities; + + /// + /// This class deals with the run settings, Test Run Parameters node and the extracting of Key Value Pair's from the parameters listed. + /// + internal class TestRunParameters + { + /// + /// Get the test run parameters from the provided reader. + /// + /// The reader. + /// The dictionary of test run parameters. + /// Throws when format is incorrect. + internal static Dictionary FromXml(XmlReader reader) + { + var testParameters = new Dictionary(); + + if (!reader.IsEmptyElement) + { + XmlRunSettingsUtilities.ThrowOnHasAttributes(reader); + reader.Read(); + + while (reader.NodeType == XmlNodeType.Element) + { + string elementName = reader.Name; + switch (elementName) + { + case "Parameter": + string paramName = null; + string paramValue = null; + for (int attIndex = 0; attIndex < reader.AttributeCount; attIndex++) + { + reader.MoveToAttribute(attIndex); + if (string.Equals(reader.Name, "Name", StringComparison.OrdinalIgnoreCase)) + { + paramName = reader.Value; + } + else if (string.Equals(reader.Name, "Value", StringComparison.OrdinalIgnoreCase)) + { + paramValue = reader.Value; + } + } + + if (paramName != null && paramValue != null) + { + testParameters[paramName] = paramValue; + } + + break; + default: + throw new SettingsException( + string.Format( + CultureInfo.CurrentCulture, + Resources.InvalidSettingsXmlElement, + Constants.TestRunParametersName, + reader.Name)); + } + + reader.Read(); + } + } + + return testParameters; + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/RunSettings/TestRunSettings.cs b/src/Microsoft.TestPlatform.ObjectModel/RunSettings/TestRunSettings.cs new file mode 100644 index 0000000000..449ea8abf0 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/RunSettings/TestRunSettings.cs @@ -0,0 +1,49 @@ +// Copyright(c) Microsoft.All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel +{ + using System.Xml; + + /// + /// Stores information about a test settings. + /// + public abstract class TestRunSettings + { + private string name; + + #region Constructor + + /// + /// Initializes with the name of the test case. + /// + /// The name of the test case. + protected TestRunSettings(string name) + { + ValidateArg.NotNullOrEmpty(name, "name"); + + this.name = name; + } + + #endregion + + #region Properties + + /// + /// Gets the name of the test settings. + /// Do not put a private setter on this + /// Chutzpah adapter checks for setters of all properties and it throws error if its private + /// during RunSettings.LoadSection() call + /// TODO: Communicate to Chutzpah and fix it + /// + public string Name => name; + + #endregion + + /// + /// Converter the setting to be an XmlElement. + /// + /// The Xml element for the run settings provided. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes", Justification = "XmlElement is required in the data collector.")] + public abstract XmlElement ToXml(); + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/TestCase.cs b/src/Microsoft.TestPlatform.ObjectModel/TestCase.cs new file mode 100644 index 0000000000..e081187784 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/TestCase.cs @@ -0,0 +1,286 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Runtime.Serialization; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Utilities; + using Newtonsoft.Json; + + /// + /// Stores information about a test case. + /// + [DataContract] + [KnownType(typeof(string[]))] + [KnownType(typeof(KeyValuePair[]))] + [KnownType(typeof(TestOutcome))] + public sealed class TestCase : TestObject + { + +#if TODO + /// + /// LocalExtensionData which can be used by Adapter developers for local transfer of extended properties. + /// Note that this data is available only for in-Proc execution, and may not be available for OutProc executors + /// + private Object m_localExtensionData; + +#endif + private Guid m_defaultId = Guid.Empty; + + #region Constructor + + [JsonConstructor] + private TestCase() { } + + /// + /// Initializes with the name of the test case. + /// + /// Fully qualified name of the test case. + /// The Uri of the executor to use for running this test. + /// Test container source from which the test is discovered. + public TestCase(string fullyQualifiedName, Uri executorUri, string source) + { + ValidateArg.NotNullOrEmpty(fullyQualifiedName, "fullyQualifiedName"); + ValidateArg.NotNull(executorUri, "executorUri"); + ValidateArg.NotNullOrEmpty(source, "source"); + + this.FullyQualifiedName = fullyQualifiedName; + this.ExecutorUri = executorUri; + this.Source = source; + } + + #endregion + + #region Properties + +#if TODO + /// + /// LocalExtensionData which can be used by Adapter developers for local transfer of extended properties. + /// Note that this data is available only for in-Proc execution, and may not be available for OutProc executors + /// + public Object LocalExtensionData + { + get { return m_localExtensionData; } + set { m_localExtensionData = value; } + } +#endif + + /// + /// Id of the test case. + /// + public Guid Id + { + get + { + var id = GetPropertyValue(TestCaseProperties.Id, Guid.Empty); + if (id == Guid.Empty) + { + // user has not specified his own Id during ctor! We will cache Id if its empty + if (m_defaultId == Guid.Empty) + { + m_defaultId = this.GetTestId(); + } + + return m_defaultId; + } + + return id; + } + set + { + SetPropertyValue(TestCaseProperties.Id, value); + } + } + + /// + /// Name of the test case. + /// + [DataMember] + public string FullyQualifiedName + { + get { return GetPropertyValue(TestCaseProperties.FullyQualifiedName, string.Empty); } + set + { + SetPropertyValue(TestCaseProperties.FullyQualifiedName, value); + + // Id is based on Name/Source, will nulll out guid and it gets calc next time we access it. + m_defaultId = Guid.Empty; + } + } + + /// + /// Display name of the test case. + /// + [DataMember] + public string DisplayName + { + get { return GetPropertyValue(TestCaseProperties.DisplayName, FullyQualifiedName); } + set { SetPropertyValue(TestCaseProperties.DisplayName, value); } + } + + /// + /// The Uri of the Executor to use for running this test. + /// + [DataMember] + public Uri ExecutorUri + { + get { return GetPropertyValue(TestCaseProperties.ExecutorUri, null); } + set { SetPropertyValue(TestCaseProperties.ExecutorUri, value); } + } + + /// + /// Test container source from which the test is discovered + /// + [DataMember] + public string Source + { + get { return GetPropertyValue(TestCaseProperties.Source, null); } + private set + { + SetPropertyValue(TestCaseProperties.Source, value); + + // Id is based on Name/Source, will nulll out guid and it gets calc next time we access it. + m_defaultId = Guid.Empty; + } + } + + /// + /// File path of the test + /// + [DataMember] + public string CodeFilePath + { + get { return GetPropertyValue(TestCaseProperties.CodeFilePath, null); } + set { SetPropertyValue(TestCaseProperties.CodeFilePath, value); } + } + + /// + /// Line number of the test + /// + [DataMember] + public int LineNumber + { + get { return GetPropertyValue(TestCaseProperties.LineNumber, -1); } + set { SetPropertyValue(TestCaseProperties.LineNumber, value); } + } + + #endregion + + #region private methods + + /// + /// Creates a Id of TestCase + /// + /// Guid test id + private Guid GetTestId() + { + //To generate id hash "ExecutorUri + source + Name"; + + // HACK: if source is a file name then just use the filename for the identifier since the + // file might have moved between discovery and execution (in appx mode for example) + // This is a hack because the Source contents should be a black box to the framework. For example in the database adapter case this is not a file path. + string source = this.Source; + + if (File.Exists(source)) + { + source = Path.GetFileName(source); + } + + string testcaseFullName = ExecutorUri.ToString() + source + FullyQualifiedName; + return EqtHash.GuidFromString(testcaseFullName); + } + #endregion + + /// + /// Override to help during debugging + /// + public override string ToString() + { + return this.FullyQualifiedName; + } + } + + /// + /// Well-known TestCase properties + /// + public static class TestCaseProperties + { + #region Private Constants + + /// + /// These are the core Test properties and may be available in commandline/TeamBuild to filter tests. + /// These Property names should not be localized. + /// + private const string IdLabel = "Id"; + private const string FullyQualifiedNameLabel = "FullyQualifiedName"; + private const string NameLabel = "Name"; + private const string ExecutorUriLabel = "Executor Uri"; + private const string SourceLabel = "Source"; + private const string FilePathLabel = "File Path"; + private const string LineNumberLabel = "Line Number"; + + #endregion + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] + public static readonly TestProperty Id = TestProperty.Register("TestCase.Id", IdLabel, string.Empty, string.Empty, typeof(Guid), ValidateGuid, TestPropertyAttributes.Hidden, typeof(TestCase)); + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] + public static readonly TestProperty FullyQualifiedName = TestProperty.Register("TestCase.FullyQualifiedName", FullyQualifiedNameLabel, string.Empty, string.Empty, typeof(string), ValidateName, TestPropertyAttributes.Hidden, typeof(TestCase)); + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] + public static readonly TestProperty DisplayName = TestProperty.Register("TestCase.DisplayName", NameLabel, string.Empty, string.Empty, typeof(string), ValidateDisplay, TestPropertyAttributes.None, typeof(TestCase)); + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] + public static readonly TestProperty ExecutorUri = TestProperty.Register("TestCase.ExecutorUri", ExecutorUriLabel, string.Empty, string.Empty, typeof(Uri), ValidateExecutorUri, TestPropertyAttributes.Hidden, typeof(TestCase)); + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] + public static readonly TestProperty Source = TestProperty.Register("TestCase.Source", SourceLabel, typeof(string), typeof(TestCase)); + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] + public static readonly TestProperty CodeFilePath = TestProperty.Register("TestCase.CodeFilePath", FilePathLabel, typeof(string), typeof(TestCase)); + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] + public static readonly TestProperty LineNumber = TestProperty.Register("TestCase.LineNumber", LineNumberLabel, typeof(int), TestPropertyAttributes.Hidden, typeof(TestCase)); + + private static bool ValidateName(object value) + { + return !(string.IsNullOrWhiteSpace((string)value)); + } + + private static bool ValidateDisplay(object value) + { + // only check for null and pass the rest up to UI for validation + return value != null; + } + + private static bool ValidateExecutorUri(object value) + { + return value != null; + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", Justification = "Required to validate the input value.")] + private static bool ValidateGuid(object value) + { + try + { + new Guid(value.ToString()); + return true; + } + catch (ArgumentNullException) + { + return false; + } + catch (FormatException) + { + return false; + } + catch (OverflowException) + { + return false; + } + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/TestObject.cs b/src/Microsoft.TestPlatform.ObjectModel/TestObject.cs new file mode 100644 index 0000000000..a396116679 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/TestObject.cs @@ -0,0 +1,375 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel +{ + using System; + using System.Collections.Generic; + using System.ComponentModel; + using System.Globalization; + using System.Linq; + using System.Reflection; + using System.Runtime.Serialization; + + using Newtonsoft.Json; + + /// + /// Base class for test related classes. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1012:AbstractTypesShouldNotHaveConstructors")] + [DataContract] + public abstract class TestObject + { + #region Fields + + /// + /// The store for all the properties registered. + /// + /// + /// Not sending custom properties over because of serialization issues. + /// Custom TypeConverters for TestProperty does not work in Core currently. + /// This is not immediately needed for dotnet-test integration. Need to revisit. + /// + [DataMember] + [JsonIgnore] +#if FullCLR + private Dictionary store; +#else + public Dictionary store; +#endif + + /// + /// Property used for Json (de)serialization of store dictionary + /// Notes: Set the ObjectCreationHandling to Replace so that JSON does not use the "store" value as the value to set + /// Notes: JSON will use "Auto" setting which does not replace the value from the payload message if "get" returns values + /// Notes: "Replace" setting will always replace the property value from payload message + /// + [JsonProperty(ObjectCreationHandling = ObjectCreationHandling.Replace)] + public List> StorekvpList + { + get + { + return this.store.ToList(); + } + + set + { + if (this.store == null) + { + this.store = new Dictionary(); + } + + foreach (var kvp in value.Where(kvp => !this.store.Keys.Contains(kvp.Key))) + { + TestProperty.Register(kvp.Key.Id, kvp.Key.Label, kvp.Key.GetValueType(), typeof(TestObject)); + this.SetPropertyValue(kvp.Key, kvp.Value); + } + } + } + + #endregion Fields + + #region Constructors + +#if FullCLR + protected TestObject() +#else + public TestObject() +#endif + { + this.store = new Dictionary(); + } + + [OnSerializing] +#if FullCLR + private void CacheLazyValuesOnSerializing(StreamingContext context) +#else + public void CacheLazyValuesOnSerializing(StreamingContext context) +#endif + { + var lazyValues = this.store.Where(kvp => kvp.Value is ILazyPropertyValue).ToArray(); + + foreach (var kvp in lazyValues) + { + var lazyValue = (ILazyPropertyValue)kvp.Value; + var value = lazyValue.Value; + this.store.Remove(kvp.Key); + + if (value != null) + { + this.store.Add(kvp.Key, value); + } + } + } + + #endregion Constructors + + #region Properties + + /// + /// Returns the TestProperties currently specified in this TestObject. + /// + public IEnumerable Properties + { + get { return this.store.Keys; } + } + + /// + /// Returns property value of the specify TestProperty + /// + /// TestObject's TestProperty + /// property value + public object GetPropertyValue(TestProperty property) + { + ValidateArg.NotNull(property, "property"); + + object defaultValue = null; + var valueType = property.GetValueType(); + + if (valueType != null && valueType.GetTypeInfo().IsValueType) + { + defaultValue = Activator.CreateInstance(valueType); + } + + return PrivateGetPropertyValue(property, defaultValue); + } + + /// + /// Returns property value of the specify TestProperty + /// + /// Property value type + /// TestObject's TestProperty + /// default property value if property is not present + /// property value + public T GetPropertyValue(TestProperty property, T defaultValue) + { + return GetPropertyValue(property, defaultValue, CultureInfo.InvariantCulture); + } + + /// + /// Set TestProperty's value + /// + /// Property value type + /// TestObject's TestProperty + /// value to be set + public void SetPropertyValue(TestProperty property, T value) + { + SetPropertyValue(property, value, CultureInfo.InvariantCulture); + } + + /// + /// Set TestProperty's value + /// + /// Property value type + /// TestObject's TestProperty + /// value to be set + public void SetPropertyValue(TestProperty property, LazyPropertyValue value) + { + SetPropertyValue(property, value, CultureInfo.InvariantCulture); + } + + /// + /// Set TestProperty's value + /// + /// TestObject's TestProperty + /// value to be set + public void SetPropertyValue(TestProperty property, object value) + { + PrivateSetPropertyValue(property, value); + } + + /// + /// Remove test property from the current TestObject. + /// + /// + public void RemovePropertyValue(TestProperty property) + { + ValidateArg.NotNull(property, "property"); + + object value; + if (this.store.TryGetValue(property, out value)) + { + this.store.Remove(property); + } + } + + + /// + /// Returns TestProperty's value + /// + /// property's value. default value is returned if the property is not present + public T GetPropertyValue(TestProperty property, T defaultValue, CultureInfo culture) + { + ValidateArg.NotNull(property, "property"); + ValidateArg.NotNull(culture, "culture"); + + object objValue = PrivateGetPropertyValue(property, defaultValue); + + return ConvertPropertyTo(property, culture, objValue); + } + + /// + /// Set TestProperty's value to the specified value T. + /// + public void SetPropertyValue(TestProperty property, T value, CultureInfo culture) + { + ValidateArg.NotNull(property, "property"); + ValidateArg.NotNull(culture, "culture"); + + object objValue = ConvertPropertyFrom(property, culture, value); + + PrivateSetPropertyValue(property, objValue); + } + + /// + /// Set TestProperty's value to the specified value T. + /// + public void SetPropertyValue(TestProperty property, LazyPropertyValue value, CultureInfo culture) + { + ValidateArg.NotNull(property, "property"); + ValidateArg.NotNull(culture, "culture"); + + object objValue = ConvertPropertyFrom(property, culture, value); + + PrivateSetPropertyValue(property, objValue); + } + + #endregion Property Values + + #region Helpers + /// + /// Return TestProperty's value + /// + /// + private object PrivateGetPropertyValue(TestProperty property, object defaultValue) + { + ValidateArg.NotNull(property, "property"); + + object value; + if (!this.store.TryGetValue(property, out value)) + { + value = defaultValue; + } + + return value; + } + + /// + /// Set TestProperty's value + /// + private void PrivateSetPropertyValue(TestProperty property, object value) + { + ValidateArg.NotNull(property, "property"); + + if (property.ValidateValueCallback == null || property.ValidateValueCallback(value)) + { + this.store[property] = value; + } + else + { + throw new ArgumentException(property.Label); + } + } + + /// + /// Convert passed in value from TestProperty's specified value type. + /// + /// Converted object + private static object ConvertPropertyFrom(TestProperty property, CultureInfo culture, object value) + { + ValidateArg.NotNull(property, "property"); + ValidateArg.NotNull(culture, "culture"); + + var valueType = property.GetValueType(); + + if (valueType != null && valueType.GetTypeInfo().IsAssignableFrom(typeof(T).GetTypeInfo())) + { + return value; + } + + TypeConverter converter = TypeDescriptor.GetConverter(valueType); + if (converter == null) + { + throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, Resources.ConverterNotSupported, valueType.Name)); + } + + try + { + return converter.ConvertFrom(null, culture, value); + } + catch (FormatException) + { + throw; + } + catch (Exception e) + { + // some type converters throw strange exceptions (eg: System.Exception by Int32Converter) + throw new FormatException(e.Message, e); + } + } + + /// + /// Convert passed in value into the specified type when property is registered. + /// + /// Converted object + private static T ConvertPropertyTo(TestProperty property, CultureInfo culture, object value) + { + ValidateArg.NotNull(property, "property"); + ValidateArg.NotNull(culture, "culture"); + + var lazyValue = value as LazyPropertyValue; + + if (value == null) + { + return default(T); + } + else if (value is T) + { + return (T)value; + } + else if (lazyValue != null) + { + return lazyValue.Value; + } + + var valueType = property.GetValueType(); + + TypeConverter converter = TypeDescriptor.GetConverter(valueType); + + if (converter == null) + { + throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, Resources.ConverterNotSupported, valueType.Name)); + } + + try + { + return (T)converter.ConvertTo(null, culture, value, typeof(T)); + } + catch (FormatException) + { + throw; + } + catch (Exception e) + { + // some type converters throw strange exceptions (eg: System.Exception by Int32Converter) + throw new FormatException(e.Message, e); + } + } + + #endregion Helpers + + private TraitCollection traits; + + public TraitCollection Traits + { + get + { + if (this.traits == null) + { + this.traits = new TraitCollection(this); + } + + return this.traits; + } + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/TestOutcome.cs b/src/Microsoft.TestPlatform.ObjectModel/TestOutcome.cs new file mode 100644 index 0000000000..97fcd5a4ee --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/TestOutcome.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel +{ + using System; + + /// + /// Represents the outcomes of a test case. + /// + public enum TestOutcome + { + /// + /// Test Case Does Not Have an outcome. + /// + None = 0, + + /// + /// Test Case Passed + /// + Passed = 1, + + /// + /// Test Case Failed + /// + Failed = 2, + + /// + /// Test Case Skipped + /// + Skipped = 3, + + /// + /// Test Case Not found + /// + NotFound = 4, + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/TestOutcomeHelper.cs b/src/Microsoft.TestPlatform.ObjectModel/TestOutcomeHelper.cs new file mode 100644 index 0000000000..1ff16c808b --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/TestOutcomeHelper.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel +{ + using System; + + /// + /// Helper methods for working with the TestOutcome enum. + /// + public static class TestOutcomeHelper + { + /// + /// Converts the outcome into its localized string representation. + /// + /// The outcome to get the string for. + /// The localized outcome string. + public static string GetOutcomeString(TestOutcome outcome) + { + string result = null; + + switch (outcome) + { + case TestOutcome.None: + result = Resources.TestOutcomeNone; + break; + case TestOutcome.Passed: + result = Resources.TestOutcomePassed; + break; + case TestOutcome.Failed: + result = Resources.TestOutcomeFailed; + break; + case TestOutcome.Skipped: + result = Resources.TestOutcomeSkipped; + break; + case TestOutcome.NotFound: + result = Resources.TestOutcomeNotFound; + break; + default: + throw new ArgumentOutOfRangeException("outcome"); + } + + return result; + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/TestProcessStartInfo.cs b/src/Microsoft.TestPlatform.ObjectModel/TestProcessStartInfo.cs new file mode 100644 index 0000000000..32f8b57b8a --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/TestProcessStartInfo.cs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel +{ + using System.Collections.Generic; + + /// + /// The start info of the test runner + /// + public class TestProcessStartInfo + { + /// + /// The name of the test runner exe + /// + public string FileName { get; set; } + + /// + /// The arguments for the test runner + /// + public string Arguments { get; set; } + + /// + /// The working directory for the test runner + /// + public string WorkingDirectory { get; set; } + + /// + /// The associated environment variables + /// + public IDictionary EnvironmentVariables { get; set; } + + /// + /// Any additional custom properties that might be required for the launch + /// For example - emulator ID, remote machine details etc. + /// + public IDictionary CustomProperties { get; set; } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/TestProperty/TestProperty.cs b/src/Microsoft.TestPlatform.ObjectModel/TestProperty/TestProperty.cs new file mode 100644 index 0000000000..879dcfc6e6 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/TestProperty/TestProperty.cs @@ -0,0 +1,430 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Runtime.Serialization; + using System.Reflection; + using Newtonsoft.Json; +#if NET46 +using System.Security.Permissions; +#endif + + public delegate bool ValidateValueCallback(object value); + + [DataContract] + public class TestProperty : IEquatable +#if NET46 + ,IObjectReference +#endif + { + #region Fields + + [DataMember] +#if FullCLR + private string _id; +#else + public string _id; +#endif + + [DataMember] +#if FullCLR + private string _label; +#else + public string _label; +#endif + + [DataMember] +#if FullCLR + private string _description; +#else + public string _description; +#endif + + [DataMember] +#if FullCLR + private string _category; +#else + public string _category; +#endif + + private Type valueType; + + [DataMember] +#if FullCLR + private string _strValueType; +#else + public string _strValueType; +#endif + + [DataMember] +#if FullCLR + private TestPropertyAttributes _attributes; +#else + public TestPropertyAttributes _attributes; +#endif + + private ValidateValueCallback validateValueCallback; + + #endregion Fields + + #region Constructors + + // Constructor used only while json deserialization + [JsonConstructor] + private TestProperty() { } + + private TestProperty(string id, string label, string category, string description, Type valueType, ValidateValueCallback validateValueCallback, TestPropertyAttributes attributes) + { + ValidateArg.NotNullOrEmpty(id, "id"); + ValidateArg.NotNull(label, "label"); + ValidateArg.NotNull(category, "category"); + ValidateArg.NotNull(description, "description"); + ValidateArg.NotNull(valueType, "valueType"); + + // If the type of property is unexpected, then fail as otherwise we will not be to serialize it over the wcf channel and serialize it in db. Fixed bug #754475 + if (!(valueType.GetTypeInfo().IsValueType + || + valueType.Equals(typeof(String)) + || + valueType.Equals(typeof(Uri)) + || + valueType.Equals(typeof(string[])) + || + valueType.Equals(typeof(KeyValuePair[]))) + ) + { + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.UnexpectedTypeOfProperty, valueType, id)); + } + + this._id = id; + this._label = label; + this._category = category; + this._description = description; + this._strValueType = valueType.AssemblyQualifiedName; + this.validateValueCallback = validateValueCallback; + this._attributes = attributes; + this.valueType = valueType; + } + + #endregion Constructors + + #region Properties + + public string Id + { + get { return this._id; } + set { this._id = value; } + } + + public string Label + { + get { return this._label; } + set { this._label = value; } + } + + public string Category + { + get { return this._category; } + set { this._category = value; } + } + + public string Description + { + get { return this._description; } + set { this._description = value; } + } + + /// This property is not required at the client side. + public ValidateValueCallback ValidateValueCallback + { + get { return this.validateValueCallback; } + } + + public TestPropertyAttributes Attributes + { + get { return this._attributes; } + set { this._attributes = value; } + } + + #endregion Properties + + #region IEquatable + + public override int GetHashCode() + { + return this._id.GetHashCode(); + } + + public override bool Equals(object obj) + { + return Equals(obj as TestProperty); + } + + public bool Equals(TestProperty other) + { + return (other != null) && (this._id == other._id); + } + + #endregion IEquatable + + #region Methods + + public override string ToString() + { + return this._id; + } + + /// + /// Gets the valueType. + /// + /// Only works for the valueType that is in the currently executing assembly or in Mscorlib.dll. The default valueType is of string valueType. + /// The valueType of the test property + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "This could take a bit time, is not simple enough to be a property.")] + public Type GetValueType() + { + if (valueType == null) + valueType = GetType(_strValueType); + return valueType; + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Use this in the body in debug mode")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We use the default type whenever exception thrown")] + private Type GetType(string typeName) + { + ValidateArg.NotNull(typeName, "typeName"); + + Type type = null; + + try + { + // This only works for the type is in the currently executing assembly or in Mscorlib.dll. + type = Type.GetType(typeName); + + if (type == null) + { + type = Type.GetType(typeName.Replace("Version=4.0.0.0", "Version=2.0.0.0")); // Try 2.0 version as discovery returns version of 4.0 for all cases + } + + // For UAP the type namespace for System.Uri,System.TimeSpan and System.DateTimeOffset differs from the desktop version. + // Hacking to set the correct type only for these two types. + // [Todo : aajohn] Clean this up. + if (type == null && typeName.StartsWith("System.Uri")) + { + type = typeof(System.Uri); + } + else if (type == null && typeName.StartsWith("System.TimeSpan")) + { + type = typeof(System.TimeSpan); + } + else if (type == null && typeName.StartsWith("System.DateTimeOffset")) + { + type = typeof(System.DateTimeOffset); + } + // For LineNumber property - Int is required + else if (type == null && typeName.StartsWith("System.Int16")) + { + type = typeof(System.Int16); + } + else if (type == null && typeName.StartsWith("System.Int32")) + { + type = typeof(System.Int32); + } + else if (type == null && typeName.StartsWith("System.Int64")) + { + type = typeof(System.Int64); + } + } + catch (Exception) + { +#if FullCLR + // Try to see if the typeName contains Windows Phone PKT in that case load it from + // desktop side + if (typeName.Contains(s_windowsPhonePKT)) + { + type = this.GetType(typeName.Replace(s_windowsPhonePKT, s_visualStudioPKT)); + } + + if (type == null) + { + System.Diagnostics.Debug.Fail("The test property type " + typeName + " of property " + this._id + "is not supported."); +#else + System.Diagnostics.Debug.WriteLine("The test property type " + typeName + " of property " + this._id + "is not supported."); +#endif +#if FullCLR + } +#endif + } + finally + { + // default is of string type. + if (type == null) + { + type = typeof(string); + } + } + + return type; + } + + #endregion Methods + + #region Static Fields + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA908:AvoidTypesThatRequireJitCompilationInPrecompiledAssemblies")] + private static Dictionary>> s_properties = new Dictionary>>(); + +#if FullCLR + private static string s_visualStudioPKT = "b03f5f7f11d50a3a"; + private static string s_windowsPhonePKT = "7cec85d7bea7798e"; +#endif + + #endregion Static Fields + + #region Static Methods + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA908:AvoidTypesThatRequireJitCompilationInPrecompiledAssemblies")] + public static void ClearRegisteredProperties() + { + lock (s_properties) + { + s_properties.Clear(); + } + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA908:AvoidTypesThatRequireJitCompilationInPrecompiledAssemblies")] + public static TestProperty Find(string id) + { + ValidateArg.NotNull(id, "id"); + + TestProperty result = null; + + KeyValuePair> propertyTypePair; + lock (s_properties) + { + if (s_properties.TryGetValue(id, out propertyTypePair)) + { + result = propertyTypePair.Key; + } + } + + return result; + } + + public static TestProperty Register(string id, string label, Type valueType, Type owner) + { + ValidateArg.NotNullOrEmpty(id, "id"); + ValidateArg.NotNull(label, "label"); + ValidateArg.NotNull(valueType, "valueType"); + ValidateArg.NotNull(owner, "owner"); + + return Register(id, label, string.Empty, string.Empty, valueType, null, TestPropertyAttributes.None, owner); + } + + public static TestProperty Register(string id, string label, Type valueType, TestPropertyAttributes attributes, Type owner) + { + ValidateArg.NotNullOrEmpty(id, "id"); + ValidateArg.NotNull(label, "label"); + ValidateArg.NotNull(valueType, "valueType"); + ValidateArg.NotNull(owner, "owner"); + + return Register(id, label, string.Empty, string.Empty, valueType, null, attributes, owner); + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA908:AvoidTypesThatRequireJitCompilationInPrecompiledAssemblies")] + public static TestProperty Register(string id, string label, string category, string description, Type valueType, ValidateValueCallback validateValueCallback, TestPropertyAttributes attributes, Type owner) + { + ValidateArg.NotNullOrEmpty(id, "id"); + ValidateArg.NotNull(label, "label"); + ValidateArg.NotNull(category, "category"); + ValidateArg.NotNull(description, "description"); + ValidateArg.NotNull(valueType, "valueType"); + ValidateArg.NotNull(owner, "owner"); + + TestProperty result; + + KeyValuePair> propertyTypePair; + + lock (s_properties) + { + if (s_properties.TryGetValue(id, out propertyTypePair)) + { + // verify the data valueType is valid + if (propertyTypePair.Key._strValueType == valueType.AssemblyQualifiedName) + { + // add the owner to set of owners for this GraphProperty object + propertyTypePair.Value.Add(owner); + result = propertyTypePair.Key; + } + else + { + // not the data valueType we expect, throw an exception + string message = string.Format( + CultureInfo.CurrentCulture, + Resources.Exception_RegisteredTestPropertyHasDifferentValueType, + id, + valueType.ToString(), + propertyTypePair.Key._strValueType); + + throw new InvalidOperationException(message); + } + } + else + { + // create a new TestProperty object + result = new TestProperty(id, label, category, description, valueType, validateValueCallback, attributes); + + // setup the data pair used to track owners of this GraphProperty + propertyTypePair = new KeyValuePair>(result, new HashSet()); + propertyTypePair.Value.Add(owner); + + // add to the dictionary + s_properties[id] = propertyTypePair; + } + } + + return result; + } + + public static bool TryUnregister(string id, out KeyValuePair> propertyTypePair) + { + ValidateArg.NotNullOrEmpty(id, "id"); + + lock (s_properties) + { + if (s_properties.TryGetValue(id, out propertyTypePair)) + { + return s_properties.Remove(id); + } + } + return false; + } + + #endregion Static Methods + +#if FullCLR + [SecurityPermission( + SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)] +#endif + public object GetRealObject(StreamingContext context) + { + var registeredProperty = TestProperty.Find(this._id); + if (registeredProperty == null) + { + registeredProperty = TestProperty.Register( + this._id, + this._label, + this._category, + this._description, + this.GetValueType(), + this.validateValueCallback, + this._attributes, + typeof(TestObject)); + } + + return registeredProperty; + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/TestProperty/TestPropertyAttributes.cs b/src/Microsoft.TestPlatform.ObjectModel/TestProperty/TestPropertyAttributes.cs new file mode 100644 index 0000000000..8b529bc0b0 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/TestProperty/TestPropertyAttributes.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel +{ + using System; + + [Flags] + public enum TestPropertyAttributes + { + None = 0x00, // Default + Hidden = 0x01, + Immutable = 0x02, + [Obsolete("Use TestObject.Traits collection to create traits")] + Trait = 0x04, + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/TestResult.cs b/src/Microsoft.TestPlatform.ObjectModel/TestResult.cs new file mode 100644 index 0000000000..dad7ecf44b --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/TestResult.cs @@ -0,0 +1,337 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel +{ + using System; + using System.Collections.ObjectModel; + using System.Globalization; + using System.Runtime.Serialization; + using System.Text; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities; + + /// + /// Represents the result of a test case. + /// + [DataContract] + [KnownType(typeof(TestOutcome))] + [KnownType(typeof(DateTimeOffset))] + public sealed class TestResult : TestObject + { + #region Constructor + + /// + /// Initializes with the test case the result is for. + /// + /// The test case the result is for. + public TestResult(TestCase testCase) + { + if (testCase == null) + { + throw new ArgumentNullException("testCase"); + } + + TestCase = testCase; + Messages = new Collection(); + Attachments = new Collection(); + } + + #endregion + + #region Properties + + /// + /// Test case that this result is for. + /// + [DataMember] + public TestCase TestCase { get; private set; } + + /// + /// List of attachmentment sets for this TestResult. + /// + [DataMember] + public Collection Attachments { get; private set; } + + /// + /// The outcome of the test case. + /// + [DataMember] + public TestOutcome Outcome + { + get { return GetPropertyValue(TestResultProperties.Outcome, TestOutcome.None); } + set { SetPropertyValue(TestResultProperties.Outcome, value); } + } + + /// + /// The exception message. + /// + [DataMember] + public string ErrorMessage + { + get { return GetPropertyValue(TestResultProperties.ErrorMessage, null); } + set { SetPropertyValue(TestResultProperties.ErrorMessage, value); } + } + + /// + /// The exception stack trace. + /// + [DataMember] + public string ErrorStackTrace + { + get { return GetPropertyValue(TestResultProperties.ErrorStackTrace, null); } + set { SetPropertyValue(TestResultProperties.ErrorStackTrace, value); } + } + + /// + /// TestResult Dispaly name. Used for Data Driven Test (i.e. Data Driven Test. E.g. InlineData in xUnit) + /// + [DataMember] + public string DisplayName + { + get { return GetPropertyValue(TestResultProperties.DisplayName, null); } + set { SetPropertyValue(TestResultProperties.DisplayName, value); } + } + + /// + /// The test messages. + /// + [DataMember] + public Collection Messages { get; private set; } + + /// + /// Get/Set test result ComputerName. + /// + public string ComputerName + { + get { return GetPropertyValue(TestResultProperties.ComputerName, string.Empty); } + set { SetPropertyValue(TestResultProperties.ComputerName, value); } + } + + /// + /// Get/Set test result Duration. + /// + [DataMember] + public TimeSpan Duration + { + get { return GetPropertyValue(TestResultProperties.Duration, TimeSpan.Zero); } + set { SetPropertyValue(TestResultProperties.Duration, value); } + } + + /// + /// Get/Set test result StartTime. + /// + [DataMember] + public DateTimeOffset StartTime + { + get { return GetPropertyValue(TestResultProperties.StartTime, DateTimeOffset.Now); } + set { SetPropertyValue(TestResultProperties.StartTime, value); } + } + + /// + /// Get/Set test result EndTime. + /// + [DataMember] + public DateTimeOffset EndTime + { + get { return GetPropertyValue(TestResultProperties.EndTime, DateTimeOffset.Now); } + set { SetPropertyValue(TestResultProperties.EndTime, value); } + } + + #endregion + + #region Methods + + /// + /// The name of the test and the outcome. + /// + /// + public override string ToString() + { + StringBuilder result = new StringBuilder(); + + // Add the outcome of the test and the name of the test. + + result.AppendFormat( + CultureInfo.CurrentUICulture, + Resources.BasicTestResultFormat, + TestCase.DisplayName, + TestOutcomeHelper.GetOutcomeString(Outcome)); + + // Add the error message and stack trace if this is a test failure. + if (Outcome == TestOutcome.Failed) + { + // Add Error message. + result.AppendLine(); + result.AppendFormat( + CultureInfo.CurrentUICulture, + Resources.TestFailureMessageFormat, + ErrorMessage); + + // Add stack trace if we have one. + if (!string.IsNullOrWhiteSpace(ErrorStackTrace)) + { + result.AppendLine(); + result.AppendFormat( + CultureInfo.CurrentUICulture, + Resources.TestFailureStackTraceFormat, + ErrorStackTrace); + } + } + + // Add any text messages we have. + if (Messages.Count > 0) + { + StringBuilder testMessages = new StringBuilder(); + foreach (TestResultMessage message in this.Messages) + { + if (message != null + && !string.IsNullOrEmpty(message.Category) + && !string.IsNullOrEmpty(message.Text)) + { + testMessages.AppendFormat(CultureInfo.CurrentUICulture, + Resources.TestResultMessageFormat, + message.Category, + message.Text); + } + } + + result.AppendLine(); + result.AppendFormat( + CultureInfo.CurrentUICulture, + Resources.TestResultTextMessagesFormat, + testMessages.ToString()); + } + + return result.ToString(); + } + + #endregion + } + + /// + /// Represents the test result message. + /// + [DataContract] + public class TestResultMessage + { + // Bugfix: 297759 Moving the category from the resources to the code + // so that it works on machines which has eng OS & non-eng VS and vice versa. + // + + /// + /// Standard Output Message Category + /// + public static readonly string StandardOutCategory = "StdOutMsgs"; + + /// + /// Standard Error Message Category + /// + public static readonly string StandardErrorCategory = "StdErrMsgs"; + + /// + /// Debug Trace Message Category + /// + public static readonly string DebugTraceCategory = "DbgTrcMsgs"; + + /// + /// Additional Information Message Category + /// + public static readonly string AdditionalInfoCategory = "AdtnlInfo"; + + /// + /// Contructor + /// + /// Category of the message + /// Text of the message + public TestResultMessage(string category, string text) + { + this.Category = category; + this.Text = text; + } + + /// + /// Gets the message category + /// + [DataMember] + public string Category + { + get; + private set; + } + + /// + /// Gets the message text + /// + [DataMember] + public string Text + { + get; + private set; + } + } + + + /// + /// Well-known TestResult properties + /// + public static class TestResultProperties + { +#if !FullCLR + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] + public static readonly TestProperty DisplayName = TestProperty.Register("TestResult.DisplayName", "TestResult Display Name", typeof(string), TestPropertyAttributes.Hidden, typeof(TestResult)); + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] + public static readonly TestProperty ComputerName = TestProperty.Register("TestResult.ComputerName", "Computer Name", string.Empty, string.Empty, typeof(string), ValidateComputerName, TestPropertyAttributes.None, typeof(TestResult)); + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] + public static readonly TestProperty Outcome = TestProperty.Register("TestResult.Outcome", "Outcome", string.Empty, string.Empty, typeof(TestOutcome), ValidateOutcome, TestPropertyAttributes.None, typeof(TestResult)); + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] + public static readonly TestProperty Duration = TestProperty.Register("TestResult.Duration", "Duration", typeof(TimeSpan), typeof(TestResult)); + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] + public static readonly TestProperty StartTime = TestProperty.Register("TestResult.StartTime", "Start Time", typeof(DateTimeOffset), typeof(TestResult)); + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] + public static readonly TestProperty EndTime = TestProperty.Register("TestResult.EndTime", "End Time", typeof(DateTimeOffset), typeof(TestResult)); + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] + public static readonly TestProperty ErrorMessage = TestProperty.Register("TestResult.ErrorMessage", "Error Message", typeof(string), typeof(TestResult)); + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] + public static readonly TestProperty ErrorStackTrace = TestProperty.Register("TestResult.ErrorStackTrace", "Error Stack Trace", typeof(string), typeof(TestResult)); +#else + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] + public static readonly TestProperty DisplayName = TestProperty.Register("TestResult.DisplayName", Resources.TestResultPropertyDisplayNameLabel, typeof(string), TestPropertyAttributes.Hidden, typeof(TestResult)); + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] + public static readonly TestProperty ComputerName = TestProperty.Register("TestResult.ComputerName", Resources.TestResultPropertyComputerNameLabel, string.Empty, string.Empty, typeof(string), ValidateComputerName, TestPropertyAttributes.None, typeof(TestResult)); + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] + public static readonly TestProperty Outcome = TestProperty.Register("TestResult.Outcome", Resources.TestResultPropertyOutcomeLabel, string.Empty, string.Empty, typeof(TestOutcome), ValidateOutcome, TestPropertyAttributes.None, typeof(TestResult)); + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] + public static readonly TestProperty Duration = TestProperty.Register("TestResult.Duration", Resources.TestResultPropertyDurationLabel, typeof(TimeSpan), typeof(TestResult)); + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] + public static readonly TestProperty StartTime = TestProperty.Register("TestResult.StartTime", Resources.TestResultPropertyStartTimeLabel, typeof(DateTimeOffset), typeof(TestResult)); + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] + public static readonly TestProperty EndTime = TestProperty.Register("TestResult.EndTime", Resources.TestResultPropertyEndTimeLabel, typeof(DateTimeOffset), typeof(TestResult)); + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] + public static readonly TestProperty ErrorMessage = TestProperty.Register("TestResult.ErrorMessage", Resources.TestResultPropertyErrorMessageLabel, typeof(string), typeof(TestResult)); + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] + public static readonly TestProperty ErrorStackTrace = TestProperty.Register("TestResult.ErrorStackTrace", Resources.TestResultPropertyErrorStackTraceLabel, typeof(string), typeof(TestResult)); +#endif + private static bool ValidateComputerName(object value) + { + return !(string.IsNullOrWhiteSpace((string)value)); + } + + private static bool ValidateOutcome(object value) + { + return (TestOutcome)value <= TestOutcome.NotFound && (TestOutcome)value >= TestOutcome.None; + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Trait.cs b/src/Microsoft.TestPlatform.ObjectModel/Trait.cs new file mode 100644 index 0000000000..ef9a074d2d --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Trait.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel +{ + using System; + using System.Collections.Generic; + + /// + /// Class that holds Trait. + /// A traits is Name, Value pair. + /// +#if NET46 + [Serializable] +#endif + public class Trait + { + public string Name { get; private set; } + public string Value { get; private set; } + + internal Trait(KeyValuePair data) + : this(data.Key, data.Value) + { + } + + public Trait(string name, string value) + { + ValidateArg.NotNull(name, "name"); + + this.Name = name; + this.Value = value; + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/TraitCollection.cs b/src/Microsoft.TestPlatform.ObjectModel/TraitCollection.cs new file mode 100644 index 0000000000..e42f271c04 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/TraitCollection.cs @@ -0,0 +1,105 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.ComponentModel; + using System.Globalization; + using System.Linq; + using System.Runtime.Serialization; + + /// + /// Class that holds collection of traits + /// +#if NET46 + [Serializable] +#endif + public class TraitCollection: IEnumerable + { + internal const string TraitPropertyId = "TestObject.Traits"; + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] + private static readonly TestProperty TraitsProperty = TestProperty.Register( + TraitPropertyId, +#if !NET46 + // TODO: Fix this with proper resourcing for UWP and Win 8.1 Apps + // Trying to access resources will throw "MissingManifestResourceException" percolated as "TypeInitialization" exception + "Traits", +#else + Resources.TestCasePropertyTraitsLabel, +#endif + typeof(KeyValuePair[]), +#pragma warning disable 618 + TestPropertyAttributes.Hidden | TestPropertyAttributes.Trait, +#pragma warning restore 618 + typeof(TestObject)); + +#if NET46 + [NonSerialized] +#endif + private TestObject testObject; + + internal TraitCollection(TestObject testObject) + { + ValidateArg.NotNull(testObject, "testObject"); + + this.testObject = testObject; + } + + public void Add(Trait trait) + { + ValidateArg.NotNull(trait, "trait"); + + this.AddRange(new[] { trait }); + } + + public void Add(string name, string value) + { + ValidateArg.NotNull(name, "name"); + + this.Add(new Trait(name, value)); + } + + public void AddRange(IEnumerable traits) + { + ValidateArg.NotNull(traits, "traits"); + + var existingTraits = this.GetTraits(); + this.Add(existingTraits, traits); + } + + public IEnumerator GetEnumerator() + { + var enumerable = this.GetTraits(); + return enumerable.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + + private IEnumerable GetTraits() + { + if (!this.testObject.Properties.Contains(TraitsProperty)) + { + yield break; + } + + var traits = this.testObject.GetPropertyValue[]>(TraitsProperty, Enumerable.Empty>().ToArray()); + + foreach (var trait in traits) + { + yield return new Trait(trait); + } + } + + private void Add(IEnumerable traits, IEnumerable newTraits) + { + var newValue = traits.Union(newTraits); + var newPairs = newValue.Select(t => new KeyValuePair(t.Name, t.Value)).ToArray(); + this.testObject.SetPropertyValue[]>(TraitsProperty, newPairs); + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Utilities/AssemblyHelper.cs b/src/Microsoft.TestPlatform.ObjectModel/Utilities/AssemblyHelper.cs new file mode 100644 index 0000000000..17964b683f --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Utilities/AssemblyHelper.cs @@ -0,0 +1,356 @@ +// Copyright(c) Microsoft.All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities +{ +#if NET46 + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Reflection; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; + + /// + /// Implementation of finding assembly referencies using "managed route", i.e. Assembly.Load. + /// + public static class AssemblyHelper + { + private static Version defaultVersion = new Version(); + private static Version version45 = new Version("4.5"); + + /// + /// Checks whether the source assembly directly references given assembly or not. + /// Only assembly name and publickey token are match. Version is ignored for matching. + /// Returns null if not able to check if source references assembly. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + public static bool? DoesReferencesAssembly(string source, AssemblyName referenceAssembly) + { + try + { + ValidateArg.NotNullOrEmpty(source, "source"); + ValidateArg.NotNull(referenceAssembly, "referenceAssembly"); + + Debug.Assert(!string.IsNullOrEmpty(source)); + + var referenceAssemblyName = referenceAssembly.Name; + var referenceAssemblyPublicKeyToken = referenceAssembly.GetPublicKeyToken(); + + var setupInfo = new AppDomainSetup(); + setupInfo.ApplicationBase = Path.GetDirectoryName(Path.GetFullPath(source)); + + // In Dev10 by devenv uses its own app domain host which has default optimization to share everything. + // Set LoaderOptimization to MultiDomainHost which means: + // Indicates that the application will probably host unique code in multiple domains, + // and the loader must share resources across application domains only for globally available (strong-named) + // assemblies that have been added to the global assembly cache. + setupInfo.LoaderOptimization = LoaderOptimization.MultiDomainHost; + + AppDomain ad = null; + try + { + ad = AppDomain.CreateDomain("Dependency finder domain", null, setupInfo); + + var assemblyLoadWorker = typeof(AssemblyLoadWorker); + AssemblyLoadWorker worker = null; + if (assemblyLoadWorker.Assembly.GlobalAssemblyCache) + { + worker = (AssemblyLoadWorker)ad.CreateInstanceAndUnwrap( + assemblyLoadWorker.Assembly.FullName, + assemblyLoadWorker.FullName, + false, BindingFlags.Default, null, + null, null, null); + } + else + { + // This has to be LoadFrom, otherwise we will have to use AssemblyResolver to find self. + worker = (AssemblyLoadWorker)ad.CreateInstanceFromAndUnwrap( + assemblyLoadWorker.Assembly.Location, + assemblyLoadWorker.FullName, + false, BindingFlags.Default, null, + null, null, null); + } + + return worker.CheckAssemblyReference(source, referenceAssemblyName, referenceAssemblyPublicKeyToken); + } + finally + { + if (ad != null) + { + AppDomain.Unload(ad); + } + } + } + catch + { + return null; // Return null if something goes wrong. + } + } + + /// + /// Finds platform and .Net framework version for a given test container. + /// If there is an error while infering this information, defaults (AnyCPU, None) are returned + /// for faulting container. + /// + /// + /// + public static KeyValuePair GetFrameworkVersionAndArchitectureForSource(string testSource) + { + ValidateArg.NotNullOrEmpty(testSource, "testSource"); + + var sourceDirectory = Path.GetDirectoryName(testSource); + var setupInfo = new AppDomainSetup(); + setupInfo.ApplicationBase = sourceDirectory; + setupInfo.LoaderOptimization = LoaderOptimization.MultiDomainHost; + AppDomain ad = null; + try + { + ad = AppDomain.CreateDomain("Multiargeting settings domain", null, setupInfo); + + Type assemblyLoadWorker = typeof(AssemblyLoadWorker); + AssemblyLoadWorker worker = null; + + // This has to be LoadFrom, otherwise we will have to use AssemblyResolver to find self. + worker = (AssemblyLoadWorker)ad.CreateInstanceFromAndUnwrap( + assemblyLoadWorker.Assembly.Location, + assemblyLoadWorker.FullName, + false, BindingFlags.Default, null, + null, null, null); + + string procArchType; + string frameworkVersion; + worker.GetPlatformAndFrameworkSettings(testSource, out procArchType, out frameworkVersion); + + Architecture targetPlatform = (Architecture)Enum.Parse(typeof(Architecture), procArchType); + FrameworkVersion targetFramework = FrameworkVersion.Framework45; + switch (frameworkVersion.ToUpperInvariant()) + { + case "V4.5": + targetFramework = FrameworkVersion.Framework45; + break; + + case "V4.0": + targetFramework = FrameworkVersion.Framework40; + break; + + case "V3.5": + case "V2.0": + targetFramework = FrameworkVersion.Framework35; + break; + + default: + targetFramework = FrameworkVersion.None; + break; + } + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose("Inferred Multi-Targeting settings:{0} Platform:{1} FrameworkVersion:{2}", testSource, targetPlatform, targetFramework); + } + return new KeyValuePair(targetPlatform, targetFramework); + + } + finally + { + if (ad != null) + { + AppDomain.Unload(ad); + } + } + } + + + /// + /// Returns the full name (AssemblyName.FullName) of the referenced assemblies by the assembly on the specified path. + /// + /// Returns null on failure and an empty array if there is no reference in the project. + /// + /// Full path to the assembly to get dependencies for. + public static string[] GetReferencedAssemblies(string source) + { + Debug.Assert(!string.IsNullOrEmpty(source)); + + var setupInfo = new AppDomainSetup(); + setupInfo.ApplicationBase = Path.GetDirectoryName(Path.GetFullPath(source)); + + // In Dev10 by devenv uses its own app domain host which has default optimization to share everything. + // Set LoaderOptimization to MultiDomainHost which means: + // Indicates that the application will probably host unique code in multiple domains, + // and the loader must share resources across application domains only for globally available (strong-named) + // assemblies that have been added to the global assembly cache. + setupInfo.LoaderOptimization = LoaderOptimization.MultiDomainHost; + + AppDomain ad = null; + try + { + ad = AppDomain.CreateDomain("Dependency finder domain", null, setupInfo); + + var assemblyLoadWorker = typeof(AssemblyLoadWorker); + AssemblyLoadWorker worker = null; + if (assemblyLoadWorker.Assembly.GlobalAssemblyCache) + { + worker = (AssemblyLoadWorker)ad.CreateInstanceAndUnwrap( + assemblyLoadWorker.Assembly.FullName, + assemblyLoadWorker.FullName, + false, BindingFlags.Default, null, + null, null, null); + } + else + { + // This has to be LoadFrom, otherwise we will have to use AssemblyResolver to find self. + worker = (AssemblyLoadWorker)ad.CreateInstanceFromAndUnwrap( + assemblyLoadWorker.Assembly.Location, + assemblyLoadWorker.FullName, + false, BindingFlags.Default, null, + null, null, null); + } + + return worker.GetReferencedAssemblies(source); + } + finally + { + if (ad != null) + { + AppDomain.Unload(ad); + } + } + } + + /// + /// Set the target framework for app domain setup if target framework of dll is > 4.5 + /// + /// AppdomainSetup for app domain creation + /// path of test file + public static void SetAppDomainFrameworkVersionBasedOnTestSource(AppDomainSetup setup, string testSource) + { + string assemblyVersionString = GetTargetFrameworkVersionString(testSource); + + if (GetTargetFrameworkVersionFromVersionString(assemblyVersionString).CompareTo(version45) > 0) + { + var pInfo = typeof(AppDomainSetup).GetProperty(Constants.TargetFrameworkName); + if (null != pInfo) + { + pInfo.SetValue(setup, assemblyVersionString, null); + } + } + } + + /// + /// Get the target dot net framework string for the assembly + /// + /// Path of the assembly file + /// String representation of the the target dot net framework e.g. .NETFramework,Version=v4.0 + internal static string GetTargetFrameworkVersionString(string path) + { + Debug.Assert(!string.IsNullOrEmpty(path)); + + var setupInfo = new AppDomainSetup(); + setupInfo.ApplicationBase = Path.GetDirectoryName(Path.GetFullPath(path)); + + // In Dev10 by devenv uses its own app domain host which has default optimization to share everything. + // Set LoaderOptimization to MultiDomainHost which means: + // Indicates that the application will probably host unique code in multiple domains, + // and the loader must share resources across application domains only for globally available (strong-named) + // assemblies that have been added to the global assembly cache. + // See Dev10 bug 660791. + setupInfo.LoaderOptimization = LoaderOptimization.MultiDomainHost; + + if (File.Exists(path)) + { + AppDomain ad = null; + try + { + ad = AppDomain.CreateDomain("Framework Version String Domain", null, setupInfo); + + var assemblyLoadWorker = typeof(AssemblyLoadWorker); + AssemblyLoadWorker worker = null; + if (assemblyLoadWorker.Assembly.GlobalAssemblyCache) + { + worker = (AssemblyLoadWorker)ad.CreateInstanceAndUnwrap( + assemblyLoadWorker.Assembly.FullName, + assemblyLoadWorker.FullName, + false, BindingFlags.Default, null, + null, null, null); + } + else + { + // This has to be LoadFrom, otherwise we will have to use AssemblyResolver to find self. + worker = (AssemblyLoadWorker)ad.CreateInstanceFromAndUnwrap( + assemblyLoadWorker.Assembly.Location, + assemblyLoadWorker.FullName, + false, BindingFlags.Default, null, + null, null, null); + } + + return worker.GetTargetFrameworkVersionStringFromPath(path); + } + finally + { + if (ad != null) + { + AppDomain.Unload(ad); + } + } + } + + return string.Empty; + } + + /// + /// Get the Version for the target framework version string + /// + /// Target framework string + /// Framework Version + internal static Version GetTargetFrameworkVersionFromVersionString(string version) + { + if (version.Length > Constants.DotNetFrameWorkStringPrefix.Length + 1) + { + string versionPart = version.Substring(Constants.DotNetFrameWorkStringPrefix.Length + 1); + return new Version(versionPart); + } + + return defaultVersion; + } + + /// + /// When test run is targeted for .Net4.0, set target framework of test appdomain to be v4.0. + /// With this done tests would be executed in 4.0 compatiblity mode even when .Net4.5 is installed. + /// This needs to be done using reflection, as this dll is compiled for .Net3.5. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Failure to set this property should be ignored.")] + internal static void SetNETFrameworkCompatiblityMode(AppDomainSetup setup, IRunContext runContext) + { + try + { + RunConfiguration runConfiguration = XmlRunSettingsUtilities.GetRunConfigurationNode(runContext.RunSettings.SettingsXml); + if (null != runConfiguration && Enum.Equals(runConfiguration.TargetFrameworkVersion, FrameworkVersion.Framework40)) + { + PropertyInfo pInfo = typeof(AppDomainSetup).GetProperty(Constants.TargetFrameworkName); + if (null != pInfo) + { + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose("AssemblyHelper.SetNETFrameworkCompatiblityMode: setting .NetFramework,Version=v4.0 compatiblity mode."); + } + pInfo.SetValue(setup, Constants.DotNetFramework40, null); + } + else + { + if (EqtTrace.IsWarningEnabled) + { + EqtTrace.Warning("AssemblyHelper:SetNETFrameworkCompatiblityMode: Binary compatiblity mode needed, but AppDomainSetup.TargetFrameworkName property not found. Ignoring compatiblity mode."); + } + } + } + } + catch (Exception e) + { + if (EqtTrace.IsErrorEnabled) + { + EqtTrace.Error("AssemblyHelper:SetNETFrameworkCompatiblityMode: Caught an exception:{0}", e); + } + } + } + } +#endif +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Utilities/AssemblyLoadWorker.cs b/src/Microsoft.TestPlatform.ObjectModel/Utilities/AssemblyLoadWorker.cs new file mode 100644 index 0000000000..afff40f11e --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Utilities/AssemblyLoadWorker.cs @@ -0,0 +1,386 @@ +// Copyright(c) Microsoft.All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities +{ +#if NET46 + using System; + using System.Diagnostics; + using System.IO; + using System.Linq; + using System.Reflection; + + /// + /// Does the real work of finding references using Assembly.ReflectionOnlyLoadFrom. + /// The caller is supposed to create AppDomain and create instance of given class in there. + /// + internal class AssemblyLoadWorker : MarshalByRefObject + { + /// + /// Get the target dot net framework string for the assembly + /// + /// Path of the assembly file + /// String representation of the the target dot net framework e.g. .NETFramework,Version=v4.0 + public string GetTargetFrameworkVersionStringFromPath(string path) + { + if (!File.Exists(path)) + { + return string.Empty; + } + + try + { + var a = Assembly.ReflectionOnlyLoadFrom(path); + return 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 + /// String representation of the the target dot net framework e.g. .NETFramework,Version=v4.0 + internal static string GetTargetFrameworkStringFromAssembly(Assembly assembly) + { + var dotNetVersion = string.Empty; + foreach (var data in CustomAttributeData.GetCustomAttributes(assembly)) + { + if (data.NamedArguments?.Count > 0) + { + string attributeName = data.NamedArguments[0].MemberInfo.DeclaringType.FullName; + if (string.Equals(attributeName, Constants.TargetFrameworkAttributeFullName, StringComparison.OrdinalIgnoreCase)) + { + dotNetVersion = data.ConstructorArguments[0].Value.ToString(); + break; + } + } + } + + return dotNetVersion; + } + + /// + /// Returns the full name of the referenced assemblies by the assembly on the specified path. + /// + /// Returns null on failure and an empty array if there is no reference in the project. + /// + /// Path to the assembly file to load from. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Being created in a separate app-domain"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + public string[] GetReferencedAssemblies(string path) + { + Debug.Assert(!string.IsNullOrEmpty(path)); + + Assembly a = null; + try + { + // ReflectionOnlyLoadFrom does not use the probing paths and loads from the + // specified path only and does not let code to be executed by the assembly + // in the loaded context. + a = Assembly.ReflectionOnlyLoadFrom(path); + } + catch + { + return null; + } + Debug.Assert(a != null); + + AssemblyName[] assemblies = a.GetReferencedAssemblies(); + if (assemblies == null || assemblies.Length == 0) + { + return new string[0]; + } + + return (from assembly in assemblies + select assembly.FullName).ToArray(); + } + + /// + /// Returns true if given assembly matched name and public key token. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Being created in a separate app-domain"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + public bool? CheckAssemblyReference(string path, string referenceAssemblyName, byte[] publicKeyToken) + { + Assembly a = null; + try + { + // ReflectionOnlyLoadFrom does not use the probing paths and loads from the + // specified path only and does not let code to be executed by the assembly + // in the loaded context. + // + a = Assembly.ReflectionOnlyLoadFrom(path); + + Debug.Assert(a != null); + + AssemblyName[] assemblies = a.GetReferencedAssemblies(); + + foreach (AssemblyName referencedAssembly in assemblies) + { + // Check without version. Only name and publikey token. + if (string.Compare(referencedAssembly.Name, referenceAssemblyName, StringComparison.OrdinalIgnoreCase) == 0) + { + byte[] publicKeyToken1 = referencedAssembly.GetPublicKeyToken(); + + bool isMatch = true; + if (publicKeyToken1.Length != publicKeyToken.Length) + { + continue; + } + + for (int i = 0; i < publicKeyToken1.Length; ++i) + { + if (publicKeyToken1[i] != publicKeyToken[i]) + { + isMatch = false; + break; + } + } + + if (isMatch) + { + return true; + } + } + } + } + catch + { + return null; // return null if not able to check. + } + + return false; + } + + /// + /// Finds platform and .Net framework version for a given container. + /// In case of errors defaults are returned. + /// + /// + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Being created in a separate app-domain")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + public void GetPlatformAndFrameworkSettings(string path, out string procArchType, out string frameworkVersion) + { + procArchType = Architecture.Default.ToString(); + frameworkVersion = String.Empty; + + try + { + // ReflectionOnlyLoadFrom does not use the probing paths and loads from the + // specified path only and does not let code to be executed by the assembly + // in the loaded context. + + var a = Assembly.ReflectionOnlyLoadFrom(path); + Debug.Assert(a != null); + PortableExecutableKinds peKind; + ImageFileMachine machine; + a.ManifestModule.GetPEKind(out peKind, out machine); + + // conversion to string type is needed for below reason + // -- PortableExecutableKinds.Preferred32Bit and ImageFileMachine.ARM is available only + // in .Net4.0 and above. Below code is compiled with .Net3.5 but runs in .Net4.0 + string peKindString = peKind.ToString(); + string machineTypeString = machine.ToString(); + if (string.Equals(machineTypeString, "I386", StringComparison.OrdinalIgnoreCase)) + { + if (peKindString.Contains("Preferred32Bit") || peKindString.Contains("Required32Bit")) + { + procArchType = "X86"; + } + else if (string.Equals(peKindString, "ILOnly", StringComparison.OrdinalIgnoreCase)) + { + procArchType = "AnyCPU"; + } + } + else if (string.Equals(machineTypeString, "AMD64", StringComparison.OrdinalIgnoreCase) || + string.Equals(machineTypeString, "IA64", StringComparison.OrdinalIgnoreCase)) + { + if (string.Equals(peKindString, "ILOnly, PE32Plus", StringComparison.OrdinalIgnoreCase)) + { + procArchType = "X64"; + } + } + else if (string.Equals(machineTypeString, "ARM", StringComparison.OrdinalIgnoreCase)) + { + if (string.Equals(peKindString, "ILOnly", StringComparison.OrdinalIgnoreCase)) + { + procArchType = "ARM"; + } + } + + if (string.IsNullOrEmpty(procArchType)) + { + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose("Unable to find the platform type for image:{0} with PEKind:{1}, Machine:{2}. Returning Default:{3}", path, peKindString, machineTypeString, "AnyCPU"); + } + procArchType = "AnyCPU"; + } + + + frameworkVersion = a.ImageRuntimeVersion.Substring(0, 4).ToUpperInvariant(); + + // ImageRuntimeVersion for v4.0 & v4.5 are same and it return v4.0 + // Since there is behaviourial differnece in both its important to differentiate them + // Using TargetFrameworkAttribute for the purpose. + if (string.Equals(frameworkVersion, "v4.0", StringComparison.OrdinalIgnoreCase)) + { + // Try to determine the exact .NET framework by inspecting custom attributes on assembly. + string dotNetVersion = GetTargetFrameworkStringFromAssembly(a); + if (dotNetVersion.StartsWith(Constants.DotNetFramework40, StringComparison.OrdinalIgnoreCase)) + { + frameworkVersion = "v4.0"; + } + else if (dotNetVersion.StartsWith(Constants.DotNetFramework45, StringComparison.OrdinalIgnoreCase)) + { + frameworkVersion = "v4.5"; + } + else if (dotNetVersion.Length > Constants.DotNetFrameWorkStringPrefix.Length) + { + frameworkVersion = dotNetVersion.Substring(Constants.DotNetFrameWorkStringPrefix.Length); + } + } + + } + catch (BadImageFormatException) + { + if (EqtTrace.IsErrorEnabled) + { + EqtTrace.Error("AssemblyLoadWorker:GetPlatformAndFrameworkSettings() caught BadImageFormatException. Falling to native binary."); + } + procArchType = GetArchitectureForSource(path); + } + catch (Exception ex) + { + if (EqtTrace.IsErrorEnabled) + { + EqtTrace.Error("AssemblyLoadWorker:GetPlatformAndFrameworkSettings() Returning default. Unhandled exception: {0}.", ex); + } + return; + } + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + private static string GetArchitectureForSource(string imagePath) + { + // For details refer to below code available on MSDN. + // http://code.msdn.microsoft.com/CSCheckExeType-aab06100/sourcecode?fileId=22010&pathId=1874010322 + + string archType = "AnyCPU"; + ushort machine = 0; + + uint peHeader; + const int IMAGE_FILE_MACHINE_AMD64 = 0x8664; + const int IMAGE_FILE_MACHINE_IA64 = 0x200; + const int IMAGE_FILE_MACHINE_I386 = 0x14c; + const int IMAGE_FILE_MACHINE_ARM = 0x01c0; // ARM Little-Endian + const int IMAGE_FILE_MACHINE_THUMB = 0x01c2; // ARM Thumb/Thumb-2 Little-Endian + const int IMAGE_FILE_MACHINE_ARMNT = 0x01c4; // ARM Thumb-2 Little-Endian + + try + { + //get the input stream + using (Stream fs = new FileStream(imagePath, FileMode.Open, FileAccess.Read)) + { + bool validImage = true; + + BinaryReader reader = new BinaryReader(fs); + //PE Header starts @ 0x3C (60). Its a 4 byte header. + fs.Position = 0x3C; + peHeader = reader.ReadUInt32(); + + // Check if the offset is invalid + if (peHeader > fs.Length - 5) + { + validImage = false; + } + if (validImage) + { + //Moving to PE Header start location... + fs.Position = peHeader; + + UInt32 signature = reader.ReadUInt32(); //peHeaderSignature + // 0x00004550 is the letters "PE" followed by two terminating zeroes. + if (signature != 0x00004550) + { + validImage = false; + } + + if (validImage) + { + //Read the image file header header. + machine = reader.ReadUInt16(); + reader.ReadUInt16(); //NumberOfSections + reader.ReadUInt32(); //TimeDateStamp + reader.ReadUInt32(); //PointerToSymbolTable + reader.ReadUInt32(); //NumberOfSymbols + reader.ReadUInt16(); //SizeOfOptionalHeader + reader.ReadUInt16(); //Characteristics + + // magic number.32bit or 64bit assembly. + UInt16 magic = reader.ReadUInt16(); + if (magic != 0x010B && magic != 0x020B) + { + validImage = false; + } + } + + if (validImage) + { + switch (machine) + { + case IMAGE_FILE_MACHINE_I386: + archType = "X86"; + break; + + case IMAGE_FILE_MACHINE_AMD64: + case IMAGE_FILE_MACHINE_IA64: + archType = "X64"; + break; + + case IMAGE_FILE_MACHINE_ARM: + case IMAGE_FILE_MACHINE_THUMB: + case IMAGE_FILE_MACHINE_ARMNT: + archType = "ARM"; + break; + } + } + else + { + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose("Source path {0} is not a valid image path. Returning default proc arch type {1}.", imagePath, "AnyCPU"); + } + } + } + } + } + catch (Exception ex) + { + //Ignore all exception + if (EqtTrace.IsErrorEnabled) + { + EqtTrace.Error("AssemblyLoadWorker:GetArchitectureForSource() Returning default:{0}. Unhandled exception: {1}.", "AnyCPU", ex.Message); + } + } + + return archType; + } + } +#endif +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Utilities/EqtHash.cs b/src/Microsoft.TestPlatform.ObjectModel/Utilities/EqtHash.cs new file mode 100644 index 0000000000..0edb7c8463 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Utilities/EqtHash.cs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities +{ + using System; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + +#if NET46 +using System.Security.Cryptography; +#endif + + /// + /// Wrapper class for cryptographic hashing. + /// This class uses SHA1 instead of MD5 in order to conform to the FIPS standard. + /// + public static class EqtHash + { + /// + /// Calculates a hash of the string and copies the first 128 bits of the hash + /// to a new Guid. + /// + [SuppressMessage("Microsoft.Cryptographic.Standard", "CA5354:SHA1CannotBeUsed", Justification = "Hash Algorithm is used only to gererate unique testcase id.")] + public static Guid GuidFromString(string data) + { + Debug.Assert(data != null); +#if NET46 + using (HashAlgorithm provider = new SHA1CryptoServiceProvider()) + { + byte[] hash = provider.ComputeHash(System.Text.Encoding.Unicode.GetBytes(data)); + + // Guid is always 16 bytes + Debug.Assert(Guid.Empty.ToByteArray().Length == 16, "Expected Guid to be 16 bytes"); + + byte[] toGuid = new byte[16]; + Array.Copy(hash, toGuid, 16); + + return new Guid(toGuid); + } + +#else + // Not used for CoreSystem + throw new NotImplementedException(); +#endif + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Utilities/InProcDataCollectionUtilities.cs b/src/Microsoft.TestPlatform.ObjectModel/Utilities/InProcDataCollectionUtilities.cs new file mode 100644 index 0000000000..bde585f9af --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Utilities/InProcDataCollectionUtilities.cs @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Linq; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities; + + /// + /// The in procedure data collection utilities. + /// + public static class InProcDataCollectionUtilities + { + /// + /// Gets the in process data collection run settings. + /// + public static DataCollectionRunSettings InProcDataCollectionRunSettings { get; private set; } + + + public static bool InProcDataCollectionEnabled + { + get + { + var isEnabled = InProcDataCollectionRunSettings?.IsCollectionEnabled ?? false; + return isEnabled && InProcDataCollectionRunSettings.DataCollectorSettingsList.Count > 0; + } + } + + /// + /// The read in process data collection run settings. + /// + /// + /// The run settings. + /// + public static void ReadInProcDataCollectionRunSettings(string runSettings) + { + InProcDataCollectionRunSettings = XmlRunSettingsUtilities.GetInProcDataCollectionRunSettings(runSettings); + } + + /// + /// The get data collector settings. + /// + /// + /// The . + /// + public static Collection GetInProcDataCollectorSettings() + { + if (InProcDataCollectionEnabled) + { + return InProcDataCollectionRunSettings.DataCollectorSettingsList; + } + + return null; + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Utilities/StringUtilities.cs b/src/Microsoft.TestPlatform.ObjectModel/Utilities/StringUtilities.cs new file mode 100644 index 0000000000..1ad8f8929c --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Utilities/StringUtilities.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities +{ + using System; + + /// + /// Utility methods for manipulating strings. + /// + public static class StringUtilities + { + /// + /// Prepares the string for output by converting null values to the "(null)" string + /// and removing any trailing new lines. + /// + /// The input string. + /// The string that is prepared for output. + public static string PrepareForOutput(string input) + { + string result = input; + if (input == null) + { + result = Resources.NullString; + } + + result = result.TrimEnd(Environment.NewLine.ToCharArray()); + + return result; + } + + /// + /// Checks if given string is null or a whitespace. + /// + /// string to check + /// True if string is null or a whitespace, false otherwise + public static bool IsNullOrWhiteSpace(string input) + { + if (input != null) + { + input = input.Trim(); + } + + return string.IsNullOrEmpty(input); + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Utilities/SuspendCodeCoverage.cs b/src/Microsoft.TestPlatform.ObjectModel/Utilities/SuspendCodeCoverage.cs new file mode 100644 index 0000000000..b4644fd9df --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Utilities/SuspendCodeCoverage.cs @@ -0,0 +1,74 @@ +// Copyright(c) Microsoft.All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities +{ +#if NET46 + using System; + using System.Diagnostics; + using System.Globalization; + using System.Threading; + + /// + /// Suspends the instrumentation (for code coverage) of the modules which are loaded + /// during this object is created and disposed + /// exceeded. + /// + public class SuspendCodeCoverage : IDisposable + { + #region private variables + + private const string SuspendCodeCoverageEnvVarName = "__VANGUARD_SUSPEND_INSTRUMENT__"; + private const string SuspendCodeCoverageEnvVarTrueValue = "TRUE"; + + private string prevEnvValue; + + /// + /// Whether the object is disposed or not. + /// + private bool isDisposed = false; + + #endregion + + /// + /// Constructor. Code Coverage instrumentation of the modules, which are loaded + /// during this object is created and disposed, is disabled. + /// + public SuspendCodeCoverage() + { + this.prevEnvValue = Environment.GetEnvironmentVariable(SuspendCodeCoverageEnvVarName, EnvironmentVariableTarget.Process); + Environment.SetEnvironmentVariable(SuspendCodeCoverageEnvVarName, SuspendCodeCoverageEnvVarTrueValue, EnvironmentVariableTarget.Process); + } + + #region IDisposable + + /// + /// Disposes this instance + /// + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Disposes instance. + /// + /// Should dispose. + internal void Dispose(bool disposing) + { + if (!this.isDisposed) + { + if (disposing) + { + Environment.SetEnvironmentVariable(SuspendCodeCoverageEnvVarName, this.prevEnvValue, EnvironmentVariableTarget.Process); + } + + this.isDisposed = true; + } + } + + #endregion IDisposable + } + +#endif +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Utilities/XmlReaderUtilities.cs b/src/Microsoft.TestPlatform.ObjectModel/Utilities/XmlReaderUtilities.cs new file mode 100644 index 0000000000..859cf1aed7 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Utilities/XmlReaderUtilities.cs @@ -0,0 +1,73 @@ +// Copyright(c) Microsoft.All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities +{ + using System; + using System.Globalization; + using System.Xml; + + /// + /// Utility methods for working with an XmlReader. + /// + public static class XmlReaderUtilities + { + #region Constants + + private const string RunSettingsRootNodeName = "RunSettings"; + + #endregion + + #region Utility Methods + + /// + /// Reads up to the next Element in the document. + /// + /// Reader to move to the next element. + public static void ReadToNextElement(this XmlReader reader) + { + ValidateArg.NotNull(reader, "reader"); + while (!reader.EOF && reader.Read() && reader.NodeType != XmlNodeType.Element) + { + } + } + + /// + /// Skips the current element and moves to the next Element in the document. + /// + /// Reader to move to the next element. + public static void SkipToNextElement(this XmlReader reader) + { + ValidateArg.NotNull(reader, "reader"); + reader.Skip(); + + if (reader.NodeType != XmlNodeType.Element) + { + reader.ReadToNextElement(); + } + } + + /// + /// Reads to the root node of the run settings and verifies that it is a "RunSettings" node. + /// + /// Path to the file. + /// XmlReader for the file. + public static void ReadToRootNode(XmlReader reader) + { + ValidateArg.NotNull(reader, "reader"); + + // Read to the root node. + reader.ReadToNextElement(); + + // Verify that it is a "RunSettings" node. + if (reader.Name != RunSettingsRootNodeName) + { + throw new SettingsException( + string.Format( + CultureInfo.CurrentCulture, + Resources.InvalidRunSettingsRootNode)); + } + } + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Utilities/XmlRunSettingsUtilities.cs b/src/Microsoft.TestPlatform.ObjectModel/Utilities/XmlRunSettingsUtilities.cs new file mode 100644 index 0000000000..98fc0500a4 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Utilities/XmlRunSettingsUtilities.cs @@ -0,0 +1,343 @@ +// Copyright(c) Microsoft.All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Security; + using System.Xml; + using System.Xml.XPath; + + /// + /// Utilities for the run settings XML. + /// + public class XmlRunSettingsUtilities + { + /// + /// Friendly name of the data collector + /// + private const string FriendlyName = "UnitTestIsolation"; + + /// + /// Gets the URI of the data collector + /// + private const string DataCollectorUri = "datacollector://microsoft/unittestisolation/1.0"; + + /// + /// Gets the assembly qualified name of the data collector type + /// + private const string DataCollectorAssemblyQualifiedName = "Microsoft.VisualStudio.TraceCollector.UnitTestIsolationDataCollector, Microsoft.VisualStudio.TraceCollector, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"; + + /// + /// Gets the os architecture. + /// + public static Architecture OSArchitecture + { + [SecuritySafeCritical] + get + { + string processorArch = Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE"); + string processorArch6432 = Environment.GetEnvironmentVariable("PROCESSOR_ARCHITEW6432"); + if (string.Equals(processorArch, "amd64", StringComparison.OrdinalIgnoreCase) || + string.Equals(processorArch, "ia64", StringComparison.OrdinalIgnoreCase) || + string.Equals(processorArch6432, "amd64", StringComparison.OrdinalIgnoreCase) || + string.Equals(processorArch6432, "ia64", StringComparison.OrdinalIgnoreCase)) + { + return Architecture.X64; + } + + if (string.Equals(processorArch, "x86", StringComparison.OrdinalIgnoreCase)) + { + return Architecture.X86; + } + + return Architecture.ARM; + } + } + + /// + /// Gets the settings to be used while creating XmlReader for runsettings. + /// + public static XmlReaderSettings ReaderSettings + { + get + { + var settings = new XmlReaderSettings(); + settings.IgnoreComments = true; + settings.IgnoreWhitespace = true; + return settings; + } + } + + /// + /// Examines the given XPathNavigable representation of a runsettings file and determines if it has a configuration node + /// for the data collector (used for Fakes and CodeCoverage) + /// + /// XPathNavigable representation of a runsettings file + /// The data Collector Uri. + /// True if there is a datacollector configured. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "1#")] + public static bool ContainsDataCollector(IXPathNavigable runSettingDocument, string dataCollectorUri) + { + if (runSettingDocument == null) + { + throw new ArgumentNullException("runSettingDocument"); + } + + if (dataCollectorUri == null) + { + throw new ArgumentNullException("dataCollectorUri"); + } + + var navigator = runSettingDocument.CreateNavigator(); + var nodes = navigator.Select("/RunSettings/DataCollectionRunSettings/DataCollectors/DataCollector"); + + foreach (XPathNavigator dataCollectorNavigator in nodes) + { + var uri = dataCollectorNavigator.GetAttribute("uri", string.Empty); + if (string.Equals(dataCollectorUri, uri, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + + return false; + } + + /// + /// Returns RunConfiguration from settingsXml. + /// + /// The run settings. + /// The RunConfiguration node as defined in the settings xml. + public static RunConfiguration GetRunConfigurationNode(string settingsXml) + { + var nodeValue = GetNodeValue(settingsXml, Constants.RunConfigurationSettingsName, RunConfiguration.FromXml); + if (nodeValue == default(RunConfiguration)) + { + // Return default one. + nodeValue = new RunConfiguration(); + } + return nodeValue; + } + + /// + /// Gets the set of user defined test run parameters from settings xml as key value pairs. + /// + /// The run settings xml. + /// The test run parameters defined in the run settings. + /// If there is no test run parameters section defined in the settings xml a blank dictionary is returned. + public static Dictionary GetTestRunParameters(string settingsXml) + { + var nodeValue = GetNodeValue>(settingsXml, Constants.TestRunParametersName, TestRunParameters.FromXml); + if (nodeValue == default(Dictionary)) + { + // Return default. + nodeValue = new Dictionary(); + } + return nodeValue; + } + + /// + /// Returns a value that indicates if the Fakes data collector is already configured in the settings. + /// + /// The run settings. + /// True if the fakes data collector is enabled. + public static bool ContainsFakesDataCollector(IXPathNavigable runSettings) + { + if (runSettings == null) + { + throw new ArgumentNullException("runSettings"); + } + + return ContainsDataCollector(runSettings, DataCollectorUri); + } + + /// + /// Create a default run settings + /// + public static IXPathNavigable CreateDefaultRunSettings() + { + // Create a new default xml doc that looks like this: + // + // + // + // + // + // + // + + var doc = new XmlDocument(); + var xmlDeclaration = doc.CreateNode(XmlNodeType.XmlDeclaration, "", ""); + + doc.AppendChild(xmlDeclaration); + var runSettingsNode = doc.CreateElement(Constants.RunSettingsName); + doc.AppendChild(runSettingsNode); + + var dataCollectionRunSettingsNode = doc.CreateElement(Constants.DataCollectionRunSettingsName); + runSettingsNode.AppendChild(dataCollectionRunSettingsNode); + + var dataCollectorsNode = doc.CreateElement(Constants.DataCollectorsSettingName); + dataCollectionRunSettingsNode.AppendChild(dataCollectorsNode); + +#if NET46 + return doc; +#else + return doc.ToXPathNavigable(); +#endif + } + + /// + /// Returns whether data collection is enabled in the parameter settings xml or not + /// + /// The run Settings Xml. + /// True if data collection is enabled. + public static bool IsDataCollectionEnabled(string runSettingsXml) + { + var dataCollectionRunSettings = GetDataCollectionRunSettings(runSettingsXml); + + if (dataCollectionRunSettings == null || !dataCollectionRunSettings.IsCollectionEnabled) + { + return false; + } + + return true; + } + + /// + /// Get DataCollection Run settings from the settings XML. + /// + /// The run Settings Xml. + /// The . + public static DataCollectionRunSettings GetDataCollectionRunSettings(string runSettingsXml) + { + // use XmlReader to avoid loading of the plugins in client code (mainly from VS). + if (string.IsNullOrWhiteSpace(runSettingsXml)) + { + return null; + } + + using (var stringReader = new StringReader(runSettingsXml)) + { + var reader = XmlReader.Create(stringReader, ReaderSettings); + + // read to the fist child + XmlReaderUtilities.ReadToRootNode(reader); + reader.ReadToNextElement(); + + // Read till we reach DC element or reach EOF + while (!string.Equals(reader.Name, Constants.DataCollectionRunSettingsName) + && + !reader.EOF) + { + reader.SkipToNextElement(); + } + + // If reached EOF => DC element not there + if (reader.EOF) + { + return null; + } + + // Reached here => DC element present. + return DataCollectionRunSettings.FromXml(reader); + } + } + + /// + /// Throws a settings exception if the node the reader is on has attributes defined. + /// + /// The xml reader. + internal static void ThrowOnHasAttributes(XmlReader reader) + { + if (reader.HasAttributes) + { + reader.MoveToNextAttribute(); + throw new SettingsException( + string.Format( + CultureInfo.CurrentCulture, + Resources.InvalidSettingsXmlAttribute, + Constants.RunConfigurationSettingsName, + reader.Name)); + } + } + + private static T GetNodeValue(string settingsXml, string nodeName, Func nodeParser) + { + // use XmlReader to avoid loading of the plugins in client code (mainly from VS). + if (!string.IsNullOrWhiteSpace(settingsXml)) + { + using (var stringReader = new StringReader(settingsXml)) + { + XmlReader reader = XmlReader.Create(stringReader, 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, nodeName, StringComparison.OrdinalIgnoreCase) + && + !reader.EOF) + { + reader.SkipToNextElement(); + } + + if (!reader.EOF) + { + // read nodeName element. + return nodeParser(reader); + } + } + } + + return default(T); + } + + /// + /// Get InProc DataCollection Run settings + /// + /// + /// The run Settings Xml. + /// + /// + /// The . + /// + public static DataCollectionRunSettings GetInProcDataCollectionRunSettings(string runSettingsXml) + { + // use XmlReader to avoid loading of the plugins in client code (mainly from VS). + if (!string.IsNullOrWhiteSpace(runSettingsXml)) + { + runSettingsXml = runSettingsXml.Trim(); + using (StringReader stringReader1 = new StringReader(runSettingsXml)) + { + XmlReader reader = XmlReader.Create(stringReader1, ReaderSettings); + + // read to the fist child + XmlReaderUtilities.ReadToRootNode(reader); + reader.ReadToNextElement(); + + // Read till we reach In Proc IDC element or reach EOF + while (!string.Equals(reader.Name, Constants.InProcDataCollectionRunSettingsName) + && + !reader.EOF) + { + reader.SkipToNextElement(); + } + + // If reached EOF => IDC element not there + if (reader.EOF) + { + return null; + } + + // Reached here => In Proc IDC element present. + return DataCollectionRunSettings.FromXml(reader, Constants.InProcDataCollectionRunSettingsName, Constants.InProcDataCollectorsSettingName, Constants.InProcDataCollectorSettingName); + } + } + + return null; + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/project.json b/src/Microsoft.TestPlatform.ObjectModel/project.json new file mode 100644 index 0000000000..08ef10b35e --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/project.json @@ -0,0 +1,40 @@ +{ + "version": "15.0.0-*", + "buildOptions": { + "outputName": "Microsoft.VisualStudio.TestPlatform.ObjectModel", + "delaySign": true, + "keyFile": "../../scripts/key.snk", + "warningsAsErrors": true + }, + "dependencies": { + "Newtonsoft.Json": "7.0.1", + "Microsoft.TestPlatform.CoreUtilities": "15.0.0-*" + }, + "frameworks": { + "net46": { + "frameworkAssemblies": { + "System.Xml": "", + "System.Runtime.Serialization": "" + }, + "dependencies": { + "Microsoft.Internal.Dia.Interop": "14.0.0" + } + }, + "netstandard1.5": { + "imports": [ + "dnxcore50", + "portable-net45+win8" + ], + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-24008", + "System.Runtime.Serialization.Primitives": "4.0.10", + "System.Runtime.Extensions": "4.1.0-rc3-23808", + "System.Xml.XPath.XmlDocument": "4.0.1-rc2-24027", + "System.ComponentModel.EventBasedAsync": "4.0.11-rc2-24018", + "System.Runtime.InteropServices": "4.1.0-rc2-24027", + "System.IO.FileSystem": "4.0.1-rc3-23808", + "System.ComponentModel.TypeConverter": "4.0.1-rc2-23911" + } + } + } +} diff --git a/src/Microsoft.TestPlatform.Utilities/ClientUtilities.cs b/src/Microsoft.TestPlatform.Utilities/ClientUtilities.cs new file mode 100644 index 0000000000..d8681cdbf0 --- /dev/null +++ b/src/Microsoft.TestPlatform.Utilities/ClientUtilities.cs @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Utilities +{ + using System; + using System.Diagnostics.CodeAnalysis; + using System.IO; + using System.Xml; + + /// + /// Utilities used by the client to understand the environment of the current run. + /// + public static class ClientUtilities + { + private const string TestSettingsFileXPath = "RunSettings/MSTest/SettingsFile"; + private const string ResultsDirectoryXPath = "RunSettings/RunConfiguration/ResultsDirectory"; + + /// + /// Converts the relative paths in a runsetting file to absolue ones. + /// + /// Xml Document containing Runsettings xml + /// Path of the .runsettings xml file + [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")] + public static void FixRelativePathsInRunSettings(XmlDocument xmlDocument, string path) + { + if (xmlDocument == null) + { + throw new ArgumentNullException("xPathNavigator"); + } + + if (string.IsNullOrEmpty(path)) + { + throw new ArgumentNullException("path"); + } + + string root = Path.GetDirectoryName(path); + var testRunSettingsNode = xmlDocument.SelectSingleNode(TestSettingsFileXPath); + if (testRunSettingsNode != null) + { + FixNodeFilePath(testRunSettingsNode, root); + } + + var resultsDirectoryNode = xmlDocument.SelectSingleNode(ResultsDirectoryXPath); + if (resultsDirectoryNode != null) + { + FixNodeFilePath(resultsDirectoryNode, root); + } + } + + private static void FixNodeFilePath(XmlNode node, string root) + { + string fileName = node.InnerXml; + + if (!string.IsNullOrEmpty(fileName) + && !Path.IsPathRooted(fileName)) + { + // We have a relative file path... + fileName = Path.Combine(root, fileName); + fileName = Path.GetFullPath(fileName); + + node.InnerXml = fileName; + } + } + } +} diff --git a/src/Microsoft.TestPlatform.Utilities/CodeCoverageDataAdapterUtilities.cs b/src/Microsoft.TestPlatform.Utilities/CodeCoverageDataAdapterUtilities.cs new file mode 100644 index 0000000000..42ff4eac7f --- /dev/null +++ b/src/Microsoft.TestPlatform.Utilities/CodeCoverageDataAdapterUtilities.cs @@ -0,0 +1,172 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Utilities +{ + using System; + using System.Globalization; + using System.Xml.XPath; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities; + + /// + /// The code coverage data adapter utilities. + /// + public static class CodeCoverageDataAdapterUtilities + { + #region private variables + + private const string DynamicCodeCoverageDataDiagnosticAdaterUriString = "datacollector://microsoft/CodeCoverage/2.0"; + private const string StaticCodeCoverageDataDiagnosticAdaterUriString = "datacollector://microsoft/CodeCoverage/1.0"; + + private static string xPathSeperator = "/"; + private static string[] nodeNames = new string[] { Constants.RunSettingsName, Constants.DataCollectionRunSettingsName, Constants.DataCollectorsSettingName, Constants.DataCollectorSettingName }; + + #region Default CodeCoverage Settings String + + private static string codeCoverageCollectorSettingsTemplate = +@" " + Environment.NewLine + +@" " + Environment.NewLine + +@" " + Environment.NewLine + +@" " + Environment.NewLine + +@" " + Environment.NewLine + +@" .*CPPUnitTestFramework.*" + Environment.NewLine + +@" .*vstest.console.*" + Environment.NewLine + +@" .*microsoft.intellitrace.*" + Environment.NewLine + +@" .*vstest.executionengine.*" + Environment.NewLine + +@" .*vstest.discoveryengine.*" + Environment.NewLine + +@" .*microsoft.teamfoundation.testplatform.*" + Environment.NewLine + +@" .*microsoft.visualstudio.testplatform.*" + Environment.NewLine + +@" .*microsoft.visualstudio.testwindow.*" + Environment.NewLine + +@" .*microsoft.visualstudio.mstest.*" + Environment.NewLine + +@" .*microsoft.visualstudio.qualitytools.*" + Environment.NewLine + +@" .*microsoft.vssdk.testhostadapter.*" + Environment.NewLine + +@" .*microsoft.vssdk.testhostframework.*" + Environment.NewLine + +@" .*qtagent32.*" + Environment.NewLine + +@" .*msvcr.*dll$" + Environment.NewLine + +@" .*msvcp.*dll$" + Environment.NewLine + +@" .*clr.dll$" + Environment.NewLine + +@" .*clr.ni.dll$" + Environment.NewLine + +@" .*clrjit.dll$" + Environment.NewLine + +@" .*clrjit.ni.dll$" + Environment.NewLine + +@" .*mscoree.dll$" + Environment.NewLine + +@" .*mscoreei.dll$" + Environment.NewLine + +@" .*mscoreei.ni.dll$" + Environment.NewLine + +@" .*mscorlib.dll$" + Environment.NewLine + +@" .*mscorlib.ni.dll$" + Environment.NewLine + +@" " + Environment.NewLine + +@" " + Environment.NewLine + +@" True" + Environment.NewLine + +@" True" + Environment.NewLine + +@" True" + Environment.NewLine + +@" false" + Environment.NewLine + +@" " + Environment.NewLine + +@" " + Environment.NewLine + +@" " + Environment.NewLine + +@" ^std::.*" + Environment.NewLine + +@" ^ATL::.*" + Environment.NewLine + +@" .*::__GetTestMethodInfo.*" + Environment.NewLine + +@" .*__CxxPureMSILEntry.*" + Environment.NewLine + +@" ^Microsoft::VisualStudio::CppCodeCoverageFramework::.*" + Environment.NewLine + +@" ^Microsoft::VisualStudio::CppUnitTestFramework::.*" + Environment.NewLine + +@" .*::YOU_CAN_ONLY_DESIGNATE_ONE_.*" + Environment.NewLine + +@" ^__.*" + Environment.NewLine + +@" .*::__.*" + Environment.NewLine + +@" " + Environment.NewLine + +@" " + Environment.NewLine + +@" " + Environment.NewLine + +@" " + Environment.NewLine + +@" ^System.Diagnostics.DebuggerHiddenAttribute$" + Environment.NewLine + +@" ^System.Diagnostics.DebuggerNonUserCodeAttribute$" + Environment.NewLine + +@" ^System.Runtime.CompilerServices.CompilerGeneratedAttribute$" + Environment.NewLine + +@" ^System.CodeDom.Compiler.GeneratedCodeAttribute$" + Environment.NewLine + +@" ^System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute$" + Environment.NewLine + +@" " + Environment.NewLine + +@" " + Environment.NewLine + +@" " + Environment.NewLine + +@" " + Environment.NewLine + +@" .*\\atlmfc\\.*" + Environment.NewLine + +@" .*\\vctools\\.*" + Environment.NewLine + +@" .*\\public\\sdk\\.*" + Environment.NewLine + +@" .*\\externalapis\\.*" + Environment.NewLine + +@" .*\\microsoft sdks\\.*" + Environment.NewLine + +@" .*\\vc\\include\\.*" + Environment.NewLine + +@" .*\\msclr\\.*" + Environment.NewLine + +@" .*\\ucrt\\.*" + Environment.NewLine + +@" " + Environment.NewLine + +@" " + Environment.NewLine + +@" " + Environment.NewLine + +@" " + Environment.NewLine + +@" " + Environment.NewLine + +@" " + Environment.NewLine + +@" "; + + #endregion + + #endregion + + /// + /// Updates with code coverage settings if not configured. + /// + /// The run settings document. + public static void UpdateWithCodeCoverageSettingsIfNotConfigured(IXPathNavigable runSettingsDocument) + { + ValidateArg.NotNull(runSettingsDocument, "runSettingsDocument"); + var runSettingsNavigator = runSettingsDocument.CreateNavigator(); + + bool codeCoverageConfigured = XmlRunSettingsUtilities.ContainsDataCollector(runSettingsNavigator, DynamicCodeCoverageDataDiagnosticAdaterUriString) + || XmlRunSettingsUtilities.ContainsDataCollector(runSettingsNavigator, StaticCodeCoverageDataDiagnosticAdaterUriString); + + if (codeCoverageConfigured == false) + { + var existingPath = string.Empty; + var xpaths = new string[] + { + string.Join(xPathSeperator, nodeNames, 0, 1), + string.Join(xPathSeperator, nodeNames, 0, 2), + string.Join(xPathSeperator, nodeNames, 0, 3) + }; + + foreach (var xpath in xpaths) + { + if (runSettingsNavigator.SelectSingleNode(xpath) != null) + { + existingPath = xpath; + } + else + { + break; + } + } + + // If any nodes are missing to add code coverage deafult settings, add the missing xml nodes. + XPathNavigator dataCollectorsNavigator; + if (existingPath.Equals(xpaths[2]) == false) + { + dataCollectorsNavigator = runSettingsNavigator.SelectSingleNode(existingPath); + var missingNodesText = GetMissingNodesTextIfAny(existingPath, xpaths[2]); + dataCollectorsNavigator.AppendChild(missingNodesText); + } + + dataCollectorsNavigator = runSettingsNavigator.SelectSingleNode(xpaths[2]); + dataCollectorsNavigator.AppendChild(codeCoverageCollectorSettingsTemplate); + } + } + + private static string GetMissingNodesTextIfAny(string existingPath, string fullpath) + { + var xmlText = "{0}"; + var nonExistingPath = fullpath.Substring(existingPath.Length); + var requiredNodeNames = nonExistingPath.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries); + var format = "<{0}>{1}"; + + foreach (var nodeName in requiredNodeNames) + { + xmlText = string.Format(CultureInfo.InvariantCulture, xmlText, string.Format(CultureInfo.InvariantCulture, format, nodeName, "{0}")); + } + + xmlText = string.Format(CultureInfo.InvariantCulture, xmlText, string.Empty); + return xmlText; + } + } +} diff --git a/src/Microsoft.TestPlatform.Utilities/Friends.cs b/src/Microsoft.TestPlatform.Utilities/Friends.cs new file mode 100644 index 0000000000..915d79737e --- /dev/null +++ b/src/Microsoft.TestPlatform.Utilities/Friends.cs @@ -0,0 +1,5 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Microsoft.TestPlatform.Utilities.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] diff --git a/src/Microsoft.TestPlatform.Utilities/InferRunSettingsHelper.cs b/src/Microsoft.TestPlatform.Utilities/InferRunSettingsHelper.cs new file mode 100644 index 0000000000..871c07bc2d --- /dev/null +++ b/src/Microsoft.TestPlatform.Utilities/InferRunSettingsHelper.cs @@ -0,0 +1,224 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Utilities +{ + using System; + using System.Globalization; + using System.Xml; + using System.Xml.XPath; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities; + + using Resources = Microsoft.VisualStudio.TestPlatform.Utilities.Resource; + + /// + /// Utility class for Inferring the runsettings from the current environment and the user specified command line switches. + /// + public class InferRunSettingsHelper + { + private const string RunSettingsNodeName = "RunSettings"; + private const string RunConfigurationNodeName = "RunConfiguration"; + private const string ResultsDirectoryNodeName = "ResultsDirectory"; + private const string TargetPlatformNodeName = "TargetPlatform"; + private const string TargetFrameworkNodeName = "TargetFrameworkVersion"; + private const string RunConfigurationNodePath = @"/RunSettings/RunConfiguration"; + private const string TargetPlatformNodePath = @"/RunSettings/RunConfiguration/TargetPlatform"; + private const string TargetFrameworkNodePath = @"/RunSettings/RunConfiguration/TargetFrameworkVersion"; + private const string ResultsDirectoryNodePath = @"/RunSettings/RunConfiguration/ResultsDirectory"; + + /// + /// Updates the run settings XML with the specified values. + /// + /// The navigator of the XML. + /// The architecture. + /// The framework. + /// The results directory. + public static void UpdateRunSettingsWithUserProvidedSwitches(XPathNavigator runSettingsNavigator, Architecture architecture, FrameworkVersion framework, string resultsDirectory) + { + ValidateRunConfiguration(runSettingsNavigator); + + // when runsettings specifies platform, that takes precedence over the user specified platform via command line arguments. + var shouldUpdatePlatform = true; + string nodeXml; + + TryGetPlatformXml(runSettingsNavigator, out nodeXml); + if (!string.IsNullOrEmpty(nodeXml)) + { + architecture = (Architecture)Enum.Parse(typeof(Architecture), nodeXml, true); + shouldUpdatePlatform = false; + } + + // when runsettings specifies framework, that takes precedence over the user specified input framework via the command line arguments. + var shouldUpdateFramework = true; + TryGetFrameworkXml(runSettingsNavigator, out nodeXml); + + if (!string.IsNullOrEmpty(nodeXml)) + { + framework = (FrameworkVersion)Enum.Parse(typeof(FrameworkVersion), nodeXml, true); + shouldUpdateFramework = false; + } + + EqtTrace.Verbose("Using effective platform:{0} effective framework:{1}", architecture, framework); + + // check if platform is compatible with current system architecture. + VerifyCompatibilityWithOSArchitecture(architecture); + + // Check if inputRunSettings has results directory configured. + var hasResultsDirectory = runSettingsNavigator.SelectSingleNode(ResultsDirectoryNodePath) != null; + + // Regenerate the effective settings. + if (shouldUpdatePlatform || shouldUpdateFramework || !hasResultsDirectory) + { + UpdateRunConfiguration(runSettingsNavigator, architecture, framework, resultsDirectory); + } + + runSettingsNavigator.MoveToRoot(); + } + + /// + /// Validates the RunConfiguration setting in run settings. + /// + private static void ValidateRunConfiguration(XPathNavigator runSettingsNavigator) + { + if (!runSettingsNavigator.MoveToChild(RunSettingsNodeName, string.Empty)) + { + throw new XmlException( + string.Format( + CultureInfo.CurrentCulture, + Resources.RunSettingsParseError, + Resources.MissingRunSettingsNode)); + } + + if (runSettingsNavigator.MoveToChild(RunConfigurationNodeName, string.Empty)) + { + string nodeXml; + if (!TryGetPlatformXml(runSettingsNavigator, out nodeXml)) + { + throw new XmlException( + string.Format( + CultureInfo.CurrentCulture, + Resources.RunSettingsParseError, + string.Format( + CultureInfo.CurrentCulture, + Resources.InvalidSettingsIncorrectValue, + Constants.RunConfigurationSettingsName, + nodeXml, + TargetPlatformNodeName))); + } + + if (!TryGetFrameworkXml(runSettingsNavigator, out nodeXml)) + { + throw new XmlException( + string.Format( + CultureInfo.CurrentCulture, + Resources.RunSettingsParseError, + string.Format( + CultureInfo.CurrentCulture, + Resources.InvalidSettingsIncorrectValue, + Constants.RunConfigurationSettingsName, + nodeXml, + TargetFrameworkNodeName))); + } + } + } + + /// + /// Throws SettingsException if platform is incompatible with system architecture. + /// + /// + private static void VerifyCompatibilityWithOSArchitecture(Architecture architecture) + { + var osArchitecture = XmlRunSettingsUtilities.OSArchitecture; + + if (architecture == Architecture.X86 && osArchitecture == Architecture.X64) + { + return; + } + + if (architecture == osArchitecture) + { + return; + } + + throw new SettingsException(string.Format(CultureInfo.CurrentCulture, Resources.SystemArchitectureIncompatibleWithTargetPlatform, architecture, osArchitecture)); + } + + /// + /// Regenerates the RunConfiguration node with new values under runsettings. + /// + private static void UpdateRunConfiguration( + XPathNavigator navigator, + Architecture effectivePlatform, + FrameworkVersion effectiveFramework, + string resultsDirectory) + { + var resultsDirectoryNavigator = navigator.SelectSingleNode(ResultsDirectoryNodePath); + if (null != resultsDirectoryNavigator) + { + resultsDirectory = resultsDirectoryNavigator.InnerXml; + } + + XmlUtilities.AppendOrModifyChild(navigator, RunConfigurationNodePath, RunConfigurationNodeName, null); + navigator.MoveToChild(RunConfigurationNodeName, string.Empty); + + XmlUtilities.AppendOrModifyChild(navigator, ResultsDirectoryNodePath, ResultsDirectoryNodeName, resultsDirectory); + + XmlUtilities.AppendOrModifyChild(navigator, TargetPlatformNodePath, TargetPlatformNodeName, effectivePlatform.ToString()); + XmlUtilities.AppendOrModifyChild(navigator, TargetFrameworkNodePath, TargetFrameworkNodeName, effectiveFramework.ToString()); + + navigator.MoveToRoot(); + } + + private static bool TryGetPlatformXml(XPathNavigator runSettingsNavigator, out string platformXml) + { + platformXml = XmlUtilities.GetNodeXml(runSettingsNavigator, TargetPlatformNodePath); + + if (platformXml == null) + { + return true; + } + + Func validator = (string xml) => + { + var value = (Architecture)Enum.Parse(typeof(Architecture), xml, true); + + if (!Enum.IsDefined(typeof(Architecture), value) || value == Architecture.Default || value == Architecture.AnyCPU) + { + return false; + } + + return true; + }; + + return XmlUtilities.IsValidNodeXmlValue(platformXml, validator); + } + + /// + /// Validate if TargetFrameworkVersion in run settings has valid value. + /// + private static bool TryGetFrameworkXml(XPathNavigator runSettingsNavigator, out string frameworkXml) + { + frameworkXml = XmlUtilities.GetNodeXml(runSettingsNavigator, TargetFrameworkNodePath); + + if (frameworkXml == null) + { + return true; + } + + Func validator = (string xml) => + { + var value = (FrameworkVersion)Enum.Parse(typeof(FrameworkVersion), xml, true); + + if (!Enum.IsDefined(typeof(FrameworkVersion), value) || value == FrameworkVersion.None) + { + return false; + } + + return true; + }; + + return XmlUtilities.IsValidNodeXmlValue(frameworkXml, validator); + } + } +} diff --git a/src/Microsoft.TestPlatform.Utilities/MSTestSettingsUtilities.cs b/src/Microsoft.TestPlatform.Utilities/MSTestSettingsUtilities.cs new file mode 100644 index 0000000000..1cf94d91d7 --- /dev/null +++ b/src/Microsoft.TestPlatform.Utilities/MSTestSettingsUtilities.cs @@ -0,0 +1,102 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Utilities +{ + using System; + using System.Globalization; + using System.IO; + using System.Xml; + using System.Xml.XPath; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + + /// + /// The legacy mstest.exe settings utilities. + /// + public static class MSTestSettingsUtilities + { + /// + /// Imports the parameter settings file in the default runsettings. + /// + /// + /// Settings file which need to be imported. The file extension of the settings file will be specified by property. + /// + /// Input RunSettings document to which settings file need to be imported. + /// The architecture. + /// The framework Version. + /// Updated RunSetting Xml document with imported settings. + public static IXPathNavigable Import(string settingsFile, IXPathNavigable defaultRunSettings, Architecture architecture, FrameworkVersion frameworkVersion) + { + ValidateArg.NotNull(settingsFile, "settingsFile"); + ValidateArg.NotNull(defaultRunSettings, "defaultRunSettings"); + + if (IsLegacyTestSettingsFile(settingsFile) == false) + { + throw new XmlException(string.Format(CultureInfo.CurrentCulture, Resource.UnExpectedSettingsFile)); + } + + var navigator = defaultRunSettings.CreateNavigator(); + + if (!navigator.MoveToChild(Constants.RunSettingsName, string.Empty)) + { + throw new XmlException(Resource.NoRunSettingsNodeFound); + } + + var settingsNode = GenerateMSTestXml(settingsFile); + settingsNode.MoveToRoot(); + navigator.PrependChild(settingsNode); + + // Adding RunConfig + if (!navigator.MoveToChild(Constants.RunConfigurationSettingsName, string.Empty)) + { + var doc = new XmlDocument(); + var runConfigurationNode = doc.CreateElement(Constants.RunConfigurationSettingsName); + + var targetPlatformNode = doc.CreateElement("TargetPlatform"); + targetPlatformNode.InnerXml = architecture.ToString(); + runConfigurationNode.AppendChild(targetPlatformNode); + + var targetFrameworkVersionNode = doc.CreateElement("TargetFrameworkVersion"); + targetFrameworkVersionNode.InnerXml = frameworkVersion.ToString(); + runConfigurationNode.AppendChild(targetFrameworkVersionNode); + + var runConfigNodeNavigator = runConfigurationNode.CreateNavigator(); + runConfigNodeNavigator.MoveToRoot(); + navigator.PrependChild(runConfigNodeNavigator); + } + + navigator.MoveToRoot(); + return navigator; + } + + public static bool IsLegacyTestSettingsFile(string settingsFile) + { + return string.Equals(Path.GetExtension(settingsFile), ".testSettings", StringComparison.OrdinalIgnoreCase) + || string.Equals(Path.GetExtension(settingsFile), ".testrunConfig", StringComparison.OrdinalIgnoreCase) + || string.Equals(Path.GetExtension(settingsFile), ".vsmdi", StringComparison.OrdinalIgnoreCase); + } + + private static XPathNavigator GenerateMSTestXml(string settingsFile) + { + // Generate the MSTest xml + // + // + // C:\local.testsettings + // true + // + // + XmlDocument doc = new XmlDocument(); + XmlElement mstestNode = doc.CreateElement("MSTest"); + + XmlElement testSettingsFileNode = doc.CreateElement("SettingsFile"); + testSettingsFileNode.InnerXml = settingsFile; + mstestNode.AppendChild(testSettingsFileNode); + + XmlElement forcedLegacyModeNode = doc.CreateElement("ForcedLegacyMode"); + forcedLegacyModeNode.InnerXml = "true"; + mstestNode.AppendChild(forcedLegacyModeNode); + + return mstestNode.CreateNavigator(); + } + } +} diff --git a/src/Microsoft.TestPlatform.Utilities/Microsoft.TestPlatform.Utilities.xproj b/src/Microsoft.TestPlatform.Utilities/Microsoft.TestPlatform.Utilities.xproj new file mode 100644 index 0000000000..9108458259 --- /dev/null +++ b/src/Microsoft.TestPlatform.Utilities/Microsoft.TestPlatform.Utilities.xproj @@ -0,0 +1,21 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + bcf6e952-bc36-4e24-b1c4-41988588c59d + Microsoft.VisualStudio.TestPlatform.Utilities + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\ + v4.5.2 + + + + 2.0 + + + diff --git a/src/Microsoft.TestPlatform.Utilities/ParallelRunSettingsUtilities.cs b/src/Microsoft.TestPlatform.Utilities/ParallelRunSettingsUtilities.cs new file mode 100644 index 0000000000..50dea536d5 --- /dev/null +++ b/src/Microsoft.TestPlatform.Utilities/ParallelRunSettingsUtilities.cs @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Utilities +{ + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Linq; + using System.Threading.Tasks; + using System.Xml.XPath; + + /// + /// Utility class for MaxCpuCount element of RunSetting + /// + [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Reviewed. Suppression is OK here.")] + public static class ParallelRunSettingsUtilities + { + private static string XpathOfRunSettings = @"/RunSettings"; + private static string XpathOfRunConfiguration = @"/RunSettings/RunConfiguration"; + private static string XpathOfMaxCpuCount = @"/RunSettings/RunConfiguration/MaxCpuCount"; + + /// + /// The MaxCpuCount setting template. + /// + private static readonly string MaxCpuCountSettingTemplate = @"0"; + + /// + /// The RunConfiguration with MaxCpuCount setting template. + /// + private static readonly string RunConfigurationWithMaxCpuCountSettingTemplate = @" + 0 + "; + + /// + /// This will update the RunSetting with MaxCpuCount 0 if RunSetting doesnt configured with this setting. + /// + /// RunSetting file. + public static void UpdateRunSettingsWithParallelSettingIfNotConfigured(XPathNavigator navigator) + { + var node = navigator.SelectSingleNode(XpathOfMaxCpuCount); + // run settings given by user takes precendence over parallel switch + if (node == null) + { + var runConfigurationNavigator = navigator.SelectSingleNode(XpathOfRunConfiguration); + if (runConfigurationNavigator != null) + { + runConfigurationNavigator.AppendChild(MaxCpuCountSettingTemplate); + } + else + { + runConfigurationNavigator = navigator.SelectSingleNode(XpathOfRunSettings); + runConfigurationNavigator?.AppendChild(RunConfigurationWithMaxCpuCountSettingTemplate); + } + } + } + } +} diff --git a/src/Microsoft.TestPlatform.Utilities/Properties/AssemblyInfo.cs b/src/Microsoft.TestPlatform.Utilities/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..c401b24bcc --- /dev/null +++ b/src/Microsoft.TestPlatform.Utilities/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +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: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Microsoft.TestPlatform.Utilities")] +[assembly: AssemblyTrademark("")] + +// 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("bcf6e952-bc36-4e24-b1c4-41988588c59d")] diff --git a/src/Microsoft.TestPlatform.Utilities/Resource.Designer.cs b/src/Microsoft.TestPlatform.Utilities/Resource.Designer.cs new file mode 100644 index 0000000000..e1c03eadb2 --- /dev/null +++ b/src/Microsoft.TestPlatform.Utilities/Resource.Designer.cs @@ -0,0 +1,125 @@ +//------------------------------------------------------------------------------ +// +// 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 Microsoft.VisualStudio.TestPlatform.Utilities { + using System; + using System.Reflection; + + + /// + /// 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.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class Resource { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + internal Resource() { + } + + /// + /// 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("Microsoft.TestPlatform.Utilities.Resource", typeof(Resource).GetTypeInfo().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 Run settings XML does not contain "RunSettings" node.. + /// + public static string InvalidRunSettingsXml { + get { + return ResourceManager.GetString("InvalidRunSettingsXml", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid setting '{0}'. Invalid value '{1}' specified for '{2}'.. + /// + public static string InvalidSettingsIncorrectValue { + get { + return ResourceManager.GetString("InvalidSettingsIncorrectValue", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Could not find 'RunSettings' node.. + /// + public static string MissingRunSettingsNode { + get { + return ResourceManager.GetString("MissingRunSettingsNode", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Could not find 'RunSettings' node.. + /// + public static string NoRunSettingsNodeFound { + get { + return ResourceManager.GetString("NoRunSettingsNodeFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An error occurred while loading the settings. Error: {0}.. + /// + public static string RunSettingsParseError { + get { + return ResourceManager.GetString("RunSettingsParseError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Incompatible Target platform settings '{0}' with system architecture '{1}'.. + /// + public static string SystemArchitectureIncompatibleWithTargetPlatform { + get { + return ResourceManager.GetString("SystemArchitectureIncompatibleWithTargetPlatform", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unexpected settings file specified.. + /// + public static string UnExpectedSettingsFile { + get { + return ResourceManager.GetString("UnExpectedSettingsFile", resourceCulture); + } + } + } +} diff --git a/src/Microsoft.TestPlatform.Utilities/Resource.resx b/src/Microsoft.TestPlatform.Utilities/Resource.resx new file mode 100644 index 0000000000..6a9c1f39f1 --- /dev/null +++ b/src/Microsoft.TestPlatform.Utilities/Resource.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + Run settings XML does not contain "RunSettings" node. + + + Invalid setting '{0}'. Invalid value '{1}' specified for '{2}'. + + + Could not find 'RunSettings' node. + + + Could not find 'RunSettings' node. + + + An error occurred while loading the settings. Error: {0}. + + + Incompatible Target platform settings '{0}' with system architecture '{1}'. + + + Unexpected settings file specified. + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Utilities/XmlUtilities.cs b/src/Microsoft.TestPlatform.Utilities/XmlUtilities.cs new file mode 100644 index 0000000000..8c0361f727 --- /dev/null +++ b/src/Microsoft.TestPlatform.Utilities/XmlUtilities.cs @@ -0,0 +1,101 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Utilities +{ + using System; + using System.Security; + using System.Xml; + using System.Xml.XPath; + + /// + /// Utilities class to read and operate on Xml content. + /// + internal class XmlUtilities + { + /// + /// Gets the Inner XML of the specified node. + /// + /// The xml navigator. + /// The xPath of the node. + /// + internal static string GetNodeXml(XPathNavigator runSettingsNavigator, string nodeXPath) + { + var node = runSettingsNavigator.SelectSingleNode(nodeXPath); + return node?.InnerXml; + } + + /// + /// Validates if the Node value is correct according to the provided validator. + /// + /// The node value. + /// The validator. + /// + internal static bool IsValidNodeXmlValue(string xmlNodeValue, Func validator) + { + try + { + return validator.Invoke(xmlNodeValue); + } + catch (ArgumentException) + { + return false; + } + } + + /// + /// If xml node exists with given path, its value is set to innerXml, otherwise a new node is created. + /// + /// Ensure that the navigator is set to right parent. + internal static void AppendOrModifyChild( + XPathNavigator parentNavigator, + string nodeXPath, + string nodeName, + string innerXml) + { + var childNodeNavigator = parentNavigator.SelectSingleNode(nodeXPath); + + // Todo: There isn't an equivalent API to SecurityElement.Escape in Core yet. + // So trusting that the XML is always valid for now. +#if NET46 + var secureInnerXml = SecurityElement.Escape(innerXml); +#else + var secureInnerXml = innerXml; +#endif + if (childNodeNavigator == null) + { + var doc = new XmlDocument(); + var childElement = doc.CreateElement(nodeName); + + if (!string.IsNullOrEmpty(innerXml)) + { + childElement.InnerXml = secureInnerXml; + } + + childNodeNavigator = childElement.CreateNavigator(); + parentNavigator.AppendChild(childNodeNavigator); + } + else if (!string.IsNullOrEmpty(innerXml)) + { + try + { + childNodeNavigator.InnerXml = secureInnerXml; + } + catch (XmlException) + { + // .Net Core has a bug where calling childNodeNavigator.InnerXml throws an XmlException with "Data at the root level is invalid". + // So doing the below instead. + var doc = new XmlDocument(); + + var childElement = doc.CreateElement(nodeName); + + if (!string.IsNullOrEmpty(innerXml)) + { + childElement.InnerXml = secureInnerXml; + } + + childNodeNavigator.ReplaceSelf(childElement.CreateNavigator().OuterXml); + } + } + } + } +} diff --git a/src/Microsoft.TestPlatform.Utilities/project.json b/src/Microsoft.TestPlatform.Utilities/project.json new file mode 100644 index 0000000000..4444d1889f --- /dev/null +++ b/src/Microsoft.TestPlatform.Utilities/project.json @@ -0,0 +1,34 @@ +{ + "version": "15.0.0-*", + + "buildOptions": { + "delaySign": true, + "keyFile": "../../scripts/key.snk", + "warningsAsErrors": true + }, + + "dependencies": { + "Microsoft.TestPlatform.CoreUtilities": "15.0.0-*", + "Microsoft.TestPlatform.ObjectModel": "15.0.0-*" + }, + + "runtimes": { + "win7-x64": { }, + "win7-x86": { } + }, + + "frameworks": { + "netcoreapp1.0": { + "imports": [ + "dnxcore50", + "netstandardapp1.0", + "portable-net45+win8", + "portable-net45+wp80+win8+wpa81+dnxcore50" + ], + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-24008" + } + }, + "net46": { } + } +} diff --git a/src/Microsoft.TestPlatform.VSIXCreator/Microsoft.TestPlatform.VSIXCreator.xproj b/src/Microsoft.TestPlatform.VSIXCreator/Microsoft.TestPlatform.VSIXCreator.xproj new file mode 100644 index 0000000000..d9c351a3fa --- /dev/null +++ b/src/Microsoft.TestPlatform.VSIXCreator/Microsoft.TestPlatform.VSIXCreator.xproj @@ -0,0 +1,21 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + 109f2462-67a2-4418-a4e3-5d5ebc0628f0 + Microsoft.TestPlatform.VSIXCreator + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\ + v4.5.2 + + + + 2.0 + + + diff --git a/src/Microsoft.TestPlatform.VSIXCreator/Program.cs b/src/Microsoft.TestPlatform.VSIXCreator/Program.cs new file mode 100644 index 0000000000..15a9cd41c0 --- /dev/null +++ b/src/Microsoft.TestPlatform.VSIXCreator/Program.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.VSIXCreator +{ + using System; + using System.Collections.Generic; + using System.IO.Compression; + using System.Linq; + using System.Threading.Tasks; + + public class Program + { + public static void Main(string[] args) + { + var inputDirectory = "win7-x64"; + var outputDirectory = System.Environment.CurrentDirectory; + if(args.Length > 0 && !String.IsNullOrEmpty(args[0])) + { + inputDirectory = args[0]; + } + + if (args.Length > 1 && !String.IsNullOrEmpty(args[1])) + { + outputDirectory = args[1]; + } + + var vsixFilePath = System.IO.Path.Combine(outputDirectory, "TestPlatform.vsix"); + if (System.IO.File.Exists(vsixFilePath)) + { + System.IO.File.Delete(vsixFilePath); + } + + if(System.IO.Directory.Exists(inputDirectory)) + { + ZipFile.CreateFromDirectory(inputDirectory, vsixFilePath, CompressionLevel.Fastest, false); + } + } + } +} diff --git a/src/Microsoft.TestPlatform.VSIXCreator/Properties/AssemblyInfo.cs b/src/Microsoft.TestPlatform.VSIXCreator/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..159260a73f --- /dev/null +++ b/src/Microsoft.TestPlatform.VSIXCreator/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +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: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Microsoft.TestPlatform.VSIXCreator")] +[assembly: AssemblyTrademark("")] + +// 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("109f2462-67a2-4418-a4e3-5d5ebc0628f0")] diff --git a/src/Microsoft.TestPlatform.VSIXCreator/VSIXCreator.cmd b/src/Microsoft.TestPlatform.VSIXCreator/VSIXCreator.cmd new file mode 100644 index 0000000000..ced0e5a14b --- /dev/null +++ b/src/Microsoft.TestPlatform.VSIXCreator/VSIXCreator.cmd @@ -0,0 +1,27 @@ +@IF NOT DEFINED _ECHO @ECHO OFF + +@ECHO. + +SET TPBINRELPATH=..\\..\\artifacts\\src\\Microsoft.TestPlatform.VSIXCreator\\bin\\Release + +IF EXIST "%TPBINRELPATH%" ( + IF EXIST "%TPBINRELPATH%\\net461\\Microsoft.TestPlatform.VSIXCreator.exe" ( + PUSHD %TPBINRELPATH%\\net461\\ + Microsoft.TestPlatform.VSIXCreator.exe + POPD + ) +) + +SET TPBINDEBUGPATH=..\\..\\artifacts\\src\\Microsoft.TestPlatform.VSIXCreator\\bin\\Debug + +IF EXIST "%TPBINDEBUGPATH%" ( + IF EXIST "%TPBINDEBUGPATH%\\net461\\Microsoft.TestPlatform.VSIXCreator.exe" ( + PUSHD %TPBINDEBUGPATH%\\net461\\ + Microsoft.TestPlatform.VSIXCreator.exe + POPD + ) +) + + + + diff --git a/src/Microsoft.TestPlatform.VSIXCreator/VSIXDelete.cmd b/src/Microsoft.TestPlatform.VSIXCreator/VSIXDelete.cmd new file mode 100644 index 0000000000..6bf3fbb2ec --- /dev/null +++ b/src/Microsoft.TestPlatform.VSIXCreator/VSIXDelete.cmd @@ -0,0 +1,19 @@ +@IF NOT DEFINED _ECHO @ECHO OFF + +@ECHO. + +SET TPBINRELPATH=..\\..\\artifacts\\src\\Microsoft.TestPlatform.VSIXCreator\\bin\\Release + +IF EXIST "%TPBINRELPATH%" ( + IF EXIST "%TPBINRELPATH%\\net461\\TestPlatform.vsix" ( + DEL "%TPBINRELPATH%\\net461\\TestPlatform.vsix" + ) +) + +SET TPBINDEBUGPATH=..\\..\\artifacts\\src\\Microsoft.TestPlatform.VSIXCreator\\bin\\Debug + +IF EXIST "%TPBINDEBUGPATH%" ( + IF EXIST "%TPBINDEBUGPATH%\\net461\\TestPlatform.vsix" ( + DEL "%TPBINDEBUGPATH%\\net461\\TestPlatform.vsix" + ) +) diff --git a/src/Microsoft.TestPlatform.VSIXCreator/[Content_Types].xml b/src/Microsoft.TestPlatform.VSIXCreator/[Content_Types].xml new file mode 100644 index 0000000000..2b4ba157c7 --- /dev/null +++ b/src/Microsoft.TestPlatform.VSIXCreator/[Content_Types].xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.VSIXCreator/extension.vsixmanifest b/src/Microsoft.TestPlatform.VSIXCreator/extension.vsixmanifest new file mode 100644 index 0000000000..65595c299f --- /dev/null +++ b/src/Microsoft.TestPlatform.VSIXCreator/extension.vsixmanifest @@ -0,0 +1,21 @@ + + + + Microsoft.TestPlatform.V2 + TestPlatform V2 Package + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.VSIXCreator/project.json b/src/Microsoft.TestPlatform.VSIXCreator/project.json new file mode 100644 index 0000000000..d55301fcec --- /dev/null +++ b/src/Microsoft.TestPlatform.VSIXCreator/project.json @@ -0,0 +1,47 @@ +{ + "version": "1.0.0-*", + "buildOptions": { + "emitEntryPoint": true, + "copyToOutput": { + "include": [ + "[Content_Types].xml", + "extension.vsixmanifest", + // Have to drop a time stamped version of testhost config because the build system does not honour the + // users config file when building from a dependent exe. It does honor while building from testhost.x86 project though. + "testhost.x86.exe.config", + "testhost.exe.config" + ] + } + }, + + "scripts": { + "precompile": "VSIXDelete.cmd", + "postcompile": [ "VSIXCreator.cmd" ] + }, + + "frameworks": { + "net461": { + "frameworkAssemblies": { + "System.IO.Compression": "4.0.0.0", + "System.IO.Compression.FileSystem": "4.0.0.0" + } + } + }, + "dependencies": { + "Microsoft.TestPlatform.Client": "15.0.0-*", + "Microsoft.TestPlatform.Common": "15.0.0-*", + "Microsoft.TestPlatform.CommunicationUtilities": "15.0.0-*", + "Microsoft.TestPlatform.CoreUtilities": "15.0.0-*", + "Microsoft.TestPlatform.CrossPlatEngine": "15.0.0-*", + "Microsoft.TestPlatform.ObjectModel": "15.0.0-*", + "Microsoft.TestPlatform.VsTestConsole.TranslationLayer": "15.0.0-*", + "testhost": "15.0.0-*", + "testhost.x86": "15.0.0-*", + "vstest.console": "15.0.0-*", + + "Microsoft.Internal.TestPlatform.Extensions": { + "type": "build", + "version": "15.0.0" + } + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.VSIXCreator/testhost.exe.config b/src/Microsoft.TestPlatform.VSIXCreator/testhost.exe.config new file mode 100644 index 0000000000..9f82ee613f --- /dev/null +++ b/src/Microsoft.TestPlatform.VSIXCreator/testhost.exe.config @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.VSIXCreator/testhost.x86.exe.config b/src/Microsoft.TestPlatform.VSIXCreator/testhost.x86.exe.config new file mode 100644 index 0000000000..8846256c18 --- /dev/null +++ b/src/Microsoft.TestPlatform.VSIXCreator/testhost.x86.exe.config @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Friends.cs b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Friends.cs new file mode 100644 index 0000000000..1f13559dc3 --- /dev/null +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Friends.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Runtime.CompilerServices; + +#region Product Assemblies + +#endregion + +#region Test Assemblies + +[assembly: InternalsVisibleTo("Microsoft.TestPlatform.VsTestConsole.TranslationLayer.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] + +#endregion diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/IProcessManager.cs b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/IProcessManager.cs new file mode 100644 index 0000000000..6b7b7a76cf --- /dev/null +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/IProcessManager.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.VsTestConsole.TranslationLayer.Interfaces +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + + /// + /// Defines the interface that can manage a process + /// + internal interface IProcessManager + { + /// + /// Starts the Process + /// + void StartProcess(string[] args); + + /// + /// Is Process Initialized + /// + /// True, if process initialized + bool IsProcessInitialized(); + + /// + /// Shutdown Process + /// + void ShutdownProcess(); + } +} diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/ITranslationLayerRequestSender.cs b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/ITranslationLayerRequestSender.cs new file mode 100644 index 0000000000..8681536c85 --- /dev/null +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/ITranslationLayerRequestSender.cs @@ -0,0 +1,97 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.VsTestConsole.TranslationLayer.Interfaces +{ + using System; + using System.Collections.Generic; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces; + + /// + /// Defines contract to send test platform requests to test host + /// + internal interface ITranslationLayerRequestSender : IDisposable + { + /// + /// Initializes the communication for sending requests + /// + /// Port Number of the communication channel + int InitializeCommunication(); + + /// + /// Waits for Request Handler to be connected + /// + /// Time to wait for connection + /// True, if Handler is connected + bool WaitForRequestHandlerConnection(int connectionTimeout); + + /// + /// Close the Sender + /// + void Close(); + + /// + /// Initializes the Extensions while probing additional extension paths + /// + /// Paths to check for additional extensions + void InitializeExtensions(IEnumerable pathToAdditionalExtensions); + + /// + /// Discovers the tests + /// + /// Sources for discovering tests + /// EventHandler for discovery events + void DiscoverTests(IEnumerable sources, string runSettings, ITestDiscoveryEventsHandler discoveryEventsHandler); + + /// + /// Starts the TestRun with given sources and criteria + /// + /// Sources for test run + /// RunSettings for test run + /// EventHandler for test run events + void StartTestRun(IEnumerable sources, string runSettings, ITestRunEventsHandler runEventsHandler); + + /// + /// Starts the TestRun with given test cases and criteria + /// + /// TestCases to run + /// RunSettings for test run + /// EventHandler for test run events + void StartTestRun(IEnumerable testCases, string runSettings, ITestRunEventsHandler runEventsHandler); + + /// + /// Starts the TestRun with given sources and criteria with custom test host + /// + /// Sources for test run + /// RunSettings for test run + /// EventHandler for test run events + /// Custom TestHost launcher + void StartTestRunWithCustomHost(IEnumerable sources, string runSettings, ITestRunEventsHandler runEventsHandler, ITestHostLauncher customTestHostLauncher); + + /// + /// Starts the TestRun with given test cases and criteria with custom test host + /// + /// TestCases to run + /// RunSettings for test run + /// EventHandler for test run events + /// Custom TestHost launcher + void StartTestRunWithCustomHost(IEnumerable testCases, string runSettings, ITestRunEventsHandler runEventsHandler, ITestHostLauncher customTestHostLauncher); + + /// + /// Ends the Session + /// + void EndSession(); + + /// + /// Cancel the test run + /// + void CancelTestRun(); + + /// + /// Abort the test run + /// + void AbortTestRun(); + } +} diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/IVsTestConsoleWrapper.cs b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/IVsTestConsoleWrapper.cs new file mode 100644 index 0000000000..e1df75743e --- /dev/null +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/IVsTestConsoleWrapper.cs @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.VsTestConsole.TranslationLayer.Interfaces +{ + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using System.Collections.Generic; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces; + + public interface IVsTestConsoleWrapper + { + /// + /// Starts the VstestConsole process and readies for requests + /// + void StartSession(); + + /// + /// Initialize the TestPlatform with Paths to extensions like adapters, loggers and any other extensions + /// + /// Folder Paths to where extension DLLs are present + void InitializeExtensions(IEnumerable pathToAdditionalExtensions); + + /// + /// Start Discover Tests for the given sources and discovery settings + /// + /// List of source assemblies, files to discover tests + /// Settings XML for test discovery + /// EventHandler to receive discovery events + void DiscoverTests(IEnumerable sources, string discoverySettings, ITestDiscoveryEventsHandler discoveryEventsHandler); + + /// + /// Cancels the last discovery request + /// + void CancelDiscovery(); + + /// + /// Starts a test run given a list of sources + /// + /// Sources to Run tests on + /// RunSettings XML to run the tests + /// EventHandler to receive test run events + void RunTests(IEnumerable sources, string runSettings, ITestRunEventsHandler testRunEventsHandler); + + /// + /// Starts a test run given a list of test cases + /// + /// TestCases to run + /// RunSettings XML to run the tests + /// EventHandler to receive test run events + void RunTests(IEnumerable testCases, string runSettings, ITestRunEventsHandler testRunEventsHandler); + + /// + /// Starts a test run given a list of sources by giving caller an option to start their own test host + /// + /// Sources to Run tests on + /// RunSettings XML to run the tests + /// EventHandler to receive test run events + void RunTestsWithCustomTestHost(IEnumerable sources, string runSettings, ITestRunEventsHandler testRunEventsHandler, ITestHostLauncher customTestHostLauncher); + + /// + /// Starts a test run given a list of test cases by giving caller an option to start their own test host + /// + /// TestCases to run + /// RunSettings XML to run the tests + /// EventHandler to receive test run events + void RunTestsWithCustomTestHost(IEnumerable testCases, string runSettings, ITestRunEventsHandler testRunEventsHandler, ITestHostLauncher customTestHostLauncher); + + /// + /// Cancel the last test run + /// + void CancelTestRun(); + + /// + /// Abort the last test run + /// + void AbortTestRun(); + + /// + /// Ends the VstestConsole session and stops processing requests + /// + void EndSession(); + } +} diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.xproj b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.xproj new file mode 100644 index 0000000000..d180059eaf --- /dev/null +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.xproj @@ -0,0 +1,19 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + b5e3af48-222f-4378-9ed9-f0238e2a278e + Microsoft.TestPlatform.VsTestConsole.TranslationLayer + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\ + v4.5.2 + + + 2.0 + + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Payloads/DiscoveryRequestPayload.cs b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Payloads/DiscoveryRequestPayload.cs new file mode 100644 index 0000000000..52ef60f62e --- /dev/null +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Payloads/DiscoveryRequestPayload.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.VsTestConsole.TranslationLayer.Payloads +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Runtime.Serialization; + using System.Threading.Tasks; + + /// + /// Class used to define the DiscoveryRequestPayload sent by the Vstest.console translation layers into design mode + /// + public class DiscoveryRequestPayload + { + /// + /// Settings used for the discovery request. + /// + [DataMember] + public IEnumerable Sources { get; set; } + + /// + /// Settings used for the discovery request. + /// + [DataMember] + public string RunSettings { get; set; } + } +} diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Payloads/TestRunRequestPayload.cs b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Payloads/TestRunRequestPayload.cs new file mode 100644 index 0000000000..0d06afebd3 --- /dev/null +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Payloads/TestRunRequestPayload.cs @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.VsTestConsole.TranslationLayer.Payloads +{ + using System.Collections.Generic; + using System.Runtime.Serialization; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + + /// + /// Class used to define the TestRunRequestPayload sent by the Vstest.console translation layers into design mode + /// + public class TestRunRequestPayload + { + /// + /// Gets or sets the sources for the test run request. + /// + /// + /// Making this a list instead of an IEnumerable because the json serializer fails to deserialize + /// if a linq query outputs the IEnumerable. + /// + [DataMember] + public List Sources { get; set; } + + /// + /// Gets or sets the test cases for the test run request. + /// + /// + /// Making this a list instead of an IEnumerable because the json serializer fails to deserialize + /// if a linq query outputs the IEnumerable. + /// + [DataMember] + public List TestCases { get; set; } + + /// + /// Gets or sets the settings used for the test run request. + /// + [DataMember] + public string RunSettings { get; set; } + + /// + /// Settings used for the Run request. + /// + [DataMember] + public bool KeepAlive { get; set; } + + /// + /// Is Debugging enabled + /// + [DataMember] + public bool DebuggingEnabled { get; set; } + } +} diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Properties/AssemblyInfo.cs b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..f2bc6561e6 --- /dev/null +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +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: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Microsoft.TestPlatform.ManagedConsoleWrapper")] +[assembly: AssemblyTrademark("")] + +// 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("b5e3af48-222f-4378-9ed9-f0238e2a278e")] diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/TransationLayerException.cs b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/TransationLayerException.cs new file mode 100644 index 0000000000..290300c13e --- /dev/null +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/TransationLayerException.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.VsTestConsole.TranslationLayer +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + + public class TransationLayerException : Exception + { + public TransationLayerException(string message) + : base(message) + { + } + + public TransationLayerException(string message, Exception innerException) + : base(message, innerException) + { + } + } +} diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/VsTestConsoleProcessManager.cs b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/VsTestConsoleProcessManager.cs new file mode 100644 index 0000000000..d6a2032403 --- /dev/null +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/VsTestConsoleProcessManager.cs @@ -0,0 +1,92 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.VsTestConsole.TranslationLayer +{ + using Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using System; + using System.Diagnostics; + using System.Globalization; + + /// + /// Vstest.console.exe process manager + /// + internal class VsTestConsoleProcessManager : IProcessManager + { + private string vstestConsolePath; + + private object syncObject = new object(); + + private bool vstestConsoleStarted = false; + + private bool vstestConsoleCrashed = false; + + private Process process; + + #region Constructor + + public VsTestConsoleProcessManager(string vstestConsolePath) + { + this.vstestConsolePath = vstestConsolePath; + } + + #endregion Constructor + + public bool IsProcessInitialized() + { + lock(syncObject) + { + return this.vstestConsoleStarted && !vstestConsoleCrashed && + this.process != null && !this.process.HasExited; + } + } + + /// + /// Call xUnit.console.exe with the parameters previously specified + /// + public void StartProcess(string[] args) + { + using (this.process = new Process()) + { + process.StartInfo.FileName = vstestConsolePath; + if (args != null) + { + process.StartInfo.Arguments = args.Length < 2 ? args[0] : string.Join(" ", args); + } + //process.StartInfo.WorkingDirectory = WorkingDirectory; + process.StartInfo.UseShellExecute = false; + process.StartInfo.CreateNoWindow = true; + + //process.StartInfo.RedirectStandardOutput = true; + //process.StartInfo.RedirectStandardError = true; + + EqtTrace.Verbose("VsTestCommandLineWrapper: {0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments); + + process.Exited += Process_Exited; + process.Start(); + + lock (syncObject) + { + vstestConsoleStarted = true; + } + } + } + + public void ShutdownProcess() + { + // Ideally process should die by itself + if(IsProcessInitialized()) + { + this.process.Kill(); + } + } + + private void Process_Exited(object sender, EventArgs e) + { + lock (syncObject) + { + vstestConsoleCrashed = true; + } + } + } +} diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/VsTestConsoleRequestSender.cs b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/VsTestConsoleRequestSender.cs new file mode 100644 index 0000000000..43781594e0 --- /dev/null +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/VsTestConsoleRequestSender.cs @@ -0,0 +1,321 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.VsTestConsole.TranslationLayer +{ + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using System; + using System.Linq; + using System.Collections.Generic; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel; + using System.Threading.Tasks; + using System.Threading; + using Microsoft.TestPlatform.VsTestConsole.TranslationLayer.Payloads; + using Microsoft.TestPlatform.VsTestConsole.TranslationLayer.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces; + + /// + /// VstestConsoleRequestSender for sending requests to Vstest.console.exe + /// + internal class VsTestConsoleRequestSender : ITranslationLayerRequestSender + { + private ICommunicationManager communicationManager; + + private IDataSerializer dataSerializer; + + private ManualResetEvent handShakeComplete = new ManualResetEvent(false); + + private bool handShakeSuccessful = false; + + #region Constructor + + public VsTestConsoleRequestSender() : this(new SocketCommunicationManager(), JsonDataSerializer.Instance) + { + } + + internal VsTestConsoleRequestSender(ICommunicationManager communicationManager, IDataSerializer dataSerializer) + { + this.communicationManager = communicationManager; + this.dataSerializer = dataSerializer; + } + + #endregion + + #region ITranslationLayerRequestSender + + /// + /// Initializes Communication with vstest.console.exe + /// Hosts a communication channel and asynchronously connects to vstest.console.exe + /// + /// Port Number of hosted server on this side + public int InitializeCommunication() + { + this.handShakeSuccessful = false; + this.handShakeComplete.Reset(); + int port = -1; + try + { + port = this.communicationManager.HostServer(); + this.communicationManager.AcceptClientAsync(); + + Task.Run(() => + { + this.communicationManager.WaitForClientConnection(Timeout.Infinite); + handShakeSuccessful = HandShakeWithVsTestConsole(); + this.handShakeComplete.Set(); + }); + } + catch (Exception ex) + { + EqtTrace.Error("VsTestConsoleRequestSender: Error initializing communication with VstestConsole: {0}", ex); + this.handShakeComplete.Set(); + } + + return port; + } + + /// + /// Waits for Vstest.console.exe Connection for a given timeout + /// + /// Time to wait for the connection + /// True, if successful + public bool WaitForRequestHandlerConnection(int clientConnectionTimeout) + { + var waitSucess = this.handShakeComplete.WaitOne(clientConnectionTimeout); + return waitSucess && handShakeSuccessful; + } + + /// + /// Initializes the Extensions while probing additional extension paths + /// + /// Paths to check for additional extensions + public void InitializeExtensions(IEnumerable pathToAdditionalExtensions) + { + this.communicationManager.SendMessage(MessageType.ExtensionsInitialize, pathToAdditionalExtensions); + } + + /// + /// Discover Tests using criteria and send events through eventHandler + /// + /// + /// + public void DiscoverTests(IEnumerable sources, string runSettings, ITestDiscoveryEventsHandler eventHandler) + { + this.communicationManager.SendMessage(MessageType.StartDiscovery, new DiscoveryRequestPayload() { Sources = sources, RunSettings = runSettings }); + this.ListenAndReportTestCases(eventHandler); + } + + /// + /// Starts the TestRun with given sources and criteria + /// + /// Sources for test run + /// RunSettings for test run + /// EventHandler for test run events + public void StartTestRun(IEnumerable sources, string runSettings, ITestRunEventsHandler runEventsHandler) + { + this.communicationManager.SendMessage(MessageType.TestRunAllSourcesWithDefaultHost, + new TestRunRequestPayload() { Sources = sources.ToList(), RunSettings = runSettings }); + ListenAndReportTestResults(runEventsHandler, null); + } + + /// + /// Starts the TestRun with given test cases and criteria + /// + /// TestCases to run + /// RunSettings for test run + /// EventHandler for test run events + public void StartTestRun(IEnumerable testCases, string runSettings, ITestRunEventsHandler runEventsHandler) + { + this.communicationManager.SendMessage(MessageType.TestRunSelectedTestCasesDefaultHost, + new TestRunRequestPayload() { TestCases = testCases.ToList(), RunSettings = runSettings }); + ListenAndReportTestResults(runEventsHandler, null); + } + + /// + /// Starts the TestRun with given sources and criteria with custom test host + /// + /// Sources for test run + /// RunSettings for test run + /// EventHandler for test run events + public void StartTestRunWithCustomHost(IEnumerable sources, string runSettings, ITestRunEventsHandler runEventsHandler, + ITestHostLauncher customHostLauncher) + { + this.communicationManager.SendMessage(MessageType.GetTestRunnerProcessStartInfoForRunAll, + new TestRunRequestPayload() { Sources = sources.ToList(), RunSettings = runSettings, DebuggingEnabled = customHostLauncher.IsDebug }); + ListenAndReportTestResults(runEventsHandler, customHostLauncher); + } + + /// + /// Starts the TestRun with given test cases and criteria with custom test host + /// + /// TestCases to run + /// RunSettings for test run + /// EventHandler for test run events + public void StartTestRunWithCustomHost(IEnumerable testCases, string runSettings, ITestRunEventsHandler runEventsHandler, + ITestHostLauncher customHostLauncher) + { + this.communicationManager.SendMessage(MessageType.GetTestRunnerProcessStartInfoForRunSelected, + new TestRunRequestPayload() { TestCases = testCases.ToList(), RunSettings = runSettings, DebuggingEnabled = customHostLauncher.IsDebug }); + ListenAndReportTestResults(runEventsHandler, customHostLauncher); + } + + + /// + /// Send Cancel TestRun message + /// + public void CancelTestRun() + { + this.communicationManager.SendMessage(MessageType.CancelTestRun); + } + + + /// + /// Send Abort TestRun message + /// + public void AbortTestRun() + { + this.communicationManager.SendMessage(MessageType.AbortTestRun); + } + + public void Close() + { + this.Dispose(); + } + + public void EndSession() + { + this.communicationManager.SendMessage(MessageType.SessionEnd); + } + + public void Dispose() + { + this.communicationManager?.StopServer(); + } + + #endregion + + private bool HandShakeWithVsTestConsole() + { + var success = false; + var message = this.communicationManager.ReceiveMessage(); + if (message.MessageType == MessageType.SessionConnected) + { + this.communicationManager.SendMessage(MessageType.VersionCheck); + message = this.communicationManager.ReceiveMessage(); + + if (message.MessageType == MessageType.VersionCheck) + { + var testPlatformVersion = this.dataSerializer.DeserializePayload(message); + success = testPlatformVersion == 1; + if (!success) + { + EqtTrace.Error("VsTestConsoleRequestSender: VersionCheck Failed. TestPlatform Version: {0}", testPlatformVersion); + } + } + else + { + EqtTrace.Error("VsTestConsoleRequestSender: VersionCheck Message Expected but different message received: Received MessageType: {0}", message.MessageType); + } + } + else + { + EqtTrace.Error("VsTestConsoleRequestSender: SessionConnected Message Expected but different message received: Received MessageType: {0}", message.MessageType); + } + return success; + } + + private void ListenAndReportTestCases(ITestDiscoveryEventsHandler eventHandler) + { + var isDiscoveryComplete = false; + // Cycle through the messages that the vstest.console sends. + // Currently each of the operations are not separate tasks since they should not each take much time. + // This is just a notification. + while (!isDiscoveryComplete) + { + try + { + var message = this.communicationManager.ReceiveMessage(); + if (string.Equals(MessageType.TestCasesFound, message.MessageType)) + { + var testCases = this.dataSerializer.DeserializePayload>(message); + + eventHandler.HandleDiscoveredTests(testCases); + } + else if (string.Equals(MessageType.DiscoveryComplete, message.MessageType)) + { + var discoveryCompletePayload = this.dataSerializer.DeserializePayload(message); + + eventHandler.HandleDiscoveryComplete(discoveryCompletePayload.TotalTests, discoveryCompletePayload.LastDiscoveredTests, discoveryCompletePayload.IsAborted); + isDiscoveryComplete = true; + } + else if (string.Equals(MessageType.TestMessage, message.MessageType)) + { + var testMessagePayload = this.dataSerializer.DeserializePayload(message); + eventHandler.HandleLogMessage(testMessagePayload.MessageLevel, testMessagePayload.Message); + } + } + catch(Exception ex) + { + EqtTrace.Error("VsTestConsoleRequestSender: TestDiscovery: Message Deserialization failed with {0}", ex); + // notify of a discovery complete and bail out. + eventHandler.HandleDiscoveryComplete(0, null, false); + isDiscoveryComplete = true; + } + } + } + + private void ListenAndReportTestResults(ITestRunEventsHandler eventHandler, ITestHostLauncher customHostLauncher) + { + var isTestRunComplete = false; + + // Cycle through the messages that the testhost sends. + // Currently each of the operations are not separate tasks since they should not each take much time. This is just a notification. + while (!isTestRunComplete) + { + try + { + var message = this.communicationManager.ReceiveMessage(); + + if (string.Equals(MessageType.TestRunStatsChange, message.MessageType)) + { + var testRunChangedArgs = this.dataSerializer.DeserializePayload(message); + eventHandler.HandleTestRunStatsChange(testRunChangedArgs); + } + else if (string.Equals(MessageType.ExecutionComplete, message.MessageType)) + { + var testRunCompletePayload = this.dataSerializer.DeserializePayload(message); + + eventHandler.HandleTestRunComplete( + testRunCompletePayload.TestRunCompleteArgs, + testRunCompletePayload.LastRunTests, + testRunCompletePayload.RunAttachments, + testRunCompletePayload.ExecutorUris); + isTestRunComplete = true; + } + else if (string.Equals(MessageType.TestMessage, message.MessageType)) + { + var testMessagePayload = this.dataSerializer.DeserializePayload(message); + eventHandler.HandleLogMessage(testMessagePayload.MessageLevel, testMessagePayload.Message); + } + else if (string.Equals(MessageType.CustomTestHostLaunch, message.MessageType)) + { + var testProcessStartInfo = this.dataSerializer.DeserializePayload(message); + + int processId = (customHostLauncher != null) ? customHostLauncher.LaunchTestHost(testProcessStartInfo) : -1; + this.communicationManager.SendMessage(MessageType.CustomTestHostLaunchCallback, processId); + } + } + catch (Exception exception) + { + EqtTrace.Error("VsTestConsoleRequestSender: TestExecution: Error Processing Request from DesignModeClient: {0}", exception); + // notify of a test run complete and bail out. + eventHandler.HandleTestRunComplete(new TestRunCompleteEventArgs(null, false, true, exception, null, TimeSpan.MinValue), null, null, null); + isTestRunComplete = true; + } + } + } + } +} + diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/VsTestConsoleWrapper.cs b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/VsTestConsoleWrapper.cs new file mode 100644 index 0000000000..07da096c1b --- /dev/null +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/VsTestConsoleWrapper.cs @@ -0,0 +1,144 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.VsTestConsole.TranslationLayer +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Threading.Tasks; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.TestPlatform.VsTestConsole.TranslationLayer.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces; + + /// + /// Vstest.console.exe Wrapper + /// + public class VsTestConsoleWrapper : IVsTestConsoleWrapper + { + #region Private Members + + private IProcessManager vstestConsoleProcessManager; + + private ITranslationLayerRequestSender requestSender; + + private bool sessionStarted; + + private const int ConnectionTimeout = 30 * 1000; + + private const string PORT_ARGUMENT = "/port:{0}"; + + #endregion + + #region Constructor + + public VsTestConsoleWrapper(string vstestConsolePath) : + this(new VsTestConsoleRequestSender(), new VsTestConsoleProcessManager(vstestConsolePath)) + { + } + + internal VsTestConsoleWrapper(ITranslationLayerRequestSender requestSender, IProcessManager processManager) + { + this.requestSender = requestSender; + this.vstestConsoleProcessManager = processManager; + this.sessionStarted = false; + } + + #endregion + + #region IVsTestConsoleWrapper + + public void StartSession() + { + // Start communication + var port = this.requestSender.InitializeCommunication(); + + if (port > 0) + { + // Start Vstest.console.exe + string args = string.Format(CultureInfo.InvariantCulture, PORT_ARGUMENT, port); + this.vstestConsoleProcessManager.StartProcess(new string[1] { args }); + } + else + { + // Close the sender as it failed to host server + this.requestSender.Close(); + throw new TransationLayerException("Error hosting communication channel"); + } + } + + public void InitializeExtensions(IEnumerable pathToAdditionalExtensions) + { + EnsureInitialized(); + this.requestSender.InitializeExtensions(pathToAdditionalExtensions); + } + + public void DiscoverTests(IEnumerable sources, string discoverySettings, ITestDiscoveryEventsHandler discoveryEventsHandler) + { + EnsureInitialized(); + this.requestSender.DiscoverTests(sources, discoverySettings, discoveryEventsHandler); + } + + public void CancelDiscovery() + { + // TODO: Cancel Discovery + //this.requestSender.CancelDiscovery(); + } + + public void RunTests(IEnumerable sources, string runSettings, ITestRunEventsHandler testRunEventsHandler) + { + EnsureInitialized(); + this.requestSender.StartTestRun(sources, runSettings, testRunEventsHandler); + } + + public void RunTests(IEnumerable testCases, string runSettings, ITestRunEventsHandler testRunEventsHandler) + { + EnsureInitialized(); + this.requestSender.StartTestRun(testCases, runSettings, testRunEventsHandler); + } + + public void RunTestsWithCustomTestHost(IEnumerable sources, string runSettings, ITestRunEventsHandler testRunEventsHandler, ITestHostLauncher customTestHostLauncher) + { + EnsureInitialized(); + this.requestSender.StartTestRunWithCustomHost(sources, runSettings, testRunEventsHandler, customTestHostLauncher); + } + + public void RunTestsWithCustomTestHost(IEnumerable testCases, string runSettings, ITestRunEventsHandler testRunEventsHandler, ITestHostLauncher customTestHostLauncher) + { + EnsureInitialized(); + this.requestSender.StartTestRunWithCustomHost(testCases, runSettings, testRunEventsHandler, customTestHostLauncher); + } + + public void CancelTestRun() + { + this.requestSender.CancelTestRun(); + } + + public void AbortTestRun() + { + this.requestSender.AbortTestRun(); + } + + public void EndSession() + { + this.requestSender.EndSession(); + this.requestSender.Close(); + this.sessionStarted = false; + } + + #endregion + + private void EnsureInitialized() + { + if (!this.sessionStarted && this.requestSender != null) + { + this.sessionStarted = this.requestSender.WaitForRequestHandlerConnection(ConnectionTimeout); + } + + if(!this.sessionStarted) + { + throw new TransationLayerException("Error connecting to Vstest Command Line"); + } + } + } +} diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/project.json b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/project.json new file mode 100644 index 0000000000..a2f8c5788b --- /dev/null +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/project.json @@ -0,0 +1,40 @@ +{ + "version": "15.0.0-*", + "buildOptions": { + "delaySign": true, + "keyFile": "../../scripts/key.snk", + "warningsAsErrors": true + }, + + "frameworks": { + "netstandard1.5": { + "imports": [ + "dnxcore50", + "portable-net45+win8" + ], + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-24008", + "System.Runtime.Serialization.Primitives": "4.0.10", + "System.Runtime.Extensions": "4.1.0-rc3-23808", + "System.Xml.XPath.XmlDocument": "4.0.1-rc2-24027", + "System.ComponentModel.EventBasedAsync": "4.0.11-rc2-24018", + "System.Runtime.InteropServices": "4.1.0-rc2-24027", + "System.IO.FileSystem": "4.0.1-rc3-23808", + "System.ComponentModel.TypeConverter": "4.0.1-rc2-23911", + "System.Diagnostics.Process": "4.1.0-rc2-23704", + "Microsoft.TestPlatform.CommunicationUtilities": "15.0.0-*", + "Microsoft.TestPlatform.ObjectModel": "15.0.0-*", + } + }, + "net46": { + "frameworkAssemblies": { + "System.Xml": "", + "System.Runtime.Serialization": "" + }, + "dependencies": { + "Microsoft.TestPlatform.CommunicationUtilities": "15.0.0-*", + "Microsoft.TestPlatform.ObjectModel": "15.0.0-*", + } + } + } +} diff --git a/src/datacollector.x86/Program.cs b/src/datacollector.x86/Program.cs new file mode 100644 index 0000000000..80c99e1fdb --- /dev/null +++ b/src/datacollector.x86/Program.cs @@ -0,0 +1,89 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.DataCollector.x86 +{ + using System; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.DataCollection; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.DataCollection; + + /// + /// The program. + /// + public class Program + { + /// + /// The timeout for the client to connect to the server. + /// + private const int ClientListenTimeOut = 5 * 1000; + + /// + /// Port where vstest.console is listening + /// + private static int port; + + /// + /// The main. + /// + /// + /// The args. + /// + public static void Main(string[] args) + { + try + { + ParseArgs(args); + Run(); + } + catch (Exception ex) + { + EqtTrace.Error("DataCollector: Error occured during initialization of Datacollector : {0}", ex); + } + } + + /// + /// Parse args. + /// + /// The args. + private static void ParseArgs(string[] args) + { + port = -1; + + for (var i = 0; i < args.Length; i++) + { + if (string.Equals("--port", args[i], StringComparison.OrdinalIgnoreCase) || string.Equals("-p", args[i], StringComparison.OrdinalIgnoreCase)) + { + if (i < args.Length - 1) + { + int.TryParse(args[i + 1], out port); + } + + break; + } + } + + if (port < 0) + { + throw new ArgumentException("Incorrect/No Port number"); + } + } + + private static void Run() + { + var requestHandler = new DataCollectionRequestHandler(); + requestHandler.InitializeCommunication(port); + + // Wait for the connection to the sender and start processing requests from sender + if (requestHandler.WaitForRequestSenderConnection(ClientListenTimeOut)) + { + requestHandler.ProcessRequests(); + } + else + { + EqtTrace.Info("DataCollector: RequestHandler timed out while connecting to the Sender."); + requestHandler.Close(); + throw new TimeoutException(); + } + } + } +} \ No newline at end of file diff --git a/src/datacollector.x86/Properties/AssemblyInfo.cs b/src/datacollector.x86/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..4c7808d6a7 --- /dev/null +++ b/src/datacollector.x86/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +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: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("datacollector.x86")] +[assembly: AssemblyTrademark("")] + +// 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("00dfb5c7-3850-4a65-986b-713f200482d4")] diff --git a/src/datacollector.x86/app.config b/src/datacollector.x86/app.config new file mode 100644 index 0000000000..8846256c18 --- /dev/null +++ b/src/datacollector.x86/app.config @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/datacollector.x86/datacollector.x86.xproj b/src/datacollector.x86/datacollector.x86.xproj new file mode 100644 index 0000000000..1695165499 --- /dev/null +++ b/src/datacollector.x86/datacollector.x86.xproj @@ -0,0 +1,19 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 00dfb5c7-3850-4a65-986b-713f200482d4 + Microsoft.VisualStudio.TestPlatform.DataCollector.x86 + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\ + v4.5.2 + + + 2.0 + + + \ No newline at end of file diff --git a/src/datacollector.x86/project.json b/src/datacollector.x86/project.json new file mode 100644 index 0000000000..423caa4323 --- /dev/null +++ b/src/datacollector.x86/project.json @@ -0,0 +1,39 @@ +{ + "version": "15.0.0-*", + + "buildOptions": { + "emitEntryPoint": true, + "delaySign": true, + "keyFile": "../../scripts/key.snk", + "warningsAsErrors": true, + "platform": "x86" + }, + + "dependencies": { + "Microsoft.TestPlatform.CommunicationUtilities": "15.0.0-*", + "Microsoft.TestPlatform.ObjectModel": "15.0.0-*", + "Microsoft.TestPlatform.CrossPlatEngine": "15.0.0-*" + }, + + "runtimes": { + "win7-x64": { }, + "win7-x86": { } + }, + + "frameworks": { + "netcoreapp1.0": { + "imports": [ + "dnxcore50", + "netstandardapp1.5", + "portable-net45+win8", + "portable-net45+wp80+win8+wpa81+dnxcore50" + ], + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-24027", + "Microsoft.DotNet.ProjectModel": "1.0.0-rc2-002702", + "System.ComponentModel.TypeConverter": "4.0.1-rc2-23911" + } + }, + "net46": {} + } +} diff --git a/src/datacollector/Properties/AssemblyInfo.cs b/src/datacollector/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..627d592195 --- /dev/null +++ b/src/datacollector/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +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: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("datacollector")] +[assembly: AssemblyTrademark("")] + +// 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("3572e78c-5aa5-4f68-876d-fc5322677263")] diff --git a/src/datacollector/app.config b/src/datacollector/app.config new file mode 100644 index 0000000000..49ee151f08 --- /dev/null +++ b/src/datacollector/app.config @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/datacollector/datacollector.xproj b/src/datacollector/datacollector.xproj new file mode 100644 index 0000000000..38a2d2379e --- /dev/null +++ b/src/datacollector/datacollector.xproj @@ -0,0 +1,19 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 3572e78c-5aa5-4f68-876d-fc5322677263 + Microsoft.VisualStudio.TestPlatform.DataCollector + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\ + v4.5.2 + + + 2.0 + + + \ No newline at end of file diff --git a/src/datacollector/project.json b/src/datacollector/project.json new file mode 100644 index 0000000000..0d55f02ea9 --- /dev/null +++ b/src/datacollector/project.json @@ -0,0 +1,40 @@ +{ + "version": "15.0.0-*", + + "buildOptions": { + "emitEntryPoint": true, + "delaySign": true, + "keyFile": "../../scripts/key.snk", + "warningsAsErrors": true, + "compile": [ + "../datacollector.x86/Program.cs" + ] + }, + + "dependencies": { + "Microsoft.TestPlatform.CommunicationUtilities": "15.0.0-*", + "Microsoft.TestPlatform.ObjectModel": "15.0.0-*", + "Microsoft.TestPlatform.CrossPlatEngine": "15.0.0-*" + }, + + "runtimes": { + "win7-x64": { }, + "win7-x86": { } + }, + + "frameworks": { + "netcoreapp1.0": { + "imports": [ + "dnxcore50", + "netstandardapp1.5", + "portable-net45+win8", + "portable-net45+wp80+win8+wpa81+dnxcore50" + ], + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-24027", + "System.ComponentModel.TypeConverter": "4.0.1-rc2-23911" + } + }, + "net46": { } + } +} diff --git a/src/package/[Content_Types].xml b/src/package/[Content_Types].xml new file mode 100644 index 0000000000..2b4ba157c7 --- /dev/null +++ b/src/package/[Content_Types].xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/package/extension.vsixmanifest b/src/package/extension.vsixmanifest new file mode 100644 index 0000000000..65595c299f --- /dev/null +++ b/src/package/extension.vsixmanifest @@ -0,0 +1,21 @@ + + + + Microsoft.TestPlatform.V2 + TestPlatform V2 Package + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/package/project.json b/src/package/project.json new file mode 100644 index 0000000000..90b1e30904 --- /dev/null +++ b/src/package/project.json @@ -0,0 +1,40 @@ +{ + "version": "1.0.0-*", + "buildOptions": { + "emitEntryPoint": true, + "copyToOutput": { + "include": [ + "[Content_Types].xml", + "extension.vsixmanifest", + // Have to drop a time stamped version of testhost config because the build system does not honour the + // users config file when building from a dependent exe. It does honor while building from testhost.x86 project though. + "testhost.x86.exe.config", + "testhost.exe.config" + ] + } + }, + + "frameworks": { + "net46": { + "frameworkAssemblies": { + } + } + }, + "dependencies": { + "Microsoft.TestPlatform.Client": "15.0.0-*", + "Microsoft.TestPlatform.Common": "15.0.0-*", + "Microsoft.TestPlatform.CommunicationUtilities": "15.0.0-*", + "Microsoft.TestPlatform.CoreUtilities": "15.0.0-*", + "Microsoft.TestPlatform.CrossPlatEngine": "15.0.0-*", + "Microsoft.TestPlatform.ObjectModel": "15.0.0-*", + "Microsoft.TestPlatform.VsTestConsole.TranslationLayer": "15.0.0-*", + "testhost": "15.0.0-*", + "testhost.x86": "15.0.0-*", + "vstest.console": "15.0.0-*", + + "Microsoft.Internal.TestPlatform.Extensions": { + "type": "build", + "version": "15.0.0" + } + } +} diff --git a/src/package/sign/project.json b/src/package/sign/project.json new file mode 100644 index 0000000000..872adb1df4 --- /dev/null +++ b/src/package/sign/project.json @@ -0,0 +1,11 @@ +{ + "dependencies": { + "MicroBuild.Core": "0.2.0", + }, + "frameworks": { + "net46": {} + }, + "runtimes": { + "win": {} + } +} diff --git a/src/package/sign/sign.proj b/src/package/sign/sign.proj new file mode 100644 index 0000000000..d7cebd03ff --- /dev/null +++ b/src/package/sign/sign.proj @@ -0,0 +1,95 @@ + + + + Test + 0.2.0 + + + + $(MSBuildThisFileDirectory)..\..\..\ + Release + net46 + win7-x64 + False + + $(RootDirectory)packages\ + $(RootDirectory)artifacts\$(BuildConfiguration)\$(TargetFramework)\$(TargetRuntime)\ + $(RootDirectory)artifacts\obj\$(BuildConfiguration)\$(TargetFramework)\$(TargetRuntime)\ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Microsoft + StrongName + + + + + + + + + + $(ArtifactsDirectory)..\..\ + + + + + VsixSHA2 + + + + + + + diff --git a/src/package/testhost.exe.config b/src/package/testhost.exe.config new file mode 100644 index 0000000000..9f82ee613f --- /dev/null +++ b/src/package/testhost.exe.config @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/package/testhost.x86.exe.config b/src/package/testhost.x86.exe.config new file mode 100644 index 0000000000..8846256c18 --- /dev/null +++ b/src/package/testhost.x86.exe.config @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/testhost.x86/Program.cs b/src/testhost.x86/Program.cs new file mode 100644 index 0000000000..94bacb8603 --- /dev/null +++ b/src/testhost.x86/Program.cs @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.TestHost +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Net.Sockets; + + using CrossPlatEngine; + + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + + /// + /// The program. + /// + public class Program + { + /// + /// The timeout for the client to connect to the server. + /// + private const int ClientListenTimeOut = 5 * 1000; + + /// + /// The main. + /// + /// + /// The args. + /// + public static void Main(string[] args) + { + try + { + Run(args); + } + catch (Exception ex) + { + EqtTrace.Error("TestHost: Error occured during initialization of TestHost : {0}", ex); + } + } + + /// + /// Get port number from command line arguments + /// + /// command line arguments + /// port number + private static int GetPortNumber(string[] args) + { + var port = -1; + + for (var i = 0; i < args.Length; i++) + { + if (string.Equals("--port", args[i], StringComparison.OrdinalIgnoreCase) || string.Equals("-p", args[i], StringComparison.OrdinalIgnoreCase)) + { + if (i < args.Length - 1) + { + int.TryParse(args[i + 1], out port); + } + + break; + } + } + + if (port < 0) + { + throw new ArgumentException("Incorrect/No Port number"); + } + + return port; + } + + private static void Run(string[] args) + { + var portNumber = GetPortNumber(args); + + var requestHandler = new TestRequestHandler(); + requestHandler.InitializeCommunication(portNumber); + + // setup the factory. + var managerFactory = new TestHostManagerFactory(); + + // Wait for the connection to the sender and start processing requests from sender + if (requestHandler.WaitForRequestSenderConnection(ClientListenTimeOut)) + { + requestHandler.ProcessRequests(managerFactory); + } + else + { + EqtTrace.Info("TestHost: RequestHandler timed out while connecting to the Sender."); + requestHandler.Close(); + throw new TimeoutException(); + } + } + } +} diff --git a/src/testhost.x86/Properties/AssemblyInfo.cs b/src/testhost.x86/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..31a959be2c --- /dev/null +++ b/src/testhost.x86/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +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: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Microsoft.TestHost.x86")] +[assembly: AssemblyTrademark("")] + +// 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("d00e4b49-b503-496c-9f48-f425ae05e049")] \ No newline at end of file diff --git a/src/testhost.x86/app.config b/src/testhost.x86/app.config new file mode 100644 index 0000000000..8846256c18 --- /dev/null +++ b/src/testhost.x86/app.config @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/testhost.x86/project.json b/src/testhost.x86/project.json new file mode 100644 index 0000000000..16cc8b23f3 --- /dev/null +++ b/src/testhost.x86/project.json @@ -0,0 +1,38 @@ +{ + "version": "15.0.0-*", + + "buildOptions": { + "emitEntryPoint": true, + "delaySign": true, + "keyFile": "../../scripts/key.snk", + "warningsAsErrors": true, + "platform": "x86" + }, + + "dependencies": { + "Microsoft.TestPlatform.CommunicationUtilities": "15.0.0-*", + "Microsoft.TestPlatform.ObjectModel": "15.0.0-*", + "Microsoft.TestPlatform.CrossPlatEngine": "15.0.0-*" + }, + + "runtimes": { + "win7-x64": { }, + "win7-x86": { } + }, + + "frameworks": { + "netcoreapp1.0": { + "imports": [ + "dnxcore50", + "netstandardapp1.5", + "portable-net45+win8", + "portable-net45+wp80+win8+wpa81+dnxcore50" + ], + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-24027", + "System.ComponentModel.TypeConverter": "4.0.1-rc2-23911" + } + }, + "net46": {} + } +} diff --git a/src/testhost.x86/testhost.x86.xproj b/src/testhost.x86/testhost.x86.xproj new file mode 100644 index 0000000000..6c9af5aa41 --- /dev/null +++ b/src/testhost.x86/testhost.x86.xproj @@ -0,0 +1,21 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + f69b451d-ddb5-43e3-844b-dad8d4dadefc + testhost.x86 + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\ + v4.5.2 + + + + 2.0 + + + diff --git a/src/testhost/Properties/AssemblyInfo.cs b/src/testhost/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..a5fd8d0ef5 --- /dev/null +++ b/src/testhost/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +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: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Microsoft.TestHost")] +[assembly: AssemblyTrademark("")] + +// 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("99d82e36-6b51-4db9-bd37-adbf373125ed")] diff --git a/src/testhost/app.config b/src/testhost/app.config new file mode 100644 index 0000000000..8846256c18 --- /dev/null +++ b/src/testhost/app.config @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/testhost/project.json b/src/testhost/project.json new file mode 100644 index 0000000000..5a823092bd --- /dev/null +++ b/src/testhost/project.json @@ -0,0 +1,40 @@ +{ + "version": "15.0.0-*", + + "buildOptions": { + "emitEntryPoint": true, + "delaySign": true, + "keyFile": "../../scripts/key.snk", + "warningsAsErrors": true, + "compile": [ + "../testhost.x86/Program.cs" + ] + }, + + "dependencies": { + "Microsoft.TestPlatform.CommunicationUtilities": "15.0.0-*", + "Microsoft.TestPlatform.ObjectModel": "15.0.0-*", + "Microsoft.TestPlatform.CrossPlatEngine": "15.0.0-*" + }, + + "runtimes": { + "win7-x64": { }, + "win7-x86": { } + }, + + "frameworks": { + "netcoreapp1.0": { + "imports": [ + "dnxcore50", + "netstandardapp1.5", + "portable-net45+win8", + "portable-net45+wp80+win8+wpa81+dnxcore50" + ], + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-24027", + "System.ComponentModel.TypeConverter": "4.0.1-rc2-23911" + } + }, + "net46": {} + } +} diff --git a/src/testhost/testhost.xproj b/src/testhost/testhost.xproj new file mode 100644 index 0000000000..3cfa55c761 --- /dev/null +++ b/src/testhost/testhost.xproj @@ -0,0 +1,21 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + 99d82e36-6b51-4db9-bd37-adbf373125ed + Microsoft.VisualStudio.TestPlatform.TestHost + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\ + v4.5.2 + + + + 2.0 + + + diff --git a/src/vstest.console/CommandLine/CommandArgumentPair.cs b/src/vstest.console/CommandLine/CommandArgumentPair.cs new file mode 100644 index 0000000000..e129d2710e --- /dev/null +++ b/src/vstest.console/CommandLine/CommandArgumentPair.cs @@ -0,0 +1,106 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine +{ + using System; + using System.Diagnostics.Contracts; + + /// + /// Breaks a string down into command and argument based on the following format: + /// /command:argument. + /// + internal class CommandArgumentPair + { + #region Constants + + /// + /// The separator. + /// + internal const string Separator = ":"; + + #endregion + + #region Properties + + /// + /// The command portion of the input. + /// + public string Command { get; private set; } + + /// + /// The argument portion of the input. + /// + public string Argument { get; private set; } + + #endregion + + #region Constructor + + /// + /// Breaks the provided command line switch into the command and argument pair. + /// + /// Input to break up. + public CommandArgumentPair(string input) + { + if (String.IsNullOrWhiteSpace(input)) + { + throw new ArgumentException(Resources.CannotBeNullOrEmpty, "input"); + } + Contract.Ensures(!String.IsNullOrWhiteSpace(Command)); + + this.Parse(input); + } + + /// + /// Stores the provided command and argument pair. + /// + /// The command portion of the input. + /// The argument portion of the input. + public CommandArgumentPair(string command, string argument) + { + if (String.IsNullOrWhiteSpace(command)) + { + throw new ArgumentException(Resources.CannotBeNullOrEmpty, "command"); + } + + Contract.Ensures(Command == command); + Contract.Ensures(Argument == argument); + + this.Command = command; + this.Argument = argument; + } + + #endregion + + #region Private Methods + + /// + /// Parses the input into the command and argument parts. + /// + /// Input string to parse. + private void Parse(string input) + { + Contract.Requires(!String.IsNullOrWhiteSpace(input)); + Contract.Ensures(!String.IsNullOrWhiteSpace(Command)); + Contract.Ensures(Argument != null); + + // Find the index of the seperator (":") + int index = input.IndexOf(CommandArgumentPair.Separator, StringComparison.OrdinalIgnoreCase); + + if (index == -1) + { + // No seperator was found, so use the input as the command. + this.Command = input; + this.Argument = String.Empty; + } + else + { + // Separator was found, so separate the command and the input. + this.Command = input.Substring(0, index); + this.Argument = input.Substring(index + 1); + } + } + +#endregion + } +} diff --git a/src/vstest.console/CommandLine/CommandLineErrorException.cs b/src/vstest.console/CommandLine/CommandLineErrorException.cs new file mode 100644 index 0000000000..ce67e830e8 --- /dev/null +++ b/src/vstest.console/CommandLine/CommandLineErrorException.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine +{ + using System; + + /// + /// Exception thrown by argument processors when they encounter an error with + /// the command line arguments. + /// + public class CommandLineException : Exception + { + #region Constructors + + /// + /// Creates a new CommandLineException + /// + public CommandLineException() + { + } + + /// + /// Initializes with the message. + /// + /// Message for the exception. + public CommandLineException(string message) + : base(message) + { + } + + /// + /// Initializes with message and inner exception. + /// + /// Message for the exception. + /// The inner exception. + public CommandLineException(string message, Exception innerException) + : base (message, innerException) + { + } + + #endregion + } +} diff --git a/src/vstest.console/CommandLine/CommandLineOptions.cs b/src/vstest.console/CommandLine/CommandLineOptions.cs new file mode 100644 index 0000000000..a1b5472643 --- /dev/null +++ b/src/vstest.console/CommandLine/CommandLineOptions.cs @@ -0,0 +1,267 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + + using Utilities.Helpers; + using Utilities.Helpers.Interfaces; + + /// + /// Provides access to the command-line options. + /// + internal class CommandLineOptions + { + #region Constants/Readonly + + /// + /// The default batch size. + /// + public const long DefaultBatchSize = 10; + + /// + /// The use vsix extensions key. + /// + public const string UseVsixExtensionsKey = "UseVsixExtensions"; + + /// + /// The default use vsix extensions value. + /// + public const bool DefaultUseVsixExtensionsValue = false; + + /// + /// The default retrieval timeout for fetching of test results or test cases + /// + private readonly TimeSpan DefaultRetrievalTimeout = new TimeSpan(0, 0, 0, 1, 500); + + #endregion + + #region PrivateMembers + + private static CommandLineOptions instance; + + private List sources = new List(); + + private Architecture architecture; + + private FrameworkVersion frameworkVersion; + + #endregion + + /// + /// Gets the instance. + /// + internal static CommandLineOptions Instance + { + get + { + if (instance == null) + { + instance = new CommandLineOptions(); + } + + return instance; + } + } + + #region Constructor + + /// + /// Default constructor. + /// + private CommandLineOptions() + { + this.BatchSize = DefaultBatchSize; + this.TestRunStatsEventTimeout = this.DefaultRetrievalTimeout; + this.FileHelper = new FileHelper(); +#if TODO + UseVsixExtensions = Utilities.GetAppSettingValue(UseVsixExtensionsKey, false); +#endif + } + +#endregion + + #region Properties + + /// + /// Specifies whether parallel execution is on or off. + /// + public bool Parallel { get; set; } + + /// + /// Readonly collection of all available test sources + /// + public IEnumerable Sources + { + get + { + return this.sources.AsReadOnly(); + } + } + + /// + /// Specifies whether dynamic code coverage diagnostic data adapter needs to be configured. + /// + public bool EnableCodeCoverage { get; set; } + + /// + /// Specifies whether the Fakes automatic configuration should be disabled. + /// + public bool DisableAutoFakes { get; set; } + + /// + /// Specifies whether vsixExtensions is enabled or not. + /// + public bool UseVsixExtensions { get; set; } + + /// + /// Path to the custom test adapters. + /// + public string TestAdapterPath { get; set; } + + /// + /// Port IDE process is listening to + /// + public int Port { get; set; } + + /// + /// Configuration the project is built for e.g. Debug/Release + /// + public string Configuration { get; set; } + + /// + /// Directory containing the temporary outputs + /// + public string BuildBasePath { get; set; } + + /// + /// Directory containing the binaries to run + /// + public string Output { get; set; } + + /// + /// Specifies the frequency of the runStats/discoveredTests event + /// + public long BatchSize { get; set; } + + /// + /// Specifies the timeout of the runStats event + /// + public TimeSpan TestRunStatsEventTimeout { get; set; } + + /// + /// Test case filter value for run with sources. + /// + public string TestCaseFilterValue { get; set; } + + /// + /// Specifies the Target Device + /// + public string TargetDevice { get; set; } + + /// + /// Specifies whether the target device has a Windows Phone context or not + /// + public bool HasPhoneContext + { + get + { + return !string.IsNullOrEmpty(TargetDevice); + } + } + + /// + /// Specifies the target platform type for test run. + /// + public Architecture TargetArchitecture + { + get + { + return this.architecture; + } + set + { + this.architecture = value; + this.ArchitectureSpecified = true; + } + } + + /// + /// Specifies if /Platform has been specified on command line or not. + /// + internal bool ArchitectureSpecified { get; private set; } + + internal IFileHelper FileHelper { get; set; } + + /// + /// Gets or sets the target .Net Framework version for test run. + /// + internal FrameworkVersion TargetFrameworkVersion + { + get + { + return this.frameworkVersion; + } + set + { + this.frameworkVersion = value; + this.FrameworkVersionSpecified = true; + } + } + + /// + /// Gets a value indicating whether /Framework has been specified on command line or not. + /// + internal bool FrameworkVersionSpecified { get; private set; } + + #endregion + + #region Public Methods + + /// + /// Adds a source file to look for tests in. + /// + /// Path to source file to look for tests in. + public void AddSource(string source) + { + if (String.IsNullOrWhiteSpace(source)) + { + throw new CommandLineException(Resources.CannotBeNullOrEmpty); + } + + source = source.Trim(); + if (!FileHelper.Exists(source)) + { + throw new CommandLineException( + string.Format(CultureInfo.CurrentUICulture, Resources.TestSourceFileNotFound, source)); + } + + if (this.sources.Contains(source, StringComparer.OrdinalIgnoreCase)) + { + throw new CommandLineException( + string.Format(CultureInfo.CurrentCulture, Resources.DuplicateSource, source)); + } + + this.sources.Add(source); + } + + #endregion + + #region Internal Methods + + /// + /// Resets the options. Clears the sources. + /// + internal void Reset() + { + instance = null; + } + + #endregion + } +} diff --git a/src/vstest.console/CommandLine/Executor.cs b/src/vstest.console/CommandLine/Executor.cs new file mode 100644 index 0000000000..08912c38f6 --- /dev/null +++ b/src/vstest.console/CommandLine/Executor.cs @@ -0,0 +1,290 @@ +// Copyright (c) Microsoft. All rights reserved. + +// General Flow: +// Create a command processor for each argument. +// If there is no matching command processor for an argument, output error, display help and exit. +// If throws during creation, output error and exit. +// If the help command processor has been requested, execute the help processor and exit. +// Order the command processors by priority. +// Allow command processors to validate against other command processors which are present. +// If throws during validation, output error and exit. +// Process each command processor. +// If throws during validaton, output error and exit. +// If the default (RunTests) command processor has no test containers output an error and exit +// If the default (RunTests) command processor has no tests to run output an error and exit + +// Commands metadata: +// *Command line argument. +// Priority. +// Help output. +// Required +// Single or multiple + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.Linq; + using Microsoft.VisualStudio.TestPlatform.CommandLine.Processors; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.Utilities; + using System.Diagnostics.Contracts; + using System.Reflection; + + /// + /// Performs the execution based on the arguments provided. + /// + internal class Executor + { + #region Constructor + + /// + /// Default constructor. + /// + public Executor(IOutput output) + { + this.Output = output; + } + + #endregion + + #region Properties + + /// + /// Instance to use for sending output. + /// + private IOutput Output { get; set; } + + #endregion + + #region Methods + + /// + /// Performs the execution based on the arguments provided. + /// + /// + /// Arguments provided to perform execution with. + /// + /// + /// Exit Codes - Zero (for sucessful command execution), One (for bad command) + /// + internal int Execute(params string[] args) + { + int exitCode = 0; + // If we have no arguments, set exit code to 1, add a message, and include the help processor in the args. + if (args == null || args.Length == 0 || args.Any(string.IsNullOrWhiteSpace)) + { + args = args ?? new string[0]; + exitCode = 1; + // Do not add help processor as we will go and try to check for project.json files in current dir + } + + PrintSplashScreen(); + + // Get the argument processors for the arguments. + List argumentProcessors; + exitCode |= GetArgumentProcessors(args, out argumentProcessors); + + // Verify that the arguments are valid. + exitCode |= IdentifyDuplicateArguments(argumentProcessors); + + // Quick exit for syntax error + if (exitCode == 1 + && argumentProcessors.All( + processor => processor.Metadata.Value.CommandName != HelpArgumentProcessor.CommandName)) + { + return exitCode; + } + + // Execute all argument processors + foreach(var processor in argumentProcessors) + { + if(!ExecuteArgumentProcessor(processor, out exitCode)) + { + break; + } + } + + // Use the test run result aggregator to update the exit code. + exitCode |= (TestRunResultAggregator.Instance.Outcome == TestOutcome.Passed) ? 0 : 1; + + EqtTrace.Verbose("Executor.Execute: Exiting with exit code of {0}", exitCode); + return exitCode; + } + + #endregion + + #region Private Methods + + /// + /// Get the list of argument processors for the arguments. + /// + /// Arguments provided to perform execution with. + /// List of argument processors for the arguments. + /// 0 if all of the processors were created successfully and 1 otherwise. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "processorInstance", Justification = "Done on purpose to force the instances to be created")] + private int GetArgumentProcessors(string[] args, out List processors) + { + processors = new List(); + + int result = 0; + var processorFactory = ArgumentProcessorFactory.Create(); + + foreach (string arg in args) + { + var processor = processorFactory.CreateArgumentProcessor(arg); + + if (processor != null) + { + processors.Add(processor); + } + else + { + // No known processor was found, report an error and continue + Output.Error(String.Format(CultureInfo.CurrentCulture, Resources.NoArgumentProcessorFound, arg)); + + // Add the help processor + if (result == 0) + { + result = 1; + processors.Add(processorFactory.CreateArgumentProcessor(HelpArgumentProcessor.CommandName)); + } + } + } + + // Add the internal argument processors that should always be executed. + // Examples: processors to enable loggers that are statically configured, and to start logging, + // should always be executed. + var processorsToAlwaysExecute = processorFactory.GetArgumentProcessorsToAlwaysExecute(); + processors.AddRange(processorsToAlwaysExecute); + + // Ensure we have an action argument. + EnsureActionArgumentIsPresent(processors, processorFactory); + + // Instantiate and initialize the processors in priority order. + processors.Sort((p1, p2) => Comparer.Default.Compare(p1.Metadata.Value.Priority, p2.Metadata.Value.Priority)); + foreach (var processor in processors) + { + IArgumentExecutor executorInstance; + try + { + // Ensure the instance is created. Note that the Lazy not only instantiates + // the argument processor, but also initializes it. + executorInstance = processor.Executor.Value; + } + catch (CommandLineException e) + { + Output.Error(e.Message); + result = 1; + } + } + + return result; + } + + /// + /// Verify that the arguments are valid. + /// + /// Processors to verify against. + /// 0 if successful and 1 otherwise. + private int IdentifyDuplicateArguments(IEnumerable argumentProcessors) + { + int result = 0; + + // Used to keep track of commands that are only allowed to show up once. The first time it is seen + // an entry for the command will be added to the dictionary and the value will be set to 1. If we + // see the command again and the value is 1 (meaning this is the second time we have seen the command), + // we will output an error and increment the count. This ensures that the error message will only be + // displayed once even if the user does something like /ListDiscoverers /ListDiscoverers /ListDiscoverers. + Dictionary commandSeenCount = new Dictionary(StringComparer.OrdinalIgnoreCase); + + // Check each processor. + foreach (var processor in argumentProcessors) + { + if (!processor.Metadata.Value.AllowMultiple) + { + int count; + if (!commandSeenCount.TryGetValue(processor.Metadata.Value.CommandName, out count)) + { + commandSeenCount.Add(processor.Metadata.Value.CommandName, 1); + } + else if (count == 1) + { + result = 1; + + // Update the count so we do not print the error out for this argument multiple times. + commandSeenCount[processor.Metadata.Value.CommandName] = ++count; + Output.Error(String.Format(CultureInfo.CurrentCulture, Resources.DuplicateArgumentError, processor.Metadata.Value.CommandName)); + } + } + } + + return result; + } + + /// + /// Ensures that an action argument is present and if one is not, then the default action argument is added. + /// + /// The arguments that are being processed. + /// A factory for creating argument processors. + private void EnsureActionArgumentIsPresent(List argumentProcessors, ArgumentProcessorFactory processorFactory) + { + Contract.Requires(argumentProcessors != null); + Contract.Requires(processorFactory != null); + + // Determine if any of the argument processors are actions. + var isActionIncluded = argumentProcessors.Any((processor) => processor.Metadata.Value.IsAction); + + // If no action arguments have been provided, then add the default action argument. + if (!isActionIncluded) + { + argumentProcessors.Add(processorFactory.CreateDefaultActionArgumentProcessor()); + } + } + + /// + /// Executes the argument processor + /// + /// Argument processor to execute. + /// true if continue execution, false otherwise. + private bool ExecuteArgumentProcessor(IArgumentProcessor processor, out int exitCode) + { + exitCode = 0; + var continueExecution = true; + var result = processor.Executor.Value.Execute(); + + Debug.Assert( + result >= ArgumentProcessorResult.Success && result <= ArgumentProcessorResult.Abort, + "Invalid argument processor result."); + + if (result == ArgumentProcessorResult.Fail) + { + exitCode = 1; + } + + if (result == ArgumentProcessorResult.Abort) + { + continueExecution = false; + } + + return continueExecution; + } + + /// + /// Displays the Company and Copyright splash title info immediately after launch + /// + private void PrintSplashScreen() + { + var version = typeof(Executor).GetTypeInfo().Assembly.GetName().Version; + string commandLineBanner = String.Format(CultureInfo.CurrentUICulture, Resources.MicrosoftCommandLineTitle, version.ToString()); + Output.WriteLine(commandLineBanner, OutputLevel.Information); + + Output.WriteLine(Resources.CopyrightCommandLineTitle, OutputLevel.Information); + Output.WriteLine(string.Empty, OutputLevel.Information); + } + + #endregion + } +} diff --git a/src/vstest.console/CommandLine/TestRunResultAggregator.cs b/src/vstest.console/CommandLine/TestRunResultAggregator.cs new file mode 100644 index 0000000000..38c49d1bac --- /dev/null +++ b/src/vstest.console/CommandLine/TestRunResultAggregator.cs @@ -0,0 +1,134 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine +{ + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + + /// + /// Aggregates test messages and test results received to determine the test run result. + /// + internal class TestRunResultAggregator + { + private static TestRunResultAggregator instance = null; + + #region Constructor + + /// + /// Initializes the TestRunResultAggregator + /// + /// Constructor is private since the factory method should be used to get the instance. + private TestRunResultAggregator() + { + // Outcome is passed until we see a failure. + this.Outcome = TestOutcome.Passed; + } + + #endregion + + #region Static Methods + + /// + /// Gets the instance of the test run result aggregator. + /// + /// Instance of the test run result aggregator. + public static TestRunResultAggregator Instance + { + get + { + if(instance == null) + { + instance = new TestRunResultAggregator(); + } + + return instance; + } + } + + #endregion + + #region Properties + + /// + /// The current test run outcome. + /// + public TestOutcome Outcome { get; private set; } + + #endregion + + #region Event Handlers + + /// + /// Registers to receive events from the provided test run request. + /// These events will then be broadcast to any registered loggers. + /// + /// The run request to register for events on. + public void RegisterTestRunEvents(ITestRunRequest testRunRequest) + { + ValidateArg.NotNull(testRunRequest, "testRunRequest"); + + // Register for the events. + testRunRequest.TestRunMessage += TestRunMessageHandler; + testRunRequest.OnRunCompletion += TestRunCompletionHandler; + } + + /// + /// Unregisters from the provided test run request to stop receiving events. + /// + /// The run request from which events should be unregistered. + public void UnregisterTestRunEvents(ITestRunRequest testRunRequest) + { + ValidateArg.NotNull(testRunRequest, "testRunRequest"); + + testRunRequest.TestRunMessage -= TestRunMessageHandler; + testRunRequest.OnRunCompletion -= TestRunCompletionHandler; + } + + /// + /// To mark the test run as failed. + /// + public void MarkTestRunFailed() + { + this.Outcome = TestOutcome.Failed; + } + + /// + /// Resets the outcome to default state i.e. Passed + /// + public void Reset() + { + this.Outcome = TestOutcome.Passed; + } + + /// + /// Called when a test run is complete. + /// + private void TestRunCompletionHandler(object sender, TestRunCompleteEventArgs e) + { + if (e.TestRunStatistics == null) + { + this.Outcome = TestOutcome.Failed; + return; + } + + if (e.TestRunStatistics[TestOutcome.Failed] > 0) + { + this.Outcome = TestOutcome.Failed; + } + } + + /// + /// Called when a test run message is sent. + /// + private void TestRunMessageHandler(object sender, TestRunMessageEventArgs e) + { + if (e.Level == TestMessageLevel.Error) + { + this.Outcome = TestOutcome.Failed; + } + } + + #endregion + } +} diff --git a/src/vstest.console/Friends.cs b/src/vstest.console/Friends.cs new file mode 100644 index 0000000000..1ab4c34cfc --- /dev/null +++ b/src/vstest.console/Friends.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Runtime.CompilerServices; + +#region Product Assemblies + +#endregion + +#region Test Assemblies + +[assembly: InternalsVisibleTo("vstest.console.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] + +#endregion diff --git a/src/vstest.console/Internal/ConsoleLogger.cs b/src/vstest.console/Internal/ConsoleLogger.cs new file mode 100644 index 0000000000..5a636886b0 --- /dev/null +++ b/src/vstest.console/Internal/ConsoleLogger.cs @@ -0,0 +1,345 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Internal +{ + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + using Microsoft.VisualStudio.TestPlatform.Utilities; + using System; + using System.Collections.ObjectModel; + using System.Diagnostics; + using System.Globalization; + using System.Linq; + using System.Text; + using Resources = Microsoft.VisualStudio.TestPlatform.CommandLine.Resources; + + /// + /// Logger for sending output to the console. + /// + [FriendlyName(ConsoleLogger.FriendlyName)] + [ExtensionUri(ConsoleLogger.ExtensionUri)] + internal class ConsoleLogger : ITestLogger + { + #region Constants + + /// + /// Uri used to uniquely identify the console logger. + /// + public const string ExtensionUri = "logger://Microsoft/TestPlatform/ConsoleLogger/v2"; + + /// + /// Alternate user friendly string to uniquely identify the console logger. + /// + public const string FriendlyName = "Console"; + + #endregion + + #region Fields + + private static IOutput output; + + private TestOutcome testOutcome = TestOutcome.None; + private int testsTotal = 0; + private int testsPassed = 0; + private int testsFailed = 0; + private int testsSkipped = 0; + + #endregion + + #region Constructor + + /// + /// Default constructor. + /// + public ConsoleLogger() + { + } + + /// + /// Constructor added for testing purpose + /// + /// + internal ConsoleLogger(IOutput output) + { + ConsoleLogger.output = output; + } + + #endregion + + #region Properties + /// + /// Gets instance of IOutput used for sending output. + /// + /// Protected so this can be detoured for testing purposes. + protected static IOutput Output + { + get + { + return output; + } + } + #endregion + + #region ITestLogger + + /// + /// Initializes the Test Logger. + /// + /// Events that can be registered for. + /// Test Run Directory + public void Initialize(TestLoggerEvents events, string testRunDirectory) + { + if (events == null) + { + throw new ArgumentNullException("events"); + } + + if (output == null) + { + output = ConsoleOutput.Instance; + } + + // Register for the events. + events.TestRunMessage += this.TestMessageHandler; + events.TestResult += this.TestResultHandler; + events.TestRunComplete += this.TestRunCompleteHandler; + } + + #endregion + + #region Private Methods + + /// + /// Prints the timespan onto console. + /// + private static void PrintTimeSpan(TimeSpan timeSpan) + { + if (timeSpan.TotalDays >= 1) + { + Output.WriteLine(string.Format(CultureInfo.CurrentCulture, Resources.ExecutionTimeFormatString, timeSpan.TotalDays, Resources.Days), OutputLevel.Information); + } + else if (timeSpan.TotalHours >= 1) + { + Output.WriteLine(string.Format(CultureInfo.CurrentCulture, Resources.ExecutionTimeFormatString, timeSpan.TotalHours, Resources.Hours), OutputLevel.Information); + } + else if (timeSpan.TotalMinutes >= 1) + { + Output.WriteLine(string.Format(CultureInfo.CurrentCulture, Resources.ExecutionTimeFormatString, timeSpan.TotalMinutes, Resources.Minutes), OutputLevel.Information); + } + else + { + Output.WriteLine(string.Format(CultureInfo.CurrentCulture, Resources.ExecutionTimeFormatString, timeSpan.TotalSeconds, Resources.Seconds), OutputLevel.Information); + } + } + + /// + /// Constructs a well formatted string using the given prefix before every message content on each line. + /// + private static string GetFormattedOutput(Collection testMessageCollection) + { + if (testMessageCollection != null) + { + StringBuilder sb = new StringBuilder(); + foreach (var message in testMessageCollection) + { + string prefix = String.Format(CultureInfo.CurrentCulture, "{0}{1}", Environment.NewLine, Resources.TestMessageFormattingPrefix); + string messageText = message.Text.Replace(Environment.NewLine, prefix).TrimEnd(Resources.TestMessageFormattingPrefix.ToCharArray()); + sb.AppendFormat(CultureInfo.CurrentCulture, "{0}{1}", Resources.TestMessageFormattingPrefix, messageText); + } + return sb.ToString(); + } + return String.Empty; + } + + /// + /// Collects all the messages of a particular category(Standard Output/Standard Error/Debug Traces) and returns a collection. + /// + private static Collection GetTestMessages(Collection Messages, string requiredCategory) + { + var selectedMessages = Messages.Where(msg => msg.Category.Equals(requiredCategory, StringComparison.OrdinalIgnoreCase)); + Collection requiredMessageCollection = new Collection(selectedMessages.ToList()); + return requiredMessageCollection; + } + + /// + /// outputs the Error messages, Stack Trace, and other messages for the parameter test. + /// + private static void DisplayFullInformation(TestResult result) + { + bool hasData = false; + + Debug.Assert(result != null, "a null result can not be displayed"); + if (!String.IsNullOrEmpty(result.ErrorMessage)) + { + hasData = true; + Output.WriteLine(Resources.ErrorMessageBanner, OutputLevel.Error); + string errorMessage = String.Format(CultureInfo.CurrentCulture, "{0}{1}", Resources.TestMessageFormattingPrefix, result.ErrorMessage); + Output.WriteLine(errorMessage, OutputLevel.Error); + } + + if (!String.IsNullOrEmpty(result.ErrorStackTrace)) + { + hasData = true; + Output.WriteLine(Resources.StacktraceBanner, OutputLevel.Error); + string stackTrace = String.Format(CultureInfo.CurrentCulture, "{0}", result.ErrorStackTrace); + Output.Write(stackTrace, OutputLevel.Error); + } + + Collection stdOutMessagesCollection = GetTestMessages(result.Messages, TestResultMessage.StandardOutCategory); + if (stdOutMessagesCollection.Count > 0) + { + hasData = true; + string stdOutMessages = GetFormattedOutput(stdOutMessagesCollection); + Output.WriteLine(Resources.StdOutMessagesBanner, OutputLevel.Information); + Output.Write(stdOutMessages, OutputLevel.Information); + } + + Collection stdErrMessagesCollection = GetTestMessages(result.Messages, TestResultMessage.StandardErrorCategory); + if (stdErrMessagesCollection.Count > 0) + { + hasData = true; + string stdErrMessages = GetFormattedOutput(stdErrMessagesCollection); + Output.WriteLine(Resources.StdErrMessagesBanner, OutputLevel.Error); + Output.Write(stdErrMessages, OutputLevel.Error); + } + + Collection addnlInfoMessagesCollection = GetTestMessages(result.Messages, TestResultMessage.AdditionalInfoCategory); + if (addnlInfoMessagesCollection.Count > 0) + { + hasData = true; + Output.WriteLine(Resources.AddnlInfoMessagesBanner, OutputLevel.Information); + string addnlInfoMessages = GetFormattedOutput(addnlInfoMessagesCollection); + Output.Write(addnlInfoMessages, OutputLevel.Information); + } + if (hasData) + { + Output.WriteLine(String.Empty, OutputLevel.Information); + } + } + + #endregion + + #region Event Handlers + + /// + /// Called when a test message is received. + /// + private void TestMessageHandler(object sender, TestRunMessageEventArgs e) + { + ValidateArg.NotNull(sender, "sender"); + ValidateArg.NotNull(e, "e"); + + switch (e.Level) + { + case TestMessageLevel.Informational: + Output.Information(e.Message); + break; + case TestMessageLevel.Warning: + Output.Warning(e.Message); + break; + case TestMessageLevel.Error: + this.testOutcome = TestOutcome.Failed; + Output.Error(e.Message); + break; + default: + Debug.Fail("ConsoleLogger.TestMessageHandler: The test message level is unrecognized: {0}", e.Level.ToString()); + break; + } + + Output.WriteLine(string.Empty, (OutputLevel)e.Level); + } + + /// + /// Called when a test result is received. + /// + private void TestResultHandler(object sender, TestResultEventArgs e) + { + ValidateArg.NotNull(sender, "sender"); + ValidateArg.NotNull(e, "e"); + + // Update the test count statistics based on the result of the test. + this.testsTotal++; + + string name = null; + name = !string.IsNullOrEmpty(e.Result.DisplayName) ? e.Result.DisplayName : e.Result.TestCase.FullyQualifiedName; + + if (e.Result.Outcome == TestOutcome.Skipped) + { + this.testsSkipped++; + string output = string.Format(CultureInfo.CurrentCulture, Resources.SkippedTestIndicator, name); + Output.WriteLine(output, OutputLevel.Information); + DisplayFullInformation(e.Result); + } + else if (e.Result.Outcome == TestOutcome.Failed) + { + this.testOutcome = TestOutcome.Failed; + this.testsFailed++; + string output = string.Format(CultureInfo.CurrentCulture, Resources.FailedTestIndicator, name); + Output.WriteLine(output, OutputLevel.Information); + DisplayFullInformation(e.Result); + } + else if (e.Result.Outcome == TestOutcome.Passed) + { + string output = string.Format(CultureInfo.CurrentCulture, Resources.PassedTestIndicator, name); + Output.WriteLine(output, OutputLevel.Information); + this.testsPassed++; + } + } + + /// + /// Called when a test run is completed. + /// + private void TestRunCompleteHandler(object sender, TestRunCompleteEventArgs e) + { + Output.WriteLine(string.Empty, OutputLevel.Information); + + // Printing Run-level Attachments + int runLevelAttachementCount = (e.AttachmentSets == null) ? 0 : e.AttachmentSets.Sum(attachmentSet => attachmentSet.Attachments.Count); + if (runLevelAttachementCount > 0) + { + Output.WriteLine(Resources.AttachmentsBanner, OutputLevel.Information); + foreach (AttachmentSet attachmentSet in e.AttachmentSets) + { + foreach (UriDataAttachment uriDataAttachment in attachmentSet.Attachments) + { + string attachmentOutput = string.Format(CultureInfo.CurrentCulture, Resources.AttachmentOutputFormat, uriDataAttachment.Uri.LocalPath); + Output.WriteLine(attachmentOutput, OutputLevel.Information); + } + } + Output.WriteLine(String.Empty, OutputLevel.Information); + } + + // Output a summary. + if (this.testsTotal > 0) + { + if (this.testOutcome == TestOutcome.Failed) + { + Output.WriteLine(string.Format(CultureInfo.CurrentCulture, Resources.TestRunSummary, testsTotal, testsPassed, testsFailed, testsSkipped), OutputLevel.Information); + using (new ConsoleColorHelper(ConsoleColor.Red)) + { + Output.WriteLine(Resources.TestRunFailed, OutputLevel.Error); + } + } + else + { + Output.WriteLine(string.Format(CultureInfo.CurrentCulture, Resources.TestRunSummary, testsTotal, testsPassed, testsFailed, testsSkipped), OutputLevel.Information); + using (new ConsoleColorHelper(ConsoleColor.Green)) + { + Output.WriteLine(Resources.TestRunSuccessful, OutputLevel.Information); + } + } + if (!e.ElapsedTimeInRunningTests.Equals(TimeSpan.Zero)) + { + PrintTimeSpan(e.ElapsedTimeInRunningTests); + } + else + { + EqtTrace.Info("Skipped printing test execution time on console because it looks like the test run had faced some errors"); + } + } + } + #endregion + + } +} diff --git a/src/vstest.console/Processors/BuildBasePathArgumentProcessor.cs b/src/vstest.console/Processors/BuildBasePathArgumentProcessor.cs new file mode 100644 index 0000000000..1d2209c0cd --- /dev/null +++ b/src/vstest.console/Processors/BuildBasePathArgumentProcessor.cs @@ -0,0 +1,150 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Processors +{ + using System; + using System.Diagnostics.Contracts; + using System.Globalization; + using Microsoft.VisualStudio.TestPlatform.CommandLine; + using Resources = Microsoft.VisualStudio.TestPlatform.CommandLine.Resources; + using TestPlatform.Utilities.Helpers; + using TestPlatform.Utilities.Helpers.Interfaces; + + /// + /// Argument Executor for the "/BuildBasePath" command line argument. + /// + internal class BuildBasePathArgumentProcessor : IArgumentProcessor + { + #region Constants + + /// + /// The name of the command line argument that the BuildBasePathArgumentExecutor handles. + /// + public const string CommandName = "/BuildBasePath"; + + /// + /// The short name of the command line argument that the BuildBasePathArgumentExecutor handles. + /// + public const string ShortCommandName = "/b"; + + #endregion + + private Lazy metadata; + + private Lazy executor; + + /// + /// Gets the metadata. + /// + public Lazy Metadata + { + get + { + if (this.metadata == null) + { + this.metadata = new Lazy(() => new BuildBasePathArgumentProcessorCapabilities()); + } + + return this.metadata; + } + } + + /// + /// Gets or sets the executor. + /// + public Lazy Executor + { + get + { + if (this.executor == null) + { + this.executor = new Lazy(() => new BuildBasePathArgumentExecutor(CommandLineOptions.Instance)); + } + + return this.executor; + } + + set + { + this.executor = value; + } + } + } + + internal class BuildBasePathArgumentProcessorCapabilities : BaseArgumentProcessorCapabilities + { + public override string CommandName => BuildBasePathArgumentProcessor.CommandName; + + public override string ShortCommandName => BuildBasePathArgumentProcessor.ShortCommandName; + + public override bool AllowMultiple => false; + + public override bool IsAction => false; + + public override ArgumentProcessorPriority Priority => ArgumentProcessorPriority.Normal; + + public override string HelpContentResourceName => Resources.BuildBasePathArgumentHelp; + + public override HelpContentPriority HelpPriority => HelpContentPriority.BuildBasePathArgumentProcessorHelpPriority; + } + + /// + /// Argument Executor for the "/BuildBasePath" command line argument. + /// + internal class BuildBasePathArgumentExecutor : IArgumentExecutor + { + #region Fields + + /// + /// Used for getting sources. + /// + private CommandLineOptions commandLineOptions; + + internal IFileHelper FileHelper { get; set; } + + #endregion + + #region Constructor + + /// + /// Default constructor. + /// + /// + /// The options. + /// + public BuildBasePathArgumentExecutor(CommandLineOptions options) + { + Contract.Requires(options != null); + this.commandLineOptions = options; + this.FileHelper = new FileHelper(); + } + #endregion + + #region IArgumentExecutor + + /// + /// Initializes with the argument that was provided with the command. + /// + /// Argument that was provided with the command. + public void Initialize(string argument) + { + if (!FileHelper.Exists(argument)) + { + throw new CommandLineException(string.Format(CultureInfo.CurrentUICulture, Resources.BuildBasePathNotFound)); + } + + this.commandLineOptions.BuildBasePath = argument; + } + + /// + /// The BuildBasePath is already set, return success. + /// + /// The Success + public ArgumentProcessorResult Execute() + { + return ArgumentProcessorResult.Success; + } + + #endregion + } +} diff --git a/src/vstest.console/Processors/ConfigurationArgumentProcessor.cs b/src/vstest.console/Processors/ConfigurationArgumentProcessor.cs new file mode 100644 index 0000000000..9c1acd8c88 --- /dev/null +++ b/src/vstest.console/Processors/ConfigurationArgumentProcessor.cs @@ -0,0 +1,148 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Processors +{ + using System; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.Linq; + using Microsoft.VisualStudio.TestPlatform.CommandLine; + using Resources = Microsoft.VisualStudio.TestPlatform.CommandLine.Resources; + + /// + /// Argument Executor for the "/Configuration" command line argument. + /// + internal class ConfigurationArgumentProcessor : IArgumentProcessor + { + #region Constants + + /// + /// The name of the command line argument that the ConfigurationArgumentExecutor handles. + /// + public const string CommandName = "/Configuration"; + + /// + /// The short name of the command line argument that the ConfigurationArgumentExecutor handles. + /// + public const string ShortCommandName = "/c"; + + #endregion + + private Lazy metadata; + + private Lazy executor; + + /// + /// Gets the metadata. + /// + public Lazy Metadata + { + get + { + if (this.metadata == null) + { + this.metadata = new Lazy(() => new ConfigurationArgumentProcessorCapabilities()); + } + + return this.metadata; + } + } + + /// + /// Gets or sets the executor. + /// + public Lazy Executor + { + get + { + if (this.executor == null) + { + this.executor = new Lazy(() => new ConfigurationArgumentExecutor(CommandLineOptions.Instance)); + } + + return this.executor; + } + + set + { + this.executor = value; + } + } + } + + internal class ConfigurationArgumentProcessorCapabilities : BaseArgumentProcessorCapabilities + { + public override string CommandName => ConfigurationArgumentProcessor.CommandName; + + public override string ShortCommandName => ConfigurationArgumentProcessor.ShortCommandName; + + public override bool AllowMultiple => false; + + public override bool IsAction => false; + + public override ArgumentProcessorPriority Priority => ArgumentProcessorPriority.Normal; + + public override string HelpContentResourceName => Resources.ConfigurationArgumentHelp; + + public override HelpContentPriority HelpPriority => HelpContentPriority.ConfigurationArgumentProcessorHelpPriority; + } + + /// + /// Argument Executor for the "/Configuration" command line argument. + /// + internal class ConfigurationArgumentExecutor : IArgumentExecutor + { + #region Fields + + private string[] ValidConfigs = { "Debug", "Release" }; + + /// + /// Used for getting sources. + /// + private CommandLineOptions commandLineOptions; + + #endregion + + #region Constructor + + /// + /// Default constructor. + /// + /// + /// The options. + /// + public ConfigurationArgumentExecutor(CommandLineOptions options) + { + Contract.Requires(options != null); + this.commandLineOptions = options; + } + #endregion + + #region IArgumentExecutor + + /// + /// Initializes with the argument that was provided with the command. + /// + /// Argument that was provided with the command. + public void Initialize(string argument) + { + if (string.IsNullOrWhiteSpace(argument) || !ValidConfigs.Contains(argument)) + { + //We might want to check for Debug/Release + throw new CommandLineException(string.Format(CultureInfo.CurrentUICulture, Resources.InvalidConfiguration)); + } + + this.commandLineOptions.Configuration = argument; + } + + /// + /// The configuration is already set, return success. + /// + /// The Success + public ArgumentProcessorResult Execute() + { + return ArgumentProcessorResult.Success; + } + #endregion + } +} diff --git a/src/vstest.console/Processors/EnableLoggerArgumentProcessor.cs b/src/vstest.console/Processors/EnableLoggerArgumentProcessor.cs new file mode 100644 index 0000000000..8e74b0e00b --- /dev/null +++ b/src/vstest.console/Processors/EnableLoggerArgumentProcessor.cs @@ -0,0 +1,237 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Processors +{ + using Microsoft.VisualStudio.TestPlatform.Common.Logging; + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Globalization; + + using Microsoft.VisualStudio.TestPlatform.CommandLine.Processors.Utilities; + + /// + /// An argument processor that allows the user to enable a specific logger + /// from the command line using the /Logger command line switch. + /// + internal class EnableLoggerArgumentProcessor : IArgumentProcessor + { + #region Constants + + /// + /// The command name. + /// + public const string CommandName = "/Logger"; + + #endregion + + #region Fields + + private Lazy metadata; + + private Lazy executor; + + /// + /// Gets or sets the executor. + /// + public Lazy Executor + { + get + { + if (this.executor == null) + { + this.executor = new Lazy(() => new EnableLoggerArgumentExecutor(TestLoggerManager.Instance)); + } + + return this.executor; + } + + set + { + this.executor = value; + } + } + + /// + /// Gets the metadata. + /// + public Lazy Metadata + { + get + { + if (this.metadata == null) + { + this.metadata = new Lazy(() => new EnableLoggerArgumentProcessorCapabilities()); + } + + return this.metadata; + } + } + + #endregion + } + + internal class EnableLoggerArgumentProcessorCapabilities : BaseArgumentProcessorCapabilities + { + /// + /// Gets the command name. + /// + public override string CommandName => EnableLoggerArgumentProcessor.CommandName; + + /// + /// Gets a value indicating whether allow multiple. + /// + public override bool AllowMultiple => true; + + /// + /// Gets a value indicating whether is action. + /// + public override bool IsAction => false; + + /// + /// Gets the priority. + /// + public override ArgumentProcessorPriority Priority => ArgumentProcessorPriority.Logging; + + /// + /// Gets the help content resource name. + /// + public override string HelpContentResourceName => Resources.EnableLoggersArgumentHelp; + + /// + /// Gets the help priority. + /// + public override HelpContentPriority HelpPriority => HelpContentPriority.EnableLoggerArgumentProcessorHelpPriority; + } + + /// + /// The argument executor. + /// + internal class EnableLoggerArgumentExecutor : IArgumentExecutor + { + #region Fields + + private readonly TestLoggerManager loggerManager; + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// + /// The logger manager. + /// + public EnableLoggerArgumentExecutor(TestLoggerManager loggerManager) + { + Contract.Requires(loggerManager != null); + this.loggerManager = loggerManager; + } + + #endregion + + #region IArgumentProcessor + + /// + /// Initializes with the argument that was provided with the command. + /// + /// Argument that was provided with the command. + public void Initialize(string argument) + { + if (string.IsNullOrWhiteSpace(argument)) + { + HandleInvalidArgument(argument); + } + else + { + string loggerIdentifier = null; + Dictionary parameters = null; + var parseSucceeded = LoggerUtilities.TryParseLoggerArgument(argument, out loggerIdentifier, out parameters); + + if (parseSucceeded) + { + // First assume the logger is specified by URI. If that fails try with friendly name. + try + { + this.AddLoggerByUri(loggerIdentifier, parameters); + } + catch (CommandLineException) + { + string loggerUri; + if (this.loggerManager.TryGetUriFromFriendlyName(loggerIdentifier, out loggerUri)) + { + this.AddLoggerByUri(loggerUri, parameters); + } + else + { + throw new CommandLineException( + String.Format( + CultureInfo.CurrentUICulture, + Resources.LoggerNotFound, + argument)); + } + } + } + else + { + HandleInvalidArgument(argument); + } + } + } + + /// + /// Execute. + /// + /// + /// The . + /// + public ArgumentProcessorResult Execute() + { + // Nothing to do since we enabled the logger in the initialize method. + return ArgumentProcessorResult.Success; + } + + #endregion + + #region Private Methods + + /// + /// Throws an exception indicating that the argument is invalid. + /// + /// Argument which is invalid. + private static void HandleInvalidArgument(string argument) + { + throw new CommandLineException( + string.Format( + CultureInfo.CurrentUICulture, + Resources.LoggerUriInvalid, + argument)); + } + + private void AddLoggerByUri(string argument, Dictionary parameters) + { + // Get the uri and if it is not valid, throw. + Uri loggerUri = null; + try + { + loggerUri = new Uri(argument); + } + catch (UriFormatException) + { + HandleInvalidArgument(argument); + } + + // Add the logger and if it is a non-existent logger, throw. + try + { + this.loggerManager.AddLogger(loggerUri, parameters); + } + catch (InvalidOperationException e) + { + throw new CommandLineException(e.Message, e); + } + } + #endregion + } +} diff --git a/src/vstest.console/Processors/EnableStaticLoggersArgumentProcessor.cs b/src/vstest.console/Processors/EnableStaticLoggersArgumentProcessor.cs new file mode 100644 index 0000000000..9f7d99f338 --- /dev/null +++ b/src/vstest.console/Processors/EnableStaticLoggersArgumentProcessor.cs @@ -0,0 +1,203 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Processors +{ + using Microsoft.VisualStudio.TestPlatform.Common.Logging; + using System; + using System.Collections.Generic; + + /// + /// An argument processor that enables all configured loggers. + /// + internal class EnableStaticLoggersArgumentProcessor : IArgumentProcessor + { + #region Constants + + /// + /// The command name. + /// + public const string CommandName = "/EnableStaticLoggers"; + + #endregion + + private Lazy metadata; + + private Lazy executor; + + /// + /// Gets or sets the executor. + /// + public Lazy Executor + { + get + { + if (this.executor == null) + { + this.executor = new Lazy(() => new EnableStaticLoggersArgumentExecutor()); + } + + return this.executor; + } + + set + { + this.executor = value; + } + } + + /// + /// Gets the metadata. + /// + public Lazy Metadata + { + get + { + if (this.metadata == null) + { + this.metadata = new Lazy(() => new EnableStaticLoggersArgumentProcessorCapabilities()); + } + + return this.metadata; + } + } + } + + /// + /// The argument capabilities. + /// + internal class EnableStaticLoggersArgumentProcessorCapabilities : BaseArgumentProcessorCapabilities + { + /// + /// Gets the command name. + /// + public override string CommandName => EnableStaticLoggersArgumentProcessor.CommandName; + + /// + /// Gets a value indicating whether allow multiple. + /// + public override bool AllowMultiple => false; + + /// + /// Gets a value indicating whether is action. + /// + public override bool IsAction => false; + + /// + /// Gets the priority. + /// + public override ArgumentProcessorPriority Priority => ArgumentProcessorPriority.Logging; + } + + /// + /// The argument e xecutor. + /// + internal class EnableStaticLoggersArgumentExecutor : IArgumentExecutor + { + #region Constructor + + /// + /// Default constructor. + /// + public EnableStaticLoggersArgumentExecutor() + { + } + + #endregion + + #region IArgumentProcessor + + /// + /// Enables the configured loggers. + /// + /// The argument used to initialize the processor. + /// is not used. + public void Initialize(string argument) + { + var logManager = TestLoggerManager.Instance; +#if NET451 + foreach (var logger in TestPlatFormSettings.Loggers.Cast()) + { + string loggerIdentifier = null; + Dictionary parameters = null; + bool parseSucceeded = LoggerUtilities.TryParseLoggerArgument(logger.Uri, out loggerIdentifier, out parameters); + + if (parseSucceeded) + { + Uri loggerUri = null; + try + { + loggerUri = new Uri(loggerIdentifier); + } + catch (UriFormatException) + { + Logger.SendMessage( + TestMessageLevel.Error, + String.Format( + CultureInfo.CurrentUICulture, + Resources.LoggerUriInvalid, + logger.Uri)); + } + + if (loggerUri != null) + { + try + { + logManager.AddLogger(loggerUri, null); + } + catch (InvalidOperationException e) + { + Logger.SendMessage( + TestMessageLevel.Error, + e.Message); + } + } + } + else + { + Logger.SendMessage( + TestMessageLevel.Error, + String.Format( + CultureInfo.CurrentUICulture, + Resources.LoggerUriInvalid, + logger.Uri)); + } + } +#else + //// todo : write code after getting clarity on config file format in dotnet core +#endif + + } + + public ArgumentProcessorResult Execute() + { + // Nothing to do. + return ArgumentProcessorResult.Success; + } + + #endregion + + #region Private Methods + +#if NET451 + /// + /// The settings for the test platform. + /// + private static TestPlatformSection TestPlatFormSettings + { + get + { + var section = ConfigurationManager.GetSection(TestPlatformSection.SectionName) as TestPlatformSection; + + if (section == null) + { + section = new TestPlatformSection(); + } + + return section; + } + } +#endif + #endregion + + } +} diff --git a/src/vstest.console/Processors/HelpArgumentProcessor.cs b/src/vstest.console/Processors/HelpArgumentProcessor.cs new file mode 100644 index 0000000000..ff4b2f3852 --- /dev/null +++ b/src/vstest.console/Processors/HelpArgumentProcessor.cs @@ -0,0 +1,185 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Processors +{ + using System; + using System.Collections.Generic; + using System.Globalization; + + using Microsoft.VisualStudio.TestPlatform.Utilities; + using Resources = Microsoft.VisualStudio.TestPlatform.CommandLine.Resources; + + // + // Argument Executor for the "/?" Help command line argument. + // + internal class HelpArgumentProcessor : IArgumentProcessor + { + #region Constants + + /// + /// The name of the command line argument that the HelpArgumentExecutor handles. + /// + public const string CommandName = "/?"; + + #endregion + + private Lazy metadata; + + private Lazy executor; + + /// + /// Gets the metadata. + /// + public Lazy Metadata + { + get + { + if (this.metadata == null) + { + this.metadata = new Lazy(() => new HelpArgumentProcessorCapabilities()); + } + + return this.metadata; + } + } + + /// + /// Gets or sets the executor. + /// + public Lazy Executor + { + get + { + if (this.executor == null) + { + this.executor = new Lazy(() => new HelpArgumentExecutor()); + } + + return this.executor; + } + + set + { + this.executor = value; + } + } + } + + /// + /// The help argument processor capabilities. + /// + internal class HelpArgumentProcessorCapabilities : BaseArgumentProcessorCapabilities + { + public override string CommandName => HelpArgumentProcessor.CommandName; + + public override string HelpContentResourceName => Resources.HelpArgumentHelp; + + public override HelpContentPriority HelpPriority => HelpContentPriority.HelpArgumentProcessorHelpPriority; + + public override bool IsAction => false; + + public override ArgumentProcessorPriority Priority => ArgumentProcessorPriority.Help; + } + + /// + /// Argument Executor for the "/?" Help command line argument. + /// + internal class HelpArgumentExecutor : IArgumentExecutor + { + #region Constructor + + /// + /// Constructs the HelpArgumentExecutor + /// + public HelpArgumentExecutor() + { + this.Output = ConsoleOutput.Instance; + } + + #endregion + + #region Properties + + /// + /// Gets the output object + /// + internal IOutput Output { get; set; } + + #endregion + + #region IArgumentExecutor Members + + public void Initialize(string argument) + { + } + + public ArgumentProcessorResult Execute() + { + // Output the stock ouput text + OutputSection(Resources.HelpUsageText); + OutputSection(Resources.HelpDescriptionText); + OutputSection(Resources.HelpOptionsText); + + // Output the help description for each available argument processor + var argumentProcessorFactory = ArgumentProcessorFactory.Create(); + List processors = new List(); + processors.AddRange(argumentProcessorFactory.AllArgumentProcessors); + processors.Sort((p1, p2) => Comparer.Default.Compare(p1.Metadata.Value.HelpPriority, p2.Metadata.Value.HelpPriority)); + foreach (var argumentProcessor in processors) + { + var helpDescription = LookupHelpDescription(argumentProcessor); + if (helpDescription != null) + { + OutputSection(helpDescription); + } + } + OutputSection(Resources.Examples); + + // When Help has finished abort any subsequent argument processor operations + return ArgumentProcessorResult.Abort; + } + + #endregion + + #region Private Methods + + /// + /// Lookup the help description for the argument procesor. + /// + /// The argument processor for which to discover any help content + /// The formatted string containing the help description if foundl null otherwise + private string LookupHelpDescription(IArgumentProcessor argumentProcessor) + { + string result = null; + + if (argumentProcessor.Metadata.Value.HelpContentResourceName != null) + { + try + { + result = string.Format( + CultureInfo.CurrentUICulture, + argumentProcessor.Metadata.Value.HelpContentResourceName); + //ResourceHelper.GetString(argumentProcessor.Metadata.HelpContentResourceName, assembly, CultureInfo.CurrentUICulture); + } + catch (Exception e) + { + Output.Warning(e.Message); + } + } + + return result; + } + + /// + /// Output a section followed by an empty line. + /// + /// Message to output. + private void OutputSection(string message) + { + Output.WriteLine(message, OutputLevel.Information); + Output.WriteLine(string.Empty, OutputLevel.Information); + } + + #endregion + } +} diff --git a/src/vstest.console/Processors/Interfaces/IArgumentExector.cs b/src/vstest.console/Processors/Interfaces/IArgumentExector.cs new file mode 100644 index 0000000000..9d4bab86a7 --- /dev/null +++ b/src/vstest.console/Processors/Interfaces/IArgumentExector.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Processors +{ + /// + /// Defines interface for interacting with a command line argument executor. + /// Items exporting this interface will be used in processing command line arguments. + /// + internal interface IArgumentExecutor + { + /// + /// Initializes the Argument Processor with the argument that was provided with the command. + /// + /// Argument that was provided with the command. + void Initialize(string argument); + + /// + /// Perform the action associated with the argument processor. + /// + /// + /// The . + /// + ArgumentProcessorResult Execute(); + } +} diff --git a/src/vstest.console/Processors/Interfaces/IArgumentProcessor.cs b/src/vstest.console/Processors/Interfaces/IArgumentProcessor.cs new file mode 100644 index 0000000000..4f841fa1f4 --- /dev/null +++ b/src/vstest.console/Processors/Interfaces/IArgumentProcessor.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Processors +{ + using System; + + /// + /// Defines interface for interacting with a command line argument processor. + /// Items exporting this interface will be used in processing command line arguments. + /// + internal interface IArgumentProcessor + { + /// + /// Gets or sets the executor. + /// + Lazy Executor { get; set; } + + /// + /// Gets the metadata. + /// + Lazy Metadata { get; } + } +} diff --git a/src/vstest.console/Processors/Interfaces/IArgumentProcessorCapabilities.cs b/src/vstest.console/Processors/Interfaces/IArgumentProcessorCapabilities.cs new file mode 100644 index 0000000000..e13e523eee --- /dev/null +++ b/src/vstest.console/Processors/Interfaces/IArgumentProcessorCapabilities.cs @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Processors +{ + /// + /// Represents capabilities for an Argument Executor + /// + internal interface IArgumentProcessorCapabilities + { + /// + /// The short name of the command the ArgumentProcessor handles. For example "/t". + /// + string ShortCommandName { get; } + + /// + /// The long name of the command the ArgumentProcessor handles. For example "/tests". + /// + string CommandName { get; } + + /// + /// Indicates if multiple of of the command are allowed. + /// + bool AllowMultiple { get; } + + /// + /// Indicates if the command is a special command. Special commands can not + /// be specified directly on the command line (like run tests). + /// + bool IsSpecialCommand { get; } + + /// + /// Indicates if the command should always be executed. + /// + bool AlwaysExecute { get; } + + /// + /// Indicates if the argument processor is a primary action. + /// + bool IsAction { get; } + + /// + /// Indicates the priority of the argument processor. + /// The priority determines the order in which processors are initialized and executed. + /// + ArgumentProcessorPriority Priority { get; } + + /// + /// The resource identifier for the Help Content associated with the decorated argument processor + /// + string HelpContentResourceName { get; } + + /// + /// Based on this enum, corresponding help text will be shown. + /// + HelpContentPriority HelpPriority { get; } + } +} diff --git a/src/vstest.console/Processors/ListTestsArgumentProcessor.cs b/src/vstest.console/Processors/ListTestsArgumentProcessor.cs new file mode 100644 index 0000000000..2ff0b9ecf9 --- /dev/null +++ b/src/vstest.console/Processors/ListTestsArgumentProcessor.cs @@ -0,0 +1,264 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Processors +{ + using System; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.Linq; + using Microsoft.VisualStudio.TestPlatform.CommandLine; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.Utilities; + using Resources = Microsoft.VisualStudio.TestPlatform.CommandLine.Resources; + using Microsoft.VisualStudio.TestPlatform.CommandLine.TestPlatformHelpers; + using Microsoft.VisualStudio.TestPlatform.Common; + using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; + using Microsoft.VisualStudio.TestPlatform.CommandLine.Processors.Utilities; + using Microsoft.VisualStudio.TestPlatform.Client.RequestHelper; + + /// + /// Argument Executor for the "/ListTests" command line argument. + /// + internal class ListTestsArgumentProcessor : IArgumentProcessor + { + #region Constants + + /// + /// The short name of the command line argument that the ListTestsArgumentExecutor handles. + /// + public const string ShortCommandName = "/lt"; + + /// + /// The name of the command line argument that the ListTestsArgumentExecutor handles. + /// + public const string CommandName = "/ListTests"; + + #endregion + + private Lazy metadata; + + private Lazy executor; + + /// + /// Gets the metadata. + /// + public Lazy Metadata + { + get + { + if (this.metadata == null) + { + this.metadata = new Lazy(() => new ListTestsArgumentProcessorCapabilities()); + } + + return this.metadata; + } + } + + /// + /// Gets or sets the executor. + /// + public Lazy Executor + { + get + { + if (this.executor == null) + { + this.executor = + new Lazy( + () => + new ListTestsArgumentExecutor( + CommandLineOptions.Instance, + RunSettingsManager.Instance, + TestRequestManager.Instance)); + } + + return this.executor; + } + + set + { + this.executor = value; + } + } + } + + internal class ListTestsArgumentProcessorCapabilities : BaseArgumentProcessorCapabilities + { + public override string CommandName => ListTestsArgumentProcessor.CommandName; + + public override string ShortCommandName => ListTestsArgumentProcessor.ShortCommandName; + + public override bool AllowMultiple => false; + + public override bool IsAction => true; + + public override ArgumentProcessorPriority Priority => ArgumentProcessorPriority.Normal; + + public override string HelpContentResourceName => Resources.ListTestsHelp; + + public override HelpContentPriority HelpPriority => HelpContentPriority.ListTestsArgumentProcessorHelpPriority; + } + + /// + /// Argument Executor for the "/ListTests" command line argument. + /// + internal class ListTestsArgumentExecutor : IArgumentExecutor + { + #region Fields + + /// + /// Used for getting sources. + /// + private CommandLineOptions commandLineOptions; + + /// + /// Used for getting tests. + /// + private ITestRequestManager testRequestManager; + + /// + /// Used for sending output. + /// + internal IOutput output; + + /// + /// RunSettingsManager to get currently active run settings. + /// + private IRunSettingsProvider runSettingsManager; + + /// + /// Registers for discovery events during discovery + /// + private ITestDiscoveryEventsRegistrar discoveryEventsRegistrar; + + #endregion + + #region Constructor + + /// + /// Default constructor. + /// + /// + /// The options. + /// + public ListTestsArgumentExecutor( + CommandLineOptions options, + IRunSettingsProvider runSettingsProvider, + ITestRequestManager testRequestManager) : + this(options, runSettingsProvider, testRequestManager, ConsoleOutput.Instance) + { + } + + /// + /// Default constructor. + /// + /// + /// The options. + /// + internal ListTestsArgumentExecutor( + CommandLineOptions options, + IRunSettingsProvider runSettingsProvider, + ITestRequestManager testRequestManager, + IOutput output) + { + Contract.Requires(options != null); + + this.commandLineOptions = options; + this.output = output; + this.testRequestManager = testRequestManager; + + this.runSettingsManager = runSettingsProvider; + this.discoveryEventsRegistrar = new DiscoveryEventsRegistrar(output); + } + + #endregion + + #region IArgumentExecutor + + /// + /// Initializes with the argument that was provided with the command. + /// + /// Argument that was provided with the command. + public void Initialize(string argument) + { + if (!string.IsNullOrWhiteSpace(argument)) + { + this.commandLineOptions.AddSource(argument); + } + } + + /// + /// Lists out the available discoverers. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")] + public ArgumentProcessorResult Execute() + { + Contract.Assert(this.output != null); + Contract.Assert(this.commandLineOptions != null); + + if (this.commandLineOptions.Sources.Count() <= 0) + { +#if TODO + this.logger.SendMessage(TestMessageLevel.Error, Resources.MissingTestSourceFile); +#endif + return ArgumentProcessorResult.Fail; + } + + this.output.WriteLine(Resources.ListTestsHeaderMessage, OutputLevel.Information); + + var runSettings = RunSettingsUtilities.GetRunSettings(this.runSettingsManager, this.commandLineOptions); + + var success = this.testRequestManager.DiscoverTests( + new DiscoveryRequestPayload() { Sources = this.commandLineOptions.Sources, RunSettings = runSettings }, + this.discoveryEventsRegistrar); + + return success ? ArgumentProcessorResult.Success : ArgumentProcessorResult.Fail; + } + + #endregion + + private class DiscoveryEventsRegistrar : ITestDiscoveryEventsRegistrar + { + private IOutput output; + + /// + /// Specifies whether some tests were found in the sources or not. + /// + private bool? testsFoundInAnySource = false; + + public DiscoveryEventsRegistrar(IOutput output) + { + this.output = output; + } + + public void RegisterDiscoveryEvents(IDiscoveryRequest discoveryRequest) + { + discoveryRequest.OnDiscoveredTests += this.discoveryRequest_OnDiscoveredTests; + } + + public void UnregisterDiscoveryEvents(IDiscoveryRequest discoveryRequest) + { + discoveryRequest.OnDiscoveredTests -= this.discoveryRequest_OnDiscoveredTests; + this.testsFoundInAnySource = null; + } + + private void discoveryRequest_OnDiscoveredTests(Object sender, DiscoveredTestsEventArgs args) + { + // List out each of the tests. + foreach (var test in args.DiscoveredTestCases) + { + if (!testsFoundInAnySource.Value) + { + testsFoundInAnySource = true; + } + + output.WriteLine(String.Format(CultureInfo.CurrentUICulture, + Resources.AvailableTestsFormat, + test.DisplayName), + OutputLevel.Information); + } + } + } + } +} diff --git a/src/vstest.console/Processors/OutputArgumentProcessor.cs b/src/vstest.console/Processors/OutputArgumentProcessor.cs new file mode 100644 index 0000000000..945919ac41 --- /dev/null +++ b/src/vstest.console/Processors/OutputArgumentProcessor.cs @@ -0,0 +1,150 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Processors +{ + using System; + using System.Diagnostics.Contracts; + using System.Globalization; + using Microsoft.VisualStudio.TestPlatform.CommandLine; + using Resources = Microsoft.VisualStudio.TestPlatform.CommandLine.Resources; + + using TestPlatform.Utilities.Helpers; + using TestPlatform.Utilities.Helpers.Interfaces; + + /// + /// Argument Executor for the "/Output" command line argument. + /// + internal class OutputArgumentProcessor : IArgumentProcessor + { + #region Constants + + /// + /// The name of the command line argument that the OutputArgumentExecutor handles. + /// + public const string CommandName = "/Output"; + + /// + /// The short name of the command line argument that the OutputArgumentExecutor handles. + /// + public const string ShortCommandName = "/o"; + + #endregion + + private Lazy metadata; + + private Lazy executor; + + /// + /// Gets the metadata. + /// + public Lazy Metadata + { + get + { + if (this.metadata == null) + { + this.metadata = new Lazy(() => new OutputArgumentProcessorCapabilities()); + } + + return this.metadata; + } + } + + /// + /// Gets or sets the executor. + /// + public Lazy Executor + { + get + { + if (this.executor == null) + { + this.executor = new Lazy(() => new OutputArgumentExecutor(CommandLineOptions.Instance)); + } + + return this.executor; + } + + set + { + this.executor = value; + } + } + } + + internal class OutputArgumentProcessorCapabilities : BaseArgumentProcessorCapabilities + { + public override string CommandName => OutputArgumentProcessor.CommandName; + + public override string ShortCommandName => OutputArgumentProcessor.ShortCommandName; + + public override bool AllowMultiple => false; + + public override bool IsAction => false; + + public override ArgumentProcessorPriority Priority => ArgumentProcessorPriority.Normal; + + public override string HelpContentResourceName => Resources.OutputArgumentHelp; + + public override HelpContentPriority HelpPriority => HelpContentPriority.OutputArgumentProcessorHelpPriority; + } + + /// + /// Argument Executor for the "/Output" command line argument. + /// + internal class OutputArgumentExecutor : IArgumentExecutor + { + #region Fields + + /// + /// Used for getting sources. + /// + private CommandLineOptions commandLineOptions; + + internal IFileHelper FileHelper { get; set; } + + #endregion + + #region Constructor + + /// + /// Default constructor. + /// + /// + /// The options. + /// + public OutputArgumentExecutor(CommandLineOptions options) + { + Contract.Requires(options != null); + this.commandLineOptions = options; + this.FileHelper = new FileHelper(); + } + #endregion + + #region IArgumentExecutor + + /// + /// Initializes with the argument that was provided with the command. + /// + /// Argument that was provided with the command. + public void Initialize(string argument) + { + if (!FileHelper.Exists(argument)) + { + throw new CommandLineException(string.Format(CultureInfo.CurrentUICulture, Resources.OutputPathNotFound)); + } + + this.commandLineOptions.Output = argument; + } + + /// + /// The output path is already set, return success. + /// + /// The Success + public ArgumentProcessorResult Execute() + { + return ArgumentProcessorResult.Success; + } + #endregion + } +} diff --git a/src/vstest.console/Processors/ParallelArgumentProcessor.cs b/src/vstest.console/Processors/ParallelArgumentProcessor.cs new file mode 100644 index 0000000000..874b2dc4cd --- /dev/null +++ b/src/vstest.console/Processors/ParallelArgumentProcessor.cs @@ -0,0 +1,138 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Processors +{ + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.Linq; + using System.Threading.Tasks; + + /// + /// Parallel Option Argument processor that allows the user to specify if tests are to be run in parallel. + /// + internal class ParallelArgumentProcessor : IArgumentProcessor + { + #region Constants + + public const string CommandName = "/Parallel"; + + #endregion + + private Lazy metadata; + + private Lazy executor; + + /// + /// Gets the metadata. + /// + public Lazy Metadata + { + get + { + if (this.metadata == null) + { + this.metadata = new Lazy(() => new ParallelArgumentProcessorCapabilities()); + } + + return this.metadata; + } + } + + /// + /// Gets or sets the executor. + /// + public Lazy Executor + { + get + { + if (this.executor == null) + { + this.executor = new Lazy(() => new ParallelArgumentExecutor(CommandLineOptions.Instance)); + } + + return this.executor; + } + + set + { + this.executor = value; + } + } + } + + internal class ParallelArgumentProcessorCapabilities : BaseArgumentProcessorCapabilities + { + public override string CommandName => ParallelArgumentProcessor.CommandName; + + public override bool AllowMultiple => false; + + public override bool IsAction => false; + + public override ArgumentProcessorPriority Priority => ArgumentProcessorPriority.AutoUpdateRunSettings; + + public override string HelpContentResourceName => Resources.ParallelArgumentProcessorHelp; + + public override HelpContentPriority HelpPriority => HelpContentPriority.ParallelArgumentProcessorHelpPriority; + } + + /// + /// Argument Executor for the "/Parallel" command line argument. + /// + internal class ParallelArgumentExecutor : IArgumentExecutor + { + #region Fields + + /// + /// Used for getting sources. + /// + private CommandLineOptions commandLineOptions; + + #endregion + + #region Constructor + + /// + /// Default constructor. + /// + /// + /// The options. + /// + public ParallelArgumentExecutor(CommandLineOptions options) + { + Contract.Requires(options != null); + this.commandLineOptions = options; + } + #endregion + + #region IArgumentExecutor + + /// + /// Initializes with the argument that was provided with the command. + /// + /// Argument that was provided with the command. + public void Initialize(string argument) + { + // parallel does not require any argument, throws exception if argument specified + if (!string.IsNullOrWhiteSpace(argument)) + { + throw new CommandLineException( + string.Format(CultureInfo.CurrentCulture, Resources.InvalidParallelCommand, argument)); + } + + commandLineOptions.Parallel = true; + } + + /// + /// The output path is already set, return success. + /// + /// The Success + public ArgumentProcessorResult Execute() + { + return ArgumentProcessorResult.Success; + } + + #endregion + } +} diff --git a/src/vstest.console/Processors/PlatformArgumentProcessor.cs b/src/vstest.console/Processors/PlatformArgumentProcessor.cs new file mode 100644 index 0000000000..584f4eecd7 --- /dev/null +++ b/src/vstest.console/Processors/PlatformArgumentProcessor.cs @@ -0,0 +1,160 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Processors +{ + using System; + using System.Diagnostics.Contracts; + using System.Globalization; + + using Resources = Microsoft.VisualStudio.TestPlatform.CommandLine.Resources; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + + /// + /// An argument processor that allows the user to specify the target platform architecture + /// for test run. + /// + internal class PlatformArgumentProcessor : IArgumentProcessor + { + #region Constants + + /// + /// The name of the command line argument that the OutputArgumentExecutor handles. + /// + public const string CommandName = "/Platform"; + + #endregion + + private Lazy metadata; + + private Lazy executor; + + /// + /// Gets the metadata. + /// + public Lazy Metadata + { + get + { + if (this.metadata == null) + { + this.metadata = new Lazy(() => new PlatformArgumentProcessorCapabilities()); + } + + return this.metadata; + } + } + + /// + /// Gets or sets the executor. + /// + public Lazy Executor + { + get + { + if (this.executor == null) + { + this.executor = new Lazy(() => new PlatformArgumentExecutor(CommandLineOptions.Instance)); + } + + return this.executor; + } + + set + { + this.executor = value; + } + } + } + + internal class PlatformArgumentProcessorCapabilities : BaseArgumentProcessorCapabilities + { + public override string CommandName => PlatformArgumentProcessor.CommandName; + + public override bool AllowMultiple => false; + + public override bool IsAction => false; + + public override ArgumentProcessorPriority Priority => ArgumentProcessorPriority.AutoUpdateRunSettings; + + public override string HelpContentResourceName => Resources.PlatformArgumentHelp; + + public override HelpContentPriority HelpPriority => HelpContentPriority.PlatformArgumentProcessorHelpPriority; + } + + /// + /// Argument Executor for the "/Platform" command line argument. + /// + internal class PlatformArgumentExecutor : IArgumentExecutor + { + #region Fields + + /// + /// Used for getting sources. + /// + private CommandLineOptions commandLineOptions; + + #endregion + + #region Constructor + + /// + /// Default constructor. + /// + /// + /// The options. + /// + public PlatformArgumentExecutor(CommandLineOptions options) + { + Contract.Requires(options != null); + this.commandLineOptions = options; + } + #endregion + + #region IArgumentExecutor + + /// + /// Initializes with the argument that was provided with the command. + /// + /// Argument that was provided with the command. + public void Initialize(string argument) + { + if (string.IsNullOrWhiteSpace(argument)) + { + throw new CommandLineException(Resources.PlatformTypeRequired); + } + + Architecture platform; + var validPlatform = Enum.TryParse(argument, true, out platform); + if (validPlatform) + { + validPlatform = platform == Architecture.X86 || platform == Architecture.X64 || platform == Architecture.ARM; + } + + if (validPlatform) + { + this.commandLineOptions.TargetArchitecture = platform; + } + else + { + throw new CommandLineException( + string.Format(CultureInfo.CurrentCulture, Resources.InvalidPlatformType, argument)); + } + + if (EqtTrace.IsInfoEnabled) + { + EqtTrace.Info("Using platform:{0}", this.commandLineOptions.TargetArchitecture); + } + } + + /// + /// The output path is already set, return success. + /// + /// The Success + public ArgumentProcessorResult Execute() + { + return ArgumentProcessorResult.Success; + } + + #endregion + } +} diff --git a/src/vstest.console/Processors/PortArgumentProcessor.cs b/src/vstest.console/Processors/PortArgumentProcessor.cs new file mode 100644 index 0000000000..8344f76047 --- /dev/null +++ b/src/vstest.console/Processors/PortArgumentProcessor.cs @@ -0,0 +1,184 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Processors +{ + using Microsoft.VisualStudio.TestPlatform.CommandLine; + using System; + using System.Diagnostics.Contracts; + using TestPlatformHelpers; + using Resources = Microsoft.VisualStudio.TestPlatform.CommandLine.Resources; + using Microsoft.VisualStudio.TestPlatform.Client.RequestHelper; + using Microsoft.VisualStudio.TestPlatform.Client.DesignMode; + + /// + /// Argument Executor for the "/Port" command line argument. + /// + internal class PortArgumentProcessor : IArgumentProcessor + { + #region Constants + + /// + /// The name of the command line argument that the PortArgumentExecutor handles. + /// + public const string CommandName = "/Port"; + + #endregion + + private Lazy metadata; + + private Lazy executor; + + /// + /// Gets the metadata. + /// + public Lazy Metadata + { + get + { + if (this.metadata == null) + { + this.metadata = new Lazy(() => new PortArgumentProcessorCapabilities()); + } + + return this.metadata; + } + } + + /// + /// Gets or sets the executor. + /// + public Lazy Executor + { + get + { + if (this.executor == null) + { + this.executor = new Lazy(() => + new PortArgumentExecutor(CommandLineOptions.Instance, TestRequestManager.Instance)); + } + + return this.executor; + } + + set + { + this.executor = value; + } + } + } + + internal class PortArgumentProcessorCapabilities : BaseArgumentProcessorCapabilities + { + public override string CommandName => PortArgumentProcessor.CommandName; + + public override bool AllowMultiple => false; + + public override bool IsAction => false; + + public override ArgumentProcessorPriority Priority => ArgumentProcessorPriority.Normal; + + public override string HelpContentResourceName => Resources.PortArgumentHelp; + + public override HelpContentPriority HelpPriority => HelpContentPriority.PortArgumentProcessorHelpPriority; + } + + /// + /// Argument Executor for the "/Port" command line argument. + /// + internal class PortArgumentExecutor : IArgumentExecutor + { + #region Fields + + /// + /// Used for getting sources. + /// + private CommandLineOptions commandLineOptions; + + /// + /// Test Request Manager + /// + private ITestRequestManager testRequestManager; + + /// + /// Initializes Design mode when called + /// + private Func designModeInitializer; + + /// + /// IDesignModeClient + /// + private IDesignModeClient designModeClient; + + #endregion + + #region Constructor + + /// + /// Default constructor. + /// + /// + /// The options. + /// + public PortArgumentExecutor(CommandLineOptions options, ITestRequestManager testRequestManager) : + this (options, testRequestManager, InitializeDesignMode) + { + } + + /// + /// For Unit testing only + /// + internal PortArgumentExecutor(CommandLineOptions options, ITestRequestManager testRequestManager, Func designModeInitializer) + { + Contract.Requires(options != null); + this.commandLineOptions = options; + this.testRequestManager = testRequestManager; + this.designModeInitializer = designModeInitializer; + } + + #endregion + + #region IArgumentExecutor + + /// + /// Initializes with the argument that was provided with the command. + /// + /// Argument that was provided with the command. + public void Initialize(string argument) + { + int portNumber; + if (string.IsNullOrWhiteSpace(argument) || !int.TryParse(argument, out portNumber)) + { + throw new CommandLineException(Resources.InvalidPortArgument); + } + + this.commandLineOptions.Port = portNumber; + this.designModeClient = this.designModeInitializer?.Invoke(); + } + + /// + /// The port is already set, return success. + /// + /// The Success + public ArgumentProcessorResult Execute() + { + try + { + this.designModeClient?.ConnectToClientAndProcessRequests(this.commandLineOptions.Port, this.testRequestManager); + } + catch(TimeoutException) + { + // Todo:sasin log the exception + return ArgumentProcessorResult.Fail; + } + return ArgumentProcessorResult.Success; + } + + #endregion + + private static IDesignModeClient InitializeDesignMode() + { + DesignModeClient.Initialize(); + return DesignModeClient.Instance; + } + } +} diff --git a/src/vstest.console/Processors/RunSettingsArgumentProcessor.cs b/src/vstest.console/Processors/RunSettingsArgumentProcessor.cs new file mode 100644 index 0000000000..760bb7968f --- /dev/null +++ b/src/vstest.console/Processors/RunSettingsArgumentProcessor.cs @@ -0,0 +1,205 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Processors +{ + using System; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.Xml; + using System.Xml.XPath; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities; + using Microsoft.VisualStudio.TestPlatform.Utilities; + using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces; + using Resources = Microsoft.VisualStudio.TestPlatform.CommandLine.Resources; + using Microsoft.VisualStudio.TestPlatform.Common; + using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; + using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers; + + internal class RunSettingsArgumentProcessor : IArgumentProcessor + { + #region Constants + + /// + /// The name of the command line argument that the PortArgumentExecutor handles. + /// + public const string CommandName = "/Settings"; + + #endregion + + private Lazy metadata; + + private Lazy executor; + + /// + /// Gets the metadata. + /// + public Lazy Metadata + { + get + { + if (this.metadata == null) + { + this.metadata = new Lazy(() => new RunSettingsArgumentProcessorCapabilities()); + } + + return this.metadata; + } + } + + /// + /// Gets or sets the executor. + /// + public Lazy Executor + { + get + { + if (this.executor == null) + { + this.executor = new Lazy(() => new RunSettingsArgumentExecutor(CommandLineOptions.Instance, RunSettingsManager.Instance)); + } + + return this.executor; + } + + set + { + this.executor = value; + } + } + } + + internal class RunSettingsArgumentProcessorCapabilities : BaseArgumentProcessorCapabilities + { + public override string CommandName => RunSettingsArgumentProcessor.CommandName; + + public override bool AllowMultiple => false; + + public override bool IsAction => false; + + public override ArgumentProcessorPriority Priority => ArgumentProcessorPriority.RunSettings; + + public override string HelpContentResourceName => Resources.RunSettingsArgumentHelp; + + public override HelpContentPriority HelpPriority => HelpContentPriority.RunSettingsArgumentProcessorHelpPriority; + } + + internal class RunSettingsArgumentExecutor : IArgumentExecutor + { + private CommandLineOptions commandLineOptions; + private IRunSettingsProvider runSettingsManager; + + internal IFileHelper FileHelper { get; set; } + + internal RunSettingsArgumentExecutor(CommandLineOptions commandLineOptions, IRunSettingsProvider runSettingsManager) + { + this.commandLineOptions = commandLineOptions; + this.runSettingsManager = runSettingsManager; + this.FileHelper = new FileHelper(); + } + + public void Initialize(string argument) + { + if (string.IsNullOrWhiteSpace(argument)) + { + throw new CommandLineException(Resources.RunSettingsRequired); + } + + if (!this.FileHelper.Exists(argument)) + { + throw new CommandLineException( + string.Format( + CultureInfo.CurrentCulture, + Resources.RunSettingsFileNotFound, + argument)); + } + + Contract.EndContractBlock(); + + // Load up the run settings and set it as the active run settings. + try + { + IXPathNavigable document = this.GetRunSettingsDocument(argument); + + var runSettings = new RunSettings(); + + // Currently do not see the need to load the settings providers in the console process. + runSettings.LoadSettingsXml(document.CreateNavigator().OuterXml); + + this.runSettingsManager.SetActiveRunSettings(runSettings); + } + catch (XmlException exception) + { + throw new CommandLineException(Resources.MalformedRunSettingsFile, exception); + } + catch (SettingsException exception) + { + throw new CommandLineException(exception.Message, exception); + } + } + + protected virtual XmlReader GetReaderForFile(string runSettingsFile) + { + return XmlReader.Create(runSettingsFile, XmlRunSettingsUtilities.ReaderSettings); + } + + private IXPathNavigable GetRunSettingsDocument(string runSettingsFile) + { + IXPathNavigable runSettingsDocument; + + if (!MSTestSettingsUtilities.IsLegacyTestSettingsFile(runSettingsFile)) + { + using (XmlReader reader = this.GetReaderForFile(runSettingsFile)) + { + var settingsDocument = new XmlDocument(); + settingsDocument.Load(reader); + ClientUtilities.FixRelativePathsInRunSettings(settingsDocument, runSettingsFile); +#if NET46 + runSettingsDocument = settingsDocument; +#else + runSettingsDocument = settingsDocument.ToXPathNavigable(); +#endif + } + } + else + { + runSettingsDocument = XmlRunSettingsUtilities.CreateDefaultRunSettings(); + + FrameworkVersion frameworkVersion = FrameworkVersion.Framework45; + + if (this.commandLineOptions.FrameworkVersionSpecified && this.commandLineOptions.TargetFrameworkVersion != FrameworkVersion.Framework45) + { + IOutput output = ConsoleOutput.Instance; + output.Warning(Resources.TestSettingsFrameworkMismatch, this.commandLineOptions.TargetFrameworkVersion.ToString(), FrameworkVersion.Framework45.ToString()); + } + + var architecture = this.commandLineOptions.ArchitectureSpecified + ? this.commandLineOptions.TargetArchitecture + : Architecture.X86; + + runSettingsDocument = MSTestSettingsUtilities.Import(runSettingsFile, runSettingsDocument, architecture, frameworkVersion); + } + + if (this.commandLineOptions.EnableCodeCoverage == true) + { + try + { + CodeCoverageDataAdapterUtilities.UpdateWithCodeCoverageSettingsIfNotConfigured(runSettingsDocument); + } + catch (XPathException e) + { + throw new SettingsException(Resources.MalformedRunSettingsFile, e); + } + } + + return runSettingsDocument; + } + + public ArgumentProcessorResult Execute() + { + // Nothing to do here, the work was done in initialization. + return ArgumentProcessorResult.Success; + } + } +} diff --git a/src/vstest.console/Processors/RunSpecificTestsArgumentProcessor.cs b/src/vstest.console/Processors/RunSpecificTestsArgumentProcessor.cs new file mode 100644 index 0000000000..6ebcf385c5 --- /dev/null +++ b/src/vstest.console/Processors/RunSpecificTestsArgumentProcessor.cs @@ -0,0 +1,335 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Processors +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Diagnostics.Contracts; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Resources = Microsoft.VisualStudio.TestPlatform.CommandLine.Resources; + using Microsoft.VisualStudio.TestPlatform.Utilities; + using System.Collections.ObjectModel; + using System.Globalization; + + using Microsoft.VisualStudio.TestPlatform.Common.Logging; + using Microsoft.VisualStudio.TestPlatform.CommandLine.TestPlatformHelpers; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces; + using Microsoft.VisualStudio.TestPlatform.Common; + using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; + using Microsoft.VisualStudio.TestPlatform.CommandLine.Processors.Utilities; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.Client.RequestHelper; + + internal class RunSpecificTestsArgumentProcessor : IArgumentProcessor + { + public const string CommandName = "/Tests"; + + private Lazy metadata; + + private Lazy executor; + + public Lazy Metadata + { + get + { + if (this.metadata == null) + { + this.metadata = new Lazy(() => new RunSpecificTestsArgumentProcessorCapabilities()); + } + return this.metadata; + } + } + + public Lazy Executor + { + get + { + if (this.executor == null) + { + this.executor = new Lazy(() => + new RunSpecificTestsArgumentExecutor( + CommandLineOptions.Instance, + RunSettingsManager.Instance, + TestRequestManager.Instance)); + } + + return this.executor; + } + set + { + this.executor = value; + } + } + } + + internal class RunSpecificTestsArgumentProcessorCapabilities : BaseArgumentProcessorCapabilities + { + public override string CommandName => RunSpecificTestsArgumentProcessor.CommandName; + + public override bool IsAction => true; + + public override bool AllowMultiple => false; + + public override string HelpContentResourceName => Resources.RunSpecificTestsHelp; + + public override HelpContentPriority HelpPriority => HelpContentPriority.RunSpecificTestsArgumentProcessorHelpPriority; + + public override ArgumentProcessorPriority Priority => ArgumentProcessorPriority.Normal; + } + + internal class RunSpecificTestsArgumentExecutor : IArgumentExecutor + { + #region Fields + + /// + /// Used for getting sources. + /// + private CommandLineOptions commandLineOptions; + + /// + /// The instance of testPlatforms + /// + private ITestRequestManager testRequestManager; + + /// + /// Used for sending output. + /// + internal IOutput output; + + /// + /// RunSettingsManager to get currently active run settings. + /// + private IRunSettingsProvider runSettingsManager; + +#if TODO + /// + /// Gets the instance of logger object. + /// + private IMessageLogger logger; +#endif + + /// + /// Given Collection of strings for filtering test cases + /// + private Collection selectedTestNames; + + /// + /// Used for tracking the total no. of tests discovered from the given sources. + /// + private long discoveredTestCount = 0; + + /// + /// Collection of test cases that match atleast one of the given search strings + /// + private Collection selectedTestCases = new Collection(); + + /// + /// Effective run settings applicable to test run after inferring the multi-targeting settings. + /// + private string effectiveRunSettings = null; + + /// + /// List of filters that have not yet been discovered + /// + HashSet undiscoveredFilters = new HashSet(); + + /// + /// Registers for discovery events during discovery + /// + private ITestDiscoveryEventsRegistrar discoveryEventsRegistrar; + +#endregion + + #region Constructor + + /// + /// Default constructor. + /// + public RunSpecificTestsArgumentExecutor( + CommandLineOptions options, + IRunSettingsProvider runSettingsProvider, + ITestRequestManager testRequestManager) + { + Contract.Requires(options != null); + Contract.Requires(testRequestManager != null); + + this.commandLineOptions = options; + this.testRequestManager = testRequestManager; + + this.runSettingsManager = RunSettingsManager.Instance; + this.output = ConsoleOutput.Instance; + this.discoveryEventsRegistrar = new DiscoveryEventsRegistrar(discoveryRequest_OnDiscoveredTests); + } + +#endregion + + #region IArgumentProcessor + + /// + /// Splits given the search strings and adds to selectTestNamesCollection. + /// + /// + public void Initialize(string argument) + { + if (!string.IsNullOrWhiteSpace(argument)) + { + selectedTestNames = new Collection(argument.Split(new string[] { Resources.SearchStringDelimiter }, StringSplitOptions.RemoveEmptyEntries)); + } + if (selectedTestNames == null || selectedTestNames.Count <= 0) + { + throw new CommandLineException(Resources.SpecificTestsRequired); + } + + // by default all filters are not discovered on launch + undiscoveredFilters = new HashSet(selectedTestNames); + } + + /// + /// Execute specific tests that match any of the given strings. + /// + /// + public ArgumentProcessorResult Execute() + { + Contract.Assert(output != null); + Contract.Assert(commandLineOptions != null); + Contract.Assert(testRequestManager != null); + + if (commandLineOptions.Sources.Count() <= 0) + { +#if TODO + logger.SendMessage(TestMessageLevel.Error, Resources.MissingTestSourceFile); +#endif + return ArgumentProcessorResult.Fail; + } + + if (!string.IsNullOrWhiteSpace(commandLineOptions.TestCaseFilterValue)) + { +#if TODO + logger.SendMessage(TestMessageLevel.Error, Resources.InvalidTestCaseFilterValueForSpecificTests); +#endif + return ArgumentProcessorResult.Fail; + } + + bool result = false; + + this.effectiveRunSettings = RunSettingsUtilities.GetRunSettings(this.runSettingsManager, this.commandLineOptions); + + // Discover tests from sources and filter on every discovery reported. + result = DiscoverTestsAndSelectSpecified(commandLineOptions.Sources); + + // Now that tests are discovered and filtered, we run only those selected tests. + result = result && ExecuteSelectedTests(); + + return result ? ArgumentProcessorResult.Success : ArgumentProcessorResult.Fail; + } + + #endregion + + #region Private Methods + + /// + /// Discovers tests from the given sources and selects only specified tests. + /// + /// TestPlatform created based on the command line options + private bool DiscoverTestsAndSelectSpecified(IEnumerable sources) + { + output.WriteLine(Resources.StartingDiscovery, OutputLevel.Information); + return this.testRequestManager.DiscoverTests( + new DiscoveryRequestPayload() { Sources = sources, RunSettings = effectiveRunSettings }, this.discoveryEventsRegistrar); + } + + /// + /// Executes the selected tests + /// + private bool ExecuteSelectedTests() + { + bool result = true; + if (selectedTestCases.Count > 0) + { + if (undiscoveredFilters.Count() != 0) + { + string missingFilters = string.Join(", ", undiscoveredFilters); + string warningMessage = string.Format(CultureInfo.CurrentCulture, Resources.SomeTestsUnavailableAfterFiltering, discoveredTestCount, missingFilters); + output.Warning(warningMessage); + } + + // for command line keep alive is always false. + bool keepAlive = false; + + EqtTrace.Verbose("RunSpecificTestsArgumentProcessor:Execute: Test run is queued."); + var runRequestPayload = new TestRunRequestPayload() { TestCases = selectedTestCases.ToList(), RunSettings = effectiveRunSettings, KeepAlive = keepAlive }; + result &= this.testRequestManager.RunTests(runRequestPayload, null, null); + } + else + { + string warningMessage; + if (discoveredTestCount > 0) + { + // No tests that matched any of the given strings. + warningMessage = string.Format(CultureInfo.CurrentCulture, Resources.NoTestsAvailableAfterFiltering, discoveredTestCount, String.Join(", ", selectedTestNames)); + } + else + { + // No tests were discovered from the given sources. + warningMessage = string.Format(CultureInfo.CurrentUICulture, Resources.NoTestsAvailableInSources, string.Join(", ", commandLineOptions.Sources)); + + if (!commandLineOptions.UseVsixExtensions) + { + warningMessage = string.Format(CultureInfo.CurrentCulture, Resources.NoTestsFoundWarningMessageWithSuggestionToUseVsix, warningMessage, Resources.SuggestUseVsixExtensionsIfNoTestsIsFound); + } + } + output.Warning(warningMessage); + } + return result; + } + + /// + /// Filter discovered tests and find matching tests from given search strings. + /// Any name of the test that can match multiple strings will be added only once. + /// + /// + /// + private void discoveryRequest_OnDiscoveredTests(Object sender, DiscoveredTestsEventArgs args) + { + discoveredTestCount += args.DiscoveredTestCases.Count(); + foreach (var testCase in args.DiscoveredTestCases) + { + foreach (var nameCriteria in selectedTestNames) + { + if (testCase.FullyQualifiedName.IndexOf(nameCriteria, StringComparison.OrdinalIgnoreCase) != -1) + { + selectedTestCases.Add(testCase); + + // If a testcase matched then a filter matched - so remove the filter from not found list + undiscoveredFilters.Remove(nameCriteria); + break; + } + } + } + } + + #endregion + + private class DiscoveryEventsRegistrar : ITestDiscoveryEventsRegistrar + { + private EventHandler discoveredTestsHandler; + + public DiscoveryEventsRegistrar(EventHandler discoveredTestsHandler) + { + this.discoveredTestsHandler = discoveredTestsHandler; + } + + public void RegisterDiscoveryEvents(IDiscoveryRequest discoveryRequest) + { + discoveryRequest.OnDiscoveredTests += this.discoveredTestsHandler; + } + + public void UnregisterDiscoveryEvents(IDiscoveryRequest discoveryRequest) + { + discoveryRequest.OnDiscoveredTests -= this.discoveredTestsHandler; + } + } + } +} \ No newline at end of file diff --git a/src/vstest.console/Processors/RunTestsArgumentProcessor.cs b/src/vstest.console/Processors/RunTestsArgumentProcessor.cs new file mode 100644 index 0000000000..28092de345 --- /dev/null +++ b/src/vstest.console/Processors/RunTestsArgumentProcessor.cs @@ -0,0 +1,243 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Processors +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Diagnostics.Contracts; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Resources = Microsoft.VisualStudio.TestPlatform.CommandLine.Resources; + using Microsoft.VisualStudio.TestPlatform.Utilities; + + using Microsoft.VisualStudio.TestPlatform.CommandLine.TestPlatformHelpers; + + using Microsoft.VisualStudio.TestPlatform.CommandLine.Processors.Utilities; + using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; + using Microsoft.VisualStudio.TestPlatform.Common; + using Microsoft.VisualStudio.TestPlatform.Client.RequestHelper; + + internal class RunTestsArgumentProcessor : IArgumentProcessor + { + public const string CommandName = "/RunTests"; + + private Lazy metadata; + + private Lazy executor; + + public Lazy Metadata + { + get + { + if (this.metadata == null) + { + this.metadata = new Lazy(() => new RunTestsArgumentProcessorCapabilities()); + } + return this.metadata; + } + } + + public Lazy Executor + { + get + { + if (this.executor == null) + { + this.executor = new Lazy(() => + new RunTestsArgumentExecutor( + CommandLineOptions.Instance, + RunSettingsManager.Instance, + TestRequestManager.Instance)); + } + + return this.executor; + } + set + { + this.executor = value; + } + } + } + + internal class RunTestsArgumentProcessorCapabilities : BaseArgumentProcessorCapabilities + { + public override string CommandName => RunTestsArgumentProcessor.CommandName; + + public override bool AllowMultiple => false; + + public override bool IsAction => true; + + public override ArgumentProcessorPriority Priority => ArgumentProcessorPriority.Normal; + + public override string HelpContentResourceName => Resources.RunTestsArgumentHelp; + + public override HelpContentPriority HelpPriority => HelpContentPriority.RunTestsArgumentProcessorHelpPriority; + + public override bool IsSpecialCommand => true; + + public override bool AlwaysExecute => false; + } + + internal class RunTestsArgumentExecutor : IArgumentExecutor + { + #region Fields + + /// + /// Used for getting tests to run. + /// + private CommandLineOptions commandLineOptions; + + /// + /// The instance of testPlatforms + /// + private ITestRequestManager testRequestManager; + + /// + /// Used for sending discovery messages. + /// + internal IOutput output; + + /// + /// Settings manager to get currently active settings. + /// + private IRunSettingsProvider runSettingsManager; + + /// + /// Registers and Unregisters for test run events before and after test run + /// + private ITestRunEventsRegistrar testRunEventsRegistrar; + + #endregion + + #region Constructor + + /// + /// Default constructor. + /// + public RunTestsArgumentExecutor( + CommandLineOptions commandLineOptions, + IRunSettingsProvider runSettingsProvider, + ITestRequestManager testRequestManager) + { + Contract.Requires(commandLineOptions != null); + + this.commandLineOptions = commandLineOptions; + + this.output = ConsoleOutput.Instance; + + this.runSettingsManager = RunSettingsManager.Instance; + this.testRequestManager = testRequestManager; + this.testRunEventsRegistrar = new TestRunRequestEventsRegistrar(); + } + + #endregion + + public void Initialize(string argument) + { + // Nothing to do. + } + + /// + /// Execute all of the tests. + /// + public ArgumentProcessorResult Execute() + { + Contract.Assert(this.commandLineOptions != null); + + // Ensure a test source file was provided + var anySource = this.commandLineOptions.Sources.FirstOrDefault(); + if (anySource == null) + { +#if TODO + logger.SendMessage(TestMessageLevel.Error, Resources.MissingTestSourceFile); +#endif + return ArgumentProcessorResult.Fail; + } + + this.output.WriteLine(Resources.StartingExecution, OutputLevel.Information); + + var success = true; + if (this.commandLineOptions.Sources.Any()) + { + success = this.RunTests(this.commandLineOptions.Sources); + } + + return success ? ArgumentProcessorResult.Success : ArgumentProcessorResult.Fail; + } + + private bool RunTests(IEnumerable sources) + { + // create/start test run + if (EqtTrace.IsInfoEnabled) + { + EqtTrace.Info("RunTestsArgumentProcessor:Execute: Test run is starting."); + } + + var runSettings = RunSettingsUtilities.GetRunSettings(this.runSettingsManager, this.commandLineOptions); + + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose("RunTestsArgumentProcessor:Execute: Queuing Test run."); + } + + // for command line keep alive is always false. + // for Windows Store apps it should be false, as Windows Store apps executor should terminate after finishing the test execution. + var keepAlive = false; + + var runRequestPayload = new TestRunRequestPayload() { Sources = this.commandLineOptions.Sources.ToList(), RunSettings = runSettings, KeepAlive = keepAlive }; + var result = this.testRequestManager.RunTests(runRequestPayload, null, this.testRunEventsRegistrar); + + if (EqtTrace.IsInfoEnabled) + { + EqtTrace.Info("RunTestsArgumentProcessor:Execute: Test run is completed."); + } + + return result; + } + + private class TestRunRequestEventsRegistrar : ITestRunEventsRegistrar + { + /// + /// Specifies whether some tests were found in the test run or not. + /// + protected bool? testsFoundInAnySource = null; + + public void RegisterTestRunEvents(ITestRunRequest testRunRequest) + { + testRunRequest.OnRunCompletion += TestRunRequest_OnRunCompletion; + } + + public void UnregisterTestRunEvents(ITestRunRequest testRunRequest) + { + testRunRequest.OnRunCompletion -= TestRunRequest_OnRunCompletion; + // reset + this.testsFoundInAnySource = null; + } + + /// + /// Handles the TestRunRequest complete event + /// + /// + /// RunCompletion args + private void TestRunRequest_OnRunCompletion(object sender, TestRunCompleteEventArgs e) + { + // If run is not aborted/cancelled then check the count of executed tests. + // we need to check if there are any tests executed - to try show some help info to user to check for installed vsix extensions + if (!e.IsAborted && !e.IsCanceled) + { + this.testsFoundInAnySource = (e.TestRunStatistics == null) ? false : (e.TestRunStatistics.ExecutedTests > 0); + + // TODO: We need to show a message to check for vsix extensions if no tests are executed + // Indicate the user to use vsix extensions command if there are no tests found + //if (Utilities.ShouldIndicateTheUserToUseVsixExtensionsCommand(testsFoundInAnySource, commandLineOptions)) + //{ + // output.Information(Resources.SuggestUseVsixExtensionsIfNoTestsIsFound); + // output.WriteLine(string.Empty, OutputLevel.Information); + //} + } + } + } + } +} diff --git a/src/vstest.console/Processors/TestAdapterPathArgumentProcessor.cs b/src/vstest.console/Processors/TestAdapterPathArgumentProcessor.cs new file mode 100644 index 0000000000..bff58d7036 --- /dev/null +++ b/src/vstest.console/Processors/TestAdapterPathArgumentProcessor.cs @@ -0,0 +1,201 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Processors +{ + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.IO; + + using Microsoft.VisualStudio.TestPlatform.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.Utilities; + + /// + /// Allows the user to specify a path to load custom adapters from. + /// + internal class TestAdapterPathArgumentProcessor : IArgumentProcessor + { + #region Constants + + /// + /// The name of the command line argument that the ListTestsArgumentExecutor handles. + /// + public const string CommandName = "/TestAdapterPath"; + + #endregion + + private Lazy metadata; + + private Lazy executor; + + /// + /// Gets the metadata. + /// + public Lazy Metadata + { + get + { + if (this.metadata == null) + { + this.metadata = new Lazy(() => new TestAdapterPathArgumentProcessorCapabilities()); + } + + return this.metadata; + } + } + + /// + /// Gets or sets the executor. + /// + public Lazy Executor + { + get + { + if (this.executor == null) + { + this.executor = new Lazy(() => new TestAdapterPathArgumentExecutor(CommandLineOptions.Instance, TestPlatformFactory.GetTestPlatform(), ConsoleOutput.Instance)); + } + + return this.executor; + } + + set + { + this.executor = value; + } + } + } + + /// + /// The argument capabilities. + /// + internal class TestAdapterPathArgumentProcessorCapabilities : BaseArgumentProcessorCapabilities + { + public override string CommandName => TestAdapterPathArgumentProcessor.CommandName; + + public override bool AllowMultiple => false; + + public override bool IsAction => false; + + public override ArgumentProcessorPriority Priority => ArgumentProcessorPriority.TestAdapterPath; + + public override string HelpContentResourceName => CommandLine.Resources.TestAdapterPathHelp; + + public override HelpContentPriority HelpPriority => HelpContentPriority.TestAdapterPathArgumentProcessorHelpPriority; + } + + /// + /// The argument executor. + /// + internal class TestAdapterPathArgumentExecutor : IArgumentExecutor + { + #region Fields + + /// + /// Used for getting sources. + /// + private CommandLineOptions commandLineOptions; + + /// + /// The test platform instance. + /// + private ITestPlatform testPlatform; + + /// + /// Used for sending output. + /// + private IOutput output; + + #endregion + + #region Constructor + + /// + /// Default constructor. + /// + /// The options. + /// The test platform + public TestAdapterPathArgumentExecutor(CommandLineOptions options, ITestPlatform testPlatform, IOutput output) + { + Contract.Requires(options != null); + + this.commandLineOptions = options; + this.testPlatform = testPlatform; + this.output = output; + } + + #endregion + + #region IArgumentExecutor + + /// + /// Initializes with the argument that was provided with the command. + /// + /// Argument that was provided with the command. + public void Initialize(string argument) + { + if (string.IsNullOrWhiteSpace(argument)) + { + throw new CommandLineException( + string.Format(CultureInfo.CurrentCulture, CommandLine.Resources.TestAdapterPathValueRequired)); + } + + string customAdaptersPath; + + try + { + // Remove leading and trailing ' " ' chars... + argument = argument.Trim().Trim(new char[] { '\"' }); + + customAdaptersPath = Path.GetFullPath(argument); + if (!Directory.Exists(customAdaptersPath)) + { + throw new DirectoryNotFoundException(CommandLine.Resources.TestAdapterPathDoesNotExist); + } + } + catch (Exception e) + { + throw new CommandLineException( + string.Format(CultureInfo.CurrentCulture, CommandLine.Resources.InvalidTestAdapterPathCommand, argument, e.Message)); + } + + this.commandLineOptions.TestAdapterPath = customAdaptersPath; + var adapterFiles = new List(this.GetTestAdaptersFromDirectory(customAdaptersPath)); + + if (adapterFiles.Count > 0) + { + this.testPlatform.UpdateExtensions(adapterFiles, false); + } + else + { + // Print a warning about not finding any test adapter in provided path... + this.output.Warning(CommandLine.Resources.NoAdaptersFoundInTestAdapterPath, argument); + this.output.WriteLine(string.Empty, OutputLevel.Information); + } + } + + /// + /// Executes the argument processor. + /// + /// The . + public ArgumentProcessorResult Execute() + { + // Nothing to do since we updated the parameter during initialize parameter + return ArgumentProcessorResult.Success; + } + + /// + /// Gets the test adapters from directory. + /// + /// The directory. + /// The list of test adapter assemblies. + internal virtual IEnumerable GetTestAdaptersFromDirectory(string directory) + { + return Directory.EnumerateFiles(directory, @"*.TestAdapter.dll", SearchOption.AllDirectories); + } + + #endregion + } +} diff --git a/src/vstest.console/Processors/TestCaseFilterArgumentProcessor.cs b/src/vstest.console/Processors/TestCaseFilterArgumentProcessor.cs new file mode 100644 index 0000000000..6b00608f56 --- /dev/null +++ b/src/vstest.console/Processors/TestCaseFilterArgumentProcessor.cs @@ -0,0 +1,137 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Processors +{ + using System; + using System.Diagnostics.Contracts; + using System.Globalization; + using Microsoft.VisualStudio.TestPlatform.CommandLine; + using Resources = Microsoft.VisualStudio.TestPlatform.CommandLine.Resources; + + /// + /// Argument Executor for the "/TestCaseFilter" command line argument. + /// + internal class TestCaseFilterArgumentProcessor : IArgumentProcessor + { + #region Constants + + /// + /// The name of the command line argument that the TestCaseFilterArgumentExecutor handles. + /// + public const string CommandName = "/TestCaseFilter"; + + #endregion + + private Lazy metadata; + + private Lazy executor; + + /// + /// Gets the metadata. + /// + public Lazy Metadata + { + get + { + if (this.metadata == null) + { + this.metadata = new Lazy(() => new TestCaseFilterArgumentProcessorCapabilities()); + } + + return this.metadata; + } + } + + /// + /// Gets or sets the executor. + /// + public Lazy Executor + { + get + { + if (this.executor == null) + { + this.executor = new Lazy(() => new TestCaseFilterArgumentExecutor(CommandLineOptions.Instance)); + } + + return this.executor; + } + + set + { + this.executor = value; + } + } + } + + internal class TestCaseFilterArgumentProcessorCapabilities : BaseArgumentProcessorCapabilities + { + public override string CommandName => TestCaseFilterArgumentProcessor.CommandName; + + public override bool AllowMultiple => false; + + public override bool IsAction => false; + + public override ArgumentProcessorPriority Priority => ArgumentProcessorPriority.Normal; + + public override string HelpContentResourceName => Resources.TestCaseFilterArgumentHelp; + + public override HelpContentPriority HelpPriority => HelpContentPriority.TestCaseFilterArgumentProcessorHelpPriority; + } + + /// + /// Argument Executor for the "/TestCaseFilter" command line argument. + /// + internal class TestCaseFilterArgumentExecutor : IArgumentExecutor + { + #region Fields + + /// + /// Used for getting sources. + /// + private CommandLineOptions commandLineOptions; + + #endregion + + #region Constructor + + /// + /// Default constructor. + /// + /// + /// The options. + /// + public TestCaseFilterArgumentExecutor(CommandLineOptions options) + { + Contract.Requires(options != null); + this.commandLineOptions = options; + } + #endregion + + #region IArgumentExecutor + + /// + /// Initializes with the argument that was provided with the command. + /// + /// Argument that was provided with the command. + public void Initialize(string argument) + { + if (string.IsNullOrWhiteSpace(argument)) + { + throw new CommandLineException(string.Format(CultureInfo.CurrentUICulture, Resources.TestCaseFilterValueRequired)); + } + + this.commandLineOptions.TestCaseFilterValue = argument; + } + + /// + /// The TestCaseFilter is already set, return success. + /// + /// The Success + public ArgumentProcessorResult Execute() + { + return ArgumentProcessorResult.Success; + } + #endregion + } +} diff --git a/src/vstest.console/Processors/TestSourceArgumentProcessor.cs b/src/vstest.console/Processors/TestSourceArgumentProcessor.cs new file mode 100644 index 0000000000..720bcb96ce --- /dev/null +++ b/src/vstest.console/Processors/TestSourceArgumentProcessor.cs @@ -0,0 +1,132 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Processors +{ + using System; + using System.Diagnostics.Contracts; + using Microsoft.VisualStudio.TestPlatform.CommandLine; + + /// + /// Argument Executor which handles adding the source provided to the TestManager. + /// + internal class TestSourceArgumentProcessor : IArgumentProcessor + { + #region Constants + + /// + /// The command name. + /// + public const string CommandName = "TestSource"; + + #endregion + + private Lazy metadata; + + private Lazy executor; + + /// + /// Gets the metadata. + /// + public Lazy Metadata + { + get + { + if (this.metadata == null) + { + this.metadata = new Lazy(() => new TestSourceArgumentProcessorCapabilities()); + } + return this.metadata; + } + } + + /// + /// Gets or sets the executor. + /// + public Lazy Executor + { + get + { + if (this.executor == null) + { + this.executor = new Lazy(() => new TestSourceArgumentExecutor(CommandLineOptions.Instance)); + } + + return this.executor; + } + set + { + this.executor = value; + } + } + } + + /// + /// The test source argument processor capabilities. + /// + internal class TestSourceArgumentProcessorCapabilities : BaseArgumentProcessorCapabilities + { + public override string CommandName => TestSourceArgumentProcessor.CommandName; + + public override ArgumentProcessorPriority Priority => ArgumentProcessorPriority.Normal; + + public override bool IsSpecialCommand => true; + } + + /// + /// Argument Executor which handles adding the source provided to the TestManager. + /// + internal class TestSourceArgumentExecutor : IArgumentExecutor + { + #region Fields + + /// + /// Used for adding sources to the test manager. + /// + private CommandLineOptions testSources; + + #endregion + + #region Constructor + + /// + /// Default constructor. + /// + /// + /// The test Sources. + /// + public TestSourceArgumentExecutor(CommandLineOptions testSources) + { + Contract.Requires(testSources != null); + this.testSources = testSources; + } + + #endregion + + #region IArgumentExecutor + + /// + /// Initializes with the argument that was provided with the command. + /// + /// Argument that was provided with the command. + public void Initialize(string argument) + { + Contract.Assert(this.testSources != null); + this.testSources.AddSource(argument); + } + + /// + /// Executes the argument processor. + /// + /// + /// The . + /// + public ArgumentProcessorResult Execute() + { + // Nothing to do. Our work was done during initialize. + return ArgumentProcessorResult.Success; + } + + #endregion + + } +} diff --git a/src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs b/src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs new file mode 100644 index 0000000000..04e3ef8fb8 --- /dev/null +++ b/src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs @@ -0,0 +1,306 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Processors +{ + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + + /// + /// Used to create the appropriate instance of an argument processor. + /// + internal class ArgumentProcessorFactory + { + #region Constants + + /// + /// The command starter. + /// + internal const string CommandStarter = "/"; + + #endregion + + #region Fields + + /// + /// Available argument processors. + /// + private IEnumerable argumentProcessors; + private Dictionary commandToProcessorMap; + private Dictionary specialCommandToProcessorMap; + + #endregion + + #region Constructor + + /// + /// Initializes the argument processor factory. + /// + /// + /// The argument Processors. + /// + /// + /// This is not public because the static Create method should be used to access the instance. + /// + protected ArgumentProcessorFactory( + IEnumerable argumentProcessors) + { + Contract.Requires(argumentProcessors != null); + this.argumentProcessors = argumentProcessors; + } + + #endregion + + #region Static Methods + + public static IEnumerable DefaultArgumentProcessors => new List { + new HelpArgumentProcessor(), + new TestSourceArgumentProcessor(), + new ListTestsArgumentProcessor(), + new RunTestsArgumentProcessor(), + new TestAdapterPathArgumentProcessor(), + new OutputArgumentProcessor(), + new BuildBasePathArgumentProcessor(), + new ConfigurationArgumentProcessor(), + new PortArgumentProcessor(), + new RunSettingsArgumentProcessor(), + new PlatformArgumentProcessor(), + new EnableLoggerArgumentProcessor(), + new ParallelArgumentProcessor() + }; + + /// + /// Creates ArgumentProcessorFactory. + /// + /// ArgumentProcessorFactory. + internal static ArgumentProcessorFactory Create() + { + // Get the ArgumentProcessorFactory + return new ArgumentProcessorFactory(DefaultArgumentProcessors); + } + + /// + /// Creates ArgumentProcessorFactory with given list of processors + /// + /// ArgumentProcessorFactory. + internal static ArgumentProcessorFactory Create(IEnumerable processorList) + { + return new ArgumentProcessorFactory(processorList); + } + + #endregion + + #region Properties + + /// + /// Returns all of the available argument processors. + /// + public IEnumerable AllArgumentProcessors + { + get { return argumentProcessors; } + } + + /// + /// Gets a mapping between command and Argument Executor. + /// + internal Dictionary CommandToProcessorMap + { + get + { + // Build the mapping if it does not already exist. + if (this.commandToProcessorMap == null) + { + BuildCommandMaps(); + } + + return this.commandToProcessorMap; + } + } + + /// + /// Gets a mapping between special commands and their Argument Processors. + /// + internal Dictionary SpecialCommandToProcessorMap + { + get + { + // Build the mapping if it does not already exist. + if (this.specialCommandToProcessorMap == null) + { + BuildCommandMaps(); + } + + return this.specialCommandToProcessorMap; + } + } + + #endregion + + #region Public Methods + + /// + /// Creates the argument processor associated with the provided command line argument. + /// The Lazy that is returned will initialize the underlying argument processor when it is first accessed. + /// + /// Command line argument to create the argument processor for. + /// The argument processor or null if one was not found. + public IArgumentProcessor CreateArgumentProcessor(string argument) + { + if (String.IsNullOrWhiteSpace(argument)) + { + throw new ArgumentException("Cannot be null or empty", "argument"); + } + Contract.EndContractBlock(); + + // Parse the input into its command and argument parts. + var pair = new CommandArgumentPair(argument); + + // Find the associated argument processor. + IArgumentProcessor argumentProcessor; + CommandToProcessorMap.TryGetValue(pair.Command, out argumentProcessor); + + // If an argument processor was not found for the command, see if it is a test source + // argument. + if (argumentProcessor == null) + { + argumentProcessor = TryGetTestSourceArgument(argument, ref pair); + } + + if (argumentProcessor != null) + { + argumentProcessor = WrapLazyProcessorToInitializeOnInstantiation(argumentProcessor, pair.Argument); + } + + return argumentProcessor; + } + + /// + /// Creates the default action argument processor. + /// The Lazy that is returned will initialize the underlying argument processor when it is first accessed. + /// + /// The default action argument processor. + public IArgumentProcessor CreateDefaultActionArgumentProcessor() + { + var argumentProcessor = SpecialCommandToProcessorMap[RunTestsArgumentProcessor.CommandName]; + return WrapLazyProcessorToInitializeOnInstantiation(argumentProcessor, null); + } + + /// + /// Gets the argument processors that are tagged as special and to be always executed. + /// The Lazy's that are returned will initialize the underlying argument processor when first accessed. + /// + /// The argument processors that are tagged as special and to be always executed. + public IEnumerable GetArgumentProcessorsToAlwaysExecute() + { + return SpecialCommandToProcessorMap.Values + .Where(lazyProcessor => lazyProcessor.Metadata.Value.IsSpecialCommand && lazyProcessor.Metadata.Value.AlwaysExecute); + } + + #endregion + + #region Private Methods + + /// + /// Checks the provided argument to see if it could be a test source and returns the test source + /// argument processor if it could be a test source. + /// + /// The command to test to see if it is a test source. + /// The test source argument processor or null if the commnad is not a test source. + private IArgumentProcessor TryGetTestSourceArgument(string argument, ref CommandArgumentPair pair) + { + Contract.Requires(!String.IsNullOrWhiteSpace(argument)); + IArgumentProcessor result = null; + + // If the command does not begin with the command starter ("/"), then + // we considerer it to be a test source. + if (!argument.StartsWith(CommandStarter, StringComparison.OrdinalIgnoreCase)) + { + result = SpecialCommandToProcessorMap[TestSourceArgumentProcessor.CommandName]; + + // Update the command pair since the command is actually the argument in the case of + // a test source. + pair = new CommandArgumentPair(TestSourceArgumentProcessor.CommandName, argument); + } + + return result; + } + + /// + /// Builds the command to processor map and special command to processor map. + /// + private void BuildCommandMaps() + { + this.commandToProcessorMap = new Dictionary(StringComparer.OrdinalIgnoreCase); + this.specialCommandToProcessorMap = new Dictionary(StringComparer.OrdinalIgnoreCase); + + foreach (IArgumentProcessor argumentProcessor in this.argumentProcessors) + { + // Add the command to the appropriate dictionary. + var processorsMap = argumentProcessor.Metadata.Value.IsSpecialCommand + ? this.specialCommandToProcessorMap + : this.commandToProcessorMap; + + processorsMap.Add(argumentProcessor.Metadata.Value.CommandName, argumentProcessor); + if (!string.IsNullOrEmpty(argumentProcessor.Metadata.Value.ShortCommandName)) + { + processorsMap.Add(argumentProcessor.Metadata.Value.ShortCommandName, argumentProcessor); + } + } + } + + /// + /// Decorates a lazy argument processor so that the real processor is initialized when the lazy value is obtained. + /// + /// The lazy processor. + /// The argument with which the real processor should be initialized. + /// The decorated lazy processor. + private static IArgumentProcessor WrapLazyProcessorToInitializeOnInstantiation( + IArgumentProcessor processor, + string initArg) + { + var processorExecutor = processor.Executor; + var lazyArgumentProcessor = new Lazy(() => + { + IArgumentExecutor instance = null; + try + { + instance = processorExecutor.Value; + } + catch (Exception e) + { + if (EqtTrace.IsErrorEnabled) + { + EqtTrace.Error("ArgumentProcessorFactory.WrapLazyProcessorToInitializeOnInstantiation: Exception creating argument processor: {0}", e); + } + throw; + } + + if (instance != null) + { + try + { + instance.Initialize(initArg); + } + catch (Exception e) + { + if (EqtTrace.IsErrorEnabled) + { + EqtTrace.Error("ArgumentProcessorFactory.WrapLazyProcessorToInitializeOnInstantiation: Exception initializing argument processor: {0}", e); + } + throw; + } + } + + return instance; + }, System.Threading.LazyThreadSafetyMode.PublicationOnly); + processor.Executor = lazyArgumentProcessor; + + return processor; + } + +#endregion + } +} diff --git a/src/vstest.console/Processors/Utilities/ArgumentProcessorPriority.cs b/src/vstest.console/Processors/Utilities/ArgumentProcessorPriority.cs new file mode 100644 index 0000000000..02eb10a279 --- /dev/null +++ b/src/vstest.console/Processors/Utilities/ArgumentProcessorPriority.cs @@ -0,0 +1,64 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Processors +{ + /// + /// Defines the priority of argument processors. + /// + internal enum ArgumentProcessorPriority + { + /// + /// Maximum priority for a processor. + /// + Maximum = 0, + + /// + /// Priority of the Help Content argument processor. + /// + Help = Maximum, + + /// + /// Priority of UseVsixArgumentProcessor. + /// The priority of useVsix processor is more than the logger because logger’s initialization + /// loads the extensions which are incomplete if vsix processor is enabled + /// + VsixExtensions = 1, + + /// + /// Priority of TestAdapterPathArgumentProcessor. + /// The priority of TestAdapterPath processor is more than the logger because logger’s initialization + /// loads the extensions which are incomplete if custom test adapter is enabled + /// + TestAdapterPath = 1, + + /// + /// Priority of processors that needs to update runsettings. + /// + AutoUpdateRunSettings = 3, + + /// + /// Priority of processors related to Run Settings. + /// + RunSettings = 5, + + /// + /// Priority of processors related to logging. + /// + Logging = 10, + + /// + /// Priority of the StartLogging processor. + /// + StartLogging = 11, + + /// + /// Priority of a typical processor. + /// + Normal = 50, + + /// + /// Minimum priority for a processor. + /// + Minimum = 100 + } +} diff --git a/src/vstest.console/Processors/Utilities/ArgumentProcessorResult.cs b/src/vstest.console/Processors/Utilities/ArgumentProcessorResult.cs new file mode 100644 index 0000000000..11c3a303f5 --- /dev/null +++ b/src/vstest.console/Processors/Utilities/ArgumentProcessorResult.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Processors +{ + /// + /// Return values from argument processors. + /// + public enum ArgumentProcessorResult + { + /// + /// Return value indicating no errors. + /// + Success = 0, + + /// + /// Return value indicating an error occurred + /// + Fail = 1, + + /// + /// Return value indicating that the current processor succeeded and subsequent processors should be skipped + /// + Abort = 2 + } +} diff --git a/src/vstest.console/Processors/Utilities/BaseArgumentProcessorCapabilities.cs b/src/vstest.console/Processors/Utilities/BaseArgumentProcessorCapabilities.cs new file mode 100644 index 0000000000..665573f879 --- /dev/null +++ b/src/vstest.console/Processors/Utilities/BaseArgumentProcessorCapabilities.cs @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Processors +{ + /// + /// The base argument processor capabilities. + /// + internal abstract class BaseArgumentProcessorCapabilities : IArgumentProcessorCapabilities + { + /// + /// Gets a value indicating whether allow multiple. + /// + public virtual bool AllowMultiple => true; + + /// + /// Gets a value indicating whether always execute. + /// + public virtual bool AlwaysExecute => false; + + /// + /// Gets the command name. + /// + public abstract string CommandName { get; } + + /// + /// Gets the command name. + /// + public virtual string ShortCommandName => null; + + /// + /// Gets the help content resource name. + /// + public virtual string HelpContentResourceName => null; + + /// + /// Gets the help priority. + /// + public virtual HelpContentPriority HelpPriority => HelpContentPriority.None; + + /// + /// Gets a value indicating whether is action. + /// + public virtual bool IsAction => false; + + /// + /// Gets a value indicating whether is special command. + /// + public virtual bool IsSpecialCommand => false; + + /// + /// Gets the priority. + /// + public virtual ArgumentProcessorPriority Priority => ArgumentProcessorPriority.Normal; + } +} diff --git a/src/vstest.console/Processors/Utilities/HelpContentPriority.cs b/src/vstest.console/Processors/Utilities/HelpContentPriority.cs new file mode 100644 index 0000000000..658c54703c --- /dev/null +++ b/src/vstest.console/Processors/Utilities/HelpContentPriority.cs @@ -0,0 +1,130 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Processors +{ + /// + /// Attribute to be used for displaying the help content in required order. + /// + internal enum HelpContentPriority + { + /// + /// No Content to be shown + /// + None, + + /// + /// RunTestsArgumentProcessor Help + /// + RunTestsArgumentProcessorHelpPriority, + + /// + /// RunSettingsArgumentProcessor Help + /// + RunSettingsArgumentProcessorHelpPriority, + + /// + /// RunSpecificTestsArgumentProcessor Help + /// + RunSpecificTestsArgumentProcessorHelpPriority, + + /// + /// EnableCodeCoverageArgumentProcessor Help + /// + EnableCodeCoverageArgumentProcessorHelpPriority, + + /// + /// InIsolationArgumentProcessor Help + /// + InIsolationArgumentProcessorHelpPriority, + + /// + /// DisableAutoFakesArgumentProcessor Help + /// + DisableAutoFakesArgumentProcessorHelpPriority, + + /// + /// UseVsixArgumentProcessor Help + /// + UseVsixArgumentProcessorHelpPriority, + + /// + /// TestAdapterPathArgumentProcessor Help + /// + TestAdapterPathArgumentProcessorHelpPriority, + + /// + /// PlatformArgumentProcessor Help + /// + PlatformArgumentProcessorHelpPriority, + + /// + /// FrameworkArgumentProcessor Help + /// + FrameworkArgumentProcessorHelpPriority, + + /// + /// ParallelArgumentProcessor Help + /// + ParallelArgumentProcessorHelpPriority, + + /// + /// TestCaseFilterArgumentProcessor Help + /// + TestCaseFilterArgumentProcessorHelpPriority, + + /// + /// HelpArgumentExecutor + /// + HelpArgumentProcessorHelpPriority, + + /// + /// EnableLoggerArgumentProcessor Help + /// + EnableLoggerArgumentProcessorHelpPriority, + + /// + /// ListTestsArgumentExecutor Help + /// + ListTestsArgumentProcessorHelpPriority, + + /// + /// ListDiscoverersArgumentProcessor Help + /// + ListDiscoverersArgumentProcessorHelpPriority, + + /// + /// ListExecutorsArgumentProcessor Help + /// + ListExecutorsArgumentProcessorHelpPriority, + + /// + /// ListLoggersArgumentProcessor Help + /// + ListLoggersArgumentProcessorHelpPriority, + + /// + /// ListSettingProviderArgumentProcessor Help + /// + ListSettingsProvidersArgumentProcessorHelpPriority, + + /// + /// PortArgumentProcessor Help + /// + PortArgumentProcessorHelpPriority, + + /// + /// ConfigurationArgumentProcessor Help + /// + ConfigurationArgumentProcessorHelpPriority, + + /// + /// ConfigurationArgumentProcessor Help + /// + OutputArgumentProcessorHelpPriority, + + /// + /// ConfigurationArgumentProcessor Help + /// + BuildBasePathArgumentProcessorHelpPriority, + } +} diff --git a/src/vstest.console/Processors/Utilities/JsonUtilities.cs b/src/vstest.console/Processors/Utilities/JsonUtilities.cs new file mode 100644 index 0000000000..f12284a446 --- /dev/null +++ b/src/vstest.console/Processors/Utilities/JsonUtilities.cs @@ -0,0 +1,140 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Processors.Utilities +{ + using DotNet.ProjectModel.Graph; + using Microsoft.DotNet.ProjectModel; + using NuGet.Packaging; + using ObjectModel; + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using TestPlatform.Utilities.Helpers; + using TestPlatform.Utilities.Helpers.Interfaces; + using Resources = Resources; + + /// + /// Utility class for processing the json files + /// + public static class JsonUtilities + { + #region Constants/Static Members + + private const string DefaultConfiguration = "Debug"; + private const string JsonFileType = "json"; + + private const string TestRunnerPrefix = "dotnet-test-"; + + /// + /// Project.FileName is an API of DotnetCore API + /// Project refers to XPROJ container and FileName refers to "json" file name (project.json) + /// + private static readonly string JsonFileName = Project.FileName; + + internal static IFileHelper FileHelper { get; set; } = new FileHelper(); + + #endregion + + #region Private Methods + + /// + /// Returns the dictionary of TestAdapterPaths and AssemblyPaths + /// + /// + /// + public static Dictionary> GetTestRunnerAndAssemblyInfo(IEnumerable sources) + { + var resultDictionary = new Dictionary>(); + + foreach (var source in sources) + { + string assemblyPath = null; + string testRunnerPath = null; + if (source.EndsWith(JsonFileType)) + { + // Get the projectContext from the project Json file if exists + // User is expected to provide a full path to json folder, or path upto its directory + // If user provided nothing, try to find in current folder + var projectContext = GetProjectContextFromSource(source); + + // Read data from created context + var testRunner = projectContext.ProjectFile.TestRunner; + + var options = CommandLineOptions.Instance; + var configuration = options.Configuration ?? DefaultConfiguration; + var outputpaths = projectContext.GetOutputPaths(configuration, options.BuildBasePath, options.Output); + assemblyPath = outputpaths.RuntimeFiles.Assembly; + + testRunnerPath = GetTestRunnerPath(projectContext, string.Concat(TestRunnerPrefix, testRunner)); + + if (!FileHelper.Exists(assemblyPath)) + { + throw new InvalidOperationException(Resources.AssemblyPathInvalid); + } + } + + assemblyPath = assemblyPath ?? source; + testRunnerPath = testRunnerPath ?? Constants.UnspecifiedAdapterPath; + IEnumerable assemblySources; + if (resultDictionary.TryGetValue(testRunnerPath, out assemblySources)) + { + assemblySources = assemblySources.Concat(new List { assemblyPath }); + resultDictionary[testRunnerPath] = assemblySources; + } + else + { + resultDictionary.Add(testRunnerPath, new List { assemblyPath }); + } + } + return resultDictionary; + } + + private static string GetTestRunnerPath(ProjectContext projectContext, string commandName) + { + var toolLibrary = projectContext.LockFile.Targets + .FirstOrDefault(t => t.TargetFramework.GetShortFolderName() + .Equals(projectContext.TargetFramework.GetShortFolderName())) + ?.Libraries.FirstOrDefault(l => l.Name == commandName); + + var toolAssembly = toolLibrary?.RuntimeAssemblies + .FirstOrDefault(r => Path.GetFileNameWithoutExtension(r.Path) == commandName); + + var packageDirectory = new VersionFolderPathResolver(projectContext.PackagesDirectory) + .GetInstallPath(toolLibrary.Name, toolLibrary.Version); + + return Path.Combine(packageDirectory, toolAssembly.Path); + } + + /// + /// Returns the path to project.json file + /// if User gave full path to "project.json" - do nothing + /// If User gave path to a directory (use current dir if not) - append "project.json" + /// + /// + /// + private static ProjectContext GetProjectContextFromSource(string source) + { + // user might not provide any value - use current directory + source = source ?? Directory.GetCurrentDirectory(); + + // if user provided a directory or current dir - append "project.json" to path + // Example: C:\temp\ - will become - C:\temp\project.json + if (!source.EndsWith(JsonFileName)) + { + source = Path.Combine(source, JsonFileName); + } + + // Verify if project.json exists + if (!FileHelper.Exists(source)) + { + throw new InvalidOperationException(string.Format(Resources.ProjectPathNotFound, source)); + } + + // Get default context from json + return ProjectContext.CreateContextForEachFramework(source).FirstOrDefault(); + } + + #endregion + } +} diff --git a/src/vstest.console/Processors/Utilities/LoggerUtilities.cs b/src/vstest.console/Processors/Utilities/LoggerUtilities.cs new file mode 100644 index 0000000000..8edead8582 --- /dev/null +++ b/src/vstest.console/Processors/Utilities/LoggerUtilities.cs @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Processors.Utilities +{ + using System; + using System.Collections.Generic; + + using Common.Logging; + + using ObjectModel; + using ObjectModel.Logging; + + internal class LoggerUtilities + { + internal static void RaiseTestRunError(TestLoggerManager loggerManager, TestRunResultAggregator testRunResultAggregator, Exception exception) + { + // testRunResultAggregator can be null, if error is being raised in discovery context. + if (null != testRunResultAggregator) + { + testRunResultAggregator.MarkTestRunFailed(); + } + + TestRunMessageEventArgs errorMessage = new TestRunMessageEventArgs(TestMessageLevel.Error, exception.Message); + loggerManager.SendTestRunError(errorMessage); + + // Send inner exception only when its message is different to avoid duplicate. + if (exception is TestPlatformException && exception.InnerException != null && string.Compare(exception.Message, exception.InnerException.Message, StringComparison.CurrentCultureIgnoreCase) != 0) + { + errorMessage = new TestRunMessageEventArgs(TestMessageLevel.Error, exception.InnerException.Message); + loggerManager.SendTestRunError(errorMessage); + } + } + + /// + /// Parses the parameters passed as name values pairs along with the logger argument. + /// + /// Logger argument + /// Receives logger Uri or friendly name. + /// Receives parse name value pairs. + /// True is successful, false otherwise. + public static bool TryParseLoggerArgument(string argument, out string loggerIdentifier, out Dictionary parameters) + { + loggerIdentifier = null; + parameters = null; + + var parseSucceeded = true; + char[] ArgumentSeperator = new char[] { ';' }; + char[] NameValueSeperator = new char[] { '=' }; + + var argumentParts = argument.Split(ArgumentSeperator, StringSplitOptions.RemoveEmptyEntries); + + if (argumentParts.Length > 0 && !argumentParts[0].Contains("=")) + { + loggerIdentifier = argumentParts[0]; + + if (argumentParts.Length > 1) + { + parameters = new Dictionary(StringComparer.OrdinalIgnoreCase); + for (int index = 1; index < argumentParts.Length; ++index) + { + string[] nameValuePair = argumentParts[index].Split(NameValueSeperator, StringSplitOptions.RemoveEmptyEntries); + if (nameValuePair.Length == 2) + { + parameters[nameValuePair[0]] = nameValuePair[1]; + } + else + { + parseSucceeded = false; + break; + } + } + } + } + else + { + parseSucceeded = false; + } + + return parseSucceeded; + } + } +} diff --git a/src/vstest.console/Processors/Utilities/RunSettingsUtilities.cs b/src/vstest.console/Processors/Utilities/RunSettingsUtilities.cs new file mode 100644 index 0000000000..b8711dae87 --- /dev/null +++ b/src/vstest.console/Processors/Utilities/RunSettingsUtilities.cs @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Processors.Utilities +{ + using System.IO; + using System.Xml; + + using Microsoft.VisualStudio.TestPlatform.Utilities; + using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + + /// + /// Utilities to get the run settings from the provider and the commandline options specified. + /// + internal class RunSettingsUtilities + { + private const string EmptyRunSettings = @""; + + /// + /// Gets the run settings to be used for the session. + /// + /// The current provider of run settings. + /// The command line options specified. + /// + internal static string GetRunSettings(IRunSettingsProvider runSettingsProvider, CommandLineOptions commandlineOptions) + { + var runSettings = runSettingsProvider?.ActiveRunSettings?.SettingsXml; + + if (string.IsNullOrWhiteSpace(runSettings)) + { + runSettings = EmptyRunSettings; + } + + runSettings = GetEffectiveRunSettings(runSettings, commandlineOptions); + + return runSettings; + } + + /// + /// Gets the effective run settings adding the commandline options to the run settings if not already present. + /// + /// The run settings XML. + /// The command line options. + /// Effective run settings. + private static string GetEffectiveRunSettings(string runSettings, CommandLineOptions commandLineOptions) + { + var architecture = Constants.DefaultPlatform; + + if (commandLineOptions != null && commandLineOptions.ArchitectureSpecified) + { + architecture = commandLineOptions.TargetArchitecture; + } + + var framework = Constants.DefaultFramework; + + if (commandLineOptions != null && commandLineOptions.FrameworkVersionSpecified) + { + framework = commandLineOptions.TargetFrameworkVersion; + } + + var defaultResultsDirectory = Path.Combine(Directory.GetCurrentDirectory(), Constants.ResultsDirectoryName); + + var document = new XmlDocument(); + document.LoadXml(runSettings); + var navigator = document.CreateNavigator(); + + InferRunSettingsHelper.UpdateRunSettingsWithUserProvidedSwitches(navigator, architecture, framework, defaultResultsDirectory); + + if (commandLineOptions != null && commandLineOptions.Parallel) + { + ParallelRunSettingsUtilities.UpdateRunSettingsWithParallelSettingIfNotConfigured(navigator); + } + + return navigator.OuterXml; + } + } +} diff --git a/src/vstest.console/Program.cs b/src/vstest.console/Program.cs new file mode 100644 index 0000000000..9223566410 --- /dev/null +++ b/src/vstest.console/Program.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine +{ + using Microsoft.VisualStudio.TestPlatform.Utilities; + + /// + /// Main entry point for the command line runner. + /// + public static class Program + { + /// + /// Main entry point. Hands off execution to the executor class. + /// + /// Arguments provided on the command line. + /// 0 if everything was successful and 1 otherwise. + public static int Main(string[] args) + { + return new Executor(ConsoleOutput.Instance).Execute(args); + } + } +} diff --git a/src/vstest.console/Properties/AssemblyInfo.cs b/src/vstest.console/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..394c568a72 --- /dev/null +++ b/src/vstest.console/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +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: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("vstest.console")] +[assembly: AssemblyTrademark("")] + +// 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("4d89e2f0-2e9d-41ca-a394-322a2e9a2c0b")] diff --git a/src/vstest.console/Resources.Designer.cs b/src/vstest.console/Resources.Designer.cs new file mode 100644 index 0000000000..0871310f72 --- /dev/null +++ b/src/vstest.console/Resources.Designer.cs @@ -0,0 +1,1346 @@ +//------------------------------------------------------------------------------ +// +// 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 Microsoft.VisualStudio.TestPlatform.CommandLine { + using System; + using System.Reflection; + + + /// + /// 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.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + internal Resources() { + } + + /// + /// 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("vstest.console.Resources", typeof(Resources).GetTypeInfo().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 Additional Information Messages:. + /// + public static string AddnlInfoMessagesBanner { + get { + return ResourceManager.GetString("AddnlInfoMessagesBanner", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Could not start test run for unit tests for Windows Store app: {0}.. + /// + public static string AppContainerTestPrerequisiteFail { + get { + return ResourceManager.GetString("AppContainerTestPrerequisiteFail", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No test found in the specified test containers. Additionally, Microsoft Windows Store Unit test adapter does not support .appxbundle files. Create an appx (set Generate App bundle option to Never) when creating App Package and try again.. + /// + public static string AppxBundleSourceWarning { + get { + return ResourceManager.GetString("AppxBundleSourceWarning", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unable to find the assembly under test. Please make sure that the project is built.. + /// + public static string AssemblyPathInvalid { + get { + return ResourceManager.GetString("AssemblyPathInvalid", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0}. + /// + public static string AttachmentOutputFormat { + get { + return ResourceManager.GetString("AttachmentOutputFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Attachments:. + /// + public static string AttachmentsBanner { + get { + return ResourceManager.GetString("AttachmentsBanner", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The following Test Discovery Add-Ins are available:. + /// + public static string AvailableDiscoverersHeaderMessage { + get { + return ResourceManager.GetString("AvailableDiscoverersHeaderMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The following Test Execution Add-Ins are available:. + /// + public static string AvailableExecutorsHeaderMessage { + get { + return ResourceManager.GetString("AvailableExecutorsHeaderMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0}. + /// + public static string AvailableExtensionFormat { + get { + return ResourceManager.GetString("AvailableExtensionFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0}: {1}. + /// + public static string AvailableExtensionsMetadataFormat { + get { + return ResourceManager.GetString("AvailableExtensionsMetadataFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The following Test Logger Add-Ins are available:. + /// + public static string AvailableLoggersHeaderMessage { + get { + return ResourceManager.GetString("AvailableLoggersHeaderMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The following Settings Providers Add-Ins are available:. + /// + public static string AvailableSettingsProvidersHeaderMessage { + get { + return ResourceManager.GetString("AvailableSettingsProvidersHeaderMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0}. + /// + public static string AvailableTestsFormat { + get { + return ResourceManager.GetString("AvailableTestsFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The /BatchSize argument requires the size of the batch. Example: /BatchSize:10. + /// + public static string BatchSizeRequired { + get { + return ResourceManager.GetString("BatchSizeRequired", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to /BuildBasePath:<BuildBasePath> + /// The directory containing the temporary outputs.. + /// + public static string BuildBasePathArgumentHelp { + get { + return ResourceManager.GetString("BuildBasePathArgumentHelp", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The BuildBasePath was not found, provide a valid path and try again.. + /// + public static string BuildBasePathNotFound { + get { + return ResourceManager.GetString("BuildBasePathNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot be null or empty. + /// + public static string CannotBeNullOrEmpty { + get { + return ResourceManager.GetString("CannotBeNullOrEmpty", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Error: {0}. + /// + public static string CommandLineError { + get { + return ResourceManager.GetString("CommandLineError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Information: {0}. + /// + public static string CommandLineInformational { + get { + return ResourceManager.GetString("CommandLineInformational", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Warning: {0}. + /// + public static string CommandLineWarning { + get { + return ResourceManager.GetString("CommandLineWarning", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to , {0}. + /// + public static string CommaSeparatedFormat { + get { + return ResourceManager.GetString("CommaSeparatedFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to /Configuration:<Configuration> + /// The configuration the project is built for i.e. Debug/Release. + /// + public static string ConfigurationArgumentHelp { + get { + return ResourceManager.GetString("ConfigurationArgumentHelp", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Copyright (c) Microsoft Corporation. All rights reserved.. + /// + public static string CopyrightCommandLineTitle { + get { + return ResourceManager.GetString("CopyrightCommandLineTitle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Days. + /// + public static string Days { + get { + return ResourceManager.GetString("Days", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Code coverage is not available for Windows Store apps. Code coverage analysis skipped for this test run.. + /// + public static string DisablingCodeCoverageInAppContainerTestExecution { + get { + return ResourceManager.GetString("DisablingCodeCoverageInAppContainerTestExecution", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Code coverage is not available for Windows Phone apps. Code coverage analysis skipped for this test run.. + /// + public static string DisablingCodeCoverageInPhoneAppContainerTestExecution { + get { + return ResourceManager.GetString("DisablingCodeCoverageInPhoneAppContainerTestExecution", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Diagnostic data adapters are not supported when running unit tests for Windows Store apps. Remove diagnostic data adapters settings from settings.. + /// + public static string DisablingDataCollectionInAppContainerTestExecution { + get { + return ResourceManager.GetString("DisablingDataCollectionInAppContainerTestExecution", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Diagnostic data adapters are not supported when running unit tests for Windows Phone apps. Remove diagnostic data adapters settings from settings.. + /// + public static string DisablingDataCollectionInPhoneAppContainerTestExecution { + get { + return ResourceManager.GetString("DisablingDataCollectionInPhoneAppContainerTestExecution", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Reading diagnostic data adapter settings threw an running '{0}'. All diagnostic data adapters will be skipped in this run.. + /// + public static string DisablingDCOnExceptionWhileParsingDCInfo { + get { + return ResourceManager.GetString("DisablingDCOnExceptionWhileParsingDCInfo", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Discovery failed for given sources. Exception : {0}. + /// + public static string DiscoveryFailed { + get { + return ResourceManager.GetString("DiscoveryFailed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The parameter "{0}" should be provided only once.. + /// + public static string DuplicateArgumentError { + get { + return ResourceManager.GetString("DuplicateArgumentError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Duplicate source {0} specified.. + /// + public static string DuplicateSource { + get { + return ResourceManager.GetString("DuplicateSource", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to /EnableCodeCoverage + /// Enables data diagnostic adapter 'CodeCoverage' in the test run. Default + /// settings are used if not specified using settings file.. + /// + public static string EnableCodeCoverageArgumentProcessorHelp { + get { + return ResourceManager.GetString("EnableCodeCoverageArgumentProcessorHelp", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to /logger:<Logger Uri/FriendlyName> + /// Specify a logger for test results. For example, to log results into a + /// Visual Studio Test Results File (TRX) use /logger:trx. + /// To publish test results to Team Foundation Server, use TfsPublisher as shown below + /// Example: /logger:TfsPublisher; + /// Collection=<team project collection url>; + /// BuildName=<build name>; + /// TeamProject=<team project name> + /// [;Platform=<Defaults to "Any CPU">] + /// [rest of string was truncated]";. + /// + public static string EnableLoggersArgumentHelp { + get { + return ResourceManager.GetString("EnableLoggersArgumentHelp", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Error Message:. + /// + public static string ErrorMessageBanner { + get { + return ResourceManager.GetString("ErrorMessageBanner", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to To run tests in the same process: + /// >vstest.console.exe tests.dll + /// To run tests in a separate process: + /// >vstest.console.exe /inIsolation tests.dll + /// To run tests with additional settings such as data collectors: + /// >vstest.console.exe tests.dll /Settings:Local.RunSettings. + /// + public static string Examples { + get { + return ResourceManager.GetString("Examples", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Exception occurred when instantiating extension '{0}': {1}. + /// + public static string ExceptionFromExtension { + get { + return ResourceManager.GetString("ExceptionFromExtension", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Test execution time: {0:0.0000} {1}. + /// + public static string ExecutionTimeFormatString { + get { + return ResourceManager.GetString("ExecutionTimeFormatString", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Uri: {0}. + /// + public static string ExtensionUriFormat { + get { + return ResourceManager.GetString("ExtensionUriFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Failed {0}. + /// + public static string FailedTestIndicator { + get { + return ResourceManager.GetString("FailedTestIndicator", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to '{0}' not found.. + /// + public static string FileNotFound { + get { + return ResourceManager.GetString("FileNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to /Framework:<Framework Version> + /// Target .Net Framework version to be used for test execution. + /// Valid values are Framework35, Framework40 and Framework45. + /// + public static string FrameworkArgumentHelp { + get { + return ResourceManager.GetString("FrameworkArgumentHelp", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The /Framework argument requires the target .Net Framework version for the test run. Example: /Framework:Framework40. + /// + public static string FrameworkVersionRequired { + get { + return ResourceManager.GetString("FrameworkVersionRequired", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to /? + /// Display this usage message.. + /// + public static string HelpArgumentHelp { + get { + return ResourceManager.GetString("HelpArgumentHelp", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Description: Runs tests from the specified files.. + /// + public static string HelpDescriptionText { + get { + return ResourceManager.GetString("HelpDescriptionText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Options:. + /// + public static string HelpOptionsText { + get { + return ResourceManager.GetString("HelpOptionsText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Usage: vstest.console.exe [TestFileNames] [Options]. + /// + public static string HelpUsageText { + get { + return ResourceManager.GetString("HelpUsageText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Hours. + /// + public static string Hours { + get { + return ResourceManager.GetString("Hours", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to /InIsolation + /// Runs the tests in an isolated process. This makes vstest.console.exe + /// process less likely to be stopped on an error in the tests, but tests + /// may run slower.. + /// + public static string InIsolationHelp { + get { + return ResourceManager.GetString("InIsolationHelp", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid batch size {0}. The batch size should be greater than zero. Example: /BatchSize:10. + /// + public static string InvalidBatchSize { + get { + return ResourceManager.GetString("InvalidBatchSize", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The given configuration is invalid.. + /// + public static string InvalidConfiguration { + get { + return ResourceManager.GetString("InvalidConfiguration", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Argument {0} is not expected in the 'EnableCodeCoverage' command. Specify the command without the argument (Example: vstest.console.exe myTests.dll /EnableCodeCoverage) and try again.. + /// + public static string InvalidEnableCodeCoverageCommand { + get { + return ResourceManager.GetString("InvalidEnableCodeCoverageCommand", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid .Net Framework version:{0}. Supported .Net Framework versions are Framework35, Framework40 and Framework45.. + /// + public static string InvalidFrameworkVersion { + get { + return ResourceManager.GetString("InvalidFrameworkVersion", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Argument {0} is not expected in the 'InIsolation' command. Specify the command without the argument (Example: vstest.console.exe myTests.dll /InIsolation) and try again.. + /// + public static string InvalidInIsolationCommand { + get { + return ResourceManager.GetString("InvalidInIsolationCommand", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Argument {0} is not expected in the 'Parallel' command. Specify the command without the argument (Example: vstest.console.exe myTests.dll /Parallel) and try again.. + /// + public static string InvalidParallelCommand { + get { + return ResourceManager.GetString("InvalidParallelCommand", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid platform type:{0}. Valid platform types are x86, x64 and Arm.. + /// + public static string InvalidPlatformType { + get { + return ResourceManager.GetString("InvalidPlatformType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The /Port argument requires the port number which is an integer. Specify the port for socket connection and receiving the event messages.. + /// + public static string InvalidPortArgument { + get { + return ResourceManager.GetString("InvalidPortArgument", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The path '{0}' specified in the 'TestAdapterPath' is invalid. Error: {1}. + /// + public static string InvalidTestAdapterPathCommand { + get { + return ResourceManager.GetString("InvalidTestAdapterPathCommand", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The /TestCaseFilter argument cannot be specified with /Tests. Filtering of test cases is not applicable when tests are specified.. + /// + public static string InvalidTestCaseFilterValueForSpecificTests { + get { + return ResourceManager.GetString("InvalidTestCaseFilterValueForSpecificTests", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Argument {0} is not expected in the 'UseVsixExtensions' command. Specify the command indicating whether the vsix extensions should be used or skipped (Example: vstest.console.exe myTests.dll /UseVsixExtensions:true) and try again.. + /// + public static string InvalidUseVsixExtensionsCommand { + get { + return ResourceManager.GetString("InvalidUseVsixExtensionsCommand", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to /ListDiscoverers + /// Lists installed test discoverers.. + /// + public static string ListDiscoverersHelp { + get { + return ResourceManager.GetString("ListDiscoverersHelp", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to /ListExecutors + /// Lists installed test executors.. + /// + public static string ListExecutorsHelp { + get { + return ResourceManager.GetString("ListExecutorsHelp", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to /ListLoggers + /// Lists installed test loggers.. + /// + public static string ListLoggersHelp { + get { + return ResourceManager.GetString("ListLoggersHelp", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to /ListSettingsProviders + /// Lists installed test settings providers.. + /// + public static string ListSettingsProvidersHelp { + get { + return ResourceManager.GetString("ListSettingsProvidersHelp", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The following Tests are available:. + /// + public static string ListTestsHeaderMessage { + get { + return ResourceManager.GetString("ListTestsHeaderMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to /ListTests:<File Name> + /// Lists discovered tests from the given test container.. + /// + public static string ListTestsHelp { + get { + return ResourceManager.GetString("ListTestsHelp", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to FriendlyName: {0}. + /// + public static string LoggerFriendlyNameFormat { + get { + return ResourceManager.GetString("LoggerFriendlyNameFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Could not find a test logger with URI or FriendlyName '{0}'.. + /// + public static string LoggerNotFound { + get { + return ResourceManager.GetString("LoggerNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Uri: {0}. + /// + public static string LoggerUriFormat { + get { + return ResourceManager.GetString("LoggerUriFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The Test Logger URI '{0}' is not valid. The Test Logger will be ignored.. + /// + public static string LoggerUriInvalid { + get { + return ResourceManager.GetString("LoggerUriInvalid", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Settings file provided do not confirm to required format. . + /// + public static string MalformedRunSettingsFile { + get { + return ResourceManager.GetString("MalformedRunSettingsFile", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Microsoft (R) Test Execution Command Line Tool Version {0}. + /// + public static string MicrosoftCommandLineTitle { + get { + return ResourceManager.GetString("MicrosoftCommandLineTitle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Minutes. + /// + public static string Minutes { + get { + return ResourceManager.GetString("Minutes", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No test source files were specified.. + /// + public static string MissingTestSourceFile { + get { + return ResourceManager.GetString("MissingTestSourceFile", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The path '{0}' specified in the 'TestAdapterPath' does not contain any test adapters, provide a valid path and try again.. + /// + public static string NoAdaptersFoundInTestAdapterPath { + get { + return ResourceManager.GetString("NoAdaptersFoundInTestAdapterPath", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unrecognized parameter "{0}".. + /// + public static string NoArgumentProcessorFound { + get { + return ResourceManager.GetString("NoArgumentProcessorFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No arguments were specified.. + /// + public static string NoArgumentsProvided { + get { + return ResourceManager.GetString("NoArgumentsProvided", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} is built for {1}/{2}. The test assemblies specified in a run should have a common target .Net framework and platform.. + /// + public static string NonDefaultFrameworkAndOrArchDetected { + get { + return ResourceManager.GetString("NonDefaultFrameworkAndOrArchDetected", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to App package '{0}' does not has test executor entry point. For running unit tests for Windows Store apps, create app package using Windows Store app Unit Test Library project.. + /// + public static string NoTestEntryPoint { + get { + return ResourceManager.GetString("NoTestEntryPoint", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A total of {0} tests were discovered but no test matches the specified selection criteria({1}). Use right value(s) and try again.. + /// + public static string NoTestsAvailableAfterFiltering { + get { + return ResourceManager.GetString("NoTestsAvailableAfterFiltering", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No test is available in {0}. Make sure that installed test discoverers & executors, platform & framework version settings are appropriate and try again.. + /// + public static string NoTestsAvailableInSources { + get { + return ResourceManager.GetString("NoTestsAvailableInSources", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} {1}. + /// + public static string NoTestsFoundWarningMessageWithSuggestionToUseVsix { + get { + return ResourceManager.GetString("NoTestsFoundWarningMessageWithSuggestionToUseVsix", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to /Output:<Output> + /// The directory containing the binaries to run.. + /// + public static string OutputArgumentHelp { + get { + return ResourceManager.GetString("OutputArgumentHelp", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The Output path was not found, provide a valid path and try again.. + /// + public static string OutputPathNotFound { + get { + return ResourceManager.GetString("OutputPathNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to /Parallel + ///Specifies that the tests be executed in parallel. By default up to all available cores on the machine may be used. The number of cores to use may be configured using a settings file.. + /// + public static string ParallelArgumentProcessorHelp { + get { + return ResourceManager.GetString("ParallelArgumentProcessorHelp", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Passed {0}. + /// + public static string PassedTestIndicator { + get { + return ResourceManager.GetString("PassedTestIndicator", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Could not start test run for the tests for Windows Phone app: {0}.. + /// + public static string PhoneAppContainerTestPrerequisiteFail { + get { + return ResourceManager.GetString("PhoneAppContainerTestPrerequisiteFail", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to App package '{0}' does not has test executor entry point. For running unit tests for Windows Phone apps, create app package using Windows Phone Unit Test App project.. + /// + public static string PhoneNoTestEntryPoint { + get { + return ResourceManager.GetString("PhoneNoTestEntryPoint", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to /Platform:<Platform type> + /// Target platform architecture to be used for test execution. + /// Valid values are x86, x64 and ARM.. + /// + public static string PlatformArgumentHelp { + get { + return ResourceManager.GetString("PlatformArgumentHelp", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The /Platform argument requires the target platform type for the test run to be provided. Example: /Platform:x86. + /// + public static string PlatformTypeRequired { + get { + return ResourceManager.GetString("PlatformTypeRequired", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to /Port:<Port> + /// The Port for socket connection and receiving the event messages.. + /// + public static string PortArgumentHelp { + get { + return ResourceManager.GetString("PortArgumentHelp", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The Path {0} does not exist.. + /// + public static string ProjectPathNotFound { + get { + return ResourceManager.GetString("ProjectPathNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to /Settings:<Settings File> + /// Settings to use when running tests.. + /// + public static string RunSettingsArgumentHelp { + get { + return ResourceManager.GetString("RunSettingsArgumentHelp", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The Settings file '{0}' could not be found.. + /// + public static string RunSettingsFileNotFound { + get { + return ResourceManager.GetString("RunSettingsFileNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The /Settings parameter requires a settings file to be provided.. + /// + public static string RunSettingsRequired { + get { + return ResourceManager.GetString("RunSettingsRequired", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Only one app package (.appx file) can be specified for running tests.. + /// + public static string RunSingleAppContainerSource { + get { + return ResourceManager.GetString("RunSingleAppContainerSource", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to /Tests:<Test Names> + /// Run tests with names that match the provided values. To provide multiple + /// values, separate them by commas. + /// Examples: /Tests:TestMethod1 + /// /Tests:TestMethod1,testMethod2. + /// + public static string RunSpecificTestsHelp { + get { + return ResourceManager.GetString("RunSpecificTestsHelp", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to [TestFileNames] + /// Run tests from the specified files. Separate multiple test file names + /// by spaces. + /// Examples: mytestproject.dll + /// mytestproject.dll myothertestproject.exe. + /// + public static string RunTestsArgumentHelp { + get { + return ResourceManager.GetString("RunTestsArgumentHelp", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to ,. + /// + public static string SearchStringDelimiter { + get { + return ResourceManager.GetString("SearchStringDelimiter", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Seconds. + /// + public static string Seconds { + get { + return ResourceManager.GetString("Seconds", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to SettingName: {0}. + /// + public static string SettingFormat { + get { + return ResourceManager.GetString("SettingFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Skipped {0}. + /// + public static string SkippedTestIndicator { + get { + return ResourceManager.GetString("SkippedTestIndicator", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A total of {0} tests were discovered but some tests do not match the specified selection criteria({1}). Use right value(s) and try again.. + /// + public static string SomeTestsUnavailableAfterFiltering { + get { + return ResourceManager.GetString("SomeTestsUnavailableAfterFiltering", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The /Tests argument requires one or more specific test names or their substrings. + /// Examples: /Tests:TestsMethod1, /Tests:TestMethod1,method2 . + /// + public static string SpecificTestsRequired { + get { + return ResourceManager.GetString("SpecificTestsRequired", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Stack Trace:. + /// + public static string StacktraceBanner { + get { + return ResourceManager.GetString("StacktraceBanner", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Starting test discovery, please wait.... + /// + public static string StartingDiscovery { + get { + return ResourceManager.GetString("StartingDiscovery", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Starting test execution, please wait.... + /// + public static string StartingExecution { + get { + return ResourceManager.GetString("StartingExecution", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Standard Error Messages:. + /// + public static string StdErrMessagesBanner { + get { + return ResourceManager.GetString("StdErrMessagesBanner", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Standard Output Messages:. + /// + public static string StdOutMessagesBanner { + get { + return ResourceManager.GetString("StdOutMessagesBanner", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Additionally, you can try specifying '/UseVsixExtensions' command if the test discoverer & executor is installed on the machine as vsix extensions and your installation supports vsix extensions. Example: vstest.console.exe myTests.dll /UseVsixExtensions:true. + /// + public static string SuggestUseVsixExtensionsIfNoTestsIsFound { + get { + return ResourceManager.GetString("SuggestUseVsixExtensionsIfNoTestsIsFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The Test Discovery Add-Ins installed through a VSIX installation are ignored. Use the /UseVsixExtensions parameter to include them, if your installation supports vsix extensions. Example: vstest.console.exe {0} /UseVsixExtensions:true. + /// + public static string SuggestUseVsixExtensionsInListDiscoverersCommand { + get { + return ResourceManager.GetString("SuggestUseVsixExtensionsInListDiscoverersCommand", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The Test Executor Add-Ins installed through a VSIX installation are ignored. Use the /UseVsixExtensions parameter to include them, if your installation supports vsix extensions. Example: vstest.console.exe {0} /UseVsixExtensions:true. + /// + public static string SuggestUseVsixExtensionsInListExecutorsCommand { + get { + return ResourceManager.GetString("SuggestUseVsixExtensionsInListExecutorsCommand", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The Test Logger Add-Ins installed through a VSIX installation are ignored. Use the /UseVsixExtensions parameter to include them, if your installation supports vsix extensions. Example: vstest.console.exe {0} /UseVsixExtensions:true. + /// + public static string SuggestUseVsixExtensionsInListLoggersCommand { + get { + return ResourceManager.GetString("SuggestUseVsixExtensionsInListLoggersCommand", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The Settings Providers Add-Ins installed through a VSIX installation are ignored. Use the /UseVsixExtensions parameter to include them, if your installation supports vsix extensions. Example: vstest.console.exe {0} /UseVsixExtensions:true. + /// + public static string SuggestUseVsixExtensionsInListSettingsProviderCommand { + get { + return ResourceManager.GetString("SuggestUseVsixExtensionsInListSettingsProviderCommand", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Supported File Types:. + /// + public static string SupportedFileTypesIndicator { + get { + return ResourceManager.GetString("SupportedFileTypesIndicator", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0}. + /// + public static string SupportedFileWithoutSeparator { + get { + return ResourceManager.GetString("SupportedFileWithoutSeparator", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0},. + /// + public static string SupportedFileWithSeparator { + get { + return ResourceManager.GetString("SupportedFileWithSeparator", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Using Isolation mode to run unit tests for Windows Store apps. Use the /InIsolation parameter to suppress this warning.. + /// + public static string SwitchToIsolationInAppContainerMode { + get { + return ResourceManager.GetString("SwitchToIsolationInAppContainerMode", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Using Isolation mode to run tests as required by effective Platform:{0} and .Net Framework:{1} settings for test run. Use the /inIsolation parameter to suppress this warning.. + /// + public static string SwitchToIsolationInMultiTargetingMode { + get { + return ResourceManager.GetString("SwitchToIsolationInMultiTargetingMode", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Using Isolation mode to run unit tests for Windows Phone apps. Use the /InIsolation parameter to suppress this warning.. + /// + public static string SwitchToIsolationInPhoneAppContainerMode { + get { + return ResourceManager.GetString("SwitchToIsolationInPhoneAppContainerMode", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Using Isolation mode to run the tests as diagnostic data adapters were enabled in the runsettings. Use the /inIsolation parameter to suppress this warning.. + /// + public static string SwitchToNoIsolation { + get { + return ResourceManager.GetString("SwitchToNoIsolation", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The custom test adapter search path provided was not found, provide a valid path and try again.. + /// + public static string TestAdapterPathDoesNotExist { + get { + return ResourceManager.GetString("TestAdapterPathDoesNotExist", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to /TestAdapterPath + /// This makes vstest.console.exe process use custom test adapters + /// from a given path (if any) in the test run. + /// Example /TestAdapterPath:<pathToCustomAdapters>. + /// + public static string TestAdapterPathHelp { + get { + return ResourceManager.GetString("TestAdapterPathHelp", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The /TestAdapterPath parameter requires a value, which is path of a location containing custom test adapters. Example: /TestAdapterPath:c:\MyCustomAdapters. + /// + public static string TestAdapterPathValueRequired { + get { + return ResourceManager.GetString("TestAdapterPathValueRequired", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to /TestCaseFilter:<Expression> + /// Run tests that match the given expression. + /// <Expression> is of the format <property>Operator<value>[|&<Expression>] + /// where Operator is one of =, != or ~ (Operator ~ has 'contains' + /// semantics and is applicable for string properties like DisplayName). + /// Parenthesis () can be used to group sub-expressions. + /// Examples: /TestCaseFilter:"Priority=1" + /// /TestCaseFilter:"(FullyQualifiedName~Nightly + /// [rest of string was truncated]";. + /// + public static string TestCaseFilterArgumentHelp { + get { + return ResourceManager.GetString("TestCaseFilterArgumentHelp", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The /TestCaseFilter argument requires the filter value. + /// Filter value can be <property>=<value> type. + /// Examples: "Priority=1", "TestCategory=Nightly". + /// + public static string TestCaseFilterValueRequired { + get { + return ResourceManager.GetString("TestCaseFilterValueRequired", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to . + /// + public static string TestMessageFormattingPrefix { + get { + return ResourceManager.GetString("TestMessageFormattingPrefix", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Test Run Failed.. + /// + public static string TestRunFailed { + get { + return ResourceManager.GetString("TestRunFailed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Test Run Successful.. + /// + public static string TestRunSuccessful { + get { + return ResourceManager.GetString("TestRunSuccessful", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Total tests: {0}. Passed: {1}. Failed: {2}. Skipped: {3}.. + /// + public static string TestRunSummary { + get { + return ResourceManager.GetString("TestRunSummary", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Using framework {0} to run the tests. Specify /Framework:{1} to suppress this warning.. + /// + public static string TestSettingsFrameworkMismatch { + get { + return ResourceManager.GetString("TestSettingsFrameworkMismatch", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The test source file "{0}" provided was not found.. + /// + public static string TestSourceFileNotFound { + get { + return ResourceManager.GetString("TestSourceFileNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Time elapsed :. + /// + public static string TimeElapsed { + get { + return ResourceManager.GetString("TimeElapsed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Default Executor Uri: {0}. + /// + public static string UriOfDefaultExecutor { + get { + return ResourceManager.GetString("UriOfDefaultExecutor", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to /UseVsixExtensions + /// This makes vstest.console.exe process use or skip the VSIX extensions + /// installed(if any) in the test run. + /// Example /UseVsixExtensions:true. + /// + public static string UseVsixExtensionsHelp { + get { + return ResourceManager.GetString("UseVsixExtensionsHelp", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The /UseVsixExtensions parameter requires a value. If 'true', the installed VSIX extensions (if any) will be used in the test run. If false, they will be ignored. Example: /UseVsixExtensions:true. + /// + public static string UseVsixExtensionsValueRequired { + get { + return ResourceManager.GetString("UseVsixExtensionsValueRequired", resourceCulture); + } + } + } +} diff --git a/src/vstest.console/Resources.resx b/src/vstest.console/Resources.resx new file mode 100644 index 0000000000..4dbf893500 --- /dev/null +++ b/src/vstest.console/Resources.resx @@ -0,0 +1,596 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + The following Test Discovery Add-Ins are available: + + + The following Test Execution Add-Ins are available: + + + {0} + {Locked} + + + {0}: {1} + + + The following Test Logger Add-Ins are available: + + + {0} + {Locked} + + + Error: {0} + + + , {0} + Format used to comma separate a list of values. + + + The parameter "{0}" should be provided only once. + + + Exception occurred when instantiating extension '{0}': {1} + + + The following Tests are available: + + + Unrecognized parameter "{0}". + + + The test source file "{0}" provided was not found. + + + The Test Logger URI '{0}' is not valid. The Test Logger will be ignored. + + + Information: {0} + + + Warning: {0} + + + /? + Display this usage message. + + + Copyright (c) Microsoft Corporation. All rights reserved. + + + Microsoft (R) Test Execution Command Line Tool Version {0} + + + /logger:<Logger Uri/FriendlyName> + Specify a logger for test results. For example, to log results into a + Visual Studio Test Results File (TRX) use /logger:trx. + To publish test results to Team Foundation Server, use TfsPublisher as shown below + Example: /logger:TfsPublisher; + Collection=<team project collection url>; + BuildName=<build name>; + TeamProject=<team project name> + [;Platform=<Defaults to "Any CPU">] + [;Flavor=<Defaults to "Debug">] + [;RunTitle=<title>] + + + Description: Runs tests from the specified files. + + + Options: + Section Header for subsequent command help listing + + + Usage: vstest.console.exe [TestFileNames] [Options] + + + No test source files were specified. + + + No arguments were specified. + + + [TestFileNames] + Run tests from the specified files. Separate multiple test file names + by spaces. + Examples: mytestproject.dll + mytestproject.dll myothertestproject.exe + + + The following Settings Providers Add-Ins are available: + + + /Settings:<Settings File> + Settings to use when running tests. + + + The /Settings parameter requires a settings file to be provided. + + + The Settings file '{0}' could not be found. + + + Test Run Failed. + + + Test Run Successful. + + + Duplicate source {0} specified. + + + Argument {0} is not expected in the 'InIsolation' command. Specify the command without the argument (Example: vstest.console.exe myTests.dll /InIsolation) and try again. + + + Additionally, you can try specifying '/UseVsixExtensions' command if the test discoverer & executor is installed on the machine as vsix extensions and your installation supports vsix extensions. Example: vstest.console.exe myTests.dll /UseVsixExtensions:true + + + The /UseVsixExtensions parameter requires a value. If 'true', the installed VSIX extensions (if any) will be used in the test run. If false, they will be ignored. Example: /UseVsixExtensions:true + + + Argument {0} is not expected in the 'UseVsixExtensions' command. Specify the command indicating whether the vsix extensions should be used or skipped (Example: vstest.console.exe myTests.dll /UseVsixExtensions:true) and try again. + + + /InIsolation + Runs the tests in an isolated process. This makes vstest.console.exe + process less likely to be stopped on an error in the tests, but tests + may run slower. + + + /UseVsixExtensions + This makes vstest.console.exe process use or skip the VSIX extensions + installed(if any) in the test run. + Example /UseVsixExtensions:true + + + The /BatchSize argument requires the size of the batch. Example: /BatchSize:10 + + + Invalid batch size {0}. The batch size should be greater than zero. Example: /BatchSize:10 + + + To run tests in the same process: + >vstest.console.exe tests.dll + To run tests in a separate process: + >vstest.console.exe /inIsolation tests.dll + To run tests with additional settings such as data collectors: + >vstest.console.exe tests.dll /Settings:Local.RunSettings + + + /ListDiscoverers + Lists installed test discoverers. + + + /ListExecutors + Lists installed test executors. + + + /ListLoggers + Lists installed test loggers. + + + /ListSettingsProviders + Lists installed test settings providers. + + + /ListTests:<File Name> + Lists discovered tests from the given test container. + + + Time elapsed : + + + The /Tests argument requires one or more specific test names or their substrings. + Examples: /Tests:TestsMethod1, /Tests:TestMethod1,method2 + + + A total of {0} tests were discovered but no test matches the specified selection criteria({1}). Use right value(s) and try again. + + + , + + + /Tests:<Test Names> + Run tests with names that match the provided values. To provide multiple + values, separate them by commas. + Examples: /Tests:TestMethod1 + /Tests:TestMethod1,testMethod2 + Please verify if the console output looks good after modifiaction. + + + Using Isolation mode to run the tests as diagnostic data adapters were enabled in the runsettings. Use the /inIsolation parameter to suppress this warning. + + + Using Isolation mode to run unit tests for Windows Store apps. Use the /InIsolation parameter to suppress this warning. + + + Diagnostic data adapters are not supported when running unit tests for Windows Store apps. Remove diagnostic data adapters settings from settings. + + + {0} {1} + + + FriendlyName: {0} + + + Uri: {0} + + + The Test Logger Add-Ins installed through a VSIX installation are ignored. Use the /UseVsixExtensions parameter to include them, if your installation supports vsix extensions. Example: vstest.console.exe {0} /UseVsixExtensions:true + + + The Test Executor Add-Ins installed through a VSIX installation are ignored. Use the /UseVsixExtensions parameter to include them, if your installation supports vsix extensions. Example: vstest.console.exe {0} /UseVsixExtensions:true + + + The Test Discovery Add-Ins installed through a VSIX installation are ignored. Use the /UseVsixExtensions parameter to include them, if your installation supports vsix extensions. Example: vstest.console.exe {0} /UseVsixExtensions:true + + + The Settings Providers Add-Ins installed through a VSIX installation are ignored. Use the /UseVsixExtensions parameter to include them, if your installation supports vsix extensions. Example: vstest.console.exe {0} /UseVsixExtensions:true + + + SettingName: {0} + + + Supported File Types: + + + {0} + + + {0}, + + + Default Executor Uri: {0} + + + Uri: {0} + + + Invalid platform type:{0}. Valid platform types are x86, x64 and Arm. + + + The /Platform argument requires the target platform type for the test run to be provided. Example: /Platform:x86 + + + /Platform:<Platform type> + Target platform architecture to be used for test execution. + Valid values are x86, x64 and ARM. + + + Using Isolation mode to run tests as required by effective Platform:{0} and .Net Framework:{1} settings for test run. Use the /inIsolation parameter to suppress this warning. + + + /Framework:<Framework Version> + Target .Net Framework version to be used for test execution. + Valid values are Framework35, Framework40 and Framework45 + + + The /Framework argument requires the target .Net Framework version for the test run. Example: /Framework:Framework40 + + + Invalid .Net Framework version:{0}. Supported .Net Framework versions are Framework35, Framework40 and Framework45. + + + Could not start test run for unit tests for Windows Store app: {0}. + + + /TestCaseFilter:<Expression> + Run tests that match the given expression. + <Expression> is of the format <property>Operator<value>[|&<Expression>] + where Operator is one of =, != or ~ (Operator ~ has 'contains' + semantics and is applicable for string properties like DisplayName). + Parenthesis () can be used to group sub-expressions. + Examples: /TestCaseFilter:"Priority=1" + /TestCaseFilter:"(FullyQualifiedName~Nightly + |Name=MyTestMethod)" + + + The /TestCaseFilter argument requires the filter value. + Filter value can be <property>=<value> type. + Examples: "Priority=1", "TestCategory=Nightly" + + + The /TestCaseFilter argument cannot be specified with /Tests. Filtering of test cases is not applicable when tests are specified. + + + {0} is built for {1}/{2}. The test assemblies specified in a run should have a common target .Net framework and platform. + + + Only one app package (.appx file) can be specified for running tests. + + + Starting test discovery, please wait... + + + Starting test execution, please wait... + + + Reading diagnostic data adapter settings threw an running '{0}'. All diagnostic data adapters will be skipped in this run. + + + '{0}' not found. + + + /EnableCodeCoverage + Enables data diagnostic adapter 'CodeCoverage' in the test run. Default + settings are used if not specified using settings file. + + + Argument {0} is not expected in the 'EnableCodeCoverage' command. Specify the command without the argument (Example: vstest.console.exe myTests.dll /EnableCodeCoverage) and try again. + + + Settings file provided do not confirm to required format. + + + App package '{0}' does not has test executor entry point. For running unit tests for Windows Store apps, create app package using Windows Store app Unit Test Library project. + + + Code coverage is not available for Windows Store apps. Code coverage analysis skipped for this test run. + + + A total of {0} tests were discovered but some tests do not match the specified selection criteria({1}). Use right value(s) and try again. + + + /TestAdapterPath + This makes vstest.console.exe process use custom test adapters + from a given path (if any) in the test run. + Example /TestAdapterPath:<pathToCustomAdapters> + + + The /TestAdapterPath parameter requires a value, which is path of a location containing custom test adapters. Example: /TestAdapterPath:c:\MyCustomAdapters + + + The path '{0}' specified in the 'TestAdapterPath' is invalid. Error: {1} + + + The custom test adapter search path provided was not found, provide a valid path and try again. + + + The path '{0}' specified in the 'TestAdapterPath' does not contain any test adapters, provide a valid path and try again. + + + Could not start test run for the tests for Windows Phone app: {0}. + + + Using Isolation mode to run unit tests for Windows Phone apps. Use the /InIsolation parameter to suppress this warning. + + + Code coverage is not available for Windows Phone apps. Code coverage analysis skipped for this test run. + + + Diagnostic data adapters are not supported when running unit tests for Windows Phone apps. Remove diagnostic data adapters settings from settings. + + + App package '{0}' does not has test executor entry point. For running unit tests for Windows Phone apps, create app package using Windows Phone Unit Test App project. + + + Using framework {0} to run the tests. Specify /Framework:{1} to suppress this warning. + + + No test found in the specified test containers. Additionally, Microsoft Windows Store Unit test adapter does not support .appxbundle files. Create an appx (set Generate App bundle option to Never) when creating App Package and try again. + + + /Parallel +Specifies that the tests be executed in parallel. By default up to all available cores on the machine may be used. The number of cores to use may be configured using a settings file. + + + Argument {0} is not expected in the 'Parallel' command. Specify the command without the argument (Example: vstest.console.exe myTests.dll /Parallel) and try again. + + + Cannot be null or empty + + + /Port:<Port> + The Port for socket connection and receiving the event messages. + + + The /Port argument requires the port number which is an integer. Specify the port for socket connection and receiving the event messages. + + + Unable to find the assembly under test. Please make sure that the project is built. + + + The Path {0} does not exist. + + + /BuildBasePath:<BuildBasePath> + The directory containing the temporary outputs. + + + /Configuration:<Configuration> + The configuration the project is built for i.e. Debug/Release + + + The given configuration is invalid. + + + /Output:<Output> + The directory containing the binaries to run. + + + The BuildBasePath was not found, provide a valid path and try again. + + + The Output path was not found, provide a valid path and try again. + + + Additional Information Messages: + + + Days + + + Error Message: + + + Test execution time: {0:0.0000} {1} + + + Hours + + + Could not find a test logger with URI or FriendlyName '{0}'. + + + Minutes + + + Seconds + + + Stack Trace: + + + Standard Error Messages: + + + Standard Output Messages: + + + + + + Attachments: + + + {0} + + + No test is available in {0}. Make sure that installed test discoverers & executors, platform & framework version settings are appropriate and try again. + + + Failed {0} + Message which is written to the console when a test fails. + + + Passed {0} + Message which is written to the console when a test passes. + + + Skipped {0} + + + Total tests: {0}. Passed: {1}. Failed: {2}. Skipped: {3}. + + + Discovery failed for given sources. Exception : {0} + + \ No newline at end of file diff --git a/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs b/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs new file mode 100644 index 0000000000..d26a8424cf --- /dev/null +++ b/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs @@ -0,0 +1,248 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.TestPlatformHelpers +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.Client; + using Microsoft.VisualStudio.TestPlatform.Client.RequestHelper; + using Microsoft.VisualStudio.TestPlatform.Common.Logging; + using Microsoft.VisualStudio.TestPlatform.CommandLine.Internal; + using Microsoft.VisualStudio.TestPlatform.CommandLine.Processors.Utilities; + using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces; + + /// + /// Defines the TestRequestManger which can fire off discovery and test run requests + /// + internal class TestRequestManager : ITestRequestManager + { + private ITestPlatform testPlatform; + + private CommandLineOptions commandLineOptions; + + private TestLoggerManager testLoggerManager; + + private TestRunResultAggregator testRunResultAggregator; + + private static ITestRequestManager testRequestManagerInstance; + + private const int runRequestTimeout = 5000; + + /// + /// Maintains the current active execution request + /// Assumption : There can only be one active execution request. + /// + private ITestRunRequest currentTestRunRequest; + + private EventWaitHandle runRequestCreatedEventHandle = new AutoResetEvent(false); + + #region Constructor + + public TestRequestManager() : + this(CommandLineOptions.Instance, + TestPlatformFactory.GetTestPlatform(), + TestLoggerManager.Instance, + TestRunResultAggregator.Instance) + { + } + + internal TestRequestManager(CommandLineOptions commandLineOptions, + ITestPlatform testPlatform, + TestLoggerManager testLoggerManager, + TestRunResultAggregator testRunResultAggregator) + { + this.testPlatform = testPlatform; + this.commandLineOptions = commandLineOptions; + this.testLoggerManager = testLoggerManager; + this.testRunResultAggregator = testRunResultAggregator; + + // Always enable logging for discovery or run requests + this.testLoggerManager.EnableLogging(); + + // TODO: Is this required for design mode + // Add console logger as a listener to logger events. + var consoleLogger = new ConsoleLogger(); + consoleLogger.Initialize(this.testLoggerManager.LoggerEvents, null); + } + + #endregion + + public static ITestRequestManager Instance + { + get + { + if (testRequestManagerInstance == null) + { + testRequestManagerInstance = new TestRequestManager(); + } + return testRequestManagerInstance; + } + } + + #region ITestRequestManager + + /// + /// Initializes the extensions while probing additional paths + /// + /// Paths to Additional extensions + public void InitializeExtensions(IEnumerable pathToAdditionalExtensions) + { + testPlatform.Initialize(pathToAdditionalExtensions, false, true); + } + + /// + /// Resets the command options + /// + public void ResetOptions() + { + this.commandLineOptions.Reset(); + } + + /// + /// Discover Tests given a list of sources, runsettings + /// + /// Discovery payload + /// EventHandler for discovered tests + /// True, if successful + public bool DiscoverTests(DiscoveryRequestPayload discoveryPayload, ITestDiscoveryEventsRegistrar discoveryEventsRegistrar) + { + bool success = false; + var adapterSourceMap = JsonUtilities.GetTestRunnerAndAssemblyInfo(discoveryPayload.Sources); + + // create discovery request + var criteria = new DiscoveryCriteria(discoveryPayload.Sources, this.commandLineOptions.BatchSize, TimeSpan.MaxValue, discoveryPayload.RunSettings); + using (IDiscoveryRequest discoveryRequest = this.testPlatform.CreateDiscoveryRequest(criteria)) + { + try + { + testLoggerManager?.RegisterDiscoveryEvents(discoveryRequest); + discoveryEventsRegistrar?.RegisterDiscoveryEvents(discoveryRequest); + + discoveryRequest.DiscoverAsync(); + discoveryRequest.WaitForCompletion(); + + success = true; + } + catch (Exception ex) + { + if (ex is TestPlatformException || + ex is SettingsException || + ex is InvalidOperationException) + { +#if TODO + Utilities.RaiseTestRunError(testLoggerManager, null, ex); +#endif + success = false; + } + else + { + throw; + } + } + finally + { + testLoggerManager?.UnregisterDiscoveryEvents(discoveryRequest); + discoveryEventsRegistrar?.UnregisterDiscoveryEvents(discoveryRequest); + } + } + + return success; + } + + /// + /// Run Tests with given a set of testcases + /// + /// TestRun request Payload + /// TestHost Launcher for the run + /// event registrar for run events + /// True, if sucessful + public bool RunTests(TestRunRequestPayload testRunRequestPayload, ITestHostLauncher testHostLauncher, ITestRunEventsRegistrar testRunEventsRegistrar) + { + TestRunCriteria runCriteria = null; + if (testRunRequestPayload.Sources != null && testRunRequestPayload.Sources.Count() > 0) + { + var adapterSourceMap = JsonUtilities.GetTestRunnerAndAssemblyInfo(testRunRequestPayload.Sources); + + runCriteria = new TestRunCriteria(adapterSourceMap, + commandLineOptions.BatchSize, + testRunRequestPayload.KeepAlive, + testRunRequestPayload.RunSettings, + commandLineOptions.TestRunStatsEventTimeout, + testHostLauncher); + runCriteria.TestCaseFilter = commandLineOptions.TestCaseFilterValue; + } + else + { + runCriteria = new TestRunCriteria(testRunRequestPayload.TestCases, commandLineOptions.BatchSize, testRunRequestPayload.KeepAlive, + testRunRequestPayload.RunSettings, commandLineOptions.TestRunStatsEventTimeout, testHostLauncher); + } + + return RunTests(runCriteria, testRunEventsRegistrar); + } + + /// + /// Cancel the test run + /// + public void CancelTestRun() + { + this.runRequestCreatedEventHandle.WaitOne(runRequestTimeout); + this.currentTestRunRequest?.CancelAsync(); + } + + public void AbortTestRun() + { + this.runRequestCreatedEventHandle.WaitOne(runRequestTimeout); + this.currentTestRunRequest?.Abort(); + } + + #endregion + + private bool RunTests(TestRunCriteria testRunCriteria, ITestRunEventsRegistrar testRunEventsRegistrar) + { + bool success = true; + using (this.currentTestRunRequest = testPlatform.CreateTestRunRequest(testRunCriteria)) + { + this.runRequestCreatedEventHandle.Set(); + try + { + testLoggerManager.RegisterTestRunEvents(currentTestRunRequest); + testRunResultAggregator.RegisterTestRunEvents(currentTestRunRequest); + testRunEventsRegistrar?.RegisterTestRunEvents(currentTestRunRequest); + + currentTestRunRequest.ExecuteAsync(); + + // Wait for the run completion event + currentTestRunRequest.WaitForCompletion(); + } + catch (Exception ex) + { + if (ex is TestPlatformException || + ex is SettingsException || + ex is InvalidOperationException) + { + LoggerUtilities.RaiseTestRunError(testLoggerManager, testRunResultAggregator, ex); + success = false; + } + else + { + throw; + } + } + finally + { + testLoggerManager.UnregisterTestRunEvents(currentTestRunRequest); + testRunResultAggregator.UnregisterTestRunEvents(currentTestRunRequest); + testRunEventsRegistrar?.UnregisterTestRunEvents(currentTestRunRequest); + } + } + this.currentTestRunRequest = null; + + return success; + } + } +} \ No newline at end of file diff --git a/src/vstest.console/project.json b/src/vstest.console/project.json new file mode 100644 index 0000000000..56f45d9a60 --- /dev/null +++ b/src/vstest.console/project.json @@ -0,0 +1,42 @@ +{ + "version": "15.0.0-*", + + "buildOptions": { + "emitEntryPoint": true, + "delaySign": true, + "keyFile": "../../scripts/key.snk", + "warningsAsErrors": true + }, + + "dependencies": { + "Microsoft.DotNet.ProjectModel": "1.0.0-rc2-002702", + "Microsoft.TestPlatform.CoreUtilities": "15.0.0-*", + "Microsoft.TestPlatform.ObjectModel": "15.0.0-*", + "Microsoft.TestPlatform.Client": "15.0.0-*", + "Microsoft.TestPlatform.CommunicationUtilities": "15.0.0-*", + "Microsoft.TestPlatform.Utilities": "15.0.0-*" + }, + + "runtimes": { + "win7-x64": { }, + "win7-x86": { } + }, + + "frameworks": { + "netcoreapp1.0": { + "imports": [ + "dnxcore50", + "netstandardapp1.0", + "portable-net45+win8", + "portable-net45+wp80+win8+wpa81+dnxcore50" + ], + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-24027", + "System.ComponentModel.TypeConverter": "4.0.1-rc2-23911", + "System.Diagnostics.Contracts": "4.0.0", + "System.Xml.XPath": "4.0.1-rc2-24027" + } + }, + "net46": {} + } +} diff --git a/src/vstest.console/vstest.console.xproj b/src/vstest.console/vstest.console.xproj new file mode 100644 index 0000000000..50733fc93e --- /dev/null +++ b/src/vstest.console/vstest.console.xproj @@ -0,0 +1,21 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + 4d89e2f0-2e9d-41ca-a394-322a2e9a2c0b + Microsoft.VisualStudio.TestPlatform.CommandLine + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\ + v4.5.2 + + + + 2.0 + + + diff --git a/test/Microsoft.TestPlatform.Client.UnitTests/DesignMode/DesignModeClientTests.cs b/test/Microsoft.TestPlatform.Client.UnitTests/DesignMode/DesignModeClientTests.cs new file mode 100644 index 0000000000..279595e862 --- /dev/null +++ b/test/Microsoft.TestPlatform.Client.UnitTests/DesignMode/DesignModeClientTests.cs @@ -0,0 +1,224 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Client.UnitTests.DesignMode +{ + using System; + using System.Linq; + using System.Threading; + + using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; + + using Microsoft.VisualStudio.TestPlatform.Client.DesignMode; + using Microsoft.VisualStudio.TestPlatform.Client.RequestHelper; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + using Moq; + + using Newtonsoft.Json; + using Newtonsoft.Json.Linq; + + using ObjectModel.Client; + using ObjectModel.Engine; + using ObjectModel.Client.Interfaces; + + [TestClass] + public class DesignModeClientTests + { + [TestMethod] + public void DesignModeClientBeforeConnectInstanceShouldReturnNull() + { + Assert.IsNull(DesignModeClient.Instance); + } + + [TestMethod] + public void DesignModeClientInitializeShouldInstantiateClassAndCreateClient() + { + DesignModeClient.Initialize(); + Assert.IsNotNull(DesignModeClient.Instance); + } + + [TestMethod] + public void DesignModeClientConnectShouldSetupChannel() + { + int portNumber = 123; + var testRequestManager = new Mock(); + var communicationManager = new Mock(); + var testDesignModeClient = new DesignModeClient(communicationManager.Object, JsonDataSerializer.Instance); + + var verCheck = new Message() { MessageType = MessageType.VersionCheck }; + var sessionEnd = new Message() { MessageType = MessageType.SessionEnd }; + communicationManager.Setup(cm => cm.WaitForServerConnection(It.IsAny())).Returns(true); + communicationManager.Setup(cm => cm.ReceiveMessage()).Returns(verCheck); + + bool verCheckCalled = false; + communicationManager.Setup(cm => cm.SendMessage(MessageType.VersionCheck, It.IsAny())).Callback + (() => + { + verCheckCalled = true; + communicationManager.Setup(cm => cm.ReceiveMessage()).Returns(sessionEnd); + }); + + testDesignModeClient.ConnectToClientAndProcessRequests(portNumber, testRequestManager.Object); + + communicationManager.Verify(cm => cm.SetupClientAsync(portNumber), Times.Once); + communicationManager.Verify(cm => cm.WaitForServerConnection(It.IsAny()), Times.Once); + Assert.IsTrue(verCheckCalled, "Version Check must be called"); + } + + [TestMethod] + public void DesignModeClientWithGetTestRunnerProcessStartInfoShouldDeserializeTestsWithTraitsCorrectly() + { + // Arrange. + var mockTestRequestManager = new Mock(); + var mockCommunicationManager = new Mock(); + + var testDesignModeClient = new DesignModeClient(mockCommunicationManager.Object, JsonDataSerializer.Instance); + + var testCase = new TestCase("A.C.M", new Uri("d:\\executor"), "A.dll"); + testCase.Traits.Add(new Trait("foo", "bar")); + + var testList = new System.Collections.Generic.List { testCase }; + var testRunPayload = new TestRunRequestPayload() { RunSettings = null, TestCases = testList }; + + var getProcessStartInfoMessage = new Message() + { + MessageType = + MessageType + .GetTestRunnerProcessStartInfoForRunSelected, + Payload = JToken.FromObject("random") + }; + + var sessionEnd = new Message() { MessageType = MessageType.SessionEnd }; + TestRunRequestPayload receivedTestRunPayload = null; + var allTasksComplete = new ManualResetEvent(false); + + // Setup mocks. + mockCommunicationManager.Setup(cm => cm.WaitForServerConnection(It.IsAny())).Returns(true); + mockCommunicationManager.Setup(cm => cm.DeserializePayload(getProcessStartInfoMessage)) + .Returns(testRunPayload); + + mockTestRequestManager.Setup( + trm => + trm.RunTests( + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Callback( + (TestRunRequestPayload trp, + ITestHostLauncher testHostManager, + ITestRunEventsRegistrar testRunEventsRegistrar) => + { + allTasksComplete.Set(); + receivedTestRunPayload = trp; + }); + + mockCommunicationManager.SetupSequence(cm => cm.ReceiveMessage()) + .Returns(getProcessStartInfoMessage) + .Returns(sessionEnd); + + // Act. + testDesignModeClient.ConnectToClientAndProcessRequests(0, mockTestRequestManager.Object); + + // wait for the internal spawned of tasks to complete. + allTasksComplete.WaitOne(1000); + + // Assert. + Assert.IsNotNull(receivedTestRunPayload); + Assert.IsNotNull(receivedTestRunPayload.TestCases); + Assert.AreEqual(1, receivedTestRunPayload.TestCases.Count); + + // Validate traits + var traits = receivedTestRunPayload.TestCases.ToArray()[0].Traits; + Assert.AreEqual("foo", traits.ToArray()[0].Name); + Assert.AreEqual("bar", traits.ToArray()[0].Value); + } + + [TestMethod] + public void DesignModeClientWithRunSelectedTestCasesShouldDeserializeTestsWithTraitsCorrectly() + { + // Arrange. + var mockTestRequestManager = new Mock(); + var mockCommunicationManager = new Mock(); + + var testDesignModeClient = new DesignModeClient(mockCommunicationManager.Object, JsonDataSerializer.Instance); + + var testCase = new TestCase("A.C.M", new Uri("d:\\executor"), "A.dll"); + testCase.Traits.Add(new Trait("foo", "bar")); + + var testList = new System.Collections.Generic.List { testCase }; + var testRunPayload = new TestRunRequestPayload() { RunSettings = null, TestCases = testList }; + + var getProcessStartInfoMessage = new Message() + { + MessageType = MessageType.TestRunSelectedTestCasesDefaultHost, + Payload = JToken.FromObject("random") + }; + + var sessionEnd = new Message() { MessageType = MessageType.SessionEnd }; + TestRunRequestPayload receivedTestRunPayload = null; + var allTasksComplete = new ManualResetEvent(false); + + // Setup mocks. + mockCommunicationManager.Setup(cm => cm.WaitForServerConnection(It.IsAny())).Returns(true); + mockCommunicationManager.Setup(cm => cm.DeserializePayload(getProcessStartInfoMessage)) + .Returns(testRunPayload); + + mockTestRequestManager.Setup( + trm => + trm.RunTests( + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Callback( + (TestRunRequestPayload trp, + ITestHostLauncher testHostManager, + ITestRunEventsRegistrar testRunEventsRegistrar) => + { + allTasksComplete.Set(); + receivedTestRunPayload = trp; + }); + + mockCommunicationManager.SetupSequence(cm => cm.ReceiveMessage()) + .Returns(getProcessStartInfoMessage) + .Returns(sessionEnd); + + // Act. + testDesignModeClient.ConnectToClientAndProcessRequests(0, mockTestRequestManager.Object); + + // wait for the internal spawned of tasks to complete. + allTasksComplete.WaitOne(1000); + + // Assert. + Assert.IsNotNull(receivedTestRunPayload); + Assert.IsNotNull(receivedTestRunPayload.TestCases); + Assert.AreEqual(1, receivedTestRunPayload.TestCases.Count); + + // Validate traits + var traits = receivedTestRunPayload.TestCases.ToArray()[0].Traits; + Assert.AreEqual("foo", traits.ToArray()[0].Name); + Assert.AreEqual("bar", traits.ToArray()[0].Value); + } + + [TestMethod] + public void DesignModeClientOnBadConnectionShouldStopServerAndThrowTimeoutException() + { + int portNumber = 123; + var designModeHandler = new Mock(); + var communicationManager = new Mock(); + var testDesignModeClient = new DesignModeClient(communicationManager.Object, JsonDataSerializer.Instance); + + communicationManager.Setup(cm => cm.WaitForServerConnection(It.IsAny())).Returns(false); + + Assert.ThrowsException(() => + testDesignModeClient.ConnectToClientAndProcessRequests(portNumber, designModeHandler.Object)); + + communicationManager.Verify(cm => cm.SetupClientAsync(portNumber), Times.Once); + communicationManager.Verify(cm => cm.WaitForServerConnection(It.IsAny()), Times.Once); + communicationManager.Verify(cm => cm.StopClient(), Times.Once); + } + } +} diff --git a/test/Microsoft.TestPlatform.Client.UnitTests/DesignMode/DesignModeTestHostLauncherFactoryTests.cs b/test/Microsoft.TestPlatform.Client.UnitTests/DesignMode/DesignModeTestHostLauncherFactoryTests.cs new file mode 100644 index 0000000000..7a61192f6e --- /dev/null +++ b/test/Microsoft.TestPlatform.Client.UnitTests/DesignMode/DesignModeTestHostLauncherFactoryTests.cs @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Client.UnitTests.DesignMode +{ + using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; + using Microsoft.VisualStudio.TestPlatform.Client.DesignMode; using Microsoft.VisualStudio.TestPlatform.Client.RequestHelper; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces; using Microsoft.VisualStudio.TestTools.UnitTesting; + using Moq; + + [TestClass] + public class DesignModeTestHostLauncherFactoryTests + { + [TestMethod] + public void DesignModeTestHostFactoryShouldReturnNonDebugLauncherIfDebuggingDisabled() + { + var mockDesignModeClient = new Mock(); + var testRunRequestPayload = new TestRunRequestPayload() { DebuggingEnabled = false }; + var launcher = DesignModeTestHostLauncherFactory.GetCustomHostLauncherForTestRun(mockDesignModeClient.Object, testRunRequestPayload); + + Assert.IsFalse(launcher.IsDebug, "Factory must not return debug launcher if debugging is disabled."); + + var testProcessStartInfo = new TestProcessStartInfo(); + + launcher.LaunchTestHost(testProcessStartInfo); + + mockDesignModeClient.Verify(md => md.LaunchCustomHost(testProcessStartInfo), Times.Once, "Launcher should use provided design mode client"); + } + + [TestMethod] + public void DesignModeTestHostFactoryShouldReturnDebugLauncherIfDebuggingEnabled() + { + var mockDesignModeClient = new Mock(); + var testRunRequestPayload = new TestRunRequestPayload() { DebuggingEnabled = true }; + var launcher = DesignModeTestHostLauncherFactory.GetCustomHostLauncherForTestRun(mockDesignModeClient.Object, testRunRequestPayload); + + Assert.IsTrue(launcher.IsDebug, "Factory must not return debug launcher if debugging is disabled."); + + var testProcessStartInfo = new TestProcessStartInfo(); + + launcher.LaunchTestHost(testProcessStartInfo); + + mockDesignModeClient.Verify(md => md.LaunchCustomHost(testProcessStartInfo), Times.Once, "Launcher should use provided design mode client"); + } + } +} diff --git a/test/Microsoft.TestPlatform.Client.UnitTests/DesignMode/DesignModeTestHostLauncherTests.cs b/test/Microsoft.TestPlatform.Client.UnitTests/DesignMode/DesignModeTestHostLauncherTests.cs new file mode 100644 index 0000000000..8a0313d4b6 --- /dev/null +++ b/test/Microsoft.TestPlatform.Client.UnitTests/DesignMode/DesignModeTestHostLauncherTests.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.VisualStudio.TestPlatform.Client.DesignMode; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using System; +using System.Collections.Generic; +using System.Threading; + +namespace Microsoft.VisualStudio.TestPlatform.Client.UnitTests.DesignMode +{ + [TestClass] + public class DesignModeTestHostLauncherTests + { + [TestMethod] + public void DesignModeTestHostLauncherLaunchTestHostShouldCallDesignModeClientToLaunchCustomHost() + { + var mockDesignModeClient = new Mock(); + var launcher = new DesignModeTestHostLauncher(mockDesignModeClient.Object); + Assert.IsFalse(launcher.IsDebug, "Default launcher must not implement debug launcher interface."); + + var testProcessStartInfo = new TestProcessStartInfo(); + + launcher.LaunchTestHost(testProcessStartInfo); + + mockDesignModeClient.Verify(md => md.LaunchCustomHost(testProcessStartInfo), Times.Once); + } + + [TestMethod] + public void DesignModeDebugTestHostLauncherLaunchTestHostShouldCallDesignModeClientToLaunchCustomHost() + { + var mockDesignModeClient = new Mock(); + var launcher = new DesignModeDebugTestHostLauncher(mockDesignModeClient.Object); + Assert.IsTrue(launcher.IsDebug, "Debug launcher must implement debug launcher interface."); + + var testProcessStartInfo = new TestProcessStartInfo(); + + launcher.LaunchTestHost(testProcessStartInfo); + + mockDesignModeClient.Verify(md => md.LaunchCustomHost(testProcessStartInfo), Times.Once); + } + } +} + diff --git a/test/Microsoft.TestPlatform.Client.UnitTests/Discovery/DiscoveryRequestTests.cs b/test/Microsoft.TestPlatform.Client.UnitTests/Discovery/DiscoveryRequestTests.cs new file mode 100644 index 0000000000..eed94a364e --- /dev/null +++ b/test/Microsoft.TestPlatform.Client.UnitTests/Discovery/DiscoveryRequestTests.cs @@ -0,0 +1,101 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Client.UnitTests.Discovery +{ + using Client.Discovery; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Moq; + using ObjectModel.Client; + using ObjectModel.Engine; + using System; + using System.Collections.Generic; + + [TestClass] + public class DiscoveryRequestTests + { + IDiscoveryRequest discoveryRequest; + Mock discoveryManager; + DiscoveryCriteria discoveryCriteria; + + [TestInitialize] + public void TestInit() + { + discoveryCriteria = new DiscoveryCriteria(new List { "foo" }, 1, null); + discoveryManager = new Mock(); + discoveryRequest = new DiscoveryRequest(discoveryCriteria, discoveryManager.Object); + } + + [TestMethod] + public void ConstructorSetsDiscoveryCriteriaAndDiscoveryManager() + { + Assert.AreEqual(discoveryCriteria, discoveryRequest.DiscoveryCriteria); + Assert.AreEqual(discoveryManager.Object, (discoveryRequest as DiscoveryRequest).DiscoveryManager); + } + + [TestMethod] + public void DiscoveryAsycIfDiscoveryRequestIsDisposedThrowsObjectDisposedException() + { + discoveryRequest.Dispose(); + + Assert.ThrowsException(() => discoveryRequest.DiscoverAsync()); + } + + [TestMethod] + public void DiscoverAsyncSetsDiscoveryInProgressAndCallManagerToDiscoverTests() + { + discoveryRequest.DiscoverAsync(); + + Assert.IsTrue((discoveryRequest as DiscoveryRequest).DiscoveryInProgress); + discoveryManager.Verify(dm => dm.DiscoverTests(discoveryCriteria, discoveryRequest as DiscoveryRequest), Times.Once); + } + + [TestMethod] + public void DiscoveryAsyncIfDiscoverTestsThrowsExceptionSetsDiscoveryInProgressToFalseAndThrowsThatException() + { + discoveryManager.Setup(dm => dm.DiscoverTests(discoveryCriteria, discoveryRequest as DiscoveryRequest)).Throws(new Exception("DummyException")); + try + { + discoveryRequest.DiscoverAsync(); + } + catch (Exception ex) + { + Assert.IsTrue(ex is Exception); + Assert.AreEqual("DummyException", ex.Message); + Assert.IsFalse((discoveryRequest as DiscoveryRequest).DiscoveryInProgress); + } + } + + [TestMethod] + public void AbortIfDiscoveryRequestDisposedShouldThrowObjectDisposedException() + { + discoveryRequest.Dispose(); + Assert.ThrowsException(() => discoveryRequest.Abort()); + } + + [TestMethod] + public void AbortIfDiscoveryIsinProgressShouldCallDiscoveryManagerAbort() + { + // Just to set the IsDiscoveryInProgress flag + discoveryRequest.DiscoverAsync(); + + discoveryRequest.Abort(); + discoveryManager.Verify(dm => dm.Abort(), Times.Once); + } + + + [TestMethod] + public void AbortIfDiscoveryIsNotInProgressShouldNotCallDiscoveryManagerAbort() + { + //DiscoveryAsyn has not been called, discoveryInProgress should be false + discoveryRequest.Abort(); + discoveryManager.Verify(dm => dm.Abort(), Times.Never); + } + + [TestMethod] + public void WaitForCompletionIfDiscoveryRequestDisposedShouldThrowObjectDisposedException() + { + discoveryRequest.Dispose(); + Assert.ThrowsException(() => discoveryRequest.WaitForCompletion()); + } + } +} diff --git a/test/Microsoft.TestPlatform.Client.UnitTests/Execution/TestRunRequestTests.cs b/test/Microsoft.TestPlatform.Client.UnitTests/Execution/TestRunRequestTests.cs new file mode 100644 index 0000000000..f65c960d72 --- /dev/null +++ b/test/Microsoft.TestPlatform.Client.UnitTests/Execution/TestRunRequestTests.cs @@ -0,0 +1,243 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Client.UnitTests.Execution +{ + using Client.Execution; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Moq; + using ObjectModel; + using ObjectModel.Client; + using ObjectModel.Client.Interfaces; + using ObjectModel.Engine; + using System; + using System.Collections.Generic; + using System.Linq; + [TestClass] + public class TestRunRequestTests + { + TestRunRequest testRunRequest; + Mock executionManager; + TestRunCriteria testRunCriteria; + + [TestInitialize] + public void TestInit() + { + testRunCriteria = new TestRunCriteria(new List { "foo" }, 1); + executionManager = new Mock(); + testRunRequest = new TestRunRequest(testRunCriteria, executionManager.Object); + } + + [TestMethod] + public void ConstructorSetsTestRunCriteriaExecutionManagerAndState() + { + Assert.AreEqual(TestRunState.Pending, testRunRequest.State); + Assert.AreEqual(testRunCriteria, testRunRequest.TestRunConfiguration); + Assert.AreEqual(executionManager.Object, testRunRequest.ExecutionManager); + } + + [TestMethod] + public void ExecuteAsycIfTestRunRequestIsDisposedThrowsObjectDisposedException() + { + testRunRequest.Dispose(); + + Assert.ThrowsException(() => testRunRequest.ExecuteAsync()); + } + + [TestMethod] + public void ExecuteAsycIfStateIsNotPendingThrowsInvalidOperationException() + { + testRunRequest.ExecuteAsync(); + Assert.ThrowsException(() => testRunRequest.ExecuteAsync()); + } + + [TestMethod] + public void ExecuteAsyncSetsStateToInProgressAndCallManagerToStartTestRun() + { + testRunRequest.ExecuteAsync(); + + Assert.AreEqual(TestRunState.InProgress, testRunRequest.State); + executionManager.Verify(em => em.StartTestRun(testRunCriteria, testRunRequest), Times.Once); + } + + [TestMethod] + public void ExecuteAsyncIfStartTestRunThrowsExceptionSetsStateToPendingAndThrowsThatException() + { + executionManager.Setup(em => em.StartTestRun(testRunCriteria, testRunRequest)).Throws(new Exception("DummyException")); + try + { + testRunRequest.ExecuteAsync(); + } + catch (Exception ex) + { + Assert.IsTrue(ex is Exception); + Assert.AreEqual("DummyException", ex.Message); + Assert.AreEqual(TestRunState.Pending, testRunRequest.State); + } + } + + [TestMethod] + public void AbortIfTestRunRequestDisposedShouldThrowObjectDisposedException() + { + testRunRequest.Dispose(); + Assert.ThrowsException(() => testRunRequest.Abort()); + } + + [TestMethod] + public void AbortIfTestRunStateIsNotInProgressShouldNotCallExecutionManagerAbort() + { + //ExecuteAsync has not been called, so State is not InProgress + testRunRequest.Abort(); + executionManager.Verify(dm => dm.Abort(), Times.Never); + } + + [TestMethod] + public void AbortIfDiscoveryIsinProgressShouldCallDiscoveryManagerAbort() + { + // Set the State to InProgress + testRunRequest.ExecuteAsync(); + + testRunRequest.Abort(); + executionManager.Verify(dm => dm.Abort(), Times.Once); + } + + [TestMethod] + public void WaitForCompletionIfTestRunRequestDisposedShouldThrowObjectDisposedException() + { + testRunRequest.Dispose(); + Assert.ThrowsException(() => testRunRequest.WaitForCompletion()); + } + + [TestMethod] + public void WaitForCompletionIfTestRunStatePendingShouldThrowInvalidOperationException() + { + Assert.ThrowsException(() => testRunRequest.WaitForCompletion()); + } + + [TestMethod] + public void CancelAsyncIfTestRunRequestDisposedThrowsObjectDisposedException() + { + testRunRequest.Dispose(); + Assert.ThrowsException(() => testRunRequest.CancelAsync()); + } + + [TestMethod] + public void CancelAsyncIfTestRunStateNotInProgressWillNotCallExecutionManagerCancel() + { + testRunRequest.CancelAsync(); + executionManager.Verify(dm => dm.Cancel(), Times.Never); + } + + [TestMethod] + public void CancelAsyncIfTestRunStateInProgressCallsExecutionManagerCancel() + { + testRunRequest.ExecuteAsync(); + testRunRequest.CancelAsync(); + executionManager.Verify(dm => dm.Cancel(), Times.Once); + } + + [TestMethod] + public void HandleTestRunStatsChangeShouldInokeListenersWithTestRunChangedEventArgs() + { + var mockStats = new Mock(); + + var testResults = new List + { + new ObjectModel.TestResult( + new ObjectModel.TestCase( + "A.C.M", + new Uri("executor://dummy"), + "A")) + }; + var activeTestCases = new List + { + new ObjectModel.TestCase( + "A.C.M2", + new Uri("executor://dummy"), + "A") + }; + var testRunChangedEventArgs = new TestRunChangedEventArgs(mockStats.Object, testResults, activeTestCases); + TestRunChangedEventArgs receivedArgs = null; + + testRunRequest.OnRunStatsChange += (object sender, TestRunChangedEventArgs e) => + { + receivedArgs = e; + }; + + // Act. + testRunRequest.HandleTestRunStatsChange(testRunChangedEventArgs); + + // Assert. + Assert.IsNotNull(receivedArgs); + Assert.AreEqual(testRunChangedEventArgs.TestRunStatistics, receivedArgs.TestRunStatistics); + CollectionAssert.AreEqual( + testRunChangedEventArgs.NewTestResults.ToList(), + receivedArgs.NewTestResults.ToList()); + CollectionAssert.AreEqual(testRunChangedEventArgs.ActiveTests.ToList(), receivedArgs.ActiveTests.ToList()); + } + + [TestMethod] + public void HandleRawMessageShouldCallOnRawMessageReceived() + { + string rawMessage = "HelloWorld"; + string messageReceived = null; + + // Call should NOT fail even if onrawmessagereceived is not registered. + testRunRequest.HandleRawMessage(rawMessage); + + EventHandler handler = (sender, e) => { messageReceived = e; }; + testRunRequest.OnRawMessageReceived += handler; + + testRunRequest.HandleRawMessage(rawMessage); + + Assert.AreEqual(rawMessage, messageReceived, "RunRequest should just pass the message as is."); + testRunRequest.OnRawMessageReceived -= handler; + } + + [TestMethod] + public void LaunchProcessWithDebuggerAttachedShouldNotCallCustomLauncherIfTestRunIsNotInProgress() + { + var mockCustomLauncher = new Mock(); + testRunCriteria = new TestRunCriteria(new List { "foo" }, 1, false, null, TimeSpan.Zero, mockCustomLauncher.Object); + executionManager = new Mock(); + testRunRequest = new TestRunRequest(testRunCriteria, executionManager.Object); + + var testProcessStartInfo = new TestProcessStartInfo(); + testRunRequest.LaunchProcessWithDebuggerAttached(testProcessStartInfo); + + mockCustomLauncher.Verify(ml => ml.LaunchTestHost(It.IsAny()), Times.Never); + } + + [TestMethod] + public void LaunchProcessWithDebuggerAttachedShouldNotCallCustomLauncherIfLauncherIsNotDebug() + { + var mockCustomLauncher = new Mock(); + testRunCriteria = new TestRunCriteria(new List { "foo" }, 1, false, null, TimeSpan.Zero, mockCustomLauncher.Object); + executionManager = new Mock(); + testRunRequest = new TestRunRequest(testRunCriteria, executionManager.Object); + + testRunRequest.ExecuteAsync(); + + var testProcessStartInfo = new TestProcessStartInfo(); + testRunRequest.LaunchProcessWithDebuggerAttached(testProcessStartInfo); + + mockCustomLauncher.Verify(ml => ml.LaunchTestHost(It.IsAny()), Times.Never); + } + + [TestMethod] + public void LaunchProcessWithDebuggerAttachedShouldCallCustomLauncherIfLauncherIsDebugAndRunInProgress() + { + var mockCustomLauncher = new Mock(); + testRunCriteria = new TestRunCriteria(new List { "foo" }, 1, false, null, TimeSpan.Zero, mockCustomLauncher.Object); + executionManager = new Mock(); + testRunRequest = new TestRunRequest(testRunCriteria, executionManager.Object); + + testRunRequest.ExecuteAsync(); + + var testProcessStartInfo = new TestProcessStartInfo(); + mockCustomLauncher.Setup(ml => ml.IsDebug).Returns(true); + testRunRequest.LaunchProcessWithDebuggerAttached(testProcessStartInfo); + + mockCustomLauncher.Verify(ml => ml.LaunchTestHost(testProcessStartInfo), Times.Once); + } + } +} diff --git a/test/Microsoft.TestPlatform.Client.UnitTests/Microsoft.TestPlatform.Client.UnitTests.xproj b/test/Microsoft.TestPlatform.Client.UnitTests/Microsoft.TestPlatform.Client.UnitTests.xproj new file mode 100644 index 0000000000..1cc2cf53e8 --- /dev/null +++ b/test/Microsoft.TestPlatform.Client.UnitTests/Microsoft.TestPlatform.Client.UnitTests.xproj @@ -0,0 +1,22 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 342ac5e0-6fb2-4fa3-97ba-268e42a4487c + Microsoft.VisualStudio.TestPlatform.Client.UnitTests + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\ + v4.5.2 + + + 2.0 + + + + + + \ No newline at end of file diff --git a/test/Microsoft.TestPlatform.Client.UnitTests/Properties/AssemblyInfo.cs b/test/Microsoft.TestPlatform.Client.UnitTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..a32031d233 --- /dev/null +++ b/test/Microsoft.TestPlatform.Client.UnitTests/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +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: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("TestPlatform.Client.UnitTests")] +[assembly: AssemblyTrademark("")] + +// 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("342ac5e0-6fb2-4fa3-97ba-268e42a4487c")] diff --git a/test/Microsoft.TestPlatform.Client.UnitTests/TestPlatformTests.cs b/test/Microsoft.TestPlatform.Client.UnitTests/TestPlatformTests.cs new file mode 100644 index 0000000000..e1375da1b4 --- /dev/null +++ b/test/Microsoft.TestPlatform.Client.UnitTests/TestPlatformTests.cs @@ -0,0 +1,117 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Client.UnitTests +{ + using System; + using System.Collections.Generic; + + using Microsoft.VisualStudio.TestPlatform.Client.Execution; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + using Moq; + + [TestClass] + public class TestPlatformTests + { + private Mock testEngine; + private Mock discoveryManager; + private Mock extensionManager; + private Mock hostManager; + private Mock executionManager; + + [TestInitialize] + public void Initialize() + { + testEngine = new Mock(); + discoveryManager = new Mock(); + extensionManager = new Mock(); + executionManager = new Mock(); + hostManager = new Mock(); + } + + [TestMethod] + public void CreateDiscoveryRequestShouldCreateDiscoveryRequestWithGivenCriteriaAndReturnIt() + { + testEngine.Setup(te => te.GetDefaultTestHostManager(ObjectModel.Architecture.X86)).Returns(hostManager.Object); + discoveryManager.Setup(dm => dm.Initialize(It.IsAny())).Verifiable(); + testEngine.Setup(te => te.GetDiscoveryManager()).Returns(discoveryManager.Object); + testEngine.Setup(te => te.GetExtensionManager()).Returns(extensionManager.Object); + var tp = new TestableTestPlatform(testEngine.Object); + + var discoveryCriteria = new DiscoveryCriteria(new List { "foo" }, 1, null); + var discoveryRequest = tp.CreateDiscoveryRequest(discoveryCriteria); + Assert.AreEqual(discoveryCriteria, discoveryRequest.DiscoveryCriteria); + } + + [TestMethod] + public void CreateDiscoveryRequestThrowsIfDiscoveryCriteriaIsNull() + { + TestPlatform tp = new TestPlatform(); + Assert.ThrowsException(() => tp.CreateDiscoveryRequest(null)); + } + + [TestMethod] + public void UpdateExtensionsShouldUpdateTheEngineWithAdditionalExtensions() + { + testEngine.Setup(te => te.GetExtensionManager()).Returns(extensionManager.Object); + var tp = new TestableTestPlatform(testEngine.Object); + + var additionalExtensions = new List { "e1.dll", "e2.dll" }; + + tp.UpdateExtensions(additionalExtensions, loadOnlyWellKnownExtensions: true); + + extensionManager.Verify(em => em.UseAdditionalExtensions(additionalExtensions, true)); + } + + [TestMethod] + public void CreateTestRunRequestShouldCreateTestRunRequestWithSpecifiedCriteria() + { + testEngine.Setup(te => te.GetDefaultTestHostManager(ObjectModel.Architecture.X86)).Returns(hostManager.Object); + executionManager.Setup(dm => dm.Initialize(It.IsAny())).Verifiable(); + testEngine.Setup(te => te.GetExecutionManager(It.IsAny())).Returns(executionManager.Object); + testEngine.Setup(te => te.GetExtensionManager()).Returns(extensionManager.Object); + var tp = new TestableTestPlatform(testEngine.Object); + + var testRunCriteria = new TestRunCriteria(new List { "foo" }, 10); + var testRunRequest = tp.CreateTestRunRequest(testRunCriteria); + var actualTestRunRequest = testRunRequest as TestRunRequest; + Assert.AreEqual(testRunCriteria, actualTestRunRequest.TestRunCriteria); + } + + [TestMethod] + public void CreateTestRunRequestShouldSetCustomHostLauncherOnEngineDefaultLauncherIfSpecified() + { + var mockCustomLauncher = new Mock(); + testEngine.Setup(te => te.GetDefaultTestHostManager(ObjectModel.Architecture.X86)).Returns(hostManager.Object); + executionManager.Setup(dm => dm.Initialize(It.IsAny())).Verifiable(); + + testEngine.Setup(te => te.GetExecutionManager(It.IsAny())).Returns(executionManager.Object); + testEngine.Setup(te => te.GetExtensionManager()).Returns(extensionManager.Object); + var tp = new TestableTestPlatform(testEngine.Object); + + var testRunCriteria = new TestRunCriteria(new List { "foo" }, 10, false, null, TimeSpan.Zero, mockCustomLauncher.Object); + var testRunRequest = tp.CreateTestRunRequest(testRunCriteria); + var actualTestRunRequest = testRunRequest as TestRunRequest; + + Assert.AreEqual(testRunCriteria, actualTestRunRequest.TestRunCriteria); + hostManager.Verify(hl => hl.SetCustomLauncher(mockCustomLauncher.Object), Times.Once); + } + + [TestMethod] + public void CreateTestRunRequestThrowsIfTestRunCriteriaIsNull() + { + var tp = new TestPlatform(); + Assert.ThrowsException(() => tp.CreateTestRunRequest(null)); + } + + private class TestableTestPlatform : TestPlatform + { + public TestableTestPlatform(ITestEngine testEngine) : base(testEngine) + { + } + } + } +} diff --git a/test/Microsoft.TestPlatform.Client.UnitTests/project.json b/test/Microsoft.TestPlatform.Client.UnitTests/project.json new file mode 100644 index 0000000000..0864e1388b --- /dev/null +++ b/test/Microsoft.TestPlatform.Client.UnitTests/project.json @@ -0,0 +1,35 @@ +{ + "version": "15.0.0-*", + + "buildOptions": { + "delaySign": true, + "keyFile": "../../scripts/key.snk", + "warningsAsErrors": true + }, + + "dependencies": { + "Microsoft.NETCore.App": { + "type": "platform", + "version": "1.0.0-rc2-3002702" + }, + "dotnet-test-mstest": { + "version": "1.0.1-preview", + "exclude": "compile" + }, + "MSTest.TestFramework": "1.0.0-preview", + "moq.netcore": "4.4.0-beta8", + "System.Diagnostics.TraceSource": "4.0.0-rc2-24015", + "Microsoft.TestPlatform.Client": "15.0.0-*" + }, + + "frameworks": { + "netcoreapp1.0": { + "imports": [ + "dnxcore50", + "portable-net45+win8" + ] + } + }, + + "testRunner": "mstest" +} diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/ExceptionUtilities.cs b/test/Microsoft.TestPlatform.Common.UnitTests/ExceptionUtilities.cs new file mode 100644 index 0000000000..da112df105 --- /dev/null +++ b/test/Microsoft.TestPlatform.Common.UnitTests/ExceptionUtilities.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.Common.UnitTests +{ + using Microsoft.VisualStudio.TestTools.UnitTesting; + using System; + + /// + /// This only exists because there is an issue with MSTest v2 and ThrowsException with a message API. + /// Move to Assert.ThrowException() with a message once the bug is fixed. + /// + public static class ExceptionUtilities + { + public static void ThrowsException(Action action , string format, params string[] args) + { + var isExceptionThrown = false; + + try + { + action(); + } + catch (Exception ex) + { + Assert.AreEqual(typeof(T), ex.GetType()); + isExceptionThrown = true; + var message = string.Format(format, args); + StringAssert.Contains(ex.Message, message); + } + + Assert.IsTrue(isExceptionThrown, "No Exception Thrown"); + } + } +} diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/TestDiscoveryExtensionManagerTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/TestDiscoveryExtensionManagerTests.cs new file mode 100644 index 0000000000..cb67378e51 --- /dev/null +++ b/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/TestDiscoveryExtensionManagerTests.cs @@ -0,0 +1,129 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.Common.UnitTests.ExtensionFramework +{ + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class TestDiscoveryExtensionManagerTests + { + [TestCleanup] + public void TestCleanup() + { + TestDiscoveryExtensionManager.Destroy(); + } + + [TestMethod] + public void CreateShouldDiscoverDiscovererExtensions() + { + TestPluginCacheTests.SetupMockExtensions(); + + var extensionManager = TestDiscoveryExtensionManager.Create(); + + Assert.IsNotNull(extensionManager.Discoverers); + Assert.IsTrue(extensionManager.Discoverers.Count() > 0); + } + + [TestMethod] + public void CreateShouldCacheDiscoveredExtensions() + { + var discoveryCount = 0; + TestPluginCacheTests.SetupMockExtensions(() => { discoveryCount++; }); + + var extensionManager = TestDiscoveryExtensionManager.Create(); + TestDiscoveryExtensionManager.Create(); + + Assert.IsNotNull(extensionManager.Discoverers); + Assert.IsTrue(extensionManager.Discoverers.Count() > 0); + Assert.AreEqual(1, discoveryCount); + } + + [TestMethod] + public void GetDiscoveryExtensionManagerShouldReturnADiscoveryManagerWithExtensions() + { + var extensionManager = + TestDiscoveryExtensionManager.GetDiscoveryExtensionManager( + typeof(TestDiscoveryExtensionManagerTests).GetTypeInfo().Assembly.Location); + + Assert.IsNotNull(extensionManager.Discoverers); + Assert.IsTrue(extensionManager.Discoverers.Count() > 0); + } + + #region LoadAndInitialize tests + + [TestMethod] + public void LoadAndInitializeShouldInitializeAllExtensions() + { + TestPluginCacheTests.SetupMockExtensions(); + + TestDiscoveryExtensionManager.LoadAndInitializeAllExtensions(false); + + var allDiscoverers = TestDiscoveryExtensionManager.Create().Discoverers; + + foreach (var discoverer in allDiscoverers) + { + Assert.IsTrue(discoverer.IsExtensionCreated); + } + } + + #endregion + } + + [TestClass] + public class TestDiscovererMetadataTests + { + [TestMethod] + public void TestDiscovererMetadataCtorDoesNotThrowWhenFileExtensionsIsNull() + { + var metadata = new TestDiscovererMetadata(null, null); + + Assert.IsNull(metadata.FileExtension); + } + + [TestMethod] + public void TestDiscovererMetadataCtorDoesNotThrowWhenFileExtensionsIsEmpty() + { + var metadata = new TestDiscovererMetadata(new List {}, null); + + Assert.IsNull(metadata.FileExtension); + } + + [TestMethod] + public void TestDiscovererMetadataCtorDoesNotThrowWhenDefaultUriIsNull() + { + var metadata = new TestDiscovererMetadata(new List { }, null); + + Assert.IsNull(metadata.DefaultExecutorUri); + } + + [TestMethod] + public void TestDiscovererMetadataCtorDoesNotThrowWhenDefaultUriIsEmpty() + { + var metadata = new TestDiscovererMetadata(new List { }, " "); + + Assert.IsNull(metadata.DefaultExecutorUri); + } + + [TestMethod] + public void TestDiscovererMetadataCtorSetsFileExtensions() + { + var extensions = new List { "csv", "dll" }; + var metadata = new TestDiscovererMetadata(extensions, null); + + CollectionAssert.AreEqual(extensions, metadata.FileExtension.ToList()); + } + + [TestMethod] + public void TestDiscovererMetadataCtorSetsDefaultUri() + { + var metadata = new TestDiscovererMetadata(null, "executor://helloworld"); + + Assert.AreEqual("executor://helloworld/", metadata.DefaultExecutorUri.AbsoluteUri); + } + } +} diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/TestExecutorExtensionManagerTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/TestExecutorExtensionManagerTests.cs new file mode 100644 index 0000000000..9af634d0af --- /dev/null +++ b/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/TestExecutorExtensionManagerTests.cs @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.Common.UnitTests.ExtensionFramework +{ + using System.Linq; + + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using System.Reflection; + [TestClass] + public class TestExecutorExtensionManagerTests + { + [TestCleanup] + public void TestCleanup() + { + TestExecutorExtensionManager.Destroy(); + } + + [TestMethod] + public void CreateShouldDiscoverExecutorExtensions() + { + TestPluginCacheTests.SetupMockExtensions(); + + var extensionManager = TestExecutorExtensionManager.Create(); + + Assert.IsNotNull(extensionManager.TestExtensions); + Assert.IsTrue(extensionManager.TestExtensions.Count() > 0); + } + + [TestMethod] + public void CreateShouldCacheDiscoveredExtensions() + { + var discoveryCount = 0; + TestPluginCacheTests.SetupMockExtensions(() => { discoveryCount++; }); + + var extensionManager = TestExecutorExtensionManager.Create(); + TestExecutorExtensionManager.Create(); + + Assert.IsNotNull(extensionManager.TestExtensions); + Assert.IsTrue(extensionManager.TestExtensions.Count() > 0); + Assert.AreEqual(1, discoveryCount); + } + + [TestMethod] + public void GetExecutorExtensionManagerShouldReturnAnExecutionManagerWithExtensions() + { + var extensionManager = + TestExecutorExtensionManager.GetExecutionExtensionManager( + typeof(TestExecutorExtensionManagerTests).GetTypeInfo().Assembly.Location); + + Assert.IsNotNull(extensionManager.TestExtensions); + Assert.IsTrue(extensionManager.TestExtensions.Count() > 0); + } + + #region LoadAndInitialize tests + + [TestMethod] + public void LoadAndInitializeShouldInitializeAllExtensions() + { + TestPluginCacheTests.SetupMockExtensions(); + + TestExecutorExtensionManager.LoadAndInitializeAllExtensions(false); + + var allExecutors = TestExecutorExtensionManager.Create().TestExtensions; + + foreach (var executor in allExecutors) + { + Assert.IsTrue(executor.IsExtensionCreated); + } + } + + #endregion + } + + [TestClass] + public class TestExecutorMetadataTests + { + [TestMethod] + public void TestExecutorMetadataCtorShouldSetExtensionUri() + { + var metadata = new TestExecutorMetadata("random"); + + Assert.AreEqual("random", metadata.ExtensionUri); + } + } +} diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/TestExtensionManagerTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/TestExtensionManagerTests.cs new file mode 100644 index 0000000000..038b2f934f --- /dev/null +++ b/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/TestExtensionManagerTests.cs @@ -0,0 +1,102 @@ +using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework; +using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.Utilities; +using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; +using Microsoft.VisualStudio.TestPlatform.Common.Logging; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace TestPlatform.Common.UnitTests.ExtensionFramework +{ + [TestClass] + public class TestExtensionManagerTests + { + private IMessageLogger messageLogger; + private TestExtensionManager testExtensionManager; + private IEnumerable> filteredTestExtensions; + private IEnumerable>> unfilteredTestExtensions; + + + [TestInitialize] + public void Initialize() + { + TestPluginCacheTests.SetupMockExtensions(); + messageLogger = TestSessionMessageLogger.Instance; + TestPluginManager.Instance.GetTestExtensions + (out unfilteredTestExtensions, out filteredTestExtensions); + + + + } + + [TestCleanup] + public void Cleanup() + { + TestSessionMessageLogger.Instance = null; + } + + [TestMethod] + public void TestExtensionManagerConstructorShouldThrowExceptionIfMessageLoggerIsNull() + { + Assert.ThrowsException(() => + { + testExtensionManager = new DummyTestExtensionManager(unfilteredTestExtensions, filteredTestExtensions, null); + } + ); + } + + [TestMethod] + public void TryGetTestExtensionShouldReturnExtensionWithCorrectUri() + { + testExtensionManager = new DummyTestExtensionManager(unfilteredTestExtensions, filteredTestExtensions, messageLogger); + var result = testExtensionManager.TryGetTestExtension(new Uri("testlogger://logger")); + + Assert.IsNotNull(result); + Assert.IsInstanceOfType(result.Value, typeof(ITestLogger)); + + } + + [TestMethod] + public void TryGetTestExtensionShouldThrowExceptionWithNullUri() + { + testExtensionManager = new DummyTestExtensionManager(unfilteredTestExtensions, filteredTestExtensions, messageLogger); + TestPluginCacheTests.SetupMockAdditionalPathExtensions(); + Assert.ThrowsException(() => + { + var result = testExtensionManager.TryGetTestExtension(default(Uri)); + } + ); + } + + [TestMethod] + public void TryGetTestExtensionShouldNotReturnExtensionWithIncorrectlUri() + { + testExtensionManager = new DummyTestExtensionManager(unfilteredTestExtensions, filteredTestExtensions, messageLogger); + var result = testExtensionManager.TryGetTestExtension(""); + Assert.IsNull(result); + } + + + [TestMethod] + public void TryGetTestExtensionWithStringUriUnitTest() + { + testExtensionManager = new DummyTestExtensionManager(unfilteredTestExtensions, filteredTestExtensions, messageLogger); + var result = testExtensionManager.TryGetTestExtension(new Uri("testlogger://logger").AbsoluteUri); + + Assert.IsNotNull(result); + Assert.IsInstanceOfType(result.Value, typeof(ITestLogger)); + } + } + + internal class DummyTestExtensionManager : TestExtensionManager + { + public DummyTestExtensionManager(IEnumerable>> unfilteredTestExtensions, IEnumerable> testExtensions, IMessageLogger logger) : base(unfilteredTestExtensions, testExtensions, logger) + { + } + + } +} diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/TestPluginCacheTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/TestPluginCacheTests.cs new file mode 100644 index 0000000000..348940567a --- /dev/null +++ b/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/TestPluginCacheTests.cs @@ -0,0 +1,645 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.Common.UnitTests.ExtensionFramework +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Reflection; + + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework; + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.Utilities; + using Microsoft.VisualStudio.TestPlatform.Common.Utilities; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + using Moq; + using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; + using Microsoft.VisualStudio.TestPlatform.Common.SettingsProvider; + [TestClass] + public class TestPluginCacheTests + { + [TestInitialize] + public void TestInit() + { + // Reset the singleton. + TestPluginCache.Instance = null; + } + + #region Properties tests + + [TestMethod] + public void InstanceShouldNotReturnANull() + { + Assert.IsNotNull(TestPluginCache.Instance); + } + + [TestMethod] + public void AreExtensionsDiscoveredShouldBeFalseByDefault() + { + Assert.IsFalse(TestPluginCache.Instance.AreDefaultExtensionsDiscovered); + } + + [TestMethod] + public void TestExtensionsShouldBeNullByDefault() + { + Assert.IsNull(TestPluginCache.Instance.TestExtensions); + } + + [TestMethod] + public void PathToAdditionalExtensionsShouldBeNullByDefault() + { + Assert.IsNull(TestPluginCache.Instance.PathToAdditionalExtensions); + } + + [TestMethod] + public void LoadOnlyWellKnownExtensionsShouldBeFalseByDefault() + { + Assert.IsFalse(TestPluginCache.Instance.LoadOnlyWellKnownExtensions); + } + + #endregion + + #region UpdateAdditionalExtensions tests + + [TestMethod] + public void UpdateAdditionalExtensionsShouldUpdateLoadOnlyWellKnownExtensions() + { + TestPluginCache.Instance.UpdateAdditionalExtensions(null, true); + Assert.IsTrue(TestPluginCache.Instance.LoadOnlyWellKnownExtensions); + } + + [TestMethod] + public void UpdateAdditionalExtensionsShouldNotThrowIfExtenionPathIsNull() + { + TestPluginCache.Instance.UpdateAdditionalExtensions(null, true); + Assert.IsNull(TestPluginCache.Instance.PathToAdditionalExtensions); + } + + [TestMethod] + public void UpdateAdditionalExtensionsShouldNotThrowIfExtenionPathIsEmpty() + { + TestPluginCache.Instance.UpdateAdditionalExtensions(new List {}, true); + Assert.IsNull(TestPluginCache.Instance.PathToAdditionalExtensions); + } + + [TestMethod] + public void UpdateAdditionalExtensionsShouldUpdateAdditionalExtensions() + { + var additionalExtensions = new List { typeof(TestPluginCacheTests).GetTypeInfo().Assembly.Location }; + TestPluginCache.Instance.UpdateAdditionalExtensions(additionalExtensions, true); + var updatedExtensions = TestPluginCache.Instance.PathToAdditionalExtensions; + + Assert.IsNotNull(updatedExtensions); + CollectionAssert.AreEqual(additionalExtensions, updatedExtensions.ToList()); + } + + [TestMethod] + public void UpdateAdditionalExtensionsShouldOnlyAddUniqueExtensionPaths() + { + var additionalExtensions = new List + { + typeof(TestPluginCacheTests).GetTypeInfo().Assembly.Location, + typeof(TestPluginCacheTests).GetTypeInfo().Assembly.Location + }; + TestPluginCache.Instance.UpdateAdditionalExtensions(additionalExtensions, true); + var updatedExtensions = TestPluginCache.Instance.PathToAdditionalExtensions; + + Assert.IsNotNull(updatedExtensions); + Assert.AreEqual(1, updatedExtensions.Count()); + CollectionAssert.AreEqual(new List { additionalExtensions.First() }, updatedExtensions.ToList()); + } + + [TestMethod] + public void UpdateAdditionalExtensionsShouldNotUpdateInvalidPaths() + { + var additionalExtensions = new List { "foo.dll" }; + TestPluginCache.Instance.UpdateAdditionalExtensions(additionalExtensions, true); + var updatedExtensions = TestPluginCache.Instance.PathToAdditionalExtensions; + + Assert.IsNotNull(updatedExtensions); + Assert.AreEqual(0, updatedExtensions.Count()); + } + + [TestMethod] + public void UpdateAdditionalExtensionsShouldResetExtensionsDiscoveredFlag() + { + + } + + #endregion + + #region GetDefaultResolutionPaths tests + + [TestMethod] + public void GetDefaultResolutionPathsShouldReturnCurrentDirectoryByDefault() + { + var resolutionPaths = TestPluginCache.Instance.GetDefaultResolutionPaths(); + + var currentDirectory = Path.GetDirectoryName(typeof(TestPluginCache).GetTypeInfo().Assembly.Location); + + Assert.IsNotNull(resolutionPaths); + CollectionAssert.AreEqual(new List { currentDirectory }, resolutionPaths.ToList()); + } + + [TestMethod] + public void GetDefaultResolutionPathsShouldReturnAdditionalExtensionPathsDirectories() + { + var currentDirectory = Path.GetDirectoryName(typeof(TestPluginCache).GetTypeInfo().Assembly.Location); + var candidateDirectory = Directory.GetParent(currentDirectory).FullName; + var extensionPaths = new List { Path.Combine(candidateDirectory, "foo.dll") }; + + // Setup mocks. + SetupMockPathUtilities(); + + TestPluginCache.Instance.UpdateAdditionalExtensions(extensionPaths, true); + var resolutionPaths = TestPluginCache.Instance.GetDefaultResolutionPaths(); + + var expectedExtensions = new List { candidateDirectory, currentDirectory }; + + Assert.IsNotNull(resolutionPaths); + CollectionAssert.AreEqual(expectedExtensions, resolutionPaths.ToList()); + } + + [TestMethod] + public void GetDefaultResolutionPathsShouldReturnDefaultExtensionsDirectoryIfPresent() + { + var testableTestPluginCache = new TestableTestPluginCache(new Mock().Object); + testableTestPluginCache.DoesDirectoryExistSetter = true; + + // Setup the testable instance. + TestPluginCache.Instance = testableTestPluginCache; + + var resolutionPaths = TestPluginCache.Instance.GetDefaultResolutionPaths(); + + var currentDirectory = Path.GetDirectoryName(typeof(TestPluginCache).GetTypeInfo().Assembly.Location); + var defaultExtensionsDirectory = Path.Combine(currentDirectory, "Extensions"); + + Assert.IsNotNull(resolutionPaths); + CollectionAssert.AreEqual(new List { currentDirectory, defaultExtensionsDirectory }, resolutionPaths.ToList()); + } + + #endregion + + #region GetResolutionPaths tests + + [TestMethod] + public void GetResolutionPathsShouldThrowIfExtensionAssemblyIsNull() + { + Assert.ThrowsException(() => TestPluginCache.Instance.GetResolutionPaths(null)); + } + + [TestMethod] + public void GetResolutionPathsShouldReturnExtensionAssemblyDirectoryAndTPCommonDirectory() + { + var resolutionPaths = TestPluginCache.Instance.GetResolutionPaths(@"C:\temp\Idonotexist.dll"); + + var tpCommonDirectory = Path.GetDirectoryName(typeof(TestPluginCache).GetTypeInfo().Assembly.Location); + var expectedPaths = new List { "C:\\temp", tpCommonDirectory }; + + CollectionAssert.AreEqual(expectedPaths, resolutionPaths.ToList()); + } + + [TestMethod] + public void GetResolutionPathsShouldNotHaveDuplicatePathsIfExtensionIsInSameDirectory() + { + var tpCommonlocation = typeof(TestPluginCache).GetTypeInfo().Assembly.Location; + + var resolutionPaths = TestPluginCache.Instance.GetResolutionPaths(tpCommonlocation); + + var expectedPaths = new List { Path.GetDirectoryName(tpCommonlocation) }; + + CollectionAssert.AreEqual(expectedPaths, resolutionPaths.ToList()); + } + + #endregion + + #region DefaultExtensions tests + + [TestMethod] + public void DefaultExtensionsShouldReturnExtensionsFolder() + { + var testableTestPluginCache = new TestableTestPluginCache(new Mock().Object); + testableTestPluginCache.DoesDirectoryExistSetter = true; + testableTestPluginCache.FilesInDirectory = (path, pattern) => { return new string[] { path }; }; + + // Setup the testable instance. + TestPluginCache.Instance = testableTestPluginCache; + + var extensionsFolder = + Path.Combine( + Path.GetDirectoryName(typeof(TestPluginCache).GetTypeInfo().Assembly.Location), + "Extensions"); + + Assert.IsNotNull(TestPluginCache.Instance.DefaultExtensionPaths); + CollectionAssert.Contains(TestPluginCache.Instance.DefaultExtensionPaths.ToList(), extensionsFolder); + } + + [TestMethod] + public void DefaultExtensionsShouldReturnEmptyIfExtensionsDirectoryDoesNotExist() + { + var testableTestPluginCache = new TestableTestPluginCache(new Mock().Object); + testableTestPluginCache.DoesDirectoryExistSetter = false; + + // Setup the testable instance. + TestPluginCache.Instance = testableTestPluginCache; + + Assert.IsNotNull(TestPluginCache.Instance.DefaultExtensionPaths); + Assert.AreEqual(0, TestPluginCache.Instance.DefaultExtensionPaths.Count()); + } + + [TestMethod] + public void DefaultExtensionsShouldReturnAllSupportedFilesUnderExtensionsFolder() + { + var testableTestPluginCache = new TestableTestPluginCache(new Mock().Object); + testableTestPluginCache.DoesDirectoryExistSetter = true; + + var dllFiles = new string[] { "t1.dll" }; + var exeFiles = new string[] { "t1.exe", "t2.exe" }; + var jsFiles = new string[] { "t1.js" }; + + testableTestPluginCache.FilesInDirectory = (path, pattern) => + { + if (pattern.Equals("*.dll")) + { + return dllFiles; + } + else if (pattern.Equals("*.exe")) + { + return exeFiles; + } + + return jsFiles; + }; + + // Setup the testable instance. + TestPluginCache.Instance = testableTestPluginCache; + + var expectedExtensions = new List(dllFiles); + expectedExtensions.AddRange(exeFiles); + + Assert.IsNotNull(TestPluginCache.Instance.DefaultExtensionPaths); + Assert.AreEqual(3, TestPluginCache.Instance.DefaultExtensionPaths.Count()); + CollectionAssert.AreEqual(expectedExtensions, TestPluginCache.Instance.DefaultExtensionPaths.ToList()); + } + + [TestMethod] + public void DefaultExtensionsShouldReturnAssemblyExtensionsIfFolderContainsDllsOnly() + { + var testableTestPluginCache = new TestableTestPluginCache(new Mock().Object); + testableTestPluginCache.DoesDirectoryExistSetter = true; + + var dllFiles = new string[] { "t1.dll", "t2.dll" }; + + testableTestPluginCache.FilesInDirectory = (path, pattern) => + { + if (pattern.Equals("*.dll")) + { + return dllFiles; + } + + return new string[] { }; + }; + + // Setup the testable instance. + TestPluginCache.Instance = testableTestPluginCache; + + Assert.IsNotNull(TestPluginCache.Instance.DefaultExtensionPaths); + Assert.AreEqual(2, TestPluginCache.Instance.DefaultExtensionPaths.Count()); + CollectionAssert.AreEqual(dllFiles, TestPluginCache.Instance.DefaultExtensionPaths.ToList()); + } + + #endregion + + #region GetTestExtensions tests + + [TestMethod] + public void GetTestExtensionsShouldReturnExtensionsInAssembly() + { + SetupMockAdditionalPathExtensions(); + + TestPluginCache.Instance.GetTestExtensions(typeof(TestPluginCacheTests).GetTypeInfo().Assembly.Location); + + Assert.IsNotNull(TestPluginCache.Instance.TestExtensions); + Assert.IsTrue(TestPluginCache.Instance.TestExtensions.TestDiscoverers.Count > 0); + } + + [TestMethod] + public void GetTestExtensionsShouldAddTestExtensionsDiscoveredToCache() + { + var testableTestPluginCache = new TestableTestPluginCache(new Mock().Object); + + // Setup mocks. + var testExtensions = new TestExtensions(); + testExtensions.TestDiscoverers = new Dictionary(); + testExtensions.TestDiscoverers.Add("td", new TestDiscovererPluginInformation(typeof(TestPluginCacheTests))); + + var extensionAssembly = typeof(TestPluginCacheTests).GetTypeInfo().Assembly.Location; + + testableTestPluginCache.TestExtensionsSetter = (IEnumerable extensionAssemblies) => + { + if (extensionAssemblies.Count() == 1 && extensionAssemblies.ToArray()[0] == extensionAssembly) + { + return testExtensions; + } + return null; + }; + + testableTestPluginCache.GetTestExtensions(extensionAssembly); + + CollectionAssert.AreEqual( + testExtensions.TestDiscoverers.Keys, + testableTestPluginCache.TestExtensions.TestDiscoverers.Keys); + } + + [TestMethod] + public void GetTestExtensionsShouldGetTestExtensionsFromCache() + { + var testableTestPluginCache = new TestableTestPluginCache(new Mock().Object); + + // Setup mocks. + var testExtensions = new TestExtensions(); + testExtensions.TestDiscoverers = new Dictionary(); + testExtensions.TestDiscoverers.Add("td", new TestDiscovererPluginInformation(typeof(TestPluginCacheTests))); + + var extensionAssembly = typeof(TestPluginCacheTests).GetTypeInfo().Assembly.Location; + var callCount = 0; + + testableTestPluginCache.TestExtensionsSetter = (IEnumerable extensionAssemblies) => + { + if (extensionAssemblies.Count() == 1 && extensionAssemblies.ToArray()[0] == extensionAssembly) + { + callCount++; + return testExtensions; + } + return null; + }; + + testableTestPluginCache.GetTestExtensions(extensionAssembly); + + testableTestPluginCache.GetTestExtensions(extensionAssembly); + + Assert.AreEqual(1, callCount); + } + + [TestMethod] + public void GetTestExtensionsShouldAddTestExtensionsToExistingCache() + { + var testableTestPluginCache = new TestableTestPluginCache(new Mock().Object); + + // Setup mocks. + var testExtensions = new TestExtensions[2]; + for (var i =0; i < 2; i++) + { + testExtensions[i] = new TestExtensions(); + testExtensions[i].TestDiscoverers = new Dictionary(); + testExtensions[i].TestDiscoverers.Add("td" + i, new TestDiscovererPluginInformation(typeof(TestPluginCacheTests))); + } + + testableTestPluginCache.TestExtensionsSetter = (IEnumerable extensionAssemblies) => + { + if (extensionAssemblies.Count() == 1 && string.Equals(extensionAssemblies.ToArray()[0], "foo1.dll")) + { + return testExtensions[0]; + } + else if (extensionAssemblies.Count() == 1 && string.Equals(extensionAssemblies.ToArray()[0], "foo2.dll")) + { + return testExtensions[1]; + } + + return null; + }; + + var extensions1 = testableTestPluginCache.GetTestExtensions("foo1.dll"); + + var extensions2 = testableTestPluginCache.GetTestExtensions("foo2.dll"); + + // Validate if the inidividual extension are returned appropriately. + CollectionAssert.AreEqual(testExtensions[0].TestDiscoverers.Keys, extensions1.TestDiscoverers.Keys); + CollectionAssert.AreEqual(testExtensions[1].TestDiscoverers.Keys, extensions2.TestDiscoverers.Keys); + + var expectedDiscoverers = new Dictionary(); + expectedDiscoverers.Add("td0" , new TestDiscovererPluginInformation(typeof(TestPluginCacheTests))); + expectedDiscoverers.Add("td1", new TestDiscovererPluginInformation(typeof(TestPluginCacheTests))); + + CollectionAssert.AreEqual( + expectedDiscoverers.Keys, + testableTestPluginCache.TestExtensions.TestDiscoverers.Keys); + } + + [TestMethod] + public void GetTestExtensionsShouldShouldThrowIfDiscovererThrows() + { + var testableTestPluginCache = new TestableTestPluginCache(new Mock().Object); + + // Setup mocks. + var testExtensions = new TestExtensions(); + testExtensions.TestDiscoverers = new Dictionary(); + testExtensions.TestDiscoverers.Add("td", new TestDiscovererPluginInformation(typeof(TestPluginCacheTests))); + + var extensionAssembly = typeof(TestPluginCacheTests).GetTypeInfo().Assembly.Location; + + testableTestPluginCache.TestExtensionsSetter = (IEnumerable extensionAssemblies) => + { throw new ArgumentException(); }; + + Assert.ThrowsException(() => testableTestPluginCache.GetTestExtensions(extensionAssembly)); + } + + #endregion + + #region DiscoverAllTestExtensions tests + + [TestMethod] + public void DiscoverAllTestExtensionsShouldDiscoverExtensionsFromDefaultExtensionsFolder() + { + var testableTestPluginCache = new TestableTestPluginCache(new Mock().Object); + testableTestPluginCache.DoesDirectoryExistSetter = true; + testableTestPluginCache.FilesInDirectory = (path, pattern) => + { + if (pattern.Equals("*.dll")) + { + return new string[] { typeof(TestPluginCacheTests).GetTypeInfo().Assembly.Location }; + } + return new string[] { }; + }; + + // Setup the testable instance. + TestPluginCache.Instance = testableTestPluginCache; + + TestPluginCache.Instance.DiscoverAllTestExtensions(); + + Assert.IsTrue(TestPluginCache.Instance.AreDefaultExtensionsDiscovered); + Assert.IsNotNull(TestPluginCache.Instance.TestExtensions); + + // Validate the discoverers to be absolutely certain. + Assert.IsTrue(TestPluginCache.Instance.TestExtensions.TestDiscoverers.Count > 0); + } + + [TestMethod] + public void DiscoverAllTestExtensionsShouldDiscoverExtensionsFromAdditionalExtensionsFolder() + { + SetupMockAdditionalPathExtensions(); + + TestPluginCache.Instance.DiscoverAllTestExtensions(); + + Assert.IsTrue(TestPluginCache.Instance.AreDefaultExtensionsDiscovered); + Assert.IsNotNull(TestPluginCache.Instance.TestExtensions); + + // Validate the discoverers to be absolutely certain. + Assert.IsTrue(TestPluginCache.Instance.TestExtensions.TestDiscoverers.Count > 0); + } + + [TestMethod] + public void DiscoverAllTestExtensionsShouldAddToTheExtensionsAlreadyDiscovered() + { + // Setup mocks. + var testableTestPluginCache = SetupMockAdditionalPathExtensions(); + + var testExtensions = new TestExtensions(); + testExtensions.TestDiscoverers = new Dictionary(); + testExtensions.TestDiscoverers.Add("td", new TestDiscovererPluginInformation(typeof(TestPluginCacheTests))); + + var extensionAssembly = typeof(TestPluginCacheTests).GetTypeInfo().Assembly.Location; + + testableTestPluginCache.TestExtensionsSetter = (IEnumerable extensionAssemblies) => + { + if (extensionAssemblies.Count() == 1 && extensionAssemblies.ToArray()[0] == "foo.dll") + { + return testExtensions; + } + else + { + var discoverer = new TestPluginDiscoverer(); + return discoverer.GetTestExtensionsInformation(extensionAssemblies, loadOnlyWellKnownExtensions: false); + } + }; + + testableTestPluginCache.GetTestExtensions("foo.dll"); + + testableTestPluginCache.DiscoverAllTestExtensions(); + + CollectionAssert.Contains(testableTestPluginCache.TestExtensions.TestDiscoverers.Keys, "td"); + } + + #endregion + + #region Setup mocks + + public static TestableTestPluginCache SetupMockAdditionalPathExtensions() + { + return SetupMockAdditionalPathExtensions( + new string[] { typeof(TestPluginCacheTests).GetTypeInfo().Assembly.Location }); + } + + public static TestableTestPluginCache SetupMockAdditionalPathExtensions(string[] extensions) + { + var testableTestPluginCache = SetupMockPathUtilities(); + + // Stub the default extensions folder. + testableTestPluginCache.DoesDirectoryExistSetter = false; + + TestPluginCache.Instance.UpdateAdditionalExtensions(extensions, true); + + return testableTestPluginCache; + } + + public static TestableTestPluginCache SetupMockPathUtilities() + { + var mockPathUtilities = new Mock(); + var testableTestPluginCache = new TestableTestPluginCache(mockPathUtilities.Object); + + mockPathUtilities.Setup(pu => pu.GetUniqueValidPaths(It.IsAny>())) + .Returns((IList paths) => { return new HashSet(paths); }); + + TestPluginCache.Instance = testableTestPluginCache; + + return testableTestPluginCache; + } + + public static void SetupMockExtensions() + { + SetupMockExtensions(() => { }); + } + + public static void SetupMockExtensions(Action callback) + { + SetupMockExtensions(new string[] { typeof(TestPluginCacheTests).GetTypeInfo().Assembly.Location }, callback); + } + + public static void SetupMockExtensions(string[] extensions, Action callback) + { + // Setup mocks. + var testableTestPluginCache = new TestableTestPluginCache(new Mock().Object); + testableTestPluginCache.DoesDirectoryExistSetter = true; + + testableTestPluginCache.FilesInDirectory = (path, pattern) => + { + if (pattern.Equals("*.dll")) + { + callback.Invoke(); + return extensions; + } + return new string[] { }; + }; + + // Setup the testable instance. + TestPluginCache.Instance = testableTestPluginCache; + } + + public static void ResetExtensionsCache() + { + TestPluginCache.Instance = null; + SettingsProviderExtensionManager.Destroy(); + } + + #endregion + } + + #region Testable implementation + + public class TestableTestPluginCache : TestPluginCache + { + public TestableTestPluginCache(IPathUtilities pathUtilities) + : base(pathUtilities) + { + } + + internal Func FilesInDirectory + { + get; + set; + } + + public bool DoesDirectoryExistSetter + { + get; + set; + } + + public Func, TestExtensions> TestExtensionsSetter { get; set; } + + internal override bool DoesDirectoryExist(string path) + { + return this.DoesDirectoryExistSetter; + } + + internal override string[] GetFilesInDirectory(string path, string searchPattern) + { + return this.FilesInDirectory.Invoke(path, searchPattern); + } + + internal override TestExtensions GetTestExtensions(IEnumerable extensions) + { + if (this.TestExtensionsSetter == null) + { + return base.GetTestExtensions(extensions); + } + else + { + return this.TestExtensionsSetter.Invoke(extensions); + } + } + } + + #endregion +} diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/TestPluginDiscovererTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/TestPluginDiscovererTests.cs new file mode 100644 index 0000000000..c79ec597eb --- /dev/null +++ b/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/TestPluginDiscovererTests.cs @@ -0,0 +1,279 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.Common.UnitTests.ExtensionFramework +{ + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using System; + using System.Collections.Generic; + using System.Reflection; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.Utilities; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using System.Xml; + using System.Linq; + [TestClass] + public class TestPluginDiscovererTests + { + private TestPluginDiscoverer testPluginDiscoverer; + + public TestPluginDiscovererTests() + { + this.testPluginDiscoverer = new TestPluginDiscoverer(); + } + + [TestMethod] + public void GetTestExtensionsInformationShouldNotThrowOnALoadException() + { + var pathToExtensions = new List { "foo.dll" }; + + // The below should not throw an exception. + Assert.IsNotNull(this.testPluginDiscoverer.GetTestExtensionsInformation(pathToExtensions, loadOnlyWellKnownExtensions: true)); + } + + [TestMethod] + public void GetTestExtensionsInformationShouldNotConsiderAbstractClasses() + { + var pathToExtensions = new List { typeof(TestPluginDiscovererTests).GetTypeInfo().Assembly.Location }; + + // The below should not throw an exception. + var testExtensions = this.testPluginDiscoverer.GetTestExtensionsInformation(pathToExtensions, loadOnlyWellKnownExtensions: true); + var discovererPluginInformation = new TestDiscovererPluginInformation(typeof(AbstractTestDiscoverer)); + Assert.IsFalse(testExtensions.TestDiscoverers.ContainsKey(discovererPluginInformation.IdentifierData)); + } + + [TestMethod] + public void GetTestExtensionsInformationShouldNotListADiscovererExtensionAsAnotherExtensionType() + { + var pathToExtensions = new List { typeof(TestPluginDiscovererTests).GetTypeInfo().Assembly.Location }; + + // The below should not throw an exception. + var testExtensions = this.testPluginDiscoverer.GetTestExtensionsInformation(pathToExtensions, loadOnlyWellKnownExtensions: true); + var discovererPluginInformation = new TestDiscovererPluginInformation(typeof(ValidDiscoverer)); + + Assert.IsFalse(testExtensions.TestExecutors.ContainsKey(discovererPluginInformation.IdentifierData)); + Assert.IsFalse(testExtensions.TestLoggers.ContainsKey(discovererPluginInformation.IdentifierData)); + Assert.IsFalse(testExtensions.TestSettingsProviders.ContainsKey(discovererPluginInformation.IdentifierData)); + } + + [TestMethod] + public void GetTestExtensionsInformationShouldReturnDiscovererExtensions() + { + var pathToExtensions = new List { typeof(TestPluginDiscovererTests).GetTypeInfo().Assembly.Location }; + + // The below should not throw an exception. + var testExtensions = this.testPluginDiscoverer.GetTestExtensionsInformation(pathToExtensions, loadOnlyWellKnownExtensions: true); + + var discovererPluginInformation = new TestDiscovererPluginInformation(typeof(ValidDiscoverer)); + var discovererPluginInformation2 = new TestDiscovererPluginInformation(typeof(ValidDiscoverer2)); + + Assert.IsTrue(testExtensions.TestDiscoverers.ContainsKey(discovererPluginInformation.IdentifierData)); + Assert.IsTrue(testExtensions.TestDiscoverers.ContainsKey(discovererPluginInformation2.IdentifierData)); + } + + [TestMethod] + public void GetTestExtensionsInformationShouldReturnExecutorExtensions() + { + var pathToExtensions = new List { typeof(TestPluginDiscovererTests).GetTypeInfo().Assembly.Location }; + + // The below should not throw an exception. + var testExtensions = this.testPluginDiscoverer.GetTestExtensionsInformation(pathToExtensions, loadOnlyWellKnownExtensions: true); + + var pluginInformation = new TestExecutorPluginInformation(typeof(ValidExecutor)); + var pluginInformation2 = new TestExecutorPluginInformation(typeof(ValidExecutor2)); + + Assert.AreEqual(2, testExtensions.TestExecutors.Keys.Where(k => k.Contains("ValidExecutor")).Count()); + Assert.IsTrue(testExtensions.TestExecutors.ContainsKey(pluginInformation.IdentifierData)); + Assert.IsTrue(testExtensions.TestExecutors.ContainsKey(pluginInformation2.IdentifierData)); + } + + [TestMethod] + public void GetTestExtensionsInformationShouldReturnLoggerExtensions() + { + var pathToExtensions = new List { typeof(TestPluginDiscovererTests).GetTypeInfo().Assembly.Location }; + + // The below should not throw an exception. + var testExtensions = this.testPluginDiscoverer.GetTestExtensionsInformation(pathToExtensions, loadOnlyWellKnownExtensions: true); + + var pluginInformation = new TestLoggerPluginInformation(typeof(ValidLogger)); + var pluginInformation2 = new TestLoggerPluginInformation(typeof(ValidLogger2)); + + Assert.AreEqual(1, testExtensions.TestLoggers.Keys.Where(k => k.Contains("csv")).Count()); + Assert.IsTrue(testExtensions.TestLoggers.ContainsKey(pluginInformation.IdentifierData)); + Assert.IsTrue(testExtensions.TestLoggers.ContainsKey(pluginInformation2.IdentifierData)); + } + + [TestMethod] + public void GetTestExtensionsInformationShouldReturnSettingsProviderExtensions() + { + var pathToExtensions = new List { typeof(TestPluginDiscovererTests).GetTypeInfo().Assembly.Location }; + + // The below should not throw an exception. + var testExtensions = this.testPluginDiscoverer.GetTestExtensionsInformation(pathToExtensions, loadOnlyWellKnownExtensions: true); + + var pluginInformation = new TestSettingsProviderPluginInformation(typeof(ValidSettingsProvider)); + var pluginInformation2 = new TestSettingsProviderPluginInformation(typeof(ValidSettingsProvider2)); + + Assert.IsTrue(testExtensions.TestSettingsProviders.Keys.Select(k => k.Contains("ValidSettingsProvider")).Count() >= 3); + Assert.IsTrue(testExtensions.TestSettingsProviders.ContainsKey(pluginInformation.IdentifierData)); + Assert.IsTrue(testExtensions.TestSettingsProviders.ContainsKey(pluginInformation2.IdentifierData)); + } + + #region implementations + + #region Discoverers + + private abstract class AbstractTestDiscoverer : ITestDiscoverer + { + public void DiscoverTests(IEnumerable sources, IDiscoveryContext discoveryContext, IMessageLogger logger, ITestCaseDiscoverySink discoverySink) + { + throw new NotImplementedException(); + } + } + + private class ValidDiscoverer : ITestDiscoverer + { + public void DiscoverTests(IEnumerable sources, IDiscoveryContext discoveryContext, IMessageLogger logger, ITestCaseDiscoverySink discoverySink) + { + throw new NotImplementedException(); + } + } + + private class ValidDiscoverer2 : ITestDiscoverer + { + public void DiscoverTests(IEnumerable sources, IDiscoveryContext discoveryContext, IMessageLogger logger, ITestCaseDiscoverySink discoverySink) + { + throw new NotImplementedException(); + } + } + + #endregion + + #region Executors + + [ExtensionUri("ValidExecutor")] + private class ValidExecutor : ITestExecutor + { + public void Cancel() + { + throw new NotImplementedException(); + } + + public void RunTests(IEnumerable sources, IRunContext runContext, IFrameworkHandle frameworkHandle) + { + throw new NotImplementedException(); + } + + public void RunTests(IEnumerable tests, IRunContext runContext, IFrameworkHandle frameworkHandle) + { + throw new NotImplementedException(); + } + } + + [ExtensionUri("ValidExecutor2")] + private class ValidExecutor2 : ITestExecutor + { + public void Cancel() + { + throw new NotImplementedException(); + } + + public void RunTests(IEnumerable sources, IRunContext runContext, IFrameworkHandle frameworkHandle) + { + throw new NotImplementedException(); + } + + public void RunTests(IEnumerable tests, IRunContext runContext, IFrameworkHandle frameworkHandle) + { + throw new NotImplementedException(); + } + } + + [ExtensionUri("ValidExecutor")] + private class DuplicateExecutor : ITestExecutor + { + public void Cancel() + { + throw new NotImplementedException(); + } + + public void RunTests(IEnumerable sources, IRunContext runContext, IFrameworkHandle frameworkHandle) + { + throw new NotImplementedException(); + } + + public void RunTests(IEnumerable tests, IRunContext runContext, IFrameworkHandle frameworkHandle) + { + throw new NotImplementedException(); + } + } + + #endregion + + #region Loggers + + [ExtensionUri("csv")] + private class ValidLogger : ITestLogger + { + public void Initialize(TestLoggerEvents events, string testRunDirectory) + { + throw new NotImplementedException(); + } + } + + [ExtensionUri("docx")] + private class ValidLogger2 : ITestLogger + { + public void Initialize(TestLoggerEvents events, string testRunDirectory) + { + throw new NotImplementedException(); + } + } + + [ExtensionUri("csv")] + private class DuplicateLogger : ITestLogger + { + public void Initialize(TestLoggerEvents events, string testRunDirectory) + { + throw new NotImplementedException(); + } + } + + #endregion + + #region Settings Providers + + [SettingsName("ValidSettingsProvider")] + private class ValidSettingsProvider : ISettingsProvider + { + public void Load(XmlReader reader) + { + throw new NotImplementedException(); + } + } + + [SettingsName("ValidSettingsProvider2")] + private class ValidSettingsProvider2 : ISettingsProvider + { + public void Load(XmlReader reader) + { + throw new NotImplementedException(); + } + } + + [SettingsName("ValidSettingsProvider")] + private class DuplicateSettingsProvider : ISettingsProvider + { + public void Load(XmlReader reader) + { + throw new NotImplementedException(); + } + } + + #endregion + + #endregion + } +} diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/TestPluginManagerTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/TestPluginManagerTests.cs new file mode 100644 index 0000000000..ea93eebce9 --- /dev/null +++ b/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/TestPluginManagerTests.cs @@ -0,0 +1,161 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.Common.UnitTests.ExtensionFramework +{ + using System; + + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + using System.Collections.Generic; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.Utilities; + using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; + + using Moq; + using Microsoft.VisualStudio.TestPlatform.Common.Utilities; + using System.Reflection; + using System.Linq; + [TestClass] + public class TestPluginManagerTests + { + [TestMethod] + public void GetTestExtensionTypeShouldReturnExtensionType() + { + var type = TestPluginManager.GetTestExtensionType(typeof(TestPluginManagerTests).AssemblyQualifiedName); + + Assert.AreEqual(typeof(TestPluginManagerTests), type); + } + + [TestMethod] + public void GetTestExtensionTypeShouldThrowIfTypeNotFound() + { + Assert.ThrowsException(() => TestPluginManager.GetTestExtensionType("randomassemblyname.random")); + } + + [TestMethod] + public void CreateTestExtensionShouldCreateExtensionTypeInstance() + { + var instance = TestPluginManager.CreateTestExtension(typeof(DummyTestDiscoverer)); + + Assert.IsNotNull(instance); + Assert.IsTrue(instance is ITestDiscoverer); + } + + [TestMethod] + public void CreateTestExtensionShouldThrowIfInstanceCannotBeCreated() + { + Assert.ThrowsException(() => TestPluginManager.CreateTestExtension(typeof(AbstractDummyLogger))); + } + + [TestMethod] + public void InstanceShouldReturnTestPluginManagerInstance() + { + var instance = TestPluginManager.Instance; + + Assert.IsNotNull(instance); + Assert.IsTrue(instance is TestPluginManager); + } + + [TestMethod] + public void InstanceShouldReturnCachedTestPluginManagerInstance() + { + var instance = TestPluginManager.Instance; + + Assert.AreEqual(instance, TestPluginManager.Instance); + } + + [TestMethod] + public void GetTestExtensionsShouldReturnTestDiscovererExtensions() + { + TestPluginCacheTests.SetupMockExtensions(); + + IEnumerable>> unfilteredTestExtensions; + IEnumerable> testExtensions; + + TestPluginManager.Instance.GetTestExtensions( + out unfilteredTestExtensions, + out testExtensions); + + Assert.IsNotNull(unfilteredTestExtensions); + Assert.IsNotNull(testExtensions); + Assert.IsTrue(testExtensions.Count() > 0); + } + + [TestMethod] + public void GetTestExtensionsShouldDiscoverExtensionsOnlyOnce() + { + var discoveryCount = 0; + TestPluginCacheTests.SetupMockExtensions(() => { discoveryCount++; }); + + IEnumerable>> unfilteredTestExtensions; + IEnumerable> testExtensions; + + TestPluginManager.Instance.GetTestExtensions( + out unfilteredTestExtensions, + out testExtensions); + + // Call this again to verify that discovery is not called again. + TestPluginManager.Instance.GetTestExtensions( + out unfilteredTestExtensions, + out testExtensions); + + Assert.IsNotNull(testExtensions); + Assert.IsTrue(testExtensions.Count() > 0); + Assert.AreEqual(1, discoveryCount); + } + + [TestMethod] + public void GetTestExtensionsForAnExtensionAssemblyShouldReturnExtensionsInThatAssembly() + { + IEnumerable>> unfilteredTestExtensions; + IEnumerable> testExtensions; + + TestPluginManager.Instance + .GetTestExtensions( + typeof(TestPluginManagerTests).GetTypeInfo().Assembly.Location, + out unfilteredTestExtensions, + out testExtensions); + + Assert.IsNotNull(testExtensions); + Assert.IsTrue(testExtensions.Count() > 0); + } + + #region implementations + + private abstract class AbstractDummyLogger : ITestLogger + { + public void Initialize(TestLoggerEvents events, string testRunDirectory) + { + throw new NotImplementedException(); + } + } + + private class DummyTestDiscoverer : ITestDiscoverer, ITestExecutor + { + public void Cancel() + { + throw new NotImplementedException(); + } + + public void DiscoverTests(IEnumerable sources, IDiscoveryContext discoveryContext, IMessageLogger logger, ITestCaseDiscoverySink discoverySink) + { + throw new NotImplementedException(); + } + + public void RunTests(IEnumerable sources, IRunContext runContext, IFrameworkHandle frameworkHandle) + { + throw new NotImplementedException(); + } + + public void RunTests(IEnumerable tests, IRunContext runContext, IFrameworkHandle frameworkHandle) + { + throw new NotImplementedException(); + } + } + + #endregion + } +} diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/Utilities/LazyExtensionTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/Utilities/LazyExtensionTests.cs new file mode 100644 index 0000000000..c7d5b89f95 --- /dev/null +++ b/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/Utilities/LazyExtensionTests.cs @@ -0,0 +1,137 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.Common.UnitTests.ExtensionFramework.Utilities +{ + using System; + using System.Linq; + + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.Utilities; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; + using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; + using Moq; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + using System.Collections.Generic; + + [TestClass] + public class LazyExtensionTests + { + #region Value tests + + [TestMethod] + public void ValueShouldCreateExtensionViaTheCallback() + { + var mockExtension = new Mock(); + LazyExtension extension = + new LazyExtension( + () => { return mockExtension.Object; }, + new Mock().Object); + + Assert.AreEqual(mockExtension.Object, extension.Value); + } + + [TestMethod] + public void ValueShouldCreateExtensionViaTestPluginManager() + { + var testDiscovererPluginInfo = new TestDiscovererPluginInformation(typeof(DummyExtension)); + LazyExtension extension = + new LazyExtension( + testDiscovererPluginInfo, + new Mock().Object); + + Assert.IsNotNull(extension.Value); + Assert.AreEqual(typeof(DummyExtension), extension.Value.GetType()); + } + + [TestMethod] + public void ValueShouldNotCreateExtensionIfAlreadyCreated() + { + var numberOfTimesExtensionCreated = 0; + var mockExtension = new Mock(); + LazyExtension extension = + new LazyExtension( + () => + { + numberOfTimesExtensionCreated++; + return mockExtension.Object; + }, + new Mock().Object); + + var temp = extension.Value; + temp = extension.Value; + + Assert.AreEqual(1, numberOfTimesExtensionCreated); + } + + #endregion + + #region metadata tests + + [TestMethod] + public void MetadataShouldReturnMetadataSpecified() + { + var testDiscovererPluginInfo = new TestDiscovererPluginInformation(typeof(DummyExtension)); + var mockMetadata = new Mock(); + LazyExtension extension = + new LazyExtension( + testDiscovererPluginInfo, + mockMetadata.Object); + + Assert.AreEqual(mockMetadata.Object, extension.Metadata); + } + + [TestMethod] + public void MetadataShouldCreateMetadataFromMetadataType() + { + var testDiscovererPluginInfo = new TestDiscovererPluginInformation(typeof(DummyExtension)); + LazyExtension extension = + new LazyExtension( + testDiscovererPluginInfo, + typeof(DummyDiscovererCapability)); + + var metadata = extension.Metadata; + Assert.IsNotNull(metadata); + Assert.AreEqual(typeof(DummyDiscovererCapability), metadata.GetType()); + CollectionAssert.AreEqual(new List { "csv" }, (metadata as ITestDiscovererCapabilities).FileExtension.ToArray()); + Assert.AreEqual("executor://unittestexecutor/", (metadata as ITestDiscovererCapabilities).DefaultExecutorUri.AbsoluteUri); + } + + #endregion + + #region Implementation + + private class DummyDiscovererCapability : ITestDiscovererCapabilities + { + public IEnumerable FileExtension + { + get; + private set; + } + + public Uri DefaultExecutorUri + { + get; + private set; + } + + public DummyDiscovererCapability(List fileExtensions, string executorURI) + { + this.FileExtension = fileExtensions; + this.DefaultExecutorUri = new Uri(executorURI); + } + } + + + [FileExtension("csv")] + [DefaultExecutorUri("executor://unittestexecutor")] + private class DummyExtension : ITestDiscoverer + { + public void DiscoverTests(IEnumerable sources, IDiscoveryContext discoveryContext, IMessageLogger logger, ITestCaseDiscoverySink discoverySink) + { + } + } + + #endregion + } +} diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/Utilities/TestDiscovererPluginInformationTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/Utilities/TestDiscovererPluginInformationTests.cs new file mode 100644 index 0000000000..9770a20e85 --- /dev/null +++ b/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/Utilities/TestDiscovererPluginInformationTests.cs @@ -0,0 +1,102 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.Common.UnitTests.ExtensionFramework.Utilities +{ + using System; + using System.Linq; + using System.Collections.Generic; + + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.Utilities; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + + [TestClass] + public class TestDiscovererPluginInformationTests + { + private TestDiscovererPluginInformation testPluginInformation; + + [TestMethod] + public void AssemblyQualifiedNameShouldReturnTestExtensionTypesName() + { + this.testPluginInformation = new TestDiscovererPluginInformation(typeof(DummyTestDiscovererWithNoFileExtensions)); + Assert.AreEqual(typeof(DummyTestDiscovererWithNoFileExtensions).AssemblyQualifiedName, this.testPluginInformation.AssemblyQualifiedName); + } + + [TestMethod] + public void IdentifierDataShouldReturnTestExtensionTypesName() + { + this.testPluginInformation = new TestDiscovererPluginInformation(typeof(DummyTestDiscovererWithNoFileExtensions)); + Assert.AreEqual(typeof(DummyTestDiscovererWithNoFileExtensions).AssemblyQualifiedName, this.testPluginInformation.IdentifierData); + } + + [TestMethod] + public void FileExtensionsShouldReturnEmptyListIfADiscovererSupportsNoFileExtensions() + { + this.testPluginInformation = new TestDiscovererPluginInformation(typeof(DummyTestDiscovererWithNoFileExtensions)); + Assert.IsNotNull(this.testPluginInformation.FileExtensions); + Assert.AreEqual(0, this.testPluginInformation.FileExtensions.Count); + } + + [TestMethod] + public void FileExtensionsShouldReturnAFileExtensionForADiscoverer() + { + this.testPluginInformation = new TestDiscovererPluginInformation(typeof(DummyTestDiscovererWithOneFileExtensions)); + CollectionAssert.AreEqual(new List { "csv"}, this.testPluginInformation.FileExtensions); + } + + [TestMethod] + public void FileExtensionsShouldReturnSupportedFileExtensionsForADiscoverer() + { + this.testPluginInformation = new TestDiscovererPluginInformation(typeof(DummyTestDiscovererWithTwoFileExtensions)); + CollectionAssert.AreEqual(new List {"csv", "docx"}, this.testPluginInformation.FileExtensions); + } + + [TestMethod] + public void DefaultExecutorUriShouldReturnEmptyListIfADiscovererDoesNotHaveOne() + { + this.testPluginInformation = new TestDiscovererPluginInformation(typeof(DummyTestDiscovererWithNoFileExtensions)); + Assert.IsNotNull(this.testPluginInformation.DefaultExecutorUri); + Assert.AreEqual(string.Empty, this.testPluginInformation.DefaultExecutorUri); + } + + [TestMethod] + public void DefaultExecutorUriShouldReturnDefaultExecutorUriOfADiscoverer() + { + this.testPluginInformation = new TestDiscovererPluginInformation(typeof(DummyTestDiscovererWithOneFileExtensions)); + Assert.AreEqual("csvexecutor", this.testPluginInformation.DefaultExecutorUri); + } + + [TestMethod] + public void MetadataShouldReturnFileExtensionsAndDefaultExecutorUri() + { + this.testPluginInformation = new TestDiscovererPluginInformation(typeof(DummyTestDiscovererWithTwoFileExtensions)); + + var expectedFileExtensions = new List { "csv", "docx" }; + var testPluginMetada = this.testPluginInformation.Metadata.ToArray(); + + CollectionAssert.AreEqual(expectedFileExtensions, (testPluginMetada[0] as List).ToArray()); + Assert.AreEqual("csvexecutor", testPluginMetada[1] as string); + } + } + + #region Implementation + + public class DummyTestDiscovererWithNoFileExtensions + { + } + + [FileExtension("csv")] + [DefaultExecutorUri("csvexecutor")] + public class DummyTestDiscovererWithOneFileExtensions + { + } + + [FileExtension("csv")] + [FileExtension("docx")] + [DefaultExecutorUri("csvexecutor")] + public class DummyTestDiscovererWithTwoFileExtensions + { + } + + #endregion +} diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/Utilities/TestExtensionPluginInformationTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/Utilities/TestExtensionPluginInformationTests.cs new file mode 100644 index 0000000000..04c3560e8d --- /dev/null +++ b/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/Utilities/TestExtensionPluginInformationTests.cs @@ -0,0 +1,76 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.Common.UnitTests.ExtensionFramework.Utilities +{ + using System; + using System.Linq; + + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.Utilities; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class TestExtensionPluginInformationTests + { + private TestableTestExtensionPluginInformation testPluginInformation; + + internal const string DefaultExtensionURI = "executor://unittest"; + + [TestMethod] + public void AssemblyQualifiedNameShouldReturnTestExtensionTypesName() + { + this.testPluginInformation = new TestableTestExtensionPluginInformation(typeof(DummyTestExtensionWithNoExtensionUri)); + Assert.AreEqual(typeof(DummyTestExtensionWithNoExtensionUri).AssemblyQualifiedName, this.testPluginInformation.AssemblyQualifiedName); + } + + [TestMethod] + public void IdentifierDataShouldReturnExtensionUri() + { + this.testPluginInformation = new TestableTestExtensionPluginInformation(typeof(DummyTestExtensionWithExtensionUri)); + Assert.AreEqual(DefaultExtensionURI, this.testPluginInformation.IdentifierData); + } + + [TestMethod] + public void ExtensionUriShouldReturnEmptyIfAnExtensionDoesNotHaveOne() + { + this.testPluginInformation = new TestableTestExtensionPluginInformation(typeof(DummyTestExtensionWithNoExtensionUri)); + Assert.IsNotNull(this.testPluginInformation.ExtensionUri); + Assert.AreEqual(string.Empty, this.testPluginInformation.ExtensionUri); + } + + [TestMethod] + public void ExtensionUriShouldReturnExtensionUriOfAnExtension() + { + this.testPluginInformation = new TestableTestExtensionPluginInformation(typeof(DummyTestExtensionWithExtensionUri)); + Assert.AreEqual(DefaultExtensionURI, this.testPluginInformation.ExtensionUri); + } + + [TestMethod] + public void MetadataShouldReturnExtensionUri() + { + this.testPluginInformation = new TestableTestExtensionPluginInformation(typeof(DummyTestExtensionWithExtensionUri)); + + CollectionAssert.AreEqual(new object[] { DefaultExtensionURI }, this.testPluginInformation.Metadata.ToArray()); + } + + #region Implementation + + private class TestableTestExtensionPluginInformation : TestExtensionPluginInformation + { + public TestableTestExtensionPluginInformation(Type testExtensionType) : base(testExtensionType) + { + } + } + + private class DummyTestExtensionWithNoExtensionUri + { + } + + [ExtensionUri(TestExtensionPluginInformationTests.DefaultExtensionURI)] + private class DummyTestExtensionWithExtensionUri + { + } + + #endregion + } +} diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/Utilities/TestExtensionsTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/Utilities/TestExtensionsTests.cs new file mode 100644 index 0000000000..065a1ed7b9 --- /dev/null +++ b/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/Utilities/TestExtensionsTests.cs @@ -0,0 +1,249 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.Common.UnitTests.ExtensionFramework.Utilities +{ + using System.Collections.Generic; + + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.Utilities; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using System.Reflection; + + [TestClass] + public class TestExtensionsTests + { + private TestExtensions testExtensions; + + [TestInitialize] + public void TestInit() + { + this.testExtensions = new TestExtensions(); + } + + [TestMethod] + public void AddExtensionsShouldNotThrowIfExtensionsIsNull() + { + this.testExtensions.AddExtensions(null); + + // Validate that the default state does not change. + Assert.IsNull(this.testExtensions.TestDiscoverers); + } + + [TestMethod] + public void AddExtensionsShouldNotThrowIfExistingExtensionCollectionIsNull() + { + var newTestExtensions = new TestExtensions(); + newTestExtensions.TestDiscoverers = + new System.Collections.Generic.Dictionary(); + + newTestExtensions.TestDiscoverers.Add( + "td", + new TestDiscovererPluginInformation(typeof(TestExtensionsTests))); + + this.testExtensions.AddExtensions(newTestExtensions); + + Assert.IsNotNull(this.testExtensions.TestDiscoverers); + CollectionAssert.AreEqual(this.testExtensions.TestDiscoverers, newTestExtensions.TestDiscoverers); + + // Validate that the others remain same. + Assert.IsNull(this.testExtensions.TestExecutors); + Assert.IsNull(this.testExtensions.TestSettingsProviders); + Assert.IsNull(this.testExtensions.TestLoggers); + } + + [TestMethod] + public void AddExtensionsShouldAddToExistingExtensionCollection() + { + var newTestExtensions = new TestExtensions(); + newTestExtensions.TestDiscoverers = + new System.Collections.Generic.Dictionary(); + + newTestExtensions.TestDiscoverers.Add( + "td1", + new TestDiscovererPluginInformation(typeof(TestExtensionsTests))); + newTestExtensions.TestDiscoverers.Add( + "td2", + new TestDiscovererPluginInformation(typeof(TestExtensionsTests))); + + this.testExtensions.TestDiscoverers = + new Dictionary(); + + this.testExtensions.TestDiscoverers.Add( + "td", + new TestDiscovererPluginInformation(typeof(TestExtensionsTests))); + + // Act. + this.testExtensions.AddExtensions(newTestExtensions); + + // Validate. + var expectedTestExtensions = new Dictionary(); + expectedTestExtensions.Add("td", new TestDiscovererPluginInformation(typeof(TestExtensionsTests))); + expectedTestExtensions.Add("td1", new TestDiscovererPluginInformation(typeof(TestExtensionsTests))); + expectedTestExtensions.Add("td2", new TestDiscovererPluginInformation(typeof(TestExtensionsTests))); + + CollectionAssert.AreEqual(this.testExtensions.TestDiscoverers.Keys, expectedTestExtensions.Keys); + + // Validate that the others remain same. + Assert.IsNull(this.testExtensions.TestExecutors); + Assert.IsNull(this.testExtensions.TestSettingsProviders); + Assert.IsNull(this.testExtensions.TestLoggers); + } + + [TestMethod] + public void AddExtensionsShouldNotAddAnAlreadyExistingExtensionToTheCollection() + { + var newTestExtensions = new TestExtensions(); + newTestExtensions.TestDiscoverers = + new System.Collections.Generic.Dictionary(); + + newTestExtensions.TestDiscoverers.Add( + "td", + new TestDiscovererPluginInformation(typeof(TestExtensionsTests))); + + this.testExtensions.TestDiscoverers = + new System.Collections.Generic.Dictionary(); + + this.testExtensions.TestDiscoverers.Add( + "td", + new TestDiscovererPluginInformation(typeof(TestExtensionsTests))); + + // Act. + this.testExtensions.AddExtensions(newTestExtensions); + + // Validate. + CollectionAssert.AreEqual(this.testExtensions.TestDiscoverers.Keys, newTestExtensions.TestDiscoverers.Keys); + + // Validate that the others remain same. + Assert.IsNull(this.testExtensions.TestExecutors); + Assert.IsNull(this.testExtensions.TestSettingsProviders); + Assert.IsNull(this.testExtensions.TestLoggers); + } + + [TestMethod] + public void AddExtensionsShouldAddAllExtensions() + { + var newTestExtensions = new TestExtensions(); + newTestExtensions.TestDiscoverers = new Dictionary(); + newTestExtensions.TestExecutors = new Dictionary(); + newTestExtensions.TestSettingsProviders = new Dictionary(); + newTestExtensions.TestLoggers = new Dictionary(); + + newTestExtensions.TestDiscoverers.Add("td", new TestDiscovererPluginInformation(typeof(TestExtensionsTests))); + newTestExtensions.TestExecutors.Add("te", new TestExecutorPluginInformation(typeof(TestExtensionsTests))); + newTestExtensions.TestSettingsProviders.Add("tsp", new TestSettingsProviderPluginInformation(typeof(TestExtensionsTests))); + newTestExtensions.TestLoggers.Add("tl", new TestLoggerPluginInformation(typeof(TestExtensionsTests))); + + // Act. + this.testExtensions.AddExtensions(newTestExtensions); + + // Validate. + CollectionAssert.AreEqual(this.testExtensions.TestDiscoverers.Keys, newTestExtensions.TestDiscoverers.Keys); + CollectionAssert.AreEqual(this.testExtensions.TestExecutors.Keys, newTestExtensions.TestExecutors.Keys); + CollectionAssert.AreEqual(this.testExtensions.TestSettingsProviders.Keys, newTestExtensions.TestSettingsProviders.Keys); + CollectionAssert.AreEqual(this.testExtensions.TestLoggers.Keys, newTestExtensions.TestLoggers.Keys); + } + + [TestMethod] + public void GetExtensionsDiscoveredFromAssemblyShouldReturnNullIfNoExtensionsPresent() + { + var assemblyLocation = typeof(TestExtensionsTests).GetTypeInfo().Assembly.Location; + + Assert.IsNull(this.testExtensions.GetExtensionsDiscoveredFromAssembly(assemblyLocation)); + } + + [TestMethod] + public void GetExtensionsDiscoveredFromAssemblyShouldNotThrowIfExtensionAssemblyIsNull() + { + this.testExtensions.TestDiscoverers = new Dictionary(); + + this.testExtensions.TestDiscoverers.Add("td", new TestDiscovererPluginInformation(typeof(TestExtensionsTests))); + + Assert.IsNull(this.testExtensions.GetExtensionsDiscoveredFromAssembly(null)); + } + + [TestMethod] + public void GetExtensionsDiscoveredFromAssemblyShouldReturnTestDiscoverers() + { + var assemblyLocation = typeof(TestExtensionsTests).GetTypeInfo().Assembly.Location; + + this.testExtensions.TestDiscoverers = new Dictionary(); + this.testExtensions.TestDiscoverers.Add("td", new TestDiscovererPluginInformation(typeof(TestExtensionsTests))); + this.testExtensions.TestDiscoverers.Add("td1", new TestDiscovererPluginInformation(typeof(TestExtensions))); + + var extensions = this.testExtensions.GetExtensionsDiscoveredFromAssembly(assemblyLocation); + + var expectedExtensions = new Dictionary(); + expectedExtensions.Add("td", new TestDiscovererPluginInformation(typeof(TestExtensionsTests))); + CollectionAssert.AreEqual(expectedExtensions.Keys, extensions.TestDiscoverers.Keys); + } + + [TestMethod] + public void GetExtensionsDiscoveredFromAssemblyShouldReturnTestExecutors() + { + var assemblyLocation = typeof(TestExtensionsTests).GetTypeInfo().Assembly.Location; + + this.testExtensions.TestExecutors = new Dictionary(); + this.testExtensions.TestExecutors.Add("te", new TestExecutorPluginInformation(typeof(TestExtensionsTests))); + this.testExtensions.TestExecutors.Add("te1", new TestExecutorPluginInformation(typeof(TestExtensions))); + + var extensions = this.testExtensions.GetExtensionsDiscoveredFromAssembly(assemblyLocation); + + var expectedExtensions = new Dictionary(); + expectedExtensions.Add("te", new TestExecutorPluginInformation(typeof(TestExtensionsTests))); + CollectionAssert.AreEqual(expectedExtensions.Keys, extensions.TestExecutors.Keys); + } + + [TestMethod] + public void GetExtensionsDiscoveredFromAssemblyShouldReturnTestSettingsProviders() + { + var assemblyLocation = typeof(TestExtensionsTests).GetTypeInfo().Assembly.Location; + + this.testExtensions.TestSettingsProviders = new Dictionary(); + this.testExtensions.TestSettingsProviders.Add("tsp", new TestSettingsProviderPluginInformation(typeof(TestExtensionsTests))); + this.testExtensions.TestSettingsProviders.Add("tsp1", new TestSettingsProviderPluginInformation(typeof(TestExtensions))); + + var extensions = this.testExtensions.GetExtensionsDiscoveredFromAssembly(assemblyLocation); + + var expectedExtensions = new Dictionary(); + expectedExtensions.Add("tsp", new TestSettingsProviderPluginInformation(typeof(TestExtensionsTests))); + CollectionAssert.AreEqual(expectedExtensions.Keys, extensions.TestSettingsProviders.Keys); + } + + [TestMethod] + public void GetExtensionsDiscoveredFromAssemblyShouldReturnTestLoggers() + { + var assemblyLocation = typeof(TestExtensionsTests).GetTypeInfo().Assembly.Location; + + this.testExtensions.TestLoggers = new Dictionary(); + this.testExtensions.TestLoggers.Add("tl", new TestLoggerPluginInformation(typeof(TestExtensionsTests))); + this.testExtensions.TestLoggers.Add("tl1", new TestLoggerPluginInformation(typeof(TestExtensions))); + + var extensions = this.testExtensions.GetExtensionsDiscoveredFromAssembly(assemblyLocation); + + var expectedExtensions = new Dictionary(); + expectedExtensions.Add("tl", new TestLoggerPluginInformation(typeof(TestExtensionsTests))); + CollectionAssert.AreEqual(expectedExtensions.Keys, extensions.TestLoggers.Keys); + } + + [TestMethod] + public void GetExtensionsDiscoveredFromAssemblyShouldReturnTestDiscoveresAndLoggers() + { + var assemblyLocation = typeof(TestExtensionsTests).GetTypeInfo().Assembly.Location; + + this.testExtensions.TestDiscoverers = new Dictionary(); + this.testExtensions.TestDiscoverers.Add("td", new TestDiscovererPluginInformation(typeof(TestExtensionsTests))); + + this.testExtensions.TestLoggers = new Dictionary(); + this.testExtensions.TestLoggers.Add("tl", new TestLoggerPluginInformation(typeof(TestExtensionsTests))); + + var extensions = this.testExtensions.GetExtensionsDiscoveredFromAssembly(assemblyLocation); + + var expectedDiscoverers = new Dictionary(); + expectedDiscoverers.Add("td", new TestDiscovererPluginInformation(typeof(TestExtensionsTests))); + CollectionAssert.AreEqual(expectedDiscoverers.Keys, extensions.TestDiscoverers.Keys); + + var expectedLoggers = new Dictionary(); + expectedLoggers.Add("tl", new TestLoggerPluginInformation(typeof(TestExtensionsTests))); + CollectionAssert.AreEqual(expectedLoggers.Keys, extensions.TestLoggers.Keys); + } + } +} diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/Utilities/TestLoggerPluginInformationTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/Utilities/TestLoggerPluginInformationTests.cs new file mode 100644 index 0000000000..2e6ba10830 --- /dev/null +++ b/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/Utilities/TestLoggerPluginInformationTests.cs @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.Common.UnitTests.ExtensionFramework.Utilities +{ + using System; + using System.Linq; + + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.Utilities; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class TestLoggerPluginInformationTests + { + private TestLoggerPluginInformation testPluginInformation; + + internal const string DefaultExtensionURI = "executor://unittest"; + + internal const string DefaultFriendlyName = "excel"; + + [TestMethod] + public void AssemblyQualifiedNameShouldReturnTestExtensionTypesName() + { + this.testPluginInformation = new TestLoggerPluginInformation(typeof(DummyTestExtensionWithNoFriendlyName)); + Assert.AreEqual(typeof(DummyTestExtensionWithNoFriendlyName).AssemblyQualifiedName, this.testPluginInformation.AssemblyQualifiedName); + } + + [TestMethod] + public void IdentifierDataShouldReturnExtensionUri() + { + this.testPluginInformation = new TestLoggerPluginInformation(typeof(DummyTestExtensionWithFriendlyName)); + Assert.AreEqual(DefaultExtensionURI, this.testPluginInformation.IdentifierData); + } + + [TestMethod] + public void FriendlyNameShouldReturnEmptyIfALoggerDoesNotHaveOne() + { + this.testPluginInformation = new TestLoggerPluginInformation(typeof(DummyTestExtensionWithNoFriendlyName)); + Assert.IsNotNull(this.testPluginInformation.FriendlyName); + Assert.AreEqual(string.Empty, this.testPluginInformation.FriendlyName); + } + + [TestMethod] + public void FriendlyNameShouldReturnFriendlyNameOfALogger() + { + this.testPluginInformation = new TestLoggerPluginInformation(typeof(DummyTestExtensionWithFriendlyName)); + Assert.AreEqual(DefaultFriendlyName, this.testPluginInformation.FriendlyName); + } + + [TestMethod] + public void MetadataShouldReturnExtensionUriAndFriendlyName() + { + this.testPluginInformation = new TestLoggerPluginInformation(typeof(DummyTestExtensionWithFriendlyName)); + + CollectionAssert.AreEqual(new object[] { DefaultExtensionURI, DefaultFriendlyName }, this.testPluginInformation.Metadata.ToArray()); + } + + #region Implementation + + private class DummyTestExtensionWithNoFriendlyName + { + } + + [FriendlyName(TestLoggerPluginInformationTests.DefaultFriendlyName)] + [ExtensionUri(TestLoggerPluginInformationTests.DefaultExtensionURI)] + private class DummyTestExtensionWithFriendlyName + { + } + + #endregion + } +} diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/Utilities/TestPluginInformationTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/Utilities/TestPluginInformationTests.cs new file mode 100644 index 0000000000..f1d0fd8452 --- /dev/null +++ b/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/Utilities/TestPluginInformationTests.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.Common.UnitTests.ExtensionFramework.Utilities +{ + using System; + using System.Linq; + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.Utilities; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + + [TestClass] + public class TestPluginInformationTests + { + private TestableTestPluginInformation testPluginInformation; + + public TestPluginInformationTests() + { + this.testPluginInformation = new TestableTestPluginInformation(typeof(TestPluginInformationTests)); + } + + [TestMethod] + public void AssemblyQualifiedNameShouldReturnTestExtensionTypesName() + { + Assert.AreEqual(typeof(TestPluginInformationTests).AssemblyQualifiedName, this.testPluginInformation.AssemblyQualifiedName); + } + + [TestMethod] + public void IdentifierDataShouldReturnTestExtensionTypesName() + { + Assert.AreEqual(typeof(TestPluginInformationTests).AssemblyQualifiedName, this.testPluginInformation.IdentifierData); + } + + [TestMethod] + public void MetadataShouldReturnTestExtensionTypesAssemblyQualifiedName() + { + CollectionAssert.AreEqual(new object[] { typeof(TestPluginInformationTests).AssemblyQualifiedName }, this.testPluginInformation.Metadata.ToArray()); + } + } + + #region Implementation + + public class TestableTestPluginInformation : TestPluginInformation + { + public TestableTestPluginInformation(Type testExtensionType) : base(testExtensionType) + { + } + } + + #endregion +} diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/Utilities/TestSettingsProviderPluginInformationTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/Utilities/TestSettingsProviderPluginInformationTests.cs new file mode 100644 index 0000000000..eb9839837e --- /dev/null +++ b/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/Utilities/TestSettingsProviderPluginInformationTests.cs @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.Common.UnitTests.ExtensionFramework.Utilities +{ + using System; + using System.Linq; + + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.Utilities; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class TestSettingsProviderPluginInformationTests + { + private TestSettingsProviderPluginInformation testPluginInformation; + + private const string DefaultSettingsName = "mstestsettings"; + + [TestMethod] + public void AssemblyQualifiedNameShouldReturnTestExtensionTypesName() + { + this.testPluginInformation = new TestSettingsProviderPluginInformation(typeof(TestPluginInformationTests)); + Assert.AreEqual(typeof(TestPluginInformationTests).AssemblyQualifiedName, this.testPluginInformation.AssemblyQualifiedName); + } + + [TestMethod] + public void IdentifierDataShouldReturnSettingsName() + { + this.testPluginInformation = new TestSettingsProviderPluginInformation(typeof(DummySettingProviderWithSettingsName)); + Assert.AreEqual(DefaultSettingsName, this.testPluginInformation.IdentifierData); + } + + [TestMethod] + public void MetadataShouldReturnSettingsProviderName() + { + this.testPluginInformation = new TestSettingsProviderPluginInformation(typeof(DummySettingProviderWithSettingsName)); + CollectionAssert.AreEqual(new object[] { DefaultSettingsName }, this.testPluginInformation.Metadata.ToArray()); + } + + [TestMethod] + public void SettingsNameShouldReturnEmptyIfASettingsProviderDoesNotHaveOne() + { + this.testPluginInformation = new TestSettingsProviderPluginInformation(typeof(DummySettingProviderWithoutSettingsName)); + Assert.IsNotNull(this.testPluginInformation.SettingsName); + Assert.AreEqual(string.Empty, this.testPluginInformation.SettingsName); + } + + [TestMethod] + public void SettingsNameShouldReturnExtensionUriOfAnExtension() + { + this.testPluginInformation = new TestSettingsProviderPluginInformation(typeof(DummySettingProviderWithSettingsName)); + Assert.AreEqual(DefaultSettingsName, this.testPluginInformation.SettingsName); + } + + #region implementation + + private class DummySettingProviderWithoutSettingsName + { + } + + [SettingsName(DefaultSettingsName)] + private class DummySettingProviderWithSettingsName + { + } + + #endregion + } +} diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/Logging/InternalTestLoggerEventsTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/Logging/InternalTestLoggerEventsTests.cs new file mode 100644 index 0000000000..f44fd3d5e4 --- /dev/null +++ b/test/Microsoft.TestPlatform.Common.UnitTests/Logging/InternalTestLoggerEventsTests.cs @@ -0,0 +1,266 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.Common.UnitTests.Logging +{ + using Microsoft.VisualStudio.TestPlatform.Common.Logging; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using System; + using System.Threading; + using TestPlatform.Common.UnitTests.Utilities; + using TestResult = Microsoft.VisualStudio.TestPlatform.ObjectModel.TestResult; + + [TestClass] + public class InternalTestLoggerEventsBehaviors + { + private TestSessionMessageLogger testSessionMessageLogger; + private InternalTestLoggerEvents loggerEvents; + [TestInitialize] + public void Initialize() + { + testSessionMessageLogger = TestSessionMessageLogger.Instance; + loggerEvents = new InternalTestLoggerEvents(testSessionMessageLogger); + } + + [TestCleanup] + public void Dispose() + { + loggerEvents.Dispose(); + TestSessionMessageLogger.Instance = null; + } + + [TestMethod] + public void RaiseMessageShouldNotThrowExceptionIfNoEventHandlersAreRegistered() + { + // Send the test mesage event. + loggerEvents.RaiseMessage(new TestRunMessageEventArgs(TestMessageLevel.Informational,"This is a string.")); + } + + [TestMethod] + [Timeout(300)] + public void RaiseMessageShouldInvokeRegisteredEventHandlerIfTestRunMessageEventArgsIsPassed() + { + EventWaitHandle waitHandle = new AutoResetEvent(false); + bool testMessageReceived = false; + TestRunMessageEventArgs eventArgs = null; + var message = "This is the test message"; + + // Register for the test message event. + loggerEvents.TestRunMessage += (sender, e) => + { + testMessageReceived = true; + eventArgs = e; + waitHandle.Set(); + }; + + loggerEvents.EnableEvents(); + // Send the test mesage event. + loggerEvents.RaiseMessage(new TestRunMessageEventArgs(TestMessageLevel.Informational, message)); + + waitHandle.WaitOne(); + Assert.IsTrue(testMessageReceived); + Assert.IsNotNull(eventArgs); + Assert.AreEqual(message, eventArgs.Message); + Assert.AreEqual(TestMessageLevel.Informational, eventArgs.Level); + } + + [TestMethod] + [Timeout(300)] + public void RaiseMessageShouldInvokeRegisteredEventHandlerIfTestTestResultEventArgsIsPassed() + { + EventWaitHandle waitHandle = new AutoResetEvent(false); + bool testResultReceived = false; + TestResultEventArgs eventArgs = null; + var result =new TestResult(new TestCase("This is a string.", new Uri("some://uri"), "DummySourceFileName")); + + // Register for the test result event. + loggerEvents.TestResult += (sender, e) => + { + testResultReceived = true; + eventArgs = e; + waitHandle.Set(); + }; + + loggerEvents.EnableEvents(); + // Send the test result event. + loggerEvents.RaiseTestResult(new TestResultEventArgs(result)); + + waitHandle.WaitOne(); + Assert.IsTrue(testResultReceived); + Assert.IsNotNull(eventArgs); + Assert.AreEqual(result, eventArgs.Result); + } + + [TestMethod] + public void RaiseTestResultShouldThrowExceptionIfNullTestResultEventArgsIsPassed() + { + Assert.ThrowsException(() => + { + loggerEvents.RaiseTestResult(null); + }); + } + + [TestMethod] + public void RaiseMessageShouldThrowExceptioIfNullTestRunMessageEventArgsIsPassed() + { + Assert.ThrowsException(() => + { + loggerEvents.RaiseMessage(null); + }); + } + + [TestMethod] + [Timeout(300)] + public void CompleteTestRunShouldInvokeRegisteredEventHandler() + { + bool testRunCompleteReceived = false; + TestRunCompleteEventArgs eventArgs = null; + + EventWaitHandle waitHandle = new AutoResetEvent(false); + + // Register for the test run complete event. + loggerEvents.TestRunComplete += (sender, e) => + { + testRunCompleteReceived = true; + eventArgs = e; + waitHandle.Set(); + }; + + // Send the test run complete event. + loggerEvents.EnableEvents(); + loggerEvents.CompleteTestRun(null, false, false, null, null, new TimeSpan()); + + waitHandle.WaitOne(); + Assert.IsTrue(testRunCompleteReceived); + Assert.IsNotNull(eventArgs); + } + + [TestMethod] + public void EnableEventsShouldSendEventsAlreadyPresentInQueueToRegisteredEventHandlers() + { + bool testResultReceived = false; + bool testMessageReceived = false; + + // Send the events. + loggerEvents.RaiseMessage(new TestRunMessageEventArgs(TestMessageLevel.Error,"This is a string.")); + loggerEvents.RaiseTestResult(new TestResultEventArgs(new TestResult(new TestCase("This is a string.", new Uri("some://uri"), "DummySourceFileName")))); + + // Register for the events. + loggerEvents.TestResult += (sender, e) => + { + testResultReceived = true; + }; + + loggerEvents.TestRunMessage += (sender, e) => + { + testMessageReceived = true; + }; + + // Enable events and verify that the events are received. + loggerEvents.EnableEvents(); + + Assert.IsTrue(testResultReceived); + Assert.IsTrue(testMessageReceived); + } + + [TestMethod] + public void DisposeShouldNotThrowExceptionIfCalledMultipleTimes() + { + var loggerEvents = GetDisposedLoggerEvents(); + loggerEvents.Dispose(); + } + + [TestMethod] + public void RaiseTestResultShouldThrowExceptionIfDisposedIsAlreadyCalled() + { + var loggerEvents = GetDisposedLoggerEvents(); + + Assert.ThrowsException(() => + { + loggerEvents.RaiseTestResult(new TestResultEventArgs(new TestResult(new TestCase("This is a string.", new Uri("some://uri"), "DummySourceFileName")))); + }); + } + + [TestMethod] + public void RaiseMessageShouldThrowExceptionIfDisposeIsAlreadyCalled() + { + var loggerEvents = GetDisposedLoggerEvents(); + + Assert.ThrowsException(() => + { + loggerEvents.RaiseMessage(new TestRunMessageEventArgs(TestMessageLevel.Error,"This is a string.")); + }); + } + + [TestMethod] + public void CompleteTestRunShouldThrowExceptionIfAlreadyDisposed() + { + var loggerEvents = GetDisposedLoggerEvents(); + + Assert.ThrowsException(() => + { + loggerEvents.CompleteTestRun(null, true, false, null, null, new TimeSpan()); + }); + } + + [TestMethod] + public void EnableEventsShouldThrowExceptionIfAlreadyDisposed() + { + var loggerEvents = GetDisposedLoggerEvents(); + + Assert.ThrowsException(() => + { + loggerEvents.EnableEvents(); + }); + } + + [TestMethod] + public void TestRunMessageLoggerProxySendMessageShouldInvokeRegisteredEventHandler() + { + var receivedRunMessage = false; + using (loggerEvents) + { + loggerEvents.TestRunMessage += (sender, e) => + { + receivedRunMessage = true; + }; + + testSessionMessageLogger.SendMessage(TestMessageLevel.Error,"This is a string."); + } + + Assert.IsTrue(receivedRunMessage); + } + + [TestMethod] + public void TestLoggerProxySendMessageShouldNotInvokeRegisterdEventHandlerIfAlreadyDisposed() + { + var receivedRunMessage = false; + loggerEvents.TestRunMessage += (sender, e) => + { + receivedRunMessage = true; + }; + + // Dispose the logger events, send a message, and verify it is not received. + loggerEvents.Dispose(); + testSessionMessageLogger.SendMessage(TestMessageLevel.Error,"This is a string."); + + Assert.IsFalse(receivedRunMessage); + } + /// + /// Gets a disposed instance of the logger events. + /// + /// Disposed instance. + private InternalTestLoggerEvents GetDisposedLoggerEvents() + { + var loggerEvents = new InternalTestLoggerEvents(testSessionMessageLogger); + loggerEvents.Dispose(); + + return loggerEvents; + } + } + + + +} \ No newline at end of file diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/Logging/TestLoggerExtensionManagerTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/Logging/TestLoggerExtensionManagerTests.cs new file mode 100644 index 0000000000..77bb42e181 --- /dev/null +++ b/test/Microsoft.TestPlatform.Common.UnitTests/Logging/TestLoggerExtensionManagerTests.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.Common.UnitTests.Logging +{ + using Microsoft.VisualStudio.TestPlatform.Common.Logging; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using System; + using TestPlatform.Common.UnitTests.ExtensionFramework; + + [TestClass] + public class TestLoggerExtensionManagerTests + { + [TestInitialize] + public void Initialize() + { + TestPluginCacheTests.SetupMockExtensions(); + } + [TestMethod] + public void CreateShouldThrowExceptionIfMessageLoggerIsNull() + { + Assert.ThrowsException(() => + { + var testLoggerExtensionManager = TestLoggerExtensionManager.Create(null); + }); + + } + + [TestMethod] + public void CreateShouldReturnInstanceOfTestLoggerExtensionManager() + { + try + { + var testLoggerExtensionManager = TestLoggerExtensionManager.Create(TestSessionMessageLogger.Instance); + Assert.IsNotNull(testLoggerExtensionManager); + Assert.IsInstanceOfType(testLoggerExtensionManager, typeof(TestLoggerExtensionManager)); + } + finally + { + TestSessionMessageLogger.Instance = null; + } + } + } +} diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/Logging/TestLoggerManagerTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/Logging/TestLoggerManagerTests.cs new file mode 100644 index 0000000000..5e1b8bb8d0 --- /dev/null +++ b/test/Microsoft.TestPlatform.Common.UnitTests/Logging/TestLoggerManagerTests.cs @@ -0,0 +1,361 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.Common.UnitTests.Logging +{ + using Microsoft.VisualStudio.TestPlatform.Common.Logging; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Moq; + using System; + using System.Collections.Generic; + using System.Threading; + using TestPlatform.Common.UnitTests.ExtensionFramework; + using TestPlatform.Common.UnitTests.Utilities; + using ObjectModel = Microsoft.VisualStudio.TestPlatform.ObjectModel; + + /// + /// Tests the behaviors of the TestLoggerManager class. + /// + [TestClass] + public class TestLoggerManagerTests + { + private static int counter = 0; + private static EventWaitHandle waitHandle = new AutoResetEvent(false); + private string loggerUri = "testlogger://logger"; + + [TestInitialize] + public void Initialize() + { + TestPluginCacheTests.SetupMockExtensions(); + } + + [TestCleanup] + public void Cleanup() + { + new DummyTestLoggerManager().Cleanup(); + } + + [TestMethod] + public void TryGetUriFromFriendlyNameShouldReturnUriIfLoggerIsAdded() + { + TestLoggerManager.Instance.AddLogger(new Uri(loggerUri), new Dictionary()); + string uri; + TestLoggerManager.Instance.TryGetUriFromFriendlyName("TestLoggerExtension", out uri); + Assert.AreEqual(uri, loggerUri); + } + + [TestMethod] + public void TryGetUriFromFriendlyNameShouldNotReturnUriIfLoggerIsNotAdded() + { + string uri; + TestLoggerManager.Instance.TryGetUriFromFriendlyName("TestLoggerExtension1", out uri); + Assert.IsNull(uri); + } + + [TestMethod] + public void TestRunRequestRaiseShouldInvokeTestRunMessageHandlerOfLoggersIfRegistered() + { + counter = 0; + waitHandle.Reset(); + TestLoggerManager.Instance.AddLogger(new Uri(loggerUri), new Dictionary()); + TestLoggerManager.Instance.EnableLogging(); + + // mock for ITestRunRequest + var testRunRequest = new Mock(); + + // Register TestRunRequest object + TestLoggerManager.Instance.RegisterTestRunEvents(testRunRequest.Object); + + //Raise an event on mock object + testRunRequest.Raise( + m => m.TestRunMessage += null, + new TestRunMessageEventArgs(TestMessageLevel.Informational, "TestRunMessage")); + waitHandle.WaitOne(); + Assert.AreEqual(counter, 1); + } + + [TestMethod] + public void TestRunRequestRaiseShouldNotInvokeTestRunMessageHandlerOfLoggersIfUnRegistered() + { + counter = 0; + waitHandle.Reset(); + // setup TestLogger + TestLoggerManager.Instance.AddLogger(new Uri(loggerUri), new Dictionary()); + TestLoggerManager.Instance.EnableLogging(); + + // mock for ITestRunRequest + var testRunRequest = new Mock(); + + // Register TestRunRequest object + TestLoggerManager.Instance.RegisterTestRunEvents(testRunRequest.Object); + + //Raise an event on mock object + testRunRequest.Raise( + m => m.TestRunMessage += null, + new TestRunMessageEventArgs(TestMessageLevel.Informational, "TestRunMessage")); + waitHandle.WaitOne(); + Assert.AreEqual(counter, 1); + + TestLoggerManager.Instance.UnregisterTestRunEvents(testRunRequest.Object); + + //Raise an event on mock object + testRunRequest.Raise( + m => m.TestRunMessage += null, + new TestRunMessageEventArgs(TestMessageLevel.Informational, "TestRunMessage")); + Assert.AreEqual(counter, 1); + } + + [TestMethod] + public void TestRunRequestRaiseShouldInvokeTestRunCompleteHandlerOfLoggersIfRegistered() + { + counter = 0; + waitHandle.Reset(); + // setup TestLogger + TestLoggerManager.Instance.AddLogger(new Uri(loggerUri), new Dictionary()); + TestLoggerManager.Instance.EnableLogging(); + + // mock for ITestRunRequest + var testRunRequest = new Mock(); + + // Register TestRunRequest object + TestLoggerManager.Instance.RegisterTestRunEvents(testRunRequest.Object); + + //Raise an event on mock object + testRunRequest.Raise( + m => m.OnRunCompletion += null, + new TestRunCompleteEventArgs(null, false, false, null, null, new TimeSpan())); + waitHandle.WaitOne(); + Assert.AreEqual(counter, 1); + } + + [TestMethod] + public void TestRunRequestRaiseShouldNotInvokeTestRunCompleteHandlerOfLoggersIfUnRegistered() + { + counter = 0; + waitHandle.Reset(); + // setup TestLogger + TestLoggerManager.Instance.AddLogger(new Uri(loggerUri), new Dictionary()); + TestLoggerManager.Instance.EnableLogging(); + + // mock for ITestRunRequest + var testRunRequest = new Mock(); + + // Register TestRunRequest object + TestLoggerManager.Instance.RegisterTestRunEvents(testRunRequest.Object); + + //Raise an event on mock object + testRunRequest.Raise( + m => m.OnRunCompletion += null, + new TestRunCompleteEventArgs(null, false, false, null, null, new TimeSpan())); + waitHandle.WaitOne(); + Assert.AreEqual(counter, 1); + + TestLoggerManager.Instance.UnregisterTestRunEvents(testRunRequest.Object); + + //Raise an event on mock object + testRunRequest.Raise( + m => m.OnRunCompletion += null, + new TestRunCompleteEventArgs(null, false, false, null, null, new TimeSpan())); + Assert.AreEqual(counter, 1); + } + + [TestMethod] + public void TestRunRequestRaiseShouldInvokeTestRunChangedHandlerOfLoggersIfRegistered() + { + counter = 0; + waitHandle.Reset(); + // setup TestLogger + TestLoggerManager.Instance.AddLogger(new Uri(loggerUri), new Dictionary()); + TestLoggerManager.Instance.EnableLogging(); + + // mock for ITestRunRequest + var testRunRequest = new Mock(); + + // Register TestRunRequest object + TestLoggerManager.Instance.RegisterTestRunEvents(testRunRequest.Object); + + //Raise an event on mock object + testRunRequest.Raise( + m => m.OnRunStatsChange += null, + new TestRunChangedEventArgs( + null, + new List() + { + new ObjectModel.TestResult( + new TestCase( + "This is a string.", + new Uri("some://uri"), + "This is a string.")) + }, + null)); + waitHandle.WaitOne(); + Assert.AreEqual(counter, 1); + } + + [TestMethod] + public void TestRunRequestRaiseShouldNotInvokeTestRunChangedHandlerOfLoggersIfUnRegistered() + { + counter = 0; + waitHandle.Reset(); + // setup TestLogger + TestLoggerManager.Instance.AddLogger(new Uri(loggerUri), new Dictionary()); + TestLoggerManager.Instance.EnableLogging(); + + // mock for ITestRunRequest + var testRunRequest = new Mock(); + + // Register TestRunRequest object + TestLoggerManager.Instance.RegisterTestRunEvents(testRunRequest.Object); + + //Raise an event on mock object + testRunRequest.Raise( + m => m.OnRunStatsChange += (e, a) => { waitHandle.Set(); }, + new TestRunChangedEventArgs( + null, + new List() + { + new ObjectModel.TestResult( + new TestCase( + "This is a string.", + new Uri("some://uri"), + "This is a string.")) + }, + null)); + waitHandle.WaitOne(); + Assert.AreEqual(counter, 1); + + TestLoggerManager.Instance.UnregisterTestRunEvents(testRunRequest.Object); + + //Raise an event on mock object + testRunRequest.Raise( + m => m.OnRunStatsChange += null, + new TestRunChangedEventArgs( + null, + new List() + { + new ObjectModel.TestResult( + new TestCase( + "This is a string.", + new Uri("some://uri"), + "This is a string.")) + }, + null)); + Assert.AreEqual(counter, 1); + } + + [TestMethod] + public void AddLoggerShouldNotThrowExceptionIfUriIsNull() + { + Assert.ThrowsException( + () => + { + TestLoggerManager.Instance.AddLogger(null, null); + }); + } + + [TestMethod] + public void AddLoggerShouldNotThrowExceptionIfUriIsNonExistent() + { + Assert.ThrowsException( + () => + { + TestLoggerManager.Instance.AddLogger(new Uri("logger://NotALogger"), null); + }); + } + + [TestMethod] + public void DisposeShouldNotThrowExceptionIfCalledMultipleTimes() + { + // Dispose the logger manager multiple times and verify that no exception is thrown. + var manager = TestLoggerManager.Instance; + manager.Dispose(); + manager.Dispose(); + } + + [TestMethod] + public void AddLoggerShouldThrowObjectDisposedExceptionAfterDisposedIsCalled() + { + TestLoggerManager.Instance.Dispose(); + + Assert.ThrowsException( + () => + { + TestLoggerManager.Instance.AddLogger(new Uri("some://uri"), null); + }); + } + + + [TestMethod] + public void EnableLoggingShouldThrowObjectDisposedExceptionAfterDisposedIsCalled() + { + TestLoggerManager.Instance.Dispose(); + Assert.ThrowsException( + () => + { + TestLoggerManager.Instance.EnableLogging(); + }); + } + + [TestMethod] + public void RegisterTestRunEventsThrowsExceptionWithNullasArgument() + { + Assert.ThrowsException( + () => + { + TestLoggerManager.Instance.RegisterTestRunEvents(null); + }); + } + + [ExtensionUri("testlogger://logger")] + [FriendlyName("TestLoggerExtension")] + private class ValidLogger3 : ITestLogger + { + public void Initialize(TestLoggerEvents events, string testRunDirectory) + { + events.TestRunMessage += TestMessageHandler; + events.TestRunComplete += Events_TestRunComplete; + events.TestResult += Events_TestResult; + } + + private void Events_TestResult(object sender, TestResultEventArgs e) + { + TestLoggerManagerTests.counter++; + TestLoggerManagerTests.waitHandle.Set(); + } + + private void Events_TestRunComplete(object sender, TestRunCompleteEventArgs e) + { + TestLoggerManagerTests.counter++; + TestLoggerManagerTests.waitHandle.Set(); + + } + + private void TestMessageHandler(object sender, TestRunMessageEventArgs e) + { + if (e.Message.Equals("TestRunMessage")) + { + TestLoggerManagerTests.counter++; + TestLoggerManagerTests.waitHandle.Set(); + + } + } + + } + + internal class DummyTestLoggerManager : TestLoggerManager + { + public DummyTestLoggerManager() + { + + } + + public void Cleanup() + { + Instance = null; + } + } + } +} + diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/Logging/TestSessionMessageLoggerTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/Logging/TestSessionMessageLoggerTests.cs new file mode 100644 index 0000000000..905360310b --- /dev/null +++ b/test/Microsoft.TestPlatform.Common.UnitTests/Logging/TestSessionMessageLoggerTests.cs @@ -0,0 +1,64 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.Common.UnitTests.Logging +{ + using System; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Microsoft.VisualStudio.TestPlatform.Common.Logging; + [TestClass] + public class TestSessionMessageLoggerTests + { + private TestSessionMessageLogger testSessionMessageLogger; + + private TestRunMessageEventArgs currentEventArgs; + + [TestInitialize] + public void TestInit() + { + this.testSessionMessageLogger = TestSessionMessageLogger.Instance; + } + + [TestCleanup] + public void TestCleanup() + { + TestSessionMessageLogger.Instance = null; + } + + [TestMethod] + public void InstanceShouldReturnALoggerInstance() + { + Assert.IsNotNull(this.testSessionMessageLogger); + } + + [TestMethod] + public void SendMessageShouldLogErrorMessages() + { + this.testSessionMessageLogger.TestRunMessage += OnMessage; + + var message = "Alert"; + this.testSessionMessageLogger.SendMessage(TestMessageLevel.Error, message); + + Assert.AreEqual(TestMessageLevel.Error, this.currentEventArgs.Level); + Assert.AreEqual(message, this.currentEventArgs.Message); + } + + [TestMethod] + public void SendMessageShouldLogErrorAsWarningIfSpecifiedSo() + { + this.testSessionMessageLogger.TestRunMessage += OnMessage; + this.testSessionMessageLogger.TreatTestAdapterErrorsAsWarnings = true; + + var message = "Alert"; + this.testSessionMessageLogger.SendMessage(TestMessageLevel.Error, message); + + Assert.AreEqual(TestMessageLevel.Warning, this.currentEventArgs.Level); + Assert.AreEqual(message, this.currentEventArgs.Message); + } + + private void OnMessage(object sender, TestRunMessageEventArgs e) + { + this.currentEventArgs = e; + } + } +} diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/Microsoft.TestPlatform.Common.UnitTests.xproj b/test/Microsoft.TestPlatform.Common.UnitTests/Microsoft.TestPlatform.Common.UnitTests.xproj new file mode 100644 index 0000000000..34ac598fd3 --- /dev/null +++ b/test/Microsoft.TestPlatform.Common.UnitTests/Microsoft.TestPlatform.Common.UnitTests.xproj @@ -0,0 +1,22 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 8a00286a-d8ea-4331-a5f5-cf76c6b7461c + TestPlatform.Common.UnitTests + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\ + v4.5.2 + + + 2.0 + + + + + + \ No newline at end of file diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/Properties/AssemblyInfo.cs b/test/Microsoft.TestPlatform.Common.UnitTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..50315fb8a1 --- /dev/null +++ b/test/Microsoft.TestPlatform.Common.UnitTests/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +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: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("TestPlatform.Common.UnitTests")] +[assembly: AssemblyTrademark("")] + +// 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("8a00286a-d8ea-4331-a5f5-cf76c6b7461c")] diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/RunSettingsManagerTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/RunSettingsManagerTests.cs new file mode 100644 index 0000000000..e259525018 --- /dev/null +++ b/test/Microsoft.TestPlatform.Common.UnitTests/RunSettingsManagerTests.cs @@ -0,0 +1,66 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.Common.UnitTests +{ + using System; + + using Microsoft.VisualStudio.TestPlatform.Common; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class RunSettingsManagerTests + { + [TestCleanup] + public void TestCleanup() + { + RunSettingsManager.Instance = null; + } + + [TestMethod] + public void InstanceShouldReturnARunSettingsManagerInstance() + { + var instance = RunSettingsManager.Instance; + + Assert.IsNotNull(instance); + Assert.AreEqual(typeof(RunSettingsManager), instance.GetType()); + } + + [TestMethod] + public void InstanceShouldReturnACachedValue() + { + var instance = RunSettingsManager.Instance; + var instance2 = RunSettingsManager.Instance; + + Assert.AreEqual(instance, instance2); + } + + [TestMethod] + public void ActiveRunSettingsShouldBeNonNullByDefault() + { + var instance = RunSettingsManager.Instance; + + Assert.IsNotNull(instance.ActiveRunSettings); + } + + [TestMethod] + public void SetActiveRunSettingsShouldThrowIfRunSettingsPassedIsNull() + { + var instance = RunSettingsManager.Instance; + + Assert.ThrowsException(() => instance.SetActiveRunSettings(null)); + } + + [TestMethod] + public void SetActiveRunSettingsShouldSetTheActiveRunSettingsProperty() + { + var instance = RunSettingsManager.Instance; + + var runSettings = new RunSettings(); + runSettings.LoadSettingsXml(""); + + instance.SetActiveRunSettings(runSettings); + + Assert.AreEqual(runSettings, instance.ActiveRunSettings); + } + } +} diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/RunSettingsTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/RunSettingsTests.cs new file mode 100644 index 0000000000..0e53f2b032 --- /dev/null +++ b/test/Microsoft.TestPlatform.Common.UnitTests/RunSettingsTests.cs @@ -0,0 +1,342 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.Common.UnitTests +{ + using System; + using System.Runtime.InteropServices; + using System.Xml; + + using ExtensionFramework; + + using Microsoft.VisualBasic; + using Microsoft.VisualStudio.TestPlatform.Common; + using Microsoft.VisualStudio.TestPlatform.Common.Logging; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class RunSettingsTests + { + [TestCleanup] + public void TestCleanup() + { + TestPluginCacheTests.ResetExtensionsCache(); + TestSessionMessageLogger.Instance = null; + } + + #region LoadSettingsXML Tests + + [TestMethod] + public void LoadSettingsXmlShouldThrowOnNullSettings() + { + var runSettings = new RunSettings(); + Assert.ThrowsException(() => runSettings.LoadSettingsXml(null)); + } + + [TestMethod] + public void LoadSettingsXmlShouldThrowOnEmptySettings() + { + var runSettings = new RunSettings(); + Assert.ThrowsException(() => runSettings.LoadSettingsXml(" ")); + } + + [TestMethod] + public void LoadSettingsXmlShoulLoadAndInitializeSettingsXml() + { + var runSettings = new RunSettings(); + var emptyRunSettings = this.GetEmptyRunSettings(); + + runSettings.LoadSettingsXml(emptyRunSettings); + + // Not doing this because when we load the xmla nd write to string it converts it to a utf-16 format. + // So they do not exactly match. + // Assert.AreEqual(emptyRunSettings, runSettings.SettingsXml); + + var expectedRunSettings = @" +"; + StringAssert.Contains(runSettings.SettingsXml, expectedRunSettings); + } + + [TestMethod] + public void LoadSettingsXmlShouldThrowOnInvalidSettings() + { + var runSettings = new RunSettings(); + var invalidSettings = this.GetInvalidRunSettings(); + + ExceptionUtilities.ThrowsException( + () => runSettings.LoadSettingsXml(invalidSettings), + "An error occurred while loading the run settings."); + } + + #endregion + + #region InitializeSettingsProviders and GetSettings tests + + [TestMethod] + public void InitializeSettingsProvidersShouldThrowOnNullSettings() + { + var runSettings = new RunSettings(); + Assert.ThrowsException(() => runSettings.InitializeSettingsProviders(null)); + } + + [TestMethod] + public void InitializeSettingsProvidersShouldWorkForEmptyRunSettings() + { + var runSettings = new RunSettings(); + + runSettings.InitializeSettingsProviders(this.GetEmptyRunSettings()); + + Assert.IsNull(runSettings.GetSettings("RunSettings")); + } + + [TestMethod] + public void InitializeSettingsProvidersShouldThrowIfNodeInRunSettingsDoesNotHaveAProvider() + { + TestPluginCacheTests.SetupMockExtensions(); + + var runSettings = new RunSettings(); + runSettings.InitializeSettingsProviders(this.GetRunSettingsWithUndefinedSettingsNodes()); + + Action action = + () => runSettings.GetSettings("OrphanNode"); + + ExceptionUtilities.ThrowsException( + action, + "Settings Provider named '{0}' was not found. The settings can not be loaded.", + "OrphanNode"); + } + + [TestMethod] + public void InitializeSettingsProvidersShouldThrowIfSettingsProviderLoadThrows() + { + TestPluginCacheTests.SetupMockExtensions(); + + var runSettings = new RunSettings(); + runSettings.InitializeSettingsProviders(this.GetRunSettingsWithBadSettingsNodes()); + + Action action = + () => runSettings.GetSettings("BadSettings"); + + ExceptionUtilities.ThrowsException( + action, + "An error occurred while initializing the settings provider named '{0}'", + "BadSettings"); + } + + [TestMethod] + public void InitializeSettingsProvidersShouldThrowIfInvalidRunSettingsIsPassed() + { + var runSettings = new RunSettings(); + ExceptionUtilities.ThrowsException( + () => runSettings.InitializeSettingsProviders(this.GetInvalidRunSettings()), + "An error occurred while loading the run settings."); + } + + [TestMethod] + public void InitializeSettingsProvidersMultipleTimesShouldThrowInvalidOperationException() + { + var runSettings = new RunSettings(); + runSettings.InitializeSettingsProviders(this.GetEmptyRunSettings()); + ExceptionUtilities.ThrowsException( + () => runSettings.InitializeSettingsProviders(this.GetEmptyRunSettings()), + "The Run Settings have already been loaded."); + } + + [TestMethod] + public void InitializeSettingsProvidersShouldLoadSettingsIntoASettingsProvider() + { + TestPluginCacheTests.SetupMockExtensions(); + + var runSettings = new RunSettings(); + runSettings.InitializeSettingsProviders(this.GetRunSettingsWithRunConfigurationNode()); + + var settingsProvider = runSettings.GetSettings("RunConfiguration"); + + Assert.IsNotNull(settingsProvider); + Assert.IsTrue(settingsProvider is RunConfigurationSettingsProvider); + + // Also validate that the settings provider gets the right subtree. + Assert.AreEqual( + "x86", + (settingsProvider as RunConfigurationSettingsProvider).SettingsTree); + } + + [TestMethod] + public void InitializeSettingsProvidersShouldLoadSettingsIntoMultipleSettingsProviders() + { + TestPluginCacheTests.SetupMockExtensions(); + + var runSettings = new RunSettings(); + runSettings.InitializeSettingsProviders(this.GetRunSettingsWithRunConfigurationAndMSTestNode()); + + var rcSettingsProvider = runSettings.GetSettings("RunConfiguration"); + var mstestSettingsProvider = runSettings.GetSettings("MSTest"); + + Assert.IsNotNull(rcSettingsProvider); + Assert.IsTrue(rcSettingsProvider is RunConfigurationSettingsProvider); + Assert.AreEqual( + "x86", + (rcSettingsProvider as RunConfigurationSettingsProvider).SettingsTree); + + Assert.IsNotNull(mstestSettingsProvider); + Assert.IsTrue(mstestSettingsProvider is MSTestSettingsProvider); + Assert.AreEqual( + "true", + (mstestSettingsProvider as MSTestSettingsProvider).SettingsTree); + } + + [TestMethod] + public void InitializeSettingsProvidersShouldWarnOfDuplicateSettings() + { + string receivedWarningMessage = null; + + TestPluginCacheTests.SetupMockExtensions(); + TestSessionMessageLogger.Instance.TestRunMessage += (object sender, TestRunMessageEventArgs e) => + { + receivedWarningMessage = e.Message; + }; + + var runSettings = new RunSettings(); + runSettings.InitializeSettingsProviders(this.GetRunSettingsWithDuplicateSettingsNodes()); + + Assert.IsNotNull(receivedWarningMessage); + Assert.AreEqual( + "Duplicated run settings section named 'RunConfiguration' found. Ignoring the duplicate settings.", + receivedWarningMessage); + } + + #endregion + + #region GetSettings tests + + [TestMethod] + public void GetSettingsShouldThrowIfSettingsNameIsNull() + { + var runSettings = new RunSettings(); + + Assert.ThrowsException(() => runSettings.GetSettings(null)); + } + + [TestMethod] + public void GetSettingsShouldThrowIfSettingsNameIsEmpty() + { + var runSettings = new RunSettings(); + + Assert.ThrowsException(() => runSettings.GetSettings(" ")); + } + + // The remaining GetSettings tests are covered in the InitializeSettingsProviders tests above. + #endregion + + #region Private methods + + private string GetEmptyRunSettings() + { + return @" + +"; + } + + private string GetRunSettingsWithUndefinedSettingsNodes() + { + return @" + + + + +"; + } + + private string GetRunSettingsWithBadSettingsNodes() + { + return @" + + + + +"; + } + + private string GetRunSettingsWithRunConfigurationNode() + { + return @" + + +x86 + +"; + } + + private string GetRunSettingsWithRunConfigurationAndMSTestNode() + { + return @" + + +x86 + + +true + +"; + } + + private string GetRunSettingsWithDuplicateSettingsNodes() + { + return @" + + + + + +"; + } + + private string GetInvalidRunSettings() + { + return @" + +"; + } + + #endregion + + #region Testable Implementations + + [SettingsName("RunConfiguration")] + private class RunConfigurationSettingsProvider : ISettingsProvider + { + public string SettingsTree { get; set; } + + public void Load(XmlReader reader) + { + reader.Read(); + this.SettingsTree = reader.ReadOuterXml(); + } + } + + [SettingsName("MSTest")] + private class MSTestSettingsProvider : ISettingsProvider + { + public string SettingsTree { get; set; } + + public void Load(XmlReader reader) + { + reader.Read(); + this.SettingsTree = reader.ReadOuterXml(); + } + } + + [SettingsName("BadSettings")] + private class BadSettingsProvider : ISettingsProvider + { + public void Load(XmlReader reader) + { + throw new Exception(); + } + } + + #endregion + } +} diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/SettingsProvider/SettingsProviderExtensionManagerTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/SettingsProvider/SettingsProviderExtensionManagerTests.cs new file mode 100644 index 0000000000..ee2a56c815 --- /dev/null +++ b/test/Microsoft.TestPlatform.Common.UnitTests/SettingsProvider/SettingsProviderExtensionManagerTests.cs @@ -0,0 +1,241 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.Common.UnitTests.SettingsProvider +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Xml; + + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.Utilities; + using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; + using Microsoft.VisualStudio.TestPlatform.Common.SettingsProvider; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + using Moq; + + using TestPlatform.Common.UnitTests.ExtensionFramework; + + [TestClass] + public class SettingsProviderExtensionManagerTests + { + [TestCleanup] + public void TestCleanup() + { + SettingsProviderExtensionManager.Destroy(); + } + + #region Constructor tests + + [TestMethod] + public void ConstructorShouldPopulateSettingsProviderMap() + { + var extensions = this.GetMockExtensions("TestableSettings"); + var unfilteredExtensions = new List>> + { + new LazyExtension> + ( + new Mock().Object, + new Dictionary()) + }; + var spm = new TestableSettingsProviderManager(extensions, unfilteredExtensions, new Mock().Object); + + Assert.IsNotNull(spm.SettingsProvidersMap); + Assert.AreEqual("TestableSettings", spm.SettingsProvidersMap.Keys.FirstOrDefault()); + } + + [TestMethod] + public void ConstructorShouldLogWarningOnDuplicateSettingsProviderNames() + { + var extensions = this.GetMockExtensions("TestableSettings", "TestableSettings"); + var unfilteredExtensions = new List>> + { + new LazyExtension> + ( + new Mock().Object, + new Dictionary()) + }; + var mockLogger = new Mock(); + var spm = new TestableSettingsProviderManager(extensions, unfilteredExtensions, mockLogger.Object); + + mockLogger.Verify( + l => + l.SendMessage( + TestMessageLevel.Error, + "Duplicate settings provider named 'TestableSettings'. Ignoring the duplicate provider.")); + + // Also validate the below. + Assert.IsNotNull(spm.SettingsProvidersMap); + Assert.AreEqual("TestableSettings", spm.SettingsProvidersMap.Keys.FirstOrDefault()); + } + + #endregion + + #region Create tests + + [TestMethod] + public void CreateShouldDiscoverSettingsProviderExtensions() + { + TestPluginCacheTests.SetupMockExtensions(); + + var extensionManager = SettingsProviderExtensionManager.Create(); + + Assert.IsNotNull(extensionManager.SettingsProvidersMap); + Assert.IsTrue(extensionManager.SettingsProvidersMap.Count > 0); + } + + [TestMethod] + public void CreateShouldCacheDiscoveredExtensions() + { + var discoveryCount = 0; + TestPluginCacheTests.SetupMockExtensions(() => { discoveryCount++; }); + + var extensionManager = SettingsProviderExtensionManager.Create(); + SettingsProviderExtensionManager.Create(); + + Assert.IsNotNull(extensionManager.SettingsProvidersMap); + Assert.IsTrue(extensionManager.SettingsProvidersMap.Count > 0); + Assert.AreEqual(1, discoveryCount); + } + + #endregion + + #region LoadAndInitialize tests + + [TestMethod] + public void LoadAndInitializeShouldInitializeAllExtensions() + { + TestPluginCacheTests.SetupMockExtensions(); + + SettingsProviderExtensionManager.LoadAndInitializeAllExtensions(false); + + var settingsProviders = SettingsProviderExtensionManager.Create().SettingsProvidersMap.Values; + + foreach (var provider in settingsProviders) + { + Assert.IsTrue(provider.IsExtensionCreated); + } + } + + #endregion + + #region GetSettingsProvider tests + + [TestMethod] + public void GetSettingsProviderShouldThrowIfSettingsNameIsNullOrEmpty() + { + var extensions = this.GetMockExtensions("TestableSettings"); + var unfilteredExtensions = new List>> + { + new LazyExtension> + ( + new Mock().Object, + new Dictionary()) + }; + var spm = new TestableSettingsProviderManager(extensions, unfilteredExtensions, new Mock().Object); + + Assert.ThrowsException(() => spm.GetSettingsProvider(null)); + Assert.ThrowsException(() => spm.GetSettingsProvider(string.Empty)); + } + + [TestMethod] + public void GetSettingsProviderShouldReturnNullIfSettingsProviderWithSpecifiedNameIsNotFound() + { + var extensions = this.GetMockExtensions("TestableSettings"); + var unfilteredExtensions = new List>> + { + new LazyExtension> + ( + new Mock().Object, + new Dictionary()) + }; + var spm = new TestableSettingsProviderManager(extensions, unfilteredExtensions, new Mock().Object); + + var sp = spm.GetSettingsProvider("RandomSettingsWhichDoesNotExist"); + + Assert.IsNull(sp); + } + + [TestMethod] + public void GetSettingsProviderShouldReturnSettingsProviderInstance() + { + var extensions = this.GetMockExtensions("TestableSettings"); + var unfilteredExtensions = new List>> + { + new LazyExtension> + ( + new Mock().Object, + new Dictionary()) + }; + var spm = new TestableSettingsProviderManager(extensions, unfilteredExtensions, new Mock().Object); + + var sp = spm.GetSettingsProvider("TestableSettings"); + + Assert.IsNotNull(sp); + Assert.IsNotNull(sp.Value); + } + + #endregion + + #region private methods + + private IEnumerable> GetMockExtensions(params string[] settingNames) + { + var settingsList = new List>(); + + foreach (var settingName in settingNames) + { + var mockSettingsProvider = new Mock(); + var metadata = new TestSettingsProviderMetadata(settingName); + + var extension = + new LazyExtension( + mockSettingsProvider.Object, + metadata); + + settingsList.Add(extension); + } + + return settingsList; + } + + #endregion + + #region Testable Implementations + + private class TestableSettingsProviderManager : SettingsProviderExtensionManager + { + public TestableSettingsProviderManager( + IEnumerable> settingsProviders, + IEnumerable>> unfilteredSettingsProviders, + IMessageLogger logger) + : base(settingsProviders, unfilteredSettingsProviders, logger) + { + } + } + + [SettingsName("Random")] + private class RandomSettingsProvider : ISettingsProvider + { + public void Load(XmlReader reader) + { + } + } + + #endregion + } + + [TestClass] + public class TestSettingsProviderMetadataTests + { + [TestMethod] + public void ConstructorShouldSetSettingsName() + { + var metadata = new TestSettingsProviderMetadata("sample"); + Assert.AreEqual("sample", metadata.SettingsName); + } + } +} diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/Utilities/ExceptionUtilitiesTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/Utilities/ExceptionUtilitiesTests.cs new file mode 100644 index 0000000000..a0e1c609c2 --- /dev/null +++ b/test/Microsoft.TestPlatform.Common.UnitTests/Utilities/ExceptionUtilitiesTests.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.Common.UnitTests.Utilities +{ + using System; + + using Microsoft.VisualStudio.TestPlatform.Common.Utilities; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class ExceptionUtilitiesTests + { + [TestMethod] + public void GetExceptionMessageShouldReturnEmptyIfExceptionIsNull() + { + Assert.AreEqual(string.Empty, ExceptionUtilities.GetExceptionMessage(null)); + } + + [TestMethod] + public void GetExceptionMessageShouldReturnExceptionMessage() + { + var exception = new ArgumentException("Some bad stuff"); + Assert.AreEqual(exception.Message, ExceptionUtilities.GetExceptionMessage(exception)); + } + + [TestMethod] + public void GetExceptionMessageShouldReturnFormattedExceptionMessageWithInnerExceptionDetails() + { + var innerException = new Exception("Bad stuff internally"); + var innerException2 = new Exception("Bad stuff internally 2", innerException); + var exception = new ArgumentException("Some bad stuff", innerException2); + var expectedMessage = exception.Message + Environment.NewLine + innerException2.Message + + Environment.NewLine + innerException.Message; + Assert.AreEqual(expectedMessage, ExceptionUtilities.GetExceptionMessage(exception)); + } + } +} diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/Utilities/PathUtilitiesTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/Utilities/PathUtilitiesTests.cs new file mode 100644 index 0000000000..6e6323661d --- /dev/null +++ b/test/Microsoft.TestPlatform.Common.UnitTests/Utilities/PathUtilitiesTests.cs @@ -0,0 +1,88 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.Common.UnitTests.Utilities +{ + using Microsoft.VisualStudio.TestPlatform.Common.Utilities; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + [TestClass] + public class PathUtilitiesTests + { + private PathUtilities pathUtilities; + + [TestInitialize] + public void TestInit() + { + this.pathUtilities = new PathUtilities(); + } + + [TestMethod] + public void GetUniqueValidPathsShouldReturnEmptyWhenPathsIsNull() + { + var pathSet = this.pathUtilities.GetUniqueValidPaths(null); + + Assert.IsNotNull(pathSet); + Assert.AreEqual(0, pathSet.Count); + } + + [TestMethod] + public void GetUniqueValidPathsShouldReturnEmptyWhenPathsIsEmpty() + { + var pathSet = this.pathUtilities.GetUniqueValidPaths(new List { }); + + Assert.IsNotNull(pathSet); + Assert.AreEqual(0, pathSet.Count); + } + + [TestMethod] + public void GetUniqueValidPathsShouldReturnExtensionPaths() + { + var extensionsList = new List + { + typeof(PathUtilitiesTests).GetTypeInfo().Assembly.Location, + typeof(PathUtilities).GetTypeInfo().Assembly.Location + }; + + var pathSet = + this.pathUtilities.GetUniqueValidPaths(extensionsList); + + Assert.IsNotNull(pathSet); + CollectionAssert.AreEqual(extensionsList, pathSet.ToList()); + } + + [TestMethod] + public void GetUniqueValidPathsShouldReturnUniquePaths() + { + var extensionsList = new List + { + typeof(PathUtilitiesTests).GetTypeInfo().Assembly.Location, + typeof(PathUtilitiesTests).GetTypeInfo().Assembly.Location + }; + + var pathSet = + this.pathUtilities.GetUniqueValidPaths(extensionsList); + + Assert.IsNotNull(pathSet); + CollectionAssert.AreEqual(new List { extensionsList[0] }, pathSet.ToList()); + } + + [TestMethod] + public void GetUniqueValidPathsShouldReturnValidPaths() + { + var extensionsList = new List + { + typeof(PathUtilitiesTests).GetTypeInfo().Assembly.Location, + "foo.dll", + "fakeextension.dll" + }; + + var pathSet = + this.pathUtilities.GetUniqueValidPaths(extensionsList); + + Assert.IsNotNull(pathSet); + CollectionAssert.AreEqual(new List { extensionsList[0] }, pathSet.ToList()); + } + } +} diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/Utilities/RunSettingsUtilitiesTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/Utilities/RunSettingsUtilitiesTests.cs new file mode 100644 index 0000000000..b3ea3a650f --- /dev/null +++ b/test/Microsoft.TestPlatform.Common.UnitTests/Utilities/RunSettingsUtilitiesTests.cs @@ -0,0 +1,110 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.Common.UnitTests.Utilities +{ + using System; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Microsoft.VisualStudio.TestPlatform.Common.Utilities; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; + using System.Xml; + using ExtensionFramework; + [TestClass] + public class RunSettingsUtilitiesTests + { + [TestMethod] + public void CreateRunSettingsShouldReturnNullIfSettingsXmlIsNullorEmpty() + { + Assert.IsNull(RunSettingsUtilities.CreateAndInitializeRunSettings(null)); + } + + [TestMethod] + public void CreateRunSettingsShouldThrowExceptionWhenInvalidXmlStringIsPassed() + { + Assert.ThrowsException(() => + { + RunSettingsUtilities.CreateAndInitializeRunSettings("abc"); + } + ); + } + + [TestMethod] + public void CreateRunSettingsShouldReturnValidRunSettings() + { + TestPluginCacheTests.SetupMockExtensions(); + string runsettings = @".\TestResultstrue"; + var result= RunSettingsUtilities.CreateAndInitializeRunSettings(runsettings); + Assert.AreEqual(DummyMsTestSetingsProvider.StringToVerify, "true"); + TestPluginCacheTests.ResetExtensionsCache(); + } + + [TestMethod] + public void GetMaxCpuCountWithNullSettingXmlShouldReturnDefaultCpuCount() + { + string settingXml = null; + int expectedResult = Constants.DefaultCpuCount; + + int result = RunSettingsUtilities.GetMaxCpuCount(settingXml); + + Assert.AreEqual(expectedResult, result); + } + + [TestMethod] + public void GetMaxCpuCountWithEmptySettingXmlShouldReturnDefaultCpuCount() + { + string settingXml = ""; + int expectedResult = Constants.DefaultCpuCount; + + int result = RunSettingsUtilities.GetMaxCpuCount(settingXml); + + Assert.AreEqual(expectedResult, result); + } + + [TestMethod] + public void GetMaxCpuCountWithSettingXmlNotHavingCpuCountShouldReturnDefaultCpuCount() + { + string settingXml = @""; + int expectedResult = Constants.DefaultCpuCount; + + int result = RunSettingsUtilities.GetMaxCpuCount(settingXml); + + Assert.AreEqual(expectedResult, result); + } + + [TestMethod] + public void GetMaxCpuCountWithSettingXmlCpuCountShouldReturnCorrectCpuCount() + { + string settingXml = @"5"; + int expectedResult = 5; + + int result = RunSettingsUtilities.GetMaxCpuCount(settingXml); + + Assert.AreEqual(expectedResult, result); + } + + [TestMethod] + public void GetMaxCpuCountWithInvalidCpuCountShouldReturnDefaultCpuCount() + { + string settingXml = @"-10"; + int expectedResult = Constants.DefaultCpuCount; + + int result = RunSettingsUtilities.GetMaxCpuCount(settingXml); + + Assert.AreEqual(expectedResult, result); + } + } + + [SettingsName("DummyMSTest")] + public class DummyMsTestSetingsProvider : ISettingsProvider + { + public void Load(XmlReader reader) + { + ValidateArg.NotNull(reader, "reader"); + reader.Read(); + StringToVerify = reader.ReadOuterXml(); + } + + public static string StringToVerify = string.Empty; + } + +} diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/project.json b/test/Microsoft.TestPlatform.Common.UnitTests/project.json new file mode 100644 index 0000000000..08ee5df4f7 --- /dev/null +++ b/test/Microsoft.TestPlatform.Common.UnitTests/project.json @@ -0,0 +1,35 @@ +{ + "version": "15.0.0-*", + + "buildOptions": { + "delaySign": true, + "keyFile": "../../scripts/key.snk", + "warningsAsErrors": true + }, + + "dependencies": { + "Microsoft.NETCore.App": { + "type": "platform", + "version": "1.0.0-rc2-3002702" + }, + "dotnet-test-mstest": { + "version": "1.0.1-preview", + "exclude": "compile" + }, + "MSTest.TestFramework": "1.0.0-preview", + "moq.netcore": "4.4.0-beta8", + "System.Diagnostics.TraceSource": "4.0.0-rc2-24015", + "Microsoft.TestPlatform.Common": "15.0.0-*" + }, + + "frameworks": { + "netcoreapp1.0": { + "imports": [ + "dnxcore50", + "portable-net45+win8" + ] + } + }, + + "testRunner": "mstest" +} diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/Microsoft.TestPlatform.CommunicationUtilities.UnitTests.xproj b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/Microsoft.TestPlatform.CommunicationUtilities.UnitTests.xproj new file mode 100644 index 0000000000..4d8a4cecc0 --- /dev/null +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/Microsoft.TestPlatform.CommunicationUtilities.UnitTests.xproj @@ -0,0 +1,22 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 5e3cbec8-e52e-4fb1-a0d2-01f75e33721d + TestPlatform.CommunicationUtilities.UnitTests + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\ + v4.5.2 + + + 2.0 + + + + + + \ No newline at end of file diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/ObjectModel/TestDiscoveryEventHandlerTests.cs b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/ObjectModel/TestDiscoveryEventHandlerTests.cs new file mode 100644 index 0000000000..bafbe824f4 --- /dev/null +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/ObjectModel/TestDiscoveryEventHandlerTests.cs @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.CommunicationUtilities.UnitTests.ObjectModel +{ + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + using Moq; + + [TestClass] + public class TestDiscoveryEventHandlerTests + { + private Mock mockClient; + private TestDiscoveryEventHandler testDiscoveryEventHandler; + + [TestInitialize] + public void InitializeTests() + { + this.mockClient = new Mock(); + this.testDiscoveryEventHandler = new TestDiscoveryEventHandler(this.mockClient.Object); + } + + [TestMethod] + public void HandleDiscoveredTestShouldSendTestCasesToClient() + { + this.testDiscoveryEventHandler.HandleDiscoveredTests(null); + this.mockClient.Verify(th => th.SendTestCases(null), Times.Once); + } + + [TestMethod] + public void HandleDiscoveryCompleteShouldInformClient() + { + this.testDiscoveryEventHandler.HandleDiscoveryComplete(0, null, false); + this.mockClient.Verify(th => th.DiscoveryComplete(0, null, false), Times.Once); + } + + [TestMethod] + public void HandleDiscoveryCompleteShouldNotSendASeparateTestFoundMessageToClient() + { + this.testDiscoveryEventHandler.HandleDiscoveryComplete(0, null, false); + this.mockClient.Verify(th => th.SendTestCases(null), Times.Never); + } + + [TestMethod] + public void HandleDiscoveryMessageShouldSendMessageToClient() + { + this.testDiscoveryEventHandler.HandleLogMessage(TestMessageLevel.Informational,null); + this.mockClient.Verify(th => th.SendLog(0, null), Times.AtLeast(1)); + } + } +} diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/ObjectModel/TestRunEventsHandlerTests.cs b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/ObjectModel/TestRunEventsHandlerTests.cs new file mode 100644 index 0000000000..c8b99995b2 --- /dev/null +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/ObjectModel/TestRunEventsHandlerTests.cs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.CommunicationUtilities.UnitTests.ObjectModel +{ + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.EventHandlers; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + using Moq; + + [TestClass] + public class TestRunEventsHandlerTests + { + private Mock mockClient; + private TestRunEventsHandler testRunEventHandler; + + [TestInitialize] + public void InitializeTests() + { + this.mockClient = new Mock(); + this.testRunEventHandler = new TestRunEventsHandler(this.mockClient.Object); + } + + [TestMethod] + public void HandleTestRunStatsChangeShouldSendTestRunStatisticsToClient() + { + this.testRunEventHandler.HandleTestRunStatsChange(null); + this.mockClient.Verify(th => th.SendTestRunStatistics(null), Times.Once); + } + + [TestMethod] + public void HandleTestRunCompleteShouldInformClient() + { + this.testRunEventHandler.HandleTestRunComplete(null, null, null, null); + this.mockClient.Verify(th => th.SendExecutionComplete(null, null, null, null), Times.Once); + } + + [TestMethod] + public void HandleTestRunMessageShouldSendMessageToClient() + { + this.testRunEventHandler.HandleLogMessage(TestMessageLevel.Informational, null); + this.mockClient.Verify(th => th.SendLog(0, null), Times.AtLeast(1)); + } + } +} diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/Properties/AssemblyInfo.cs b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..5c149cab7e --- /dev/null +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +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: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("TestPlatform.CommunicationUtilities.UnitTests")] +[assembly: AssemblyTrademark("")] + +// 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("5e3cbec8-e52e-4fb1-a0d2-01f75e33721d")] diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/TestRequestSenderTests.cs b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/TestRequestSenderTests.cs new file mode 100644 index 0000000000..69623f0e26 --- /dev/null +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/TestRequestSenderTests.cs @@ -0,0 +1,405 @@ + +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.CommunicationUtilities.UnitTests +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using System.Threading; + + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + using Moq; + + [TestClass] + public class TestRequestSenderTests + { + private ITestRequestSender testRequestSender; + private Mock mockCommunicationManager; + private Mock mockDataSerializer; + + [TestInitialize] + public void TestInit() + { + this.mockCommunicationManager = new Mock(); + this.mockDataSerializer = new Mock(); + this.testRequestSender = new TestRequestSender(this.mockCommunicationManager.Object, this.mockDataSerializer.Object); + } + + + [TestMethod] + public void InitializeCommunicationShouldHostServerAndAcceptClient() + { + this.mockCommunicationManager.Setup(mc => mc.HostServer()).Returns(123); + + var port = this.testRequestSender.InitializeCommunication(); + + this.mockCommunicationManager.Verify(mc => mc.HostServer(), Times.Once); + this.mockCommunicationManager.Verify(mc => mc.AcceptClientAsync(), Times.Once); + + Assert.AreEqual(port, 123, "Correct port must be returned."); + } + + [TestMethod] + public void WaitForRequestHandlerConnectionShouldCallWaitForClientConnection() + { + this.testRequestSender.WaitForRequestHandlerConnection(123); + + this.mockCommunicationManager.Verify(mc => mc.WaitForClientConnection(123), Times.Once); + } + + [TestMethod] + public void CloseShouldCallStopServerOnCommunicationManager() + { + this.testRequestSender.Close(); + + this.mockCommunicationManager.Verify(mc => mc.StopServer(), Times.Once); + } + + [TestMethod] + public void DisposeShouldCallStopServerOnCommunicationManager() + { + this.testRequestSender.Dispose(); + + this.mockCommunicationManager.Verify(mc => mc.StopServer(), Times.Once); + } + + + [TestMethod] + public void InitializeDiscoveryShouldSendCommunicationMessageWithCorrectParameters() + { + var paths = new List() { "Hello", "World" }; + this.testRequestSender.InitializeDiscovery(paths, false); + + this.mockCommunicationManager.Verify(mc => mc.SendMessage(MessageType.DiscoveryInitialize, paths), Times.Once); + } + + [TestMethod] + public void InitializeExecutionShouldSendCommunicationMessageWithCorrectParameters() + { + var paths = new List() { "Hello", "World" }; + this.testRequestSender.InitializeExecution(paths, true); + + this.mockCommunicationManager.Verify(mc => mc.SendMessage(MessageType.ExecutionInitialize, paths), Times.Once); + } + + [TestMethod] + public void DiscoverTestsShouldCallHandleDiscoveredTestsOnTestCaseEvent() + { + var sources = new List() { "Hello", "World" }; + string settingsXml = "SettingsXml"; + var mockHandler = new Mock(); + var discoveryCriteria = new DiscoveryCriteria(sources, 100, settingsXml); + + var testCases = new List() { new TestCase("x.y.z", new Uri("x://y"), "x.dll") }; + var rawMessage = "OnDiscoveredTests"; + var message = new Message() { MessageType = MessageType.TestCasesFound, Payload = null }; + + this.mockCommunicationManager.Setup(mc => mc.ReceiveRawMessage()).Returns(rawMessage); + this.mockDataSerializer.Setup(ds => ds.DeserializeMessage(rawMessage)).Returns(message); + this.mockDataSerializer.Setup(ds => ds.DeserializePayload>(message)).Returns(testCases); + + var completePayload = new DiscoveryCompletePayload() + { + IsAborted = false, + LastDiscoveredTests = null, + TotalTests = 1 + }; + var completeMessage = new Message() { MessageType = MessageType.DiscoveryComplete, Payload = null }; + mockHandler.Setup(mh => mh.HandleDiscoveredTests(testCases)).Callback( + () => + { + this.mockDataSerializer.Setup(ds => ds.DeserializeMessage(It.IsAny())).Returns(completeMessage); + this.mockDataSerializer.Setup(ds => ds.DeserializePayload(completeMessage)).Returns(completePayload); + }); + + this.testRequestSender.DiscoverTests(discoveryCriteria, mockHandler.Object); + + this.mockCommunicationManager.Verify(mc => mc.SendMessage(MessageType.StartDiscovery, discoveryCriteria), Times.Once); + this.mockDataSerializer.Verify(ds => ds.DeserializeMessage(rawMessage), Times.Exactly(2)); + mockHandler.Verify(mh => mh.HandleDiscoveredTests(testCases), Times.Once); + mockHandler.Verify(mh => mh.HandleRawMessage(rawMessage), Times.Exactly(2)); + } + + [TestMethod] + public void DiscoverTestsShouldCallHandleLogMessageOnTestMessage() + { + var sources = new List() { "Hello", "World" }; + string settingsXml = "SettingsXml"; + var mockHandler = new Mock(); + var discoveryCriteria = new DiscoveryCriteria(sources, 100, settingsXml); + + var rawMessage = "TestMessage"; + var messagePayload = new TestMessagePayload() { MessageLevel = TestMessageLevel.Error, Message = rawMessage }; + var message = new Message() { MessageType = MessageType.TestMessage, Payload = null }; + + this.mockCommunicationManager.Setup(mc => mc.ReceiveRawMessage()).Returns(rawMessage); + this.mockDataSerializer.Setup(ds => ds.DeserializeMessage(rawMessage)).Returns(message); + this.mockDataSerializer.Setup(ds => ds.DeserializePayload(message)).Returns(messagePayload); + + + var completePayload = new DiscoveryCompletePayload() + { + IsAborted = false, + LastDiscoveredTests = null, + TotalTests = 1 + }; + var completeMessage = new Message() { MessageType = MessageType.DiscoveryComplete, Payload = null }; + mockHandler.Setup(mh => mh.HandleLogMessage(TestMessageLevel.Error, rawMessage)).Callback( + () => + { + this.mockDataSerializer.Setup(ds => ds.DeserializeMessage(It.IsAny())).Returns(completeMessage); + this.mockDataSerializer.Setup(ds => ds.DeserializePayload(completeMessage)).Returns(completePayload); + }); + + this.testRequestSender.DiscoverTests(discoveryCriteria, mockHandler.Object); + + this.mockCommunicationManager.Verify(mc => mc.SendMessage(MessageType.StartDiscovery, discoveryCriteria), Times.Once); + this.mockDataSerializer.Verify(ds => ds.DeserializeMessage(rawMessage), Times.Exactly(2)); + mockHandler.Verify(mh => mh.HandleLogMessage(TestMessageLevel.Error, rawMessage), Times.Once); + mockHandler.Verify(mh => mh.HandleRawMessage(rawMessage), Times.Exactly(2)); + } + + [TestMethod] + public void DiscoverTestsShouldCallHandleDiscoveryCompleteOnDiscoveryCompletion() + { + var sources = new List() { "Hello", "World" }; + string settingsXml = "SettingsXml"; + var mockHandler = new Mock(); + var discoveryCriteria = new DiscoveryCriteria(sources, 100, settingsXml); + + var rawMessage = "RunComplete"; + var completePayload = new DiscoveryCompletePayload() + { + IsAborted = false, + LastDiscoveredTests = null, + TotalTests = 1 + }; + var message = new Message() { MessageType = MessageType.DiscoveryComplete, Payload = null }; + + this.mockCommunicationManager.Setup(mc => mc.ReceiveRawMessage()).Returns(rawMessage); + this.mockDataSerializer.Setup(ds => ds.DeserializeMessage(rawMessage)).Returns(message); + this.mockDataSerializer.Setup(ds => ds.DeserializePayload(message)).Returns(completePayload); + + this.testRequestSender.DiscoverTests(discoveryCriteria, mockHandler.Object); + + this.mockCommunicationManager.Verify(mc => mc.SendMessage(MessageType.StartDiscovery, discoveryCriteria), Times.Once); + this.mockDataSerializer.Verify(ds => ds.DeserializeMessage(rawMessage), Times.Once); + mockHandler.Verify(mh => mh.HandleDiscoveryComplete(1, null, false), Times.Once); + mockHandler.Verify(mh => mh.HandleRawMessage(rawMessage), Times.Once); + } + + [TestMethod] + public void StartTestRunWithSourcesShouldCallHandleTestRunStatsChange() + { + var mockHandler = new Mock(); + var runCriteria = new TestRunCriteriaWithSources(null, null, null); + + var testRunChangedArgs = new TestRunChangedEventArgs(null, null, null); + var rawMessage = "OnTestRunStatsChange"; + var message = new Message() { MessageType = MessageType.TestRunStatsChange, Payload = null }; + + this.mockCommunicationManager.Setup(mc => mc.ReceiveRawMessage()).Returns(rawMessage); + this.mockDataSerializer.Setup(ds => ds.DeserializeMessage(rawMessage)).Returns(message); + this.mockDataSerializer.Setup(ds => ds.DeserializePayload(message)).Returns(testRunChangedArgs); + + + var completePayload = new TestRunCompletePayload() + { + ExecutorUris = null, + LastRunTests = null, + RunAttachments = null, + TestRunCompleteArgs = null + }; + var completeMessage = new Message() { MessageType = MessageType.ExecutionComplete, Payload = null }; + mockHandler.Setup(mh => mh.HandleTestRunStatsChange(testRunChangedArgs)).Callback( + () => + { + this.mockDataSerializer.Setup(ds => ds.DeserializeMessage(It.IsAny())).Returns(completeMessage); + this.mockDataSerializer.Setup(ds => ds.DeserializePayload(completeMessage)).Returns(completePayload); + }); + + var waitHandle = new AutoResetEvent(false); + mockHandler.Setup(mh => mh.HandleTestRunComplete(It.IsAny(), + It.IsAny(), It.IsAny>(), It.IsAny>())).Callback + (() => waitHandle.Set()); + + this.testRequestSender.StartTestRun(runCriteria, mockHandler.Object); + + waitHandle.WaitOne(); + + this.mockCommunicationManager.Verify(mc => mc.SendMessage(MessageType.StartTestExecutionWithSources, runCriteria), Times.Once); + + // One for run stats and another for runcomplete + this.mockDataSerializer.Verify(ds => ds.DeserializeMessage(rawMessage), Times.Exactly(2)); + mockHandler.Verify(mh => mh.HandleTestRunStatsChange(testRunChangedArgs), Times.Once); + mockHandler.Verify(mh => mh.HandleRawMessage(rawMessage), Times.Exactly(2)); + } + + + [TestMethod] + public void StartTestRunWithTestsShouldCallHandleTestRunStatsChange() + { + var mockHandler = new Mock(); + var runCriteria = new TestRunCriteriaWithTests(null, null, null); + + var testRunChangedArgs = new TestRunChangedEventArgs(null, null, null); + var rawMessage = "OnTestRunStatsChange"; + var message = new Message() { MessageType = MessageType.TestRunStatsChange, Payload = null }; + + this.mockCommunicationManager.Setup(mc => mc.ReceiveRawMessage()).Returns(rawMessage); + this.mockDataSerializer.Setup(ds => ds.DeserializeMessage(rawMessage)).Returns(message); + this.mockDataSerializer.Setup(ds => ds.DeserializePayload(message)).Returns(testRunChangedArgs); + + + var completePayload = new TestRunCompletePayload() + { + ExecutorUris = null, + LastRunTests = null, + RunAttachments = null, + TestRunCompleteArgs = null + }; + var completeMessage = new Message() { MessageType = MessageType.ExecutionComplete, Payload = null }; + mockHandler.Setup(mh => mh.HandleTestRunStatsChange(testRunChangedArgs)).Callback( + () => + { + this.mockDataSerializer.Setup(ds => ds.DeserializeMessage(It.IsAny())).Returns(completeMessage); + this.mockDataSerializer.Setup(ds => ds.DeserializePayload(completeMessage)).Returns(completePayload); + }); + + this.testRequestSender.StartTestRun(runCriteria, mockHandler.Object); + + this.mockCommunicationManager.Verify(mc => mc.SendMessage(MessageType.StartTestExecutionWithTests, runCriteria), Times.Once); + this.mockDataSerializer.Verify(ds => ds.DeserializeMessage(It.IsAny()), Times.Once); + mockHandler.Verify(mh => mh.HandleTestRunStatsChange(testRunChangedArgs), Times.Once); + mockHandler.Verify(mh => mh.HandleRawMessage(rawMessage), Times.AtLeastOnce); + } + + [TestMethod] + public void StartTestRunShouldCallHandleLogMessageOnTestMessage() + { + var mockHandler = new Mock(); + var runCriteria = new TestRunCriteriaWithSources(null, null, null); + + var rawMessage = "OnLogMessage"; + var message = new Message() { MessageType = MessageType.TestMessage, Payload = null }; + var payload = new TestMessagePayload() { MessageLevel = TestMessageLevel.Error, Message = rawMessage }; + + this.mockCommunicationManager.Setup(mc => mc.ReceiveRawMessage()).Returns(rawMessage); + this.mockDataSerializer.Setup(ds => ds.DeserializeMessage(rawMessage)).Returns(message); + this.mockDataSerializer.Setup(ds => ds.DeserializePayload(message)).Returns(payload); + + var completePayload = new TestRunCompletePayload() + { + ExecutorUris = null, LastRunTests = null, RunAttachments = null, TestRunCompleteArgs = null + }; + var completeMessage = new Message() { MessageType = MessageType.ExecutionComplete, Payload = null }; + mockHandler.Setup(mh => mh.HandleLogMessage(TestMessageLevel.Error, rawMessage)).Callback( + () => + { + this.mockDataSerializer.Setup(ds => ds.DeserializeMessage(It.IsAny())).Returns(completeMessage); + this.mockDataSerializer.Setup(ds => ds.DeserializePayload(completeMessage)).Returns(completePayload); + }); + + this.testRequestSender.StartTestRun(runCriteria, mockHandler.Object); + + this.mockCommunicationManager.Verify(mc => mc.SendMessage(MessageType.StartTestExecutionWithSources, runCriteria), Times.Once); + this.mockDataSerializer.Verify(ds => ds.DeserializeMessage(It.IsAny()), Times.Once); + mockHandler.Verify(mh => mh.HandleLogMessage(payload.MessageLevel, payload.Message), Times.Once); + mockHandler.Verify(mh => mh.HandleRawMessage(rawMessage), Times.AtLeastOnce); + } + + + [TestMethod] + public void StartTestRunShouldCallLaunchProcessWithDebuggerAndWaitForCallback() + { + var mockHandler = new Mock(); + var runCriteria = new TestRunCriteriaWithSources(null, null, null); + + var rawMessage = "LaunchProcessWithDebugger"; + var message = new Message() { MessageType = MessageType.LaunchAdapterProcessWithDebuggerAttached, Payload = null }; + var payload = new TestProcessStartInfo(); + + this.mockCommunicationManager.Setup(mc => mc.ReceiveRawMessage()).Returns(rawMessage); + this.mockDataSerializer.Setup(ds => ds.DeserializeMessage(rawMessage)).Returns(message); + this.mockDataSerializer.Setup(ds => ds.DeserializePayload(message)).Returns(payload); + + var completePayload = new TestRunCompletePayload() + { + ExecutorUris = null, + LastRunTests = null, + RunAttachments = null, + TestRunCompleteArgs = null + }; + var completeMessage = new Message() { MessageType = MessageType.ExecutionComplete, Payload = null }; + mockHandler.Setup(mh => mh.LaunchProcessWithDebuggerAttached(payload)).Callback( + () => + { + this.mockDataSerializer.Setup(ds => ds.DeserializeMessage(It.IsAny())).Returns(completeMessage); + this.mockDataSerializer.Setup(ds => ds.DeserializePayload(completeMessage)).Returns(completePayload); + }); + + var waitHandle = new AutoResetEvent(false); + mockHandler.Setup(mh => mh.HandleTestRunComplete(It.IsAny(), + It.IsAny(), It.IsAny>(), It.IsAny>())).Callback + (() => waitHandle.Set()); + + this.testRequestSender.StartTestRun(runCriteria, mockHandler.Object); + + waitHandle.WaitOne(); + + this.mockCommunicationManager.Verify(mc => mc.SendMessage(MessageType.StartTestExecutionWithSources, runCriteria), Times.Once); + + this.mockDataSerializer.Verify(ds => ds.DeserializeMessage(It.IsAny()), Times.Exactly(2)); + mockHandler.Verify(mh => mh.LaunchProcessWithDebuggerAttached(payload), Times.Once); + mockHandler.Verify(mh => mh.HandleRawMessage(rawMessage), Times.Exactly(2)); + this.mockCommunicationManager.Verify(mc => mc.SendMessage(MessageType.LaunchAdapterProcessWithDebuggerAttachedCallback, It.IsAny()), Times.Once); + } + + [TestMethod] + public void StartTestRunShouldCallHandleTestRunCompleteOnRunCompletion() + { + var mockHandler = new Mock(); + var runCriteria = new TestRunCriteriaWithTests(null, null, null); + + var rawMessage = "ExecComplete"; + var message = new Message() { MessageType = MessageType.ExecutionComplete, Payload = null }; + var payload = new TestRunCompletePayload() { ExecutorUris = null, LastRunTests = null, RunAttachments = null, TestRunCompleteArgs = null }; + + this.mockCommunicationManager.Setup(mc => mc.ReceiveRawMessage()).Returns(rawMessage); + this.mockDataSerializer.Setup(ds => ds.DeserializeMessage(rawMessage)).Returns(message); + this.mockDataSerializer.Setup(ds => ds.DeserializePayload(message)).Returns(payload); + + this.testRequestSender.StartTestRun(runCriteria, mockHandler.Object); + + this.mockCommunicationManager.Verify(mc => mc.SendMessage(MessageType.StartTestExecutionWithTests, runCriteria), Times.Once); + this.mockDataSerializer.Verify(ds => ds.DeserializeMessage(rawMessage), Times.Once); + mockHandler.Verify(mh => mh.HandleTestRunComplete(null, null, null, null), Times.Once); + mockHandler.Verify(mh => mh.HandleRawMessage(rawMessage), Times.Once); + } + + [TestMethod] + public void EndSessionShouldSendCorrectEventMessage() + { + this.testRequestSender.EndSession(); + + this.mockCommunicationManager.Verify(mc => mc.SendMessage(MessageType.SessionEnd), Times.Once); + } + + [TestMethod] + public void CancelTestRunSessionShouldSendCorrectEventMessage() + { + this.testRequestSender.SendTestRunCancel(); + + this.mockCommunicationManager.Verify(mc => mc.SendMessage(MessageType.CancelTestRun), Times.Once); + } + } +} diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/project.json b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/project.json new file mode 100644 index 0000000000..04f02e2ca5 --- /dev/null +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/project.json @@ -0,0 +1,35 @@ +{ + "version": "15.0.0-*", + + "buildOptions": { + "delaySign": true, + "keyFile": "../../scripts/key.snk", + "warningsAsErrors": true + }, + + "dependencies": { + "Microsoft.NETCore.App": { + "type": "platform", + "version": "1.0.0-rc2-3002702" + }, + "dotnet-test-mstest": { + "version": "1.0.1-preview", + "exclude": "compile" + }, + "MSTest.TestFramework": "1.0.0-preview", + "moq.netcore": "4.4.0-beta8", + "System.Diagnostics.TraceSource": "4.0.0-rc2-24015", + "Microsoft.TestPlatform.CommunicationUtilities": "15.0.0-*" + }, + + "frameworks": { + "netcoreapp1.0": { + "imports": [ + "dnxcore50", + "portable-net45+win8" + ] + } + }, + + "testRunner": "mstest" +} \ No newline at end of file diff --git a/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/Microsoft.TestPlatform.CoreUtilities.UnitTests.xproj b/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/Microsoft.TestPlatform.CoreUtilities.UnitTests.xproj new file mode 100644 index 0000000000..72ed987b27 --- /dev/null +++ b/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/Microsoft.TestPlatform.CoreUtilities.UnitTests.xproj @@ -0,0 +1,22 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 83eaf11c-3fd7-49da-8673-9e81cc2bac66 + TestPlatform.CoreUtilities.UnitTests + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\ + v4.5.2 + + + 2.0 + + + + + + \ No newline at end of file diff --git a/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/Properties/AssemblyInfo.cs b/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..66df2bf5a6 --- /dev/null +++ b/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +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: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("TestPlatform.CoreUtilities.UnitTests")] +[assembly: AssemblyTrademark("")] + +// 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("83eaf11c-3fd7-49da-8673-9e81cc2bac66")] diff --git a/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/Utilities/JobQueueTests.cs b/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/Utilities/JobQueueTests.cs new file mode 100644 index 0000000000..3c9db77fe0 --- /dev/null +++ b/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/Utilities/JobQueueTests.cs @@ -0,0 +1,541 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.CoreUtilities.UnitTests +{ + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Threading; + + using Microsoft.VisualStudio.TestPlatform.Utilities; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class JobQueueTests + { + [TestMethod] + public void ConstructorThrowsWhenNullProcessHandlerIsProvided() + { + JobQueue jobQueue = null; + Assert.ThrowsException(() => + { + jobQueue = new JobQueue(null, "dp", int.MaxValue, int.MaxValue, false, (message) => { }); + }); + + if (jobQueue != null) + { + jobQueue.Dispose(); + } + } + + [TestMethod] + public void ThrowsWhenNullEmptyOrWhiteSpaceDisplayNameIsProvided() + { + JobQueue jobQueue = null; + Assert.ThrowsException(() => + { + jobQueue = new JobQueue(GetEmptyProcessHandler(), null, int.MaxValue, int.MaxValue, false, (message) => { }); + }); + Assert.ThrowsException(() => + { + jobQueue = new JobQueue(GetEmptyProcessHandler(), "", int.MaxValue, int.MaxValue, false, (message) => { }); + }); + Assert.ThrowsException(() => + { + jobQueue = new JobQueue(GetEmptyProcessHandler(), " ", int.MaxValue, int.MaxValue, false, (message) => { }); + }); + + if (jobQueue != null) + { + jobQueue.Dispose(); + } + } + + [TestMethod] + public void JobsCanBeAddedToTheQueueAndAreProcessedInTheOrderReceived() + { + // Setup the job process handler to keep track of the jobs. + var jobsProcessed = new List(); + Action processHandler = (job) => + { + jobsProcessed.Add(job); + }; + + // Setup Test Data. + var job1 = 1; + var job2 = 2; + var job3 = 3; + + // Queue the jobs and verify they are processed in the order added. + using (var queue = new JobQueue(processHandler, "dp", int.MaxValue, int.MaxValue, false, (message) => { })) + { + queue.QueueJob(job1, 0); + queue.QueueJob(job2, 0); + queue.QueueJob(job3, 0); + } + + Assert.AreEqual(job1, jobsProcessed[0]); + Assert.AreEqual(job2, jobsProcessed[1]); + Assert.AreEqual(job3, jobsProcessed[2]); + } + + [TestMethod] + public void JobsAreProcessedOnABackgroundThread() + { + // Setup the job process handler to keep track of the jobs. + var jobsProcessed = new List(); + Action processHandler = (job) => + { + jobsProcessed.Add(Thread.CurrentThread.ManagedThreadId); + }; + + // Queue the jobs and verify they are processed on a background thread. + using (var queue = new JobQueue(processHandler, "dp", int.MaxValue, int.MaxValue, false, (message) => { })) + { + queue.QueueJob("dp", 0); + } + + Assert.AreNotEqual(Thread.CurrentThread.ManagedThreadId, jobsProcessed[0]); + } + + [TestMethod] + public void ThrowsWhenQueuingAfterDisposed() + { + var queue = new JobQueue(GetEmptyProcessHandler(), "dp", int.MaxValue, int.MaxValue, false, (message) => { }); + queue.Dispose(); + + Assert.ThrowsException(() => + { + queue.QueueJob("dp", 0); + }); + } + + [TestMethod] + public void ThrowsWhenResumingAfterDisposed() + { + var queue = new JobQueue(GetEmptyProcessHandler(), "dp", int.MaxValue, int.MaxValue, false, (message) => { }); + queue.Dispose(); + + Assert.ThrowsException(() => + { + queue.Resume(); + }); + } + + [TestMethod] + public void ThrowsWhenPausingAfterDisposed() + { + var queue = new JobQueue(GetEmptyProcessHandler(), "dp", int.MaxValue, int.MaxValue, false, (message) => { }); + queue.Dispose(); + + Assert.ThrowsException(() => + { + queue.Pause(); + }); + } + + [TestMethod] + public void ThrowsWhenFlushingAfterDisposed() + { + var queue = new JobQueue(GetEmptyProcessHandler(), "dp", int.MaxValue, int.MaxValue, false, (message) => { }); + queue.Dispose(); + + Assert.ThrowsException(() => + { + queue.Flush(); + }); + } + + [TestMethod] + [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "queue is required to be disposed twice.")] + public void DisposeDoesNotThrowWhenCalledTwice() + { + var queue = new JobQueue(GetEmptyProcessHandler(), "dp", int.MaxValue, int.MaxValue, false, (message) => { }); + queue.Dispose(); + queue.Dispose(); + } + + [TestMethod] + public void OncePausedNoFurtherJobsAreProcessedUntilResumeIsCalled() + { + // Setup the job process handler to keep track of the jobs it is called with. + List processedJobs = new List(); + Action processHandler = (job) => + { + processedJobs.Add(job); + }; + + // Queue the jobs after paused and verify they are not procesed until resumed. + using (var queue = new JobQueue(processHandler, "dp", int.MaxValue, int.MaxValue, false, (message) => { })) + { + queue.Pause(); + queue.QueueJob("dp", 0); + queue.QueueJob("dp", 0); + queue.QueueJob("dp", 0); + + // Allow other threads to execute and verify no jobs processed because the queue is paused. + Thread.Sleep(0); + Assert.AreEqual(0, processedJobs.Count); + + queue.Resume(); + } + + Assert.AreEqual(3, processedJobs.Count); + } + + [TestMethod] + public void ThrowsWhenBeingDisposedWhileQueueIsPaused() + { + using (var queue = new JobQueue(GetEmptyProcessHandler(), "dp", int.MaxValue, int.MaxValue, false, (message) => { })) + { + queue.Pause(); + + Assert.ThrowsException(() => + { + queue.Dispose(); + }); + + queue.Resume(); + } + } + + [TestMethod] + public void FlushMethodWaitsForAllJobsToBeProcessedBeforeReturning() + { + // Setup the job process handler to keep track of the jobs it has processed. + var jobsProcessed = 0; + Action processHandler = (job) => + { + jobsProcessed++; + }; + + // Queue several jobs and verify they have been processed when wait returns. + using (var queue = new JobQueue(processHandler, "dp", int.MaxValue, int.MaxValue, false, (message) => { })) + { + queue.QueueJob("dp", 0); + queue.QueueJob("dp", 0); + queue.QueueJob("dp", 0); + + queue.Flush(); + + Assert.AreEqual(3, jobsProcessed); + } + } + + [TestMethod] + public void TestBlockAtEnqueueDueToLength() + { + ManualResetEvent allowJobProcessingHandlerToProceed = new ManualResetEvent(false); + AutoResetEvent jobProcessed = new AutoResetEvent(false); + + // process handler for the jobs in queue. It blocks on a job till the queue gets full and the handler sets the + // event allowHandlerToProceed. + Action processHandler = (job) => + { + allowJobProcessingHandlerToProceed.WaitOne(); + if (job.Equals("job11", StringComparison.OrdinalIgnoreCase)) + { + jobProcessed.Set(); + } + }; + + using (JobQueueWrapper queue = new JobQueueWrapper(processHandler, 5, int.MaxValue, true, allowJobProcessingHandlerToProceed)) + { + // run the same thing multiple times to ensure that the queue isn't in a erroneous state after being blocked. + for (int i = 0; i < 10; i++) + { + queue.QueueJob("job1", 0); + queue.QueueJob("job2", 0); + queue.QueueJob("job3", 0); + queue.QueueJob("job4", 0); + queue.QueueJob("job5", 0); + + // At this point only 5 jobs have been queued. Even if all are still in queue, still the need to block shouldn't have + // risen. So queue.enteredBlockingMethod would be false. + Assert.IsFalse(queue.IsEnqueueBlocked, "Entered the over-ridden blocking method at a wrong time."); + + queue.QueueJob("job6", 0); + queue.QueueJob("job7", 0); + queue.QueueJob("job8", 0); + queue.QueueJob("job9", 0); + queue.QueueJob("job10", 0); + queue.QueueJob("job11", 0); + + // By this point surely the queue would have blocked atleast once, hence setting queue.enteredBlockingMethod true. + Assert.IsTrue(queue.IsEnqueueBlocked, "Did not enter the over-ridden blocking method"); + + + // We wait till all jobs are finished, so that for the next iteration the queue is in a deterministic state. + jobProcessed.WaitOne(); + + // queue.enteredBlockingMethod is set to false to check it again in next iteration. Also + // allowJobProcessingHandlerToProceed is reset to block the handler again in next iteration. + queue.IsEnqueueBlocked = false; + allowJobProcessingHandlerToProceed.Reset(); + + // if we reach here it means that the queue was successfully blocked at some point in between job6 and job11 + // and subsequently unblocked. + } + } + } + + [TestMethod] + public void TestBlockAtEnqueueDueToSize() + { + ManualResetEvent allowJobProcessingHandlerToProceed = new ManualResetEvent(false); + AutoResetEvent jobProcessed = new AutoResetEvent(false); + + // process handler for the jobs in queue. It blocks on a job till the queue gets full and the handler sets the + // event allowHandlerToProceed. + Action processHandler = (job) => + { + allowJobProcessingHandlerToProceed.WaitOne(); + if (job.Equals("job11", StringComparison.OrdinalIgnoreCase)) + { + jobProcessed.Set(); + } + }; + + using (JobQueueWrapper queue = new JobQueueWrapper(processHandler, int.MaxValue, 40, true, allowJobProcessingHandlerToProceed)) + { + // run the same thing multiple times to ensure that the queue isn't in a erroneous state after being blocked. + for (int i = 0; i < 10; i++) + { + queue.QueueJob("job1", 8); + queue.QueueJob("job2", 8); + queue.QueueJob("job3", 8); + queue.QueueJob("job4", 8); + queue.QueueJob("job5", 8); + + // At this point exactly 80 bytes have been queued. Even if all are still in queue, still the need to block shouldn't + // have risen. So queue.enteredBlockingMethod would be false. + Assert.IsFalse(queue.IsEnqueueBlocked, "Entered the over-ridden blocking method at a wrong time."); + + queue.QueueJob("job6", 8); + queue.QueueJob("job7", 8); + queue.QueueJob("job8", 8); + queue.QueueJob("job9", 8); + queue.QueueJob("job10", 10); + queue.QueueJob("job11", 10); + + // By this point surely the queue would have blocked atleast once, hence setting queue.enteredBlockingMethod true. + Assert.IsTrue(queue.IsEnqueueBlocked, "Did not enter the over-ridden blocking method"); + + // We wait till all jobs are finished, so that for the next iteration the queue is in a deterministic state. + jobProcessed.WaitOne(); + + // queue.enteredBlockingMethod is set to false to check it again in next iteration. Also + // allowJobProcessingHandlerToProceed is reset to block the handler again in next iteration. + queue.IsEnqueueBlocked = false; + allowJobProcessingHandlerToProceed.Reset(); + + // if we reach here it means that the queue was successfully blocked at some point in between job6 and job11 + // and subsequently unblocked. + } + } + } + + [TestMethod] + public void TestBlockingDisabled() + { + ManualResetEvent allowJobProcessingHandlerToProceed = new ManualResetEvent(false); + AutoResetEvent jobProcessed = new AutoResetEvent(false); + + // process handler for the jobs in queue. It blocks on a job till the test method sets the + // event allowHandlerToProceed. + Action processHandler = (job) => + { + allowJobProcessingHandlerToProceed.WaitOne(); + if (job.Equals("job5", StringComparison.OrdinalIgnoreCase)) + { + jobProcessed.Set(); + } + }; + + using (JobQueueWrapper queue = new JobQueueWrapper(processHandler, 2, int.MaxValue, false, allowJobProcessingHandlerToProceed)) + { + // run the same thing multiple times to ensure that the queue isn't in a erroneous state after first run. + for (int i = 0; i < 10; i++) + { + queue.QueueJob("job1", 0); + queue.QueueJob("job2", 0); + + // At this point only 2 jobs have been queued. Even if all are still in queue, still the need to block shouldn't have + // risen. So queue.enteredBlockingMethod would be false regardless of the blocking disabled or not. + Assert.IsFalse(queue.IsEnqueueBlocked, "Entered the over-ridden blocking method at a wrong time."); + + queue.QueueJob("job3", 0); + queue.QueueJob("job4", 0); + queue.QueueJob("job5", 0); + + // queue.enteredBlockingMethod should still be false as the queue should not have blocked. + Assert.IsFalse(queue.IsEnqueueBlocked, "Entered the over-ridden blocking method though blocking is disabled."); + + // allow handlers to proceed. + allowJobProcessingHandlerToProceed.Set(); + + // We wait till all jobs are finished, so that for the next iteration the queue is in a deterministic state. + jobProcessed.WaitOne(); + + // queue.enteredBlockingMethod is set to false to check it again in next iteration. Also + // allowJobProcessingHandlerToProceed is reset to allow blocking the handler again in next iteration. + queue.IsEnqueueBlocked = false; + allowJobProcessingHandlerToProceed.Reset(); + + // if we reach here it means that the queue was never blocked. + } + } + } + + [TestMethod] + + public void TestLargeTestResultCanBeLoadedWithBlockingEnabled() + { + var jobProcessed = new AutoResetEvent(false); + + // process handler for the jobs in queue. + Action processHandler = (job) => + { + jobProcessed.Set(); + }; + + using (JobQueueNonBlocking queue = new JobQueueNonBlocking(processHandler)) + { + // run the same thing multiple times to ensure that the queue isn't in a erroneous state after first run. + for (var i = 0; i < 10; i++) + { + // we try to enqueue a job of size greater than bound on the queue. It should be queued without blocking as + // we check whether or not the queue size has exceeded the limit before actually queuing. + queue.QueueJob("job1", 8); + + // if queue.EnteredBlockingMethod is true, the enquing entered the over-ridden blocking method. This was not + // intended. + Assert.IsFalse(queue.EnteredBlockingMethod, "Entered the over-ridden blocking method."); + jobProcessed.WaitOne(); + } + } + } + + + [TestMethod] + + [Timeout(60000)] + public void TestDisposeUnblocksBlockedThreads() + { + var allowJobProcessingHandlerToProceed = new ManualResetEvent(false); + + using (var gotBlocked = new ManualResetEvent(false)) + { + var job1Running = new ManualResetEvent(false); + + // process handler for the jobs in queue. It blocks on a job till the test method sets the + // event allowHandlerToProceed. + Action processHandler = (job) => + { + if (job.Equals("job1", StringComparison.OrdinalIgnoreCase)) + job1Running.Set(); + + allowJobProcessingHandlerToProceed.WaitOne(); + }; + + var jobQueue = new JobQueueWrapper(processHandler, 1, int.MaxValue, true, gotBlocked); + + var queueThread = new Thread( + source => + { + jobQueue.QueueJob("job1", 0); + job1Running.WaitOne(); + jobQueue.QueueJob("job2", 0); + jobQueue.QueueJob("job3", 0); + allowJobProcessingHandlerToProceed.Set(); + }); + queueThread.Start(); + + gotBlocked.WaitOne(); + jobQueue.Dispose(); + queueThread.Join(); + } + } + + #region Implementation + + /// + /// a class that inherits from job queue and over rides the WaitForQueueToEmpty to allow for checking that blocking and + /// unblocking work as expected. + /// + internal class JobQueueWrapper : JobQueue + { + public JobQueueWrapper(Action processJob, + int maxNoOfStringsQueueCanHold, + int maxNoOfBytesQueueCanHold, + bool isBoundsEnabled, + ManualResetEvent queueGotBlocked) + : base(processJob, "foo", maxNoOfStringsQueueCanHold, maxNoOfBytesQueueCanHold, isBoundsEnabled, (message) => { }) + { + this.IsEnqueueBlocked = false; + this.queueGotBlocked = queueGotBlocked; + } + + protected override bool WaitForQueueToGetEmpty() + { + this.IsEnqueueBlocked = true; + this.queueGotBlocked.Set(); + return base.WaitForQueueToGetEmpty(); + } + + /// + /// Specifies whether enQueue was blocked or not. + /// + public bool IsEnqueueBlocked + { + get; + set; + } + + private ManualResetEvent queueGotBlocked; + } + + + + /// + /// a class that inherits from job queue and over rides the WaitForQueueToEmpty to simply setting a boolean to tell + /// whether or not the queue entered the blocking method during the enqueue process. + /// + internal class JobQueueNonBlocking : JobQueue + { + public JobQueueNonBlocking(Action processHandler) + : base(processHandler, "foo", 1, 5, true, (message) => { }) + { + EnteredBlockingMethod = false; + } + + public bool EnteredBlockingMethod { get; private set; } + + protected override bool WaitForQueueToGetEmpty() + { + EnteredBlockingMethod = true; + return true; + } + } + + #endregion + + #region Utility Methods + + /// + /// Returns a job processing handler which does nothing. + /// + /// Type of job the handler processes. + /// Job processing handler which does nothing. + private static Action GetEmptyProcessHandler() + { + Action handler = (job) => + { + }; + + return handler; + } + + #endregion + } +} diff --git a/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/project.json b/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/project.json new file mode 100644 index 0000000000..7d23b7c370 --- /dev/null +++ b/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/project.json @@ -0,0 +1,35 @@ +{ + "version": "15.0.0-*", + + "buildOptions": { + "delaySign": true, + "keyFile": "../../scripts/key.snk", + "warningsAsErrors": true + }, + + "dependencies": { + "Microsoft.NETCore.App": { + "type": "platform", + "version": "1.0.0-rc2-3002702" + }, + "dotnet-test-mstest": { + "version": "1.0.1-preview", + "exclude": "compile" + }, + "MSTest.TestFramework": "1.0.0-preview", + "moq.netcore": "4.4.0-beta8", + "System.Diagnostics.TraceSource": "4.0.0-rc2-24015", + "Microsoft.TestPlatform.CoreUtilities": "15.0.0-*" + }, + + "frameworks": { + "netcoreapp1.0": { + "imports": [ + "dnxcore50", + "portable-net45+win8" + ] + } + }, + + "testRunner": "mstest" +} \ No newline at end of file diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Adapter/FrameworkHandleTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Adapter/FrameworkHandleTests.cs new file mode 100644 index 0000000000..90a7c8ef24 --- /dev/null +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Adapter/FrameworkHandleTests.cs @@ -0,0 +1,120 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.CrossPlatEngine.UnitTests.Adapter +{ + using System; + + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Adapter; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Execution; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.ClientProtocol; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Moq; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + + [TestClass] + public class FrameworkHandleTests + { + [TestMethod] + public void EnableShutdownAfterTestRunShoudBeFalseByDefault() + { + var tec = new TestExecutionContext( + 100, + TimeSpan.MaxValue, + inIsolation: false, + keepAlive: false, + areTestCaseLevelEventsRequired: false, + isDebug: false, + testCaseFilter: string.Empty); + var frameworkHandle = new FrameworkHandle(null, new TestRunCache(100, TimeSpan.MaxValue, (s, r, ip) => { }), tec, null); + + Assert.IsFalse(frameworkHandle.EnableShutdownAfterTestRun); + } + + [TestMethod] + public void EnableShutdownAfterTestRunShoudBeSetAppropriately() + { + var tec = new TestExecutionContext( + 100, + TimeSpan.MaxValue, + inIsolation: false, + keepAlive: false, + areTestCaseLevelEventsRequired: false, + isDebug: false, + testCaseFilter: string.Empty); + var frameworkHandle = new FrameworkHandle(null, new TestRunCache(100, TimeSpan.MaxValue, (s, r, ip) => { }), tec, null); + + frameworkHandle.EnableShutdownAfterTestRun = true; + + Assert.IsTrue(frameworkHandle.EnableShutdownAfterTestRun); + } + + [TestMethod] + public void LaunchProcessWithDebuggerAttachedShouldThrowIfObjectIsDisposed() + { + var tec = new TestExecutionContext( + 100, + TimeSpan.MaxValue, + inIsolation: false, + keepAlive: false, + areTestCaseLevelEventsRequired: false, + isDebug: false, + testCaseFilter: string.Empty); + var frameworkHandle = new FrameworkHandle(null, new TestRunCache(100, TimeSpan.MaxValue, (s, r, ip) => { }), tec, null); + frameworkHandle.Dispose(); + + Assert.ThrowsException(() => frameworkHandle.LaunchProcessWithDebuggerAttached(null, null, null, null)); + } + + // TODO: Enable method once we fix the "IsDebug" in TestExecutionContext + // [TestMethod] + public void LaunchProcessWithDebuggerAttachedShouldThrowIfNotInDebugContext() + { + var tec = new TestExecutionContext( + 100, + TimeSpan.MaxValue, + inIsolation: false, + keepAlive: false, + areTestCaseLevelEventsRequired: false, + isDebug: false, + testCaseFilter: string.Empty); + var frameworkHandle = new FrameworkHandle(null, new TestRunCache(100, TimeSpan.MaxValue, (s, r, ip) => { }), tec, null); + + var isExceptionThrown = false; + try + { + frameworkHandle.LaunchProcessWithDebuggerAttached(null, null, null, null); + } + catch (InvalidOperationException exception) + { + isExceptionThrown = true; + Assert.AreEqual("This operation is not allowed in the context of a non-debug run.", exception.Message); + } + + Assert.IsTrue(isExceptionThrown); + } + + [TestMethod] + public void LaunchProcessWithDebuggerAttachedShouldCallRunEventsHandler() + { + var tec = new TestExecutionContext( + 100, + TimeSpan.MaxValue, + inIsolation: false, + keepAlive: false, + areTestCaseLevelEventsRequired: false, + isDebug: true, + testCaseFilter: string.Empty); + + var mockTestRunEventsHandler = new Mock(); + + var frameworkHandle = new FrameworkHandle(null, new TestRunCache(100, TimeSpan.MaxValue, (s, r, ip) => { }), + tec, mockTestRunEventsHandler.Object); + + frameworkHandle.LaunchProcessWithDebuggerAttached(null, null, null, null); + + mockTestRunEventsHandler.Verify(mt => + mt.LaunchProcessWithDebuggerAttached(It.IsAny()), Times.Once); + } + } +} diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Adapter/RunContextTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Adapter/RunContextTests.cs new file mode 100644 index 0000000000..4e3611f919 --- /dev/null +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Adapter/RunContextTests.cs @@ -0,0 +1,81 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.CrossPlatEngine.UnitTests.Adapter +{ + using System.Collections.Generic; + + using Microsoft.VisualStudio.TestPlatform.Common.Filtering; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Adapter; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class RunContextTests + { + private RunContext runContext; + + [TestInitialize] + public void TestInit() + { + this.runContext = new RunContext(); + } + + [TestMethod] + public void GetTestCaseFilterShouldReturnNullIfFilterExpressionIsNull() + { + this.runContext.FilterExpressionWrapper = null; + + Assert.IsNull(this.runContext.GetTestCaseFilter(null, (s) => { return null; })); + } + + [TestMethod] + public void GetTestCaseFilterShouldThrowOnfilterExpressionParsingError() + { + this.runContext.FilterExpressionWrapper = new FilterExpressionWrapper("Infinity"); + + var isExceptionThrown = false; + + try + { + this.runContext.GetTestCaseFilter(null, (s) => { return null; }); + } + catch (TestPlatformFormatException ex) + { + isExceptionThrown = true; + StringAssert.Contains(ex.Message, "Incorrect format for TestCaseFilter Error: Invalid Condition 'Infinity'. Specify the correct format and try again. Note that the incorrect format can lead to no test getting executed."); + } + + Assert.IsTrue(isExceptionThrown); + } + + [TestMethod] + public void GetTestCaseFilterShouldThrowOnInvalidProperties() + { + this.runContext.FilterExpressionWrapper = new FilterExpressionWrapper("highlyunlikelyproperty=unused"); + + var isExceptionThrown = false; + + try + { + this.runContext.GetTestCaseFilter(new List { "TestCategory" }, (s) => { return null; }); + } + catch (TestPlatformFormatException ex) + { + isExceptionThrown = true; + StringAssert.Contains(ex.Message, "No tests matched the filter because it contains one or more properties that are not valid (highlyunlikelyproperty). Specify filter expression containing valid properties (TestCategory) and try again."); + } + + Assert.IsTrue(isExceptionThrown); + } + + [TestMethod] + public void GetTestCaseFilterShouldReturnTestCaseFilter() + { + this.runContext.FilterExpressionWrapper = new FilterExpressionWrapper("TestCategory=Important"); + var filter = this.runContext.GetTestCaseFilter(new List { "TestCategory" }, (s) => { return null; }); + + Assert.IsNotNull(filter); + Assert.AreEqual("TestCategory=Important", filter.TestCaseFilterValue); + } + } +} diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Adapter/TestExecutionRecorderTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Adapter/TestExecutionRecorderTests.cs new file mode 100644 index 0000000000..038a2b2d86 --- /dev/null +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Adapter/TestExecutionRecorderTests.cs @@ -0,0 +1,152 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.CrossPlatEngine.UnitTests.Adapter +{ + using System; + using System.Collections.Generic; + + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Adapter; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Execution; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + using Moq; + + using TestPlatform.CrossPlatEngine.UnitTests.TestableImplementations; + + [TestClass] + public class TestExecutionRecorderTests + { + private TestableTestRunCache testableTestRunCache; + + [TestInitialize] + public void TestInit() + { + this.testableTestRunCache = new TestableTestRunCache(); + } + + [TestMethod] + public void AttachmentsShouldReturnEmptyListByDefault() + { + var testRecorder = new TestExecutionRecorder(null, this.testableTestRunCache); + + var attachments = testRecorder.Attachments; + + Assert.IsNotNull(attachments); + Assert.AreEqual(0, attachments.Count); + } + + [TestMethod] + public void RecordStartShouldUpdateTestRunCache() + { + var testCase = new TestCase("A.C.M", new Uri("executor://dummy"), "A"); + + var testRecorder = new TestExecutionRecorder(null, this.testableTestRunCache); + + testRecorder.RecordStart(testCase); + Assert.IsTrue(this.testableTestRunCache.TestStartedList.Contains(testCase)); + } + + [TestMethod] + public void RecordStartShouldSendTestCaseEvents() + { + var testCase = new TestCase("A.C.M", new Uri("executor://dummy"), "A"); + var mockTestCaseEventsHandler = new Mock(); + var testRecorder = new TestExecutionRecorder(mockTestCaseEventsHandler.Object, this.testableTestRunCache); + + testRecorder.RecordStart(testCase); + + mockTestCaseEventsHandler.Verify(tceh => tceh.SendTestCaseStart(testCase), Times.Once); + } + + [TestMethod] + public void RecordResultShouldUpdateTestRunCache() + { + var testCase = new TestCase("A.C.M", new Uri("executor://dummy"), "A"); + var testResult = new Microsoft.VisualStudio.TestPlatform.ObjectModel.TestResult(testCase); + var testRecorder = new TestExecutionRecorder(null, this.testableTestRunCache); + + testRecorder.RecordResult(testResult); + Assert.IsTrue(this.testableTestRunCache.TestResultList.Contains(testResult)); + } + + [TestMethod] + public void RecordResultShouldSendTestCaseEvents() + { + var testCase = new TestCase("A.C.M", new Uri("executor://dummy"), "A"); + var testResult = new Microsoft.VisualStudio.TestPlatform.ObjectModel.TestResult(testCase); + var mockTestCaseEventsHandler = new Mock(); + var testRecorder = new TestExecutionRecorder(mockTestCaseEventsHandler.Object, this.testableTestRunCache); + + testRecorder.RecordResult(testResult); + + mockTestCaseEventsHandler.Verify(tceh => tceh.SendTestResult(testResult), Times.Once); + } + + [TestMethod] + public void RecordEndShouldUpdateTestRunCache() + { + var testCase = new TestCase("A.C.M", new Uri("executor://dummy"), "A"); + + var testRecorder = new TestExecutionRecorder(null, this.testableTestRunCache); + + testRecorder.RecordEnd(testCase, TestOutcome.Passed); + Assert.IsTrue(this.testableTestRunCache.TestCompletedList.Contains(testCase)); + } + + [TestMethod] + public void RecordEndShouldSendTestCaseEvents() + { + var testCase = new TestCase("A.C.M", new Uri("executor://dummy"), "A"); + var mockTestCaseEventsHandler = new Mock(); + var testRecorder = new TestExecutionRecorder(mockTestCaseEventsHandler.Object, this.testableTestRunCache); + + testRecorder.RecordEnd(testCase, TestOutcome.Passed); + + mockTestCaseEventsHandler.Verify(tceh => tceh.SendTestCaseEnd(testCase, TestOutcome.Passed), Times.Once); + } + + [TestMethod] + public void RecordAttachmentsShouldAddToAttachmentSet() + { + var testRecorder = new TestExecutionRecorder(null, this.testableTestRunCache); + var attachmentSet = new List { new AttachmentSet(new Uri("attachment://dummy"), "attachment") }; + + testRecorder.RecordAttachments(attachmentSet); + + var attachments = testRecorder.Attachments; + + Assert.IsNotNull(attachments); + CollectionAssert.AreEqual(attachmentSet, attachments); + } + + [TestMethod] + public void RecordAttachmentsShouldAddToAttachmentSetForMultipleAttachments() + { + var testRecorder = new TestExecutionRecorder(null, this.testableTestRunCache); + var attachmentSet = new List + { + new AttachmentSet(new Uri("attachment://dummy"), "attachment"), + new AttachmentSet(new Uri("attachment://infinite"), "infinity") + }; + + testRecorder.RecordAttachments(attachmentSet); + + var attachments = testRecorder.Attachments; + + Assert.IsNotNull(attachments); + CollectionAssert.AreEqual(attachmentSet, attachments); + + var newAttachmentSet = new AttachmentSet(new Uri("attachment://median"), "mid"); + attachmentSet.Add(newAttachmentSet); + + testRecorder.RecordAttachments(new List { newAttachmentSet }); + + attachments = testRecorder.Attachments; + + Assert.IsNotNull(attachments); + CollectionAssert.AreEqual(attachmentSet, attachments); + } + } +} diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/ParallelOperationManagerTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/ParallelOperationManagerTests.cs new file mode 100644 index 0000000000..ac098ce335 --- /dev/null +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/ParallelOperationManagerTests.cs @@ -0,0 +1,108 @@ +namespace TestPlatform.CrossPlatEngine.UnitTests.Client +{ + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Moq; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + + [TestClass] + public class ParallelOperationManagerTests + { + private MockParallelOperationManager proxyParallelManager; + + [TestMethod] + public void AbstractProxyParallelManagerShouldCreateCorrectNumberOfConcurrentObjects() + { + var createdSampleClasses = new List(); + Func sampleCreator = + () => + { + var sample = new SampleConcurrentClass(); + createdSampleClasses.Add(sample); + return sample; + }; + + this.proxyParallelManager = new MockParallelOperationManager(sampleCreator, 3); + + Assert.AreEqual(3, createdSampleClasses.Count, "Number of Concurrent Objects created should be 3"); + } + + [TestMethod] + public void AbstractProxyParallelManagerShouldUpdateToCorrectNumberOfConcurrentObjects() + { + var createdSampleClasses = new List(); + Func sampleCreator = + () => + { + var sample = new SampleConcurrentClass(); + createdSampleClasses.Add(sample); + return sample; + }; + + this.proxyParallelManager = new MockParallelOperationManager(sampleCreator, 1); + + Assert.AreEqual(1, createdSampleClasses.Count, "Number of Concurrent Objects created should be 1"); + + this.proxyParallelManager.UpdateParallelLevel(4); + + Assert.AreEqual(4, createdSampleClasses.Count, "Number of Concurrent Objects created should be 4"); + } + + [TestMethod] + public void DoActionOnConcurrentObjectsShouldCallAllObjects() + { + var createdSampleClasses = new List(); + Func sampleCreator = + () => + { + var sample = new SampleConcurrentClass(); + createdSampleClasses.Add(sample); + return sample; + }; + + this.proxyParallelManager = new MockParallelOperationManager(sampleCreator, 4); + + Assert.AreEqual(4, createdSampleClasses.Count, "Number of Concurrent Objects created should be 4"); + + int count = 0; + this.proxyParallelManager.DoActionOnAllConcurrentObjects( + (sample) => + { + count++; + Assert.IsTrue(createdSampleClasses.Contains(sample), "Called object must be in the created list."); + // Make sure action is not called on same object multiple times + createdSampleClasses.Remove(sample); + }); + + Assert.AreEqual(4, count, "Number of Concurrent Objects called should be 4"); + + Assert.AreEqual(0, createdSampleClasses.Count, "All concurrent objects must be called."); + } + + private class MockParallelOperationManager : ParallelOperationManager + { + public MockParallelOperationManager(Func createNewClient, int parallelLevel) : + base(createNewClient, parallelLevel) + { + } + + public void DoActionOnAllConcurrentObjects(Action action) + { + this.DoActionOnAllManagers(action, false); + } + + protected override void DisposeInstance(SampleConcurrentClass clientInstance) + { + clientInstance.IsDisposeCalled = true; + } + } + + private class SampleConcurrentClass + { + public bool IsDisposeCalled = false; + } + } +} diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/ParallelProxyExecutionManagerTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/ParallelProxyExecutionManagerTests.cs new file mode 100644 index 0000000000..e3339072b6 --- /dev/null +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/ParallelProxyExecutionManagerTests.cs @@ -0,0 +1,542 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.CrossPlatEngine.UnitTests.Client +{ + using System.Collections.Generic; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + using Moq; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; + using System; + using System.Collections.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.Parallel; + + [TestClass] + public class ParallelProxyExecutionManagerTests + { + private IParallelProxyExecutionManager proxyParallelExecutionManager; + + [TestMethod] + public void InitializeShouldCallAllConcurrentManagersOnce() + { + var createdMockManagers = new List>(); + Func proxyManagerFunc = + () => + { + var manager = new Mock(); + createdMockManagers.Add(manager); + return manager.Object; + }; + + this.proxyParallelExecutionManager = new ParallelProxyExecutionManager(proxyManagerFunc, 3); + this.proxyParallelExecutionManager.Initialize(null); + + Assert.AreEqual(3, createdMockManagers.Count, "Number of Concurrent Managers created should be 3"); + + foreach (var manager in createdMockManagers) + { + manager.Verify(m => m.Initialize(null), Times.Once); + } + } + + [TestMethod] + public void AbortShouldCallAllConcurrentManagersOnce() + { + var createdMockManagers = new List>(); + Func proxyManagerFunc = + () => + { + var manager = new Mock(); + createdMockManagers.Add(manager); + return manager.Object; + }; + + this.proxyParallelExecutionManager = new ParallelProxyExecutionManager(proxyManagerFunc, 4); + this.proxyParallelExecutionManager.Abort(); + + Assert.AreEqual(4, createdMockManagers.Count, "Number of Concurrent Managers created should be 4"); + + foreach (var manager in createdMockManagers) + { + manager.Verify(m => m.Abort(), Times.Once); + } + } + + [TestMethod] + public void CancelShouldCallAllConcurrentManagersOnce() + { + var createdMockManagers = new List>(); + Func proxyManagerFunc = + () => + { + var manager = new Mock(); + createdMockManagers.Add(manager); + return manager.Object; + }; + + this.proxyParallelExecutionManager = new ParallelProxyExecutionManager(proxyManagerFunc, 4); + this.proxyParallelExecutionManager.Cancel(); + + Assert.AreEqual(4, createdMockManagers.Count, "Number of Concurrent Managers created should be 4"); + + foreach (var manager in createdMockManagers) + { + manager.Verify(m => m.Cancel(), Times.Once); + } + } + + [TestMethod] + public void StartTestRunShouldProcessAllSources() + { + var createdMockManagers = new List>(); + Func proxyManagerFunc = + () => + { + var manager = new Mock(); + createdMockManagers.Add(manager); + return manager.Object; + }; + + proxyParallelExecutionManager = new ParallelProxyExecutionManager(proxyManagerFunc, 2); + + var mockHandler = new Mock(); + + var sources = new List() { "1.dll", "2.dll" }; + + var testRunCriteria = new TestRunCriteria(sources, 100); + + var processedSources = new List(); + var syncObject = new object(); + foreach (var manager in createdMockManagers) + { + manager.Setup(m => m.StartTestRun(It.IsAny(), It.IsAny())). + Callback( + (criteria, handler) => + { + lock (syncObject) + { + processedSources.AddRange(criteria.Sources); + } + + Task.Delay(100).Wait(); + + handler.HandleTestRunComplete( + new TestRunCompleteEventArgs(new TestRunStatistics(new Dictionary()) + , false, false, null, null, TimeSpan.Zero) + , null, null, null); + }); + } + + AutoResetEvent completeEvent = new AutoResetEvent(false); + + mockHandler.Setup(mh => mh.HandleTestRunComplete(It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny>())) + .Callback, ICollection>( + (testRunCompleteArgs, testRunChangedEventArgs, attachmentSets, executorUris) => + { + completeEvent.Set(); + }); + + proxyParallelExecutionManager.StartTestRun(testRunCriteria, mockHandler.Object); + completeEvent.WaitOne(); + + Assert.AreEqual(sources.Count, processedSources.Count, "All Sources must be processed."); + + foreach (var source in sources) + { + bool matchFound = false; + + foreach (var processedSrc in processedSources) + { + if (processedSrc.Equals(source)) + { + if (matchFound) Assert.Fail("Concurrreny issue detected: Source['{0}'] got processed twice", processedSrc); + matchFound = true; + } + } + + Assert.IsTrue(matchFound, "Concurrency issue detected: Source['{0}'] did NOT get processed at all", source); + } + + } + + [TestMethod] + public void StartTestRunShouldProcessAllTestCases() + { + var createdMockManagers = new List>(); + Func proxyManagerFunc = + () => + { + var manager = new Mock(); + createdMockManagers.Add(manager); + return manager.Object; + }; + + proxyParallelExecutionManager = new ParallelProxyExecutionManager(proxyManagerFunc, 3); + + var mockHandler = new Mock(); + + TestCase tc1 = new TestCase("dll1.class1.test1", new Uri("hello://x/"), "1.dll"); + TestCase tc21 = new TestCase("dll2.class21.test21", new Uri("hello://x/"), "2.dll"); + TestCase tc22 = new TestCase("dll2.class21.test22", new Uri("hello://x/"), "2.dll"); + TestCase tc31 = new TestCase("dll3.class31.test31", new Uri("hello://x/"), "3.dll"); + TestCase tc32 = new TestCase("dll3.class31.test32", new Uri("hello://x/"), "3.dll"); + + var tests = new List() { tc1, tc21, tc22, tc31, tc32 }; + + var testRunCriteria = new TestRunCriteria(tests, 100); + + var processedTestCases = new List(); + var syncObject = new object(); + foreach (var manager in createdMockManagers) + { + manager.Setup(m => m.StartTestRun(It.IsAny(), It.IsAny())). + Callback( + (criteria, handler) => + { + lock (syncObject) + { + processedTestCases.AddRange(criteria.Tests); + } + + Task.Delay(100).Wait(); + + handler.HandleTestRunComplete( + new TestRunCompleteEventArgs(new TestRunStatistics(new Dictionary()) + , false, false, null, null, TimeSpan.Zero) + , null, null, null); + }); + } + + AutoResetEvent completeEvent = new AutoResetEvent(false); + + mockHandler.Setup(mh => mh.HandleTestRunComplete(It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny>())) + .Callback, ICollection>( + (testRunCompleteArgs, testRunChangedEventArgs, attachmentSets, executorUris) => + { + completeEvent.Set(); + }); + + proxyParallelExecutionManager.StartTestRun(testRunCriteria, mockHandler.Object); + completeEvent.WaitOne(); + + Assert.AreEqual(tests.Count, processedTestCases.Count, "All Tests must be processed."); + + foreach (var test in tests) + { + bool matchFound = false; + + foreach (var processedTest in processedTestCases) + { + if (processedTest.FullyQualifiedName.Equals(test.FullyQualifiedName)) + { + if (matchFound) Assert.Fail("Concurrreny issue detected: Test['{0}'] got processed twice", test.FullyQualifiedName); + matchFound = true; + } + } + + Assert.IsTrue(matchFound, "Concurrency issue detected: Test['{0}'] did NOT get processed at all", test.FullyQualifiedName); + } + + } + + + [TestMethod] + public void StartTestRunWithSourcesShouldNotSendCompleteUntilAllSourcesAreProcessed() + { + var createdMockManagers = new List>(); + Func proxyManagerFunc = + () => + { + var manager = new Mock(); + createdMockManagers.Add(manager); + return manager.Object; + }; + + proxyParallelExecutionManager = new ParallelProxyExecutionManager(proxyManagerFunc, 2); + + var mockHandler = new Mock(); + + var sources = new List() { "1.dll", "2.dll" }; + + var testRunCriteria = new TestRunCriteria(sources, 100); + + var processedSources = new List(); + var syncObject = new object(); + foreach (var manager in createdMockManagers) + { + manager.Setup(m => m.StartTestRun(It.IsAny(), It.IsAny())). + Callback( + (criteria, handler) => + { + lock (syncObject) + { + processedSources.AddRange(criteria.Sources); + } + + Task.Delay(100).Wait(); + + var completeArgs = new TestRunCompleteEventArgs(new + TestRunStatistics(new Dictionary()), + false, false, null, null, TimeSpan.FromMilliseconds(1)); + handler.HandleTestRunComplete(completeArgs, null, null, null); + }); + } + + AutoResetEvent eventHandle = new AutoResetEvent(false); + + mockHandler.Setup(m => m.HandleTestRunComplete( + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny>())).Callback + , ICollection>( + (completeArgs, runChangedArgs, runAttachments, executorUris) => + { + eventHandle.Set(); + }); + + Task.Run(() => + { + proxyParallelExecutionManager.StartTestRun(testRunCriteria, mockHandler.Object); + }); + + eventHandle.WaitOne(); + + Assert.AreEqual(sources.Count, processedSources.Count, "All Sources must be processed."); + + foreach (var source in sources) + { + bool matchFound = false; + + foreach (var processedSrc in processedSources) + { + if (processedSrc.Equals(source)) + { + if (matchFound) Assert.Fail("Concurrreny issue detected: Source['{0}'] got processed twice", processedSrc); + matchFound = true; + } + } + + Assert.IsTrue(matchFound, "Concurrency issue detected: Source['{0}'] did NOT get processed at all", source); + } + + } + + + [TestMethod] + public void StartTestRunWithTestsShouldNotSendCompleteUntilAllTestsAreProcessed() + { + var createdMockManagers = new List>(); + Func proxyManagerFunc = + () => + { + var manager = new Mock(); + createdMockManagers.Add(manager); + return manager.Object; + }; + + proxyParallelExecutionManager = new ParallelProxyExecutionManager(proxyManagerFunc, 3); + + var mockHandler = new Mock(); + + TestCase tc1 = new TestCase("dll1.class1.test1", new Uri("hello://x/"), "1.dll"); + TestCase tc21 = new TestCase("dll2.class21.test21", new Uri("hello://x/"), "2.dll"); + TestCase tc22 = new TestCase("dll2.class21.test22", new Uri("hello://x/"), "2.dll"); + TestCase tc31 = new TestCase("dll3.class31.test31", new Uri("hello://x/"), "3.dll"); + TestCase tc32 = new TestCase("dll3.class31.test32", new Uri("hello://x/"), "3.dll"); + + var tests = new List() { tc1, tc21, tc22, tc31, tc32 }; + + var testRunCriteria = new TestRunCriteria(tests, 100); + + var processedTestCases = new List(); + var syncObject = new object(); + foreach (var manager in createdMockManagers) + { + manager.Setup(m => m.StartTestRun(It.IsAny(), It.IsAny())). + Callback( + (criteria, handler) => + { + lock (syncObject) + { + processedTestCases.AddRange(criteria.Tests); + } + + Task.Delay(100).Wait(); + + var completeArgs = new TestRunCompleteEventArgs(new + TestRunStatistics(new Dictionary()), + false, false, null, null, TimeSpan.FromMilliseconds(1)); + handler.HandleTestRunComplete(completeArgs, null, null, null); + }); + } + + AutoResetEvent eventHandle = new AutoResetEvent(false); + + mockHandler.Setup(m => m.HandleTestRunComplete( + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny>())).Callback + , ICollection>( + (completeArgs, runChangedArgs, runAttachments, executorUris) => + { + eventHandle.Set(); + }); + + Task.Run(() => + { + proxyParallelExecutionManager.StartTestRun(testRunCriteria, mockHandler.Object); + }); + + eventHandle.WaitOne(); + + Assert.AreEqual(tests.Count, processedTestCases.Count, "All Tests must be processed."); + + foreach (var test in tests) + { + bool matchFound = false; + + foreach (var processedTest in processedTestCases) + { + if (processedTest.FullyQualifiedName.Equals(test.FullyQualifiedName)) + { + if (matchFound) Assert.Fail("Concurrreny issue detected: Test['{0}'] got processed twice", test.FullyQualifiedName); + matchFound = true; + } + } + + Assert.IsTrue(matchFound, "Concurrency issue detected: Test['{0}'] did NOT get processed at all", test.FullyQualifiedName); + } + + } + + + [TestMethod] + public void StartTestRunShouldAggregateRunData() + { + var createdMockManagers = new List>(); + Func proxyManagerFunc = + () => + { + var manager = new Mock(); + createdMockManagers.Add(manager); + return manager.Object; + }; + + proxyParallelExecutionManager = new ParallelProxyExecutionManager(proxyManagerFunc, 2); + + var mockHandler = new Mock(); + + var sources = new List() { "1.dll", "2.dll" }; + + var testRunCriteria = new TestRunCriteria(sources, 100); + + var processedSources = new List(); + var syncObject = new object(); + + foreach (var manager in createdMockManagers) + { + manager.Setup(m => m.StartTestRun(It.IsAny(), It.IsAny())). + Callback( + (criteria, handler) => + { + lock (syncObject) + { + processedSources.AddRange(criteria.Sources); + } + + Task.Delay(100).Wait(); + + var stats = new Dictionary(); + stats.Add(TestOutcome.Passed, 3); + stats.Add(TestOutcome.Failed, 2); + + var runAttachments = new Collection(); + runAttachments.Add(new AttachmentSet(new Uri("hello://x/"), "Hello")); + + var executorUris = new List() { "hello1" }; + + bool isCanceled = false; + bool isAborted = false; + TimeSpan timespan = TimeSpan.FromMilliseconds(100); + if (string.Equals(criteria.Sources?.FirstOrDefault(), "2.dll")) + { + isCanceled = true; + isAborted = true; + timespan = TimeSpan.FromMilliseconds(200); + } + + var completeArgs = new TestRunCompleteEventArgs(new + TestRunStatistics(stats), isCanceled, isAborted, null, runAttachments, timespan); + + handler.HandleTestRunComplete(completeArgs, null, runAttachments, executorUris); + }); + } + + AutoResetEvent eventHandle = new AutoResetEvent(false); + + mockHandler.Setup(m => m.HandleTestRunComplete( + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny>())).Callback + , ICollection>( + (completeArgs, runChangedArgs, runAttachments, executorUris) => + { + eventHandle.Set(); + + Assert.AreEqual(TimeSpan.FromMilliseconds(200), completeArgs.ElapsedTimeInRunningTests, "Time should be max of all"); + Assert.AreEqual(2, completeArgs.AttachmentSets.Count, "All Complete Arg attachments should return"); + Assert.AreEqual(2, runAttachments.Count, "All RunContextAttachments should return"); + + Assert.IsTrue(completeArgs.IsAborted, "Aborted value must be OR of all values"); + Assert.IsTrue(completeArgs.IsCanceled, "Canceled value must be OR of all values"); + + Assert.AreEqual(10, completeArgs.TestRunStatistics.ExecutedTests, "Stats must be aggregated properly"); + + Assert.AreEqual(6, completeArgs.TestRunStatistics.Stats[TestOutcome.Passed], "Stats must be aggregated properly"); + Assert.AreEqual(4, completeArgs.TestRunStatistics.Stats[TestOutcome.Failed], "Stats must be aggregated properly"); + }); + + Task.Run(() => + { + proxyParallelExecutionManager.StartTestRun(testRunCriteria, mockHandler.Object); + }); + + eventHandle.WaitOne(); + + Assert.AreEqual(sources.Count, processedSources.Count, "All Sources must be processed."); + + foreach (var source in sources) + { + bool matchFound = false; + + foreach (var processedSrc in processedSources) + { + if (processedSrc.Equals(source)) + { + if (matchFound) Assert.Fail("Concurrreny issue detected: Source['{0}'] got processed twice", processedSrc); + matchFound = true; + } + } + + Assert.IsTrue(matchFound, "Concurrency issue detected: Source['{0}'] did NOT get processed at all", source); + } + } + + } +} diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/ParallelRunDataAggregatorTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/ParallelRunDataAggregatorTests.cs new file mode 100644 index 0000000000..5b328c7c7c --- /dev/null +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/ParallelRunDataAggregatorTests.cs @@ -0,0 +1,241 @@ +using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.Parallel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Threading.Tasks; + +namespace TestPlatform.CrossPlatEngine.UnitTests.Client.Parallel +{ + [TestClass] + public class ParallelRunDataAggregatorTests + { + [TestMethod] + public void ParallelRunDataAggregatorConstructorShouldInitializeAggregatorVars() + { + var aggregator = new ParallelRunDataAggregator(); + + Assert.AreEqual(aggregator.ElapsedTime, TimeSpan.Zero, "Timespan must be initialized to zero."); + + Assert.IsNotNull(aggregator.Exceptions, "Exceptions list must not be null"); + Assert.IsNotNull(aggregator.ExecutorUris, "ExecutorUris list must not be null"); + Assert.IsNotNull(aggregator.RunCompleteArgsAttachments, "RunCompleteArgsAttachments list must not be null"); + Assert.IsNotNull(aggregator.RunContextAttachments, "RunContextAttachments list must not be null"); + + Assert.AreEqual(aggregator.Exceptions.Count, 0, "Exceptions List must be initialized as empty list."); + Assert.AreEqual(aggregator.ExecutorUris.Count, 0, "Exceptions List must be initialized as empty list."); + Assert.AreEqual(aggregator.RunCompleteArgsAttachments.Count, 0, "RunCompleteArgsAttachments List must be initialized as empty list."); + Assert.AreEqual(aggregator.RunContextAttachments.Count, 0, "RunContextAttachments List must be initialized as empty list"); + + Assert.IsFalse(aggregator.IsAborted, "Aborted must be false by default"); + + Assert.IsFalse(aggregator.IsCanceled, "Canceled must be false by default"); + } + + [TestMethod] + public void AggregateShouldAggregateRunCompleteAttachmentsCorrectly() + { + var aggregator = new ParallelRunDataAggregator(); + + var attachmentSet1 = new Collection(); + attachmentSet1.Add(new AttachmentSet(new Uri("x://hello1"), "hello1")); + + aggregator.Aggregate(null, null, null, TimeSpan.Zero, false, false, null, attachmentSet1); + + + Assert.AreEqual(aggregator.RunCompleteArgsAttachments.Count, 1, "RunCompleteArgsAttachments List must have data."); + + var attachmentSet2 = new Collection(); + attachmentSet2.Add(new AttachmentSet(new Uri("x://hello2"), "hello2")); + + aggregator.Aggregate(null, null, null, TimeSpan.Zero, false, false, null, attachmentSet2); + + Assert.AreEqual(aggregator.RunCompleteArgsAttachments.Count, 2, "RunCompleteArgsAttachments List must have aggregated data."); + } + + [TestMethod] + public void AggregateShouldAggregateRunContextAttachmentsCorrectly() + { + var aggregator = new ParallelRunDataAggregator(); + + var attachmentSet1 = new Collection(); + attachmentSet1.Add(new AttachmentSet(new Uri("x://hello1"), "hello1")); + + aggregator.Aggregate(null, null, null, TimeSpan.Zero, false, false, attachmentSet1, null); + + Assert.AreEqual(aggregator.RunContextAttachments.Count, 1, "RunContextAttachments List must have data."); + + var attachmentSet2 = new Collection(); + attachmentSet2.Add(new AttachmentSet(new Uri("x://hello2"), "hello2")); + + aggregator.Aggregate(null, null, null, TimeSpan.Zero, false, false, attachmentSet2, null); + + Assert.AreEqual(aggregator.RunContextAttachments.Count, 2, "RunContextAttachments List must have aggregated data."); + } + + + [TestMethod] + public void AggregateShouldAggregateAbortedAndCanceledCorrectly() + { + var aggregator = new ParallelRunDataAggregator(); + + aggregator.Aggregate(null, null, null, TimeSpan.Zero, isAborted: false, isCanceled: false, runContextAttachments: null, + runCompleteArgsAttachments: null); + + Assert.IsFalse(aggregator.IsAborted, "Aborted must be false"); + + Assert.IsFalse(aggregator.IsCanceled, "Canceled must be false"); + + aggregator.Aggregate(null, null, null, TimeSpan.Zero, isAborted: true, isCanceled: false, runContextAttachments: null, + runCompleteArgsAttachments: null); + + Assert.IsTrue(aggregator.IsAborted, "Aborted must be true"); + + Assert.IsFalse(aggregator.IsCanceled, "Canceled must still be false"); + + aggregator.Aggregate(null, null, null, TimeSpan.Zero, isAborted: false, isCanceled: true, runContextAttachments: null, + runCompleteArgsAttachments: null); + + Assert.IsTrue(aggregator.IsAborted, "Aborted must continue be true"); + + Assert.IsTrue(aggregator.IsCanceled, "Canceled must be true"); + + aggregator.Aggregate(null, null, null, TimeSpan.Zero, isAborted: false, isCanceled: false, runContextAttachments: null, + runCompleteArgsAttachments: null); + + Assert.IsTrue(aggregator.IsAborted, "Aborted must continue be true"); + + Assert.IsTrue(aggregator.IsCanceled, "Canceled must continue be true"); + + } + + [TestMethod] + public void AggregateShouldAggregateTimeSpanCorrectly() + { + var aggregator = new ParallelRunDataAggregator(); + + aggregator.Aggregate(null, null, null, TimeSpan.Zero, isAborted: false, isCanceled: false, runContextAttachments: null, + runCompleteArgsAttachments: null); + + Assert.AreEqual(aggregator.ElapsedTime, TimeSpan.Zero, "Timespan must be zero"); + + aggregator.Aggregate(null, null, null, TimeSpan.FromMilliseconds(100), isAborted: false, isCanceled: false, runContextAttachments: null, + runCompleteArgsAttachments: null); + + Assert.AreEqual(aggregator.ElapsedTime, TimeSpan.FromMilliseconds(100), "Timespan must be 100ms"); + + + aggregator.Aggregate(null, null, null, TimeSpan.FromMilliseconds(200), isAborted: false, isCanceled: false, runContextAttachments: null, + runCompleteArgsAttachments: null); + + Assert.AreEqual(aggregator.ElapsedTime, TimeSpan.FromMilliseconds(200), "Timespan should be Max of all 200ms"); + + aggregator.Aggregate(null, null, null, TimeSpan.FromMilliseconds(150), isAborted: false, isCanceled: false, runContextAttachments: null, + runCompleteArgsAttachments: null); + + Assert.AreEqual(aggregator.ElapsedTime, TimeSpan.FromMilliseconds(200), "Timespan should be Max of all i.e. 200ms"); + } + + [TestMethod] + public void AggregateShouldAggregateExceptionsCorrectly() + { + var aggregator = new ParallelRunDataAggregator(); + + aggregator.Aggregate(null, null, exception: null, elapsedTime: TimeSpan.Zero, isAborted: false, isCanceled: false, runContextAttachments: null, + runCompleteArgsAttachments: null); + + Assert.IsNull(aggregator.GetAggregatedException(), "Aggregated exception must be null"); + + var exception1 = new NotImplementedException(); + aggregator.Aggregate(null, null, exception: exception1, elapsedTime: TimeSpan.Zero, isAborted: false, isCanceled: false, runContextAttachments: null, + runCompleteArgsAttachments: null); + + var aggregatedException = aggregator.GetAggregatedException() as AggregateException; + Assert.IsNotNull(aggregatedException, "Aggregated exception must NOT be null"); + Assert.IsNotNull(aggregatedException.InnerExceptions, "Inner exception list must NOT be null"); + Assert.AreEqual(aggregatedException.InnerExceptions.Count, 1, "Inner exception lsit must have one element"); + Assert.AreEqual(aggregatedException.InnerExceptions[0], exception1, "Inner exception must be the one set."); + + var exception2 = new NotSupportedException(); + aggregator.Aggregate(null, null, exception: exception2, elapsedTime: TimeSpan.Zero, isAborted: false, isCanceled: false, runContextAttachments: null, + runCompleteArgsAttachments: null); + + aggregatedException = aggregator.GetAggregatedException() as AggregateException; + Assert.IsNotNull(aggregatedException, "Aggregated exception must NOT be null"); + Assert.IsNotNull(aggregatedException.InnerExceptions, "Inner exception list must NOT be null"); + Assert.AreEqual(aggregatedException.InnerExceptions.Count, 2, "Inner exception lsit must have one element"); + Assert.AreEqual(aggregatedException.InnerExceptions[1], exception2, "Inner exception must be the one set."); + } + + [TestMethod] + public void AggregateShouldAggregateExecutorUrisCorrectly() + { + var aggregator = new ParallelRunDataAggregator(); + + aggregator.Aggregate(null, null, null, TimeSpan.Zero, false, false, null, null); + + Assert.AreEqual(aggregator.ExecutorUris.Count, 0, "ExecutorUris List must not have data."); + + var uri1 = "x://hello1"; + aggregator.Aggregate(null, new List() { uri1 }, null, TimeSpan.Zero, false, false, null, null); + + Assert.AreEqual(aggregator.ExecutorUris.Count, 1, "ExecutorUris List must have data."); + Assert.IsTrue(aggregator.ExecutorUris.Contains(uri1), "ExecutorUris List must have correct data."); + + var uri2 = "x://hello2"; + aggregator.Aggregate(null, new List() { uri2 }, null, TimeSpan.Zero, false, false, null, null); + + Assert.AreEqual(aggregator.ExecutorUris.Count, 2, "ExecutorUris List must have aggregated data."); + Assert.IsTrue(aggregator.ExecutorUris.Contains(uri2), "ExecutorUris List must have correct data."); + } + + [TestMethod] + public void AggregateShouldAggregateRunStatsCorrectly() + { + var aggregator = new ParallelRunDataAggregator(); + + aggregator.Aggregate(null, null, null, TimeSpan.Zero, false, false, null, null); + + var runStats = aggregator.GetAggregatedRunStats(); + Assert.AreEqual(runStats.ExecutedTests, 0, "RunStats must not have data."); + + var stats1 = new Dictionary(); + stats1.Add(TestOutcome.Passed, 2); + stats1.Add(TestOutcome.Failed, 3); + stats1.Add(TestOutcome.Skipped, 1); + stats1.Add(TestOutcome.NotFound, 4); + stats1.Add(TestOutcome.None, 2); + + aggregator.Aggregate(new TestRunStatistics(12, stats1), null, null, TimeSpan.Zero, false, false, null, null); + + runStats = aggregator.GetAggregatedRunStats(); + Assert.AreEqual(runStats.ExecutedTests, 12, "RunStats must have aggregated data."); + Assert.AreEqual(runStats.Stats[TestOutcome.Passed], 2, "RunStats must have aggregated data."); + Assert.AreEqual(runStats.Stats[TestOutcome.Failed], 3, "RunStats must have aggregated data."); + Assert.AreEqual(runStats.Stats[TestOutcome.Skipped], 1, "RunStats must have aggregated data."); + Assert.AreEqual(runStats.Stats[TestOutcome.NotFound], 4, "RunStats must have aggregated data."); + Assert.AreEqual(runStats.Stats[TestOutcome.None], 2, "RunStats must have aggregated data."); + + + var stats2 = new Dictionary(); + stats2.Add(TestOutcome.Passed, 3); + stats2.Add(TestOutcome.Failed, 2); + stats2.Add(TestOutcome.Skipped, 2); + stats2.Add(TestOutcome.NotFound, 1); + stats2.Add(TestOutcome.None, 3); + + aggregator.Aggregate(new TestRunStatistics(11, stats2), null, null, TimeSpan.Zero, false, false, null, null); + + runStats = aggregator.GetAggregatedRunStats(); + Assert.AreEqual(runStats.ExecutedTests, 23, "RunStats must have aggregated data."); + Assert.AreEqual(runStats.Stats[TestOutcome.Passed], 5, "RunStats must have aggregated data."); + Assert.AreEqual(runStats.Stats[TestOutcome.Failed], 5, "RunStats must have aggregated data."); + Assert.AreEqual(runStats.Stats[TestOutcome.Skipped], 3, "RunStats must have aggregated data."); + Assert.AreEqual(runStats.Stats[TestOutcome.NotFound], 5, "RunStats must have aggregated data."); + Assert.AreEqual(runStats.Stats[TestOutcome.None], 5, "RunStats must have aggregated data."); + } + } +} diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/ParallelRunEventsHandlerTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/ParallelRunEventsHandlerTests.cs new file mode 100644 index 0000000000..106f09e80b --- /dev/null +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/ParallelRunEventsHandlerTests.cs @@ -0,0 +1,178 @@ +using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; +using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces; +using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.Parallel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace TestPlatform.CrossPlatEngine.UnitTests.Client +{ + [TestClass] + public class ParallelRunEventsHandlerTests + { + private ParallelRunEventsHandler parallelRunEventsHandler; + + private Mock mockProxyExecutionManager; + + private Mock mockTestRunEventsHandler; + + private Mock mockParallelProxyExecutionManager; + + private Mock mockDataSerializer; + + [TestInitialize] + public void TestInit() + { + this.mockProxyExecutionManager = new Mock(); + this.mockTestRunEventsHandler = new Mock(); + this.mockParallelProxyExecutionManager = new Mock(); + this.mockDataSerializer = new Mock(); + + this.parallelRunEventsHandler = new ParallelRunEventsHandler(this.mockProxyExecutionManager.Object, + this.mockTestRunEventsHandler.Object, this.mockParallelProxyExecutionManager.Object, + new ParallelRunDataAggregator(), this.mockDataSerializer.Object); + } + + [TestMethod] + public void HandleRawMessageShouldSendStatsChangeRawMessageToRunEventsHandler() + { + string payload = "RunStats"; + this.mockDataSerializer.Setup(mds => mds.DeserializeMessage(It.IsAny())) + .Returns(new Message() { MessageType = MessageType.TestRunStatsChange, Payload = payload }); + + this.parallelRunEventsHandler.HandleRawMessage(payload); + + this.mockTestRunEventsHandler.Verify(mt => mt.HandleRawMessage(payload), Times.Once); + } + + [TestMethod] + public void HandleRawMessageShouldSendLoggerRawMessageToRunEventsHandler() + { + string payload = "LogMessage"; + this.mockDataSerializer.Setup(mds => mds.DeserializeMessage(It.IsAny())) + .Returns(new Message() { MessageType = MessageType.TestMessage, Payload = payload }); + + this.parallelRunEventsHandler.HandleRawMessage(payload); + + this.mockTestRunEventsHandler.Verify(mt => mt.HandleRawMessage(payload), Times.Once); + } + + [TestMethod] + public void HandleRawMessageShouldNotSendRunCompleteEventRawMessageToRunEventsHandler() + { + string payload = "ExecComplete"; + this.mockDataSerializer.Setup(mds => mds.DeserializeMessage(It.IsAny())) + .Returns(new Message() { MessageType = MessageType.ExecutionComplete, Payload = payload }); + + this.parallelRunEventsHandler.HandleRawMessage(payload); + + this.mockTestRunEventsHandler.Verify(mt => mt.HandleRawMessage(It.IsAny()), Times.Never); + } + + [TestMethod] + public void HandleLogMessageShouldJustPassOnTheEventToRunEventsHandler() + { + string log = "Hello"; + this.parallelRunEventsHandler.HandleLogMessage(TestMessageLevel.Error, log); + + this.mockTestRunEventsHandler.Verify(mt => + mt.HandleLogMessage(TestMessageLevel.Error, log), Times.Once); + } + + [TestMethod] + public void HandleRunStatsChangeShouldJustPassOnTheEventToRunEventsHandler() + { + var eventArgs = new TestRunChangedEventArgs(null, null, null); + this.parallelRunEventsHandler.HandleTestRunStatsChange(eventArgs); + + this.mockTestRunEventsHandler.Verify(mt => mt.HandleTestRunStatsChange(eventArgs), Times.Once); + } + + [TestMethod] + public void LaunchProcessWithDebuggerAttachedShouldJustPassOnTheEventToRunEventsHandler() + { + var testProcessStartInfo = new TestProcessStartInfo(); + this.parallelRunEventsHandler.LaunchProcessWithDebuggerAttached(testProcessStartInfo); + + this.mockTestRunEventsHandler.Verify(mt => mt.LaunchProcessWithDebuggerAttached(testProcessStartInfo), Times.Once); + } + + [TestMethod] + public void HandleRunCompleteShouldNotCallLastChunkResultsIfNotPresent() + { + var completeArgs = new TestRunCompleteEventArgs(null, false, false, null, null, TimeSpan.Zero); + + this.mockParallelProxyExecutionManager.Setup(mp => mp.HandlePartialRunComplete( + this.mockProxyExecutionManager.Object, completeArgs, null, null, null)).Returns(false); + + this.parallelRunEventsHandler.HandleTestRunComplete(completeArgs, null, null, null); + + // Raw message must be sent + this.mockTestRunEventsHandler.Verify(mt => mt.HandleRawMessage(It.IsAny()), Times.Never); + + this.mockTestRunEventsHandler.Verify(mt => mt.HandleTestRunStatsChange(null), Times.Never); + + this.mockParallelProxyExecutionManager.Verify(mp => mp.HandlePartialRunComplete( + this.mockProxyExecutionManager.Object, completeArgs, null, null, null), Times.Once); + } + + [TestMethod] + public void HandleRunCompleteShouldCallLastChunkResultsIfPresent() + { + string payload = "RunStats"; + var lastChunk = new TestRunChangedEventArgs(null, null, null); + var completeArgs = new TestRunCompleteEventArgs(null, false, false, null, null, TimeSpan.Zero); + + this.mockDataSerializer.Setup(mds => mds.SerializeObject(MessageType.TestRunStatsChange, lastChunk)) + .Returns(payload); + + this.mockParallelProxyExecutionManager.Setup(mp => mp.HandlePartialRunComplete( + this.mockProxyExecutionManager.Object, completeArgs, null, null, null)).Returns(false); + + this.parallelRunEventsHandler.HandleTestRunComplete(completeArgs, lastChunk, null, null); + + // Raw message must be sent + this.mockTestRunEventsHandler.Verify(mt => mt.HandleRawMessage(payload), Times.Once); + + this.mockTestRunEventsHandler.Verify(mt => mt.HandleTestRunStatsChange(lastChunk), Times.Once); + + this.mockParallelProxyExecutionManager.Verify(mp => mp.HandlePartialRunComplete( + this.mockProxyExecutionManager.Object, completeArgs, null, null, null), Times.Once); + } + + [TestMethod] + public void HandleRunCompleteShouldCallTestRunCompleteOnActualHandlerIfParallelMaangerReturnsCompleteAsTrue() + { + string payload = "ExecComplete"; + var completeArgs = new TestRunCompleteEventArgs(null, false, false, null, null, TimeSpan.Zero); + + this.mockParallelProxyExecutionManager.Setup(mp => mp.HandlePartialRunComplete( + this.mockProxyExecutionManager.Object, completeArgs, null, null, null)).Returns(true); + + this.mockDataSerializer.Setup(mds => mds.SerializeObject(MessageType.ExecutionComplete)).Returns(payload); + + this.parallelRunEventsHandler.HandleTestRunComplete(completeArgs, null, null, null); + + this.mockTestRunEventsHandler.Verify(mt => mt.HandleTestRunStatsChange(null), Times.Never); + + this.mockParallelProxyExecutionManager.Verify(mp => mp.HandlePartialRunComplete( + this.mockProxyExecutionManager.Object, completeArgs, null, null, null), Times.Once); + + this.mockTestRunEventsHandler.Verify(mt => mt.HandleRawMessage(It.IsAny()), Times.Once); + + this.mockTestRunEventsHandler.Verify(mt => mt.HandleTestRunComplete( + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny>()), Times.Once); + } + } +} diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyDiscoveryManagerTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyDiscoveryManagerTests.cs new file mode 100644 index 0000000000..001cbac15c --- /dev/null +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyDiscoveryManagerTests.cs @@ -0,0 +1,155 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.CrossPlatEngine.UnitTests.Client +{ + using System.Collections.Generic; + using System.Threading; + using System.Threading.Tasks; + + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + using Moq; + + using TestPlatform.Common.UnitTests.ExtensionFramework; + + [TestClass] + public class ProxyDiscoveryManagerTests + { + private ProxyDiscoveryManager testDiscoveryManager; + + private Mock mockTestHostManager; + + private Mock mockRequestSender; + + /// + /// The client connection timeout in milliseconds for unit tests. + /// + private int testableClientConnectionTimeout = 400; + + [TestInitialize] + public void TestInit() + { + this.mockTestHostManager = new Mock(); + this.mockRequestSender = new Mock(); + this.testDiscoveryManager = new ProxyDiscoveryManager(this.mockRequestSender.Object, this.mockTestHostManager.Object, this.testableClientConnectionTimeout); + } + + [TestMethod] + public void InitializeShouldNotInitializeExtensionsOnNoExtensions() + { + // Make sure TestPlugincache is refreshed. + TestPluginCache.Instance = null; + + this.testDiscoveryManager.Initialize(this.mockTestHostManager.Object); + + this.mockRequestSender.Verify(s => s.InitializeDiscovery(It.IsAny>(), It.IsAny()), Times.Never); + } + + [TestMethod] + public void InitializeShouldInitializeExtensionsIfPresent() + { + // Make sure TestPlugincache is refreshed. + TestPluginCache.Instance = null; + + try + { + var extensions = new string[] { "e1.dll", "e2.dll" }; + + // Setup Mocks. + TestPluginCacheTests.SetupMockAdditionalPathExtensions(extensions); + this.mockRequestSender.Setup(s => s.WaitForRequestHandlerConnection(It.IsAny())).Returns(true); + + this.testDiscoveryManager.Initialize(this.mockTestHostManager.Object); + + // Also verify that we have waited for client connection. + this.mockRequestSender.Verify(s => s.WaitForRequestHandlerConnection(It.IsAny()), Times.Once); + this.mockRequestSender.Verify( + s => s.InitializeDiscovery(extensions, true), + Times.Once); + } + finally + { + TestPluginCache.Instance = null; + } + } + + [TestMethod] + public void DiscoverTestsShouldNotIntializeIfDoneSoAlready() + { + this.testDiscoveryManager.Initialize(this.mockTestHostManager.Object); + + // Setup mocks. + this.mockRequestSender.Setup(s => s.WaitForRequestHandlerConnection(It.IsAny())).Returns(true); + + // Act. + this.testDiscoveryManager.DiscoverTests(null, null); + + this.mockRequestSender.Verify(s => s.InitializeCommunication(), Times.AtMostOnce); + this.mockTestHostManager.Verify(thl => thl.LaunchTestHost(null, It.IsAny>()), Times.AtMostOnce); + } + + [TestMethod] + public void DiscoverTestsShouldIntializeIfNotInitializedAlready() + { + // Setup mocks. + this.mockRequestSender.Setup(s => s.WaitForRequestHandlerConnection(It.IsAny())).Returns(true); + + // Act. + this.testDiscoveryManager.DiscoverTests(null, null); + + this.mockRequestSender.Verify(s => s.InitializeCommunication(), Times.Once); + this.mockTestHostManager.Verify(thl => thl.LaunchTestHost(null, It.IsAny>()), Times.Once); + } + + [TestMethod] + public void DiscoverTestsShouldThrowExceptionIfClientConnectionTimeout() + { + // Setup mocks. + this.mockRequestSender.Setup(s => s.WaitForRequestHandlerConnection(It.IsAny())).Returns(false); + + // Act. + Assert.ThrowsException( + () => this.testDiscoveryManager.DiscoverTests(null, null)); + } + + + [TestMethod] + public void DiscoverTestsShouldInitiateServerDiscoveryLoop() + { + // Setup mocks. + this.mockRequestSender.Setup(s => s.WaitForRequestHandlerConnection(It.IsAny())).Returns(true); + + // Act. + this.testDiscoveryManager.DiscoverTests(null, null); + + // Assert. + this.mockRequestSender.Verify(s => s.DiscoverTests(null, null), Times.Once); + } + + [TestMethod] + public void DiscoverTestsShouldEndSessionWithTheServer() + { + // Setup mocks. + this.mockRequestSender.Setup(s => s.WaitForRequestHandlerConnection(It.IsAny())).Returns(true); + + // Act. + this.testDiscoveryManager.DiscoverTests(null, null); + + // Assert. + this.mockRequestSender.Verify(s => s.EndSession(), Times.Once); + } + + private void SignalEvent(ManualResetEvent manualResetEvent) + { + // Wait for the 100 ms. + Task.Delay(200).Wait(); + + manualResetEvent.Set(); + } + } +} diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyExecutionManagerTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyExecutionManagerTests.cs new file mode 100644 index 0000000000..fb59d18198 --- /dev/null +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyExecutionManagerTests.cs @@ -0,0 +1,224 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.CrossPlatEngine.UnitTests.Client +{ + using System.Collections.Generic; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework; + using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + using Moq; + + using TestPlatform.Common.UnitTests.ExtensionFramework; + + [TestClass] + public class ProxyExecutionManagerTests + { + private ProxyExecutionManager testExecutionManager; + + private Mock mockTestHostManager; + + private Mock mockRequestSender; + + /// + /// The client connection timeout in milliseconds for unit tests. + /// + private int testableClientConnectionTimeout = 400; + + [TestInitialize] + public void TestInit() + { + this.mockTestHostManager = new Mock(); + this.mockRequestSender = new Mock(); + this.testExecutionManager = new ProxyExecutionManager(this.mockRequestSender.Object, this.mockTestHostManager.Object, this.testableClientConnectionTimeout); + } + + [TestMethod] + public void InitializeShouldNotInitializeExtensionsOnNoExtensions() + { + // Make sure TestPlugincache is refreshed. + TestPluginCache.Instance = null; + + this.testExecutionManager.Initialize(this.mockTestHostManager.Object); + + this.mockRequestSender.Verify(s => s.InitializeExecution(It.IsAny>(), It.IsAny()), Times.Never); + } + + [TestMethod] + public void InitializeShouldInitializeExtensionsIfPresent() + { + // Make sure TestPlugincache is refreshed. + TestPluginCache.Instance = null; + + try + { + var extensions = new string[] { "e1.dll", "e2.dll" }; + + // Setup Mocks. + TestPluginCacheTests.SetupMockAdditionalPathExtensions(extensions); + this.mockRequestSender.Setup(s => s.WaitForRequestHandlerConnection(It.IsAny())).Returns(true); + + this.testExecutionManager.Initialize(this.mockTestHostManager.Object); + + // Also verify that we have waited for client connection. + this.mockRequestSender.Verify(s => s.WaitForRequestHandlerConnection(It.IsAny()), Times.Once); + this.mockRequestSender.Verify( + s => s.InitializeExecution(extensions, true), + Times.Once); + } + finally + { + TestPluginCache.Instance = null; + } + } + + [TestMethod] + public void StartTestRunShouldNotIntializeIfDoneSoAlready() + { + this.testExecutionManager.Initialize(this.mockTestHostManager.Object); + + // Setup mocks. + this.mockRequestSender.Setup(s => s.WaitForRequestHandlerConnection(It.IsAny())).Returns(true); + var mockTestRunCriteria = new Mock(new List { "source.dll" }, 10); + + // Act. + this.testExecutionManager.StartTestRun(mockTestRunCriteria.Object, null); + + this.mockRequestSender.Verify(s => s.InitializeCommunication(), Times.AtMostOnce); + this.mockTestHostManager.Verify(thl => thl.LaunchTestHost(null, It.IsAny>()), Times.AtMostOnce); + } + + [TestMethod] + public void StartTestRunShouldIntializeIfNotInitializedAlready() + { + // Setup mocks. + this.mockRequestSender.Setup(s => s.WaitForRequestHandlerConnection(It.IsAny())).Returns(true); + var mockTestRunCriteria = new Mock(new List { "source.dll" }, 10); + + // Act. + this.testExecutionManager.StartTestRun(mockTestRunCriteria.Object, null); + + this.mockRequestSender.Verify(s => s.InitializeCommunication(), Times.Once); + this.mockTestHostManager.Verify(thl => thl.LaunchTestHost(null, It.IsAny>()), Times.Once); + } + + [TestMethod] + public void StartTestRunShouldThrowExceptionIfClientConnectionTimeout() + { + // Setup mocks. + this.mockRequestSender.Setup(s => s.WaitForRequestHandlerConnection(It.IsAny())).Returns(false); + + // Act. + Assert.ThrowsException( + () => this.testExecutionManager.StartTestRun(null, null)); + } + + + [TestMethod] + public void StartTestRunShouldInitiateTestRunForSourcesThroughTheServer() + { + TestRunCriteriaWithSources testRunCriteriaPassed = null; + + // Setup mocks. + this.mockRequestSender.Setup(s => s.WaitForRequestHandlerConnection(It.IsAny())).Returns(true); + this.mockRequestSender.Setup(s => s.StartTestRun(It.IsAny(), null)) + .Callback( + (TestRunCriteriaWithSources criteria, ITestRunEventsHandler sink) => + { + testRunCriteriaPassed = criteria; + }); + var mockTestRunCriteria = new Mock(new List { "source.dll" }, 10); + + // Act. + this.testExecutionManager.StartTestRun(mockTestRunCriteria.Object, null); + + // Assert. + Assert.IsNotNull(testRunCriteriaPassed); + CollectionAssert.AreEqual( + mockTestRunCriteria.Object.AdapterSourceMap.Keys, + testRunCriteriaPassed.AdapterSourceMap.Keys); + CollectionAssert.AreEqual( + mockTestRunCriteria.Object.AdapterSourceMap.Values, + testRunCriteriaPassed.AdapterSourceMap.Values); + Assert.AreEqual( + mockTestRunCriteria.Object.FrequencyOfRunStatsChangeEvent, + testRunCriteriaPassed.TestExecutionContext.FrequencyOfRunStatsChangeEvent); + Assert.AreEqual( + mockTestRunCriteria.Object.RunStatsChangeEventTimeout, + testRunCriteriaPassed.TestExecutionContext.RunStatsChangeEventTimeout); + Assert.AreEqual( + mockTestRunCriteria.Object.TestRunSettings, + testRunCriteriaPassed.RunSettings); + } + + [TestMethod] + public void StartTestRunShouldInitiateTestRunForTestsThroughTheServer() + { + TestRunCriteriaWithTests testRunCriteriaPassed = null; + + // Setup mocks. + this.mockRequestSender.Setup(s => s.WaitForRequestHandlerConnection(It.IsAny())).Returns(true); + this.mockRequestSender.Setup(s => s.StartTestRun(It.IsAny(), null)) + .Callback( + (TestRunCriteriaWithTests criteria, ITestRunEventsHandler sink) => + { + testRunCriteriaPassed = criteria; + }); + var mockTestRunCriteria = + new Mock( + new List { new TestCase("A.C.M", new System.Uri("executor://dummy"), "source.dll") }, + 10); + + // Act. + this.testExecutionManager.StartTestRun(mockTestRunCriteria.Object, null); + + // Assert. + Assert.IsNotNull(testRunCriteriaPassed); + CollectionAssert.AreEqual(mockTestRunCriteria.Object.Tests.ToList(), testRunCriteriaPassed.Tests.ToList()); + Assert.AreEqual( + mockTestRunCriteria.Object.FrequencyOfRunStatsChangeEvent, + testRunCriteriaPassed.TestExecutionContext.FrequencyOfRunStatsChangeEvent); + Assert.AreEqual( + mockTestRunCriteria.Object.RunStatsChangeEventTimeout, + testRunCriteriaPassed.TestExecutionContext.RunStatsChangeEventTimeout); + Assert.AreEqual( + mockTestRunCriteria.Object.TestRunSettings, + testRunCriteriaPassed.RunSettings); + } + + [TestMethod] + public void DisposeShouldSignalServerSessionEnd() + { + this.testExecutionManager.Dispose(); + + this.mockRequestSender.Verify(s => s.EndSession(), Times.Once); + } + + [TestMethod] + public void DisposeShouldSignalServerSessionEndEachTime() + { + this.testExecutionManager.Dispose(); + this.testExecutionManager.Dispose(); + + this.mockRequestSender.Verify(s => s.EndSession(), Times.Exactly(2)); + } + + private void SignalEvent(ManualResetEvent manualResetEvent) + { + // Wait for the 100 ms. + Task.Delay(200).Wait(); + + manualResetEvent.Set(); + } + } +} diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyExecutionManagerWithDataCollectionTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyExecutionManagerWithDataCollectionTests.cs new file mode 100644 index 0000000000..5595b00e38 --- /dev/null +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyExecutionManagerWithDataCollectionTests.cs @@ -0,0 +1,79 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.CrossPlatEngine.UnitTests.Client +{ + using TestPlatform.CrossPlatEngine.UnitTests.DataCollection; + + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.DataCollection; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.DataCollection.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + using Moq; + + [TestClass] + public class ProxyExecutionManagerWithDataCollectionTests + { + private ProxyExecutionManager testExecutionManager; + + private Mock mockTestHostManager; + + private Mock mockRequestSender; + + private Mock mockDataCollectionClient; + + /// + /// The client connection timeout in milliseconds for unit tests. + /// + private int testableClientConnectionTimeout = 400; + + [TestInitialize] + public void TestInit() + { + this.mockTestHostManager = new Mock(); + this.mockRequestSender = new Mock(); + this.testExecutionManager = new ProxyExecutionManager(this.mockRequestSender.Object, this.mockTestHostManager.Object, this.testableClientConnectionTimeout); + this.mockDataCollectionClient = new Mock(); + } + + [TestMethod] + public void InitializeShouldInitializeDataCollectionProcessIfDataCollectionIsEnabled() + { + var proxyExecutionManager = new ProxyExecutionManagerWithDataCollection(this.mockDataCollectionClient.Object); + proxyExecutionManager.Initialize(this.mockTestHostManager.Object); + + mockDataCollectionClient.Verify(dc => dc.BeforeTestRunStart(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + } + + [TestMethod] + public void InitializeShouldCallAfterTestRunIfExceptionIsThrownWhileCreatingDataCollectionProcess() + { + mockDataCollectionClient.Setup(dc => dc.BeforeTestRunStart(It.IsAny(), It.IsAny(), It.IsAny())).Throws(new System.Exception("MyException")); + mockDataCollectionClient.Setup(dc => dc.AfterTestRunEnd(It.IsAny(), It.IsAny())); + + var proxyExecutionManager = new ProxyExecutionManagerWithDataCollection(this.mockDataCollectionClient.Object); + proxyExecutionManager.Initialize(this.mockTestHostManager.Object); + + mockDataCollectionClient.Verify(dc => dc.BeforeTestRunStart(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + mockDataCollectionClient.Verify(dc => dc.AfterTestRunEnd(It.IsAny(), It.IsAny()), Times.Once); + } + + [TestMethod] + public void InitializeShouldSaveExceptionMessagesIfThrownByDataCollectionProcess() + { + mockDataCollectionClient.Setup(dc => dc.BeforeTestRunStart(It.IsAny(), It.IsAny(), It.IsAny())).Throws(new System.Exception("MyException")); + mockDataCollectionClient.Setup(dc => dc.AfterTestRunEnd(It.IsAny(), It.IsAny())); + + ProxyDataCollectionManager proxyDataCollectonManager = new ProxyDataCollectionManager(Architecture.AnyCPU, string.Empty, new DummyDataCollectionRequestSender(), new DummyDataCollectionLauncher()); + + var proxyExecutionManager = new ProxyExecutionManagerWithDataCollection(proxyDataCollectonManager); + proxyExecutionManager.Initialize(this.mockTestHostManager.Object); + Assert.IsNotNull(proxyExecutionManager.DataCollectionRunEventsHandler.ExceptionMessages); + Assert.AreEqual(1, proxyExecutionManager.DataCollectionRunEventsHandler.ExceptionMessages.Count); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyOperationManagerTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyOperationManagerTests.cs new file mode 100644 index 0000000000..7f50a18cc6 --- /dev/null +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyOperationManagerTests.cs @@ -0,0 +1,76 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.CrossPlatEngine.UnitTests.Client +{ + using System.Collections.Generic; + + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework; + using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + using Moq; + + [TestClass] + public class ProxyOperationManagerTests + { + private TestableProxyOperationManager testOperationManager; + + private Mock mockTestHostManager; + + private Mock mockRequestSender; + + /// + /// The client connection timeout in milliseconds for unit tests. + /// + private int testableClientConnectionTimeout = 400; + + [TestInitialize] + public void TestInit() + { + this.mockTestHostManager = new Mock(); + this.mockRequestSender = new Mock(); + this.testOperationManager = new TestableProxyOperationManager(this.mockRequestSender.Object, this.testableClientConnectionTimeout); + } + + [TestMethod] + public void InitializeShouldLaunchTestHost() + { + this.testOperationManager.Initialize(this.mockTestHostManager.Object); + + // construct the command line arguments. + var commandLineArguments = new List { "--port", "0" }; + this.mockTestHostManager.Verify(thl => thl.LaunchTestHost(null, commandLineArguments), Times.Once); + } + + [TestMethod] + public void InitializeShouldSetupServerForCommunication() + { + this.testOperationManager.Initialize(this.mockTestHostManager.Object); + + this.mockRequestSender.Verify(s => s.InitializeCommunication(), Times.Once); + } + + #region Testable Implementation + + private class TestableProxyOperationManager : ProxyOperationManager + { + public TestableProxyOperationManager() + : base() + { + } + + internal TestableProxyOperationManager( + ITestRequestSender requestSender, + int clientConnectionTimeout) + : base(requestSender, null, clientConnectionTimeout) + { + } + } + + #endregion + } +} diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/DataCollection/DataCollectionTestRunEventsHandlerTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/DataCollection/DataCollectionTestRunEventsHandlerTests.cs new file mode 100644 index 0000000000..3718ab5a96 --- /dev/null +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/DataCollection/DataCollectionTestRunEventsHandlerTests.cs @@ -0,0 +1,100 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.CommunicationUtilities.UnitTests.ObjectModel +{ + using System; + using System.Collections.ObjectModel; + using System.Linq; + + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.DataCollection; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.DataCollection.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + using Moq; + + [TestClass] + public class DataCollectionTestRunEventsHandlerTests + { + private Mock baseTestRunEventsHandler; + private DataCollectionTestRunEventsHandler testRunEventHandler; + private Mock proxyDataCollectionManager; + + [TestInitialize] + public void InitializeTests() + { + this.baseTestRunEventsHandler = new Mock(); + this.proxyDataCollectionManager = new Mock(); + this.testRunEventHandler = new DataCollectionTestRunEventsHandler(this.baseTestRunEventsHandler.Object, this.proxyDataCollectionManager.Object); + } + + [TestMethod] + public void HandleLogMessageShouldSendMessageToBaseTestRunEventsHandler() + { + this.testRunEventHandler.HandleLogMessage(TestMessageLevel.Informational, null); + this.baseTestRunEventsHandler.Verify(th => th.HandleLogMessage(0, null), Times.AtLeast(1)); + } + + [TestMethod] + public void HandleRawMessageShouldSendMessageToBaseTestRunEventsHandler() + { + this.testRunEventHandler.HandleRawMessage(null); + this.baseTestRunEventsHandler.Verify(th => th.HandleRawMessage(null), Times.AtLeast(1)); + } + + [TestMethod] + public void HandleTestRunCompleteShouldAfterTestRunEndToGetAttachments() + { + this.testRunEventHandler.HandleTestRunComplete(null, null, null, null); + this.baseTestRunEventsHandler.Verify(th => th.HandleTestRunComplete(null, null, null, null), Times.AtLeast(1)); + this.proxyDataCollectionManager.Verify(dcm => dcm.AfterTestRunEnd(false, It.IsAny()), Times.Once); + } + + #region Get Combined Attachments + [TestMethod] + public void GetCombinedAttachmentSetsShouldReturnCombinedAttachments() + { + Collection Attachments1 = new Collection(); + AttachmentSet attachmentset1 = new AttachmentSet(new Uri("DataCollection://Attachment/v1"), "AttachmentV1"); + attachmentset1.Attachments.Add(new UriDataAttachment(new Uri("DataCollection://Attachment/v11"), "AttachmentV1-Attachment1")); + Attachments1.Add(attachmentset1); + + Collection Attachments2 = new Collection(); + AttachmentSet attachmentset2 = new AttachmentSet(new Uri("DataCollection://Attachment/v1"), "AttachmentV1"); + attachmentset2.Attachments.Add(new UriDataAttachment(new Uri("DataCollection://Attachment/v12"), "AttachmentV1-Attachment2")); + + Attachments2.Add(attachmentset2); + + var result = DataCollectionTestRunEventsHandler.GetCombinedAttachmentSets(Attachments1, Attachments2); + + Assert.AreEqual(1, result.Count); + Assert.AreEqual(2, result.First().Attachments.Count); + } + + [TestMethod] + public void GetCombinedAttachmentSetsShouldReturnFirstArgumentIfSecondArgumentIsNull() + { + Collection Attachments1 = new Collection(); + AttachmentSet attachmentset1 = new AttachmentSet(new Uri("DataCollection://Attachment/v1"), "AttachmentV1"); + attachmentset1.Attachments.Add(new UriDataAttachment(new Uri("DataCollection://Attachment/v11"), "AttachmentV1-Attachment1")); + Attachments1.Add(attachmentset1); + + var result = DataCollectionTestRunEventsHandler.GetCombinedAttachmentSets(Attachments1, null); + + Assert.AreEqual(1, result.Count); + Assert.AreEqual(1, result.First().Attachments.Count); + } + + [TestMethod] + public void GetCombinedAttachmentSetsShouldReturnNullIfFirstArgumentIsNull() + { + var result = DataCollectionTestRunEventsHandler.GetCombinedAttachmentSets(null, null); + Assert.IsNull(result); + } + + #endregion + } +} \ No newline at end of file diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/DataCollection/ProxyDataCollectionManagerTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/DataCollection/ProxyDataCollectionManagerTests.cs new file mode 100644 index 0000000000..e82e77fc2c --- /dev/null +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/DataCollection/ProxyDataCollectionManagerTests.cs @@ -0,0 +1,181 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.CrossPlatEngine.UnitTests.DataCollection +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.DataCollection; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.DataCollection.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + using Moq; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.DataCollection.Interfaces; + using Microsoft.VisualStudio.TestPlatform.Common.DataCollection; + + [TestClass] + public class ProxyDataCollectionManagerTests + { + private DummyDataCollectionRequestSender mockDataCollectionRequestSender; + private ProxyDataCollectionManager proxyDataCollectionManager; + private DummyDataCollectionLauncher mockDataCollectionLauncher; + + [TestInitialize] + public void Initialize() + { + this.mockDataCollectionRequestSender = new DummyDataCollectionRequestSender(); + this.mockDataCollectionLauncher = new DummyDataCollectionLauncher(); + this.proxyDataCollectionManager = new ProxyDataCollectionManager(Architecture.AnyCPU, string.Empty, this.mockDataCollectionRequestSender, this.mockDataCollectionLauncher); + } + + [TestMethod] + public void InitializeSocketCommunicationShouldInitializeCommunication() + { + this.proxyDataCollectionManager.InitializeSocketCommunication(Architecture.X86); + + Assert.IsTrue(this.mockDataCollectionLauncher.isInitialized); + Assert.IsTrue(this.mockDataCollectionLauncher.dataCollectorLaunched); + Assert.IsTrue(this.mockDataCollectionRequestSender.waitForRequestHandlerConnection); + Assert.AreEqual(5000, this.mockDataCollectionRequestSender.connectionTimeout); + } + + [TestMethod] + public void BeforeTestRunStartShouldReturnDataCollectorParameters() + { + BeforeTestRunStartResult res = new BeforeTestRunStartResult(new Dictionary(), true, 123); + this.mockDataCollectionRequestSender.BeforeTestRunStartResult = res; + + var result = proxyDataCollectionManager.BeforeTestRunStart(true, true, null); + + Assert.IsTrue(this.mockDataCollectionRequestSender.sendBeforeTestRunStartAndGetResult); + Assert.IsNotNull(result); + Assert.AreEqual(res.DataCollectionEventsPort, result.DataCollectionEventsPort); + Assert.AreEqual(res.EnvironmentVariables.Count, result.EnvironmentVariables.Count); + Assert.AreEqual(res.AreTestCaseLevelEventsRequired, result.AreTestCaseLevelEventsRequired); + } + + [TestMethod] + public void BeforeTestRunStartsShouldInvokeRunEventsHandlerIfExceptionIsThrown() + { + var mockRunEventsHandler = new Mock(); + this.mockDataCollectionRequestSender.sendBeforeTestRunStartAndGetResultThrowException = true; + + mockRunEventsHandler.Setup(eh => eh.HandleLogMessage(TestMessageLevel.Error, "SocketException")); + + var result = this.proxyDataCollectionManager.BeforeTestRunStart(true, true, mockRunEventsHandler.Object); + + mockRunEventsHandler.Verify(eh => eh.HandleLogMessage(TestMessageLevel.Error, "SocketException"), Times.Once); + Assert.AreEqual(result.IsDataCollectionStarted, false); + Assert.AreEqual(result.EnvironmentVariables, null); + Assert.AreEqual(result.AreTestCaseLevelEventsRequired, false); + Assert.AreEqual(result.DataCollectionEventsPort, 0); + } + + [TestMethod] + public void AfterTestRunEndShouldReturnAttachments() + { + var attachments = new Collection(); + var dispName = "MockAttachments"; + var uri = new Uri("Mock://Attachments"); + var attachmentSet = new AttachmentSet(uri, dispName); + attachments.Add(attachmentSet); + + this.mockDataCollectionRequestSender.Attachments = attachments; + + var result = this.proxyDataCollectionManager.AfterTestRunEnd(false, null); + + Assert.IsTrue(this.mockDataCollectionRequestSender.sendAfterTestRunStartAndGetResult); + Assert.IsNotNull(result); + Assert.AreEqual(result.Count, 1); + Assert.IsNotNull(result[0]); + Assert.AreEqual(result[0].DisplayName, dispName); + Assert.AreEqual(uri, result[0].Uri); + } + + [TestMethod] + public void AfterTestRunEndShouldInvokeRunEventsHandlerIfExceptionIsThrown() + { + var mockRunEventsHandler = new Mock(); + mockRunEventsHandler.Setup(eh => eh.HandleLogMessage(TestMessageLevel.Error, "SocketException")); + this.mockDataCollectionRequestSender.sendAfterTestRunStartAndGetResultThrowException = true; + + var result = this.proxyDataCollectionManager.AfterTestRunEnd(false, mockRunEventsHandler.Object); + + mockRunEventsHandler.Verify(eh => eh.HandleLogMessage(TestMessageLevel.Error, "SocketException"), Times.Once); + } + } + + internal class DummyDataCollectionRequestSender : IDataCollectionRequestSender + { + public bool waitForRequestHandlerConnection; + public int connectionTimeout; + public bool sendBeforeTestRunStartAndGetResult; + public bool sendAfterTestRunStartAndGetResult; + public bool sendAfterTestRunStartAndGetResultThrowException; + public bool sendBeforeTestRunStartAndGetResultThrowException; + + public BeforeTestRunStartResult BeforeTestRunStartResult { get; set; } + + public Collection Attachments { get; set; } + + public void Close() + { + throw new NotImplementedException(); + } + + public int InitializeCommunication() + { + return 1; + } + + public Collection SendAfterTestRunStartAndGetResult() + { + if (sendAfterTestRunStartAndGetResultThrowException) + { + throw new Exception("SocketException"); + } + + this.sendAfterTestRunStartAndGetResult = true; + return Attachments; + } + + public BeforeTestRunStartResult SendBeforeTestRunStartAndGetResult(string settingXml) + { + if (this.sendBeforeTestRunStartAndGetResultThrowException) + { + throw new Exception("SocketException"); + + } + + this.sendBeforeTestRunStartAndGetResult = true; + return BeforeTestRunStartResult; + } + + public bool WaitForRequestHandlerConnection(int connectionTimeout) + { + this.connectionTimeout = connectionTimeout; + this.waitForRequestHandlerConnection = true; + return true; + } + } + + internal class DummyDataCollectionLauncher : IDataCollectionLauncher + { + public bool isInitialized; + public bool dataCollectorLaunched; + public void Initialize(Architecture architecture) + { + this.isInitialized = true; + } + + public int LaunchDataCollector(IDictionary environmentVariables, IList commandLineArguments) + { + this.dataCollectorLaunched = true; + return 1; + } + } +} \ No newline at end of file diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Discovery/DiscovererEnumeratorTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Discovery/DiscovererEnumeratorTests.cs new file mode 100644 index 0000000000..78235ceeeb --- /dev/null +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Discovery/DiscovererEnumeratorTests.cs @@ -0,0 +1,443 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.CrossPlatEngine.UnitTests.Discovery +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + using System.Reflection; + + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Discovery; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + using Moq; + + using TestPlatform.Common.UnitTests.ExtensionFramework; + + [TestClass] + public class DiscovererEnumeratorTests + { + private DiscovererEnumerator discovererEnumerator; + + private DiscoveryResultCache discoveryResultCache; + + [TestInitialize] + public void TestInit() + { + this.discoveryResultCache = new DiscoveryResultCache(1000, TimeSpan.FromHours(1), (tests) => { }); + this.discovererEnumerator = new DiscovererEnumerator(this.discoveryResultCache); + + TestDiscoveryExtensionManager.Destroy(); + } + + [TestMethod] + public void LoadTestsShouldReportWarningOnNoDiscoverers() + { + TestPluginCacheTests.SetupMockExtensions( + new string[] { typeof(TestPluginCache).GetTypeInfo().Assembly.Location }, + () => { }); + var sources = new List { typeof(DiscoveryResultCacheTests).GetTypeInfo().Assembly.Location }; + var mockLogger = new Mock(); + + var extensionSourceMap = new Dictionary>(); + extensionSourceMap.Add("_none_", sources); + + this.discovererEnumerator.LoadTests(extensionSourceMap, new Mock().Object, mockLogger.Object); + + mockLogger.Verify( + l => + l.SendMessage( + TestMessageLevel.Warning, + "No test discoverer is registered to perform discovery of test cases. Register a test discoverer and try again."), + Times.Once); + } + + [TestMethod] + public void LoadTestsShouldNotCallIntoDiscoverersIfNoneMatchesSources() + { + TestPluginCacheTests.SetupMockExtensions( + new string[] { typeof(DiscovererEnumeratorTests).GetTypeInfo().Assembly.Location }, + () => { }); + var sources = new List { "temp.jpeg" }; + + var extensionSourceMap = new Dictionary>(); + extensionSourceMap.Add("_none_", sources); + + this.discovererEnumerator.LoadTests(extensionSourceMap, new Mock().Object, new Mock().Object); + + Assert.IsFalse(DllTestDiscoverer.IsDiscoverTestCalled); + } + + [TestMethod] + public void LoadTestsShouldCallIntoADiscovererThatMatchesTheSources() + { + try + { + TestPluginCacheTests.SetupMockExtensions( + new string[] { typeof(DiscovererEnumeratorTests).GetTypeInfo().Assembly.Location }, + () => { }); + + var sources = new List + { + typeof(DiscoveryResultCacheTests).GetTypeInfo().Assembly.Location, + typeof(DiscoveryResultCacheTests).GetTypeInfo().Assembly.Location + }; + + var extensionSourceMap = new Dictionary>(); + extensionSourceMap.Add("_none_", sources); + + var settings = new Mock().Object; + var logger = new Mock().Object; + + this.discovererEnumerator.LoadTests(extensionSourceMap, settings, logger); + + Assert.IsTrue(DllTestDiscoverer.IsDiscoverTestCalled); + Assert.IsFalse(JsonTestDiscoverer.IsDiscoverTestCalled); + + // Also validate that the right set of arguments were passed on to the discoverer. + CollectionAssert.AreEqual(sources, DllTestDiscoverer.Sources.ToList()); + Assert.AreEqual(settings, DllTestDiscoverer.DiscoveryContext.RunSettings); + Assert.AreEqual(logger, DllTestDiscoverer.MessageLogger); + Assert.IsNotNull(DllTestDiscoverer.DiscoverySink); + } + finally + { + this.ResetDiscoverers(); + } + } + + [TestMethod] + public void LoadTestsShouldCallIntoMultipleDiscoverersThatMatchesTheSources() + { + try + { + TestPluginCacheTests.SetupMockExtensions( + new string[] { typeof(DiscovererEnumeratorTests).GetTypeInfo().Assembly.Location }, + () => { }); + + var dllsources = new List + { + typeof(DiscoveryResultCacheTests).GetTypeInfo().Assembly.Location, + typeof(DiscoveryResultCacheTests).GetTypeInfo().Assembly.Location + }; + var jsonsources = new List + { + "test1.json", + "test2.json" + }; + var sources = new List(dllsources); + sources.AddRange(jsonsources); + + var extensionSourceMap = new Dictionary>(); + extensionSourceMap.Add("_none_", sources); + + var settings = new Mock().Object; + var logger = new Mock().Object; + + this.discovererEnumerator.LoadTests(extensionSourceMap, settings, logger); + + Assert.IsTrue(DllTestDiscoverer.IsDiscoverTestCalled); + Assert.IsTrue(JsonTestDiscoverer.IsDiscoverTestCalled); + + // Also validate that the right set of arguments were passed on to the discoverer. + CollectionAssert.AreEqual(dllsources, DllTestDiscoverer.Sources.ToList()); + Assert.AreEqual(settings, DllTestDiscoverer.DiscoveryContext.RunSettings); + Assert.AreEqual(logger, DllTestDiscoverer.MessageLogger); + Assert.IsNotNull(DllTestDiscoverer.DiscoverySink); + + CollectionAssert.AreEqual(jsonsources, JsonTestDiscoverer.Sources.ToList()); + Assert.AreEqual(settings, JsonTestDiscoverer.DiscoveryContext.RunSettings); + Assert.AreEqual(logger, JsonTestDiscoverer.MessageLogger); + Assert.IsNotNull(JsonTestDiscoverer.DiscoverySink); + } + finally + { + this.ResetDiscoverers(); + } + } + + [TestMethod] + public void LoadTestsShouldCallIntoOtherDiscoverersWhenCreatingOneFails() + { + try + { + TestPluginCacheTests.SetupMockExtensions( + new string[] { typeof(DiscovererEnumeratorTests).GetTypeInfo().Assembly.Location }, + () => { }); + + var sources = new List + { + "test1.csv", + typeof(DiscoveryResultCacheTests).GetTypeInfo().Assembly.Location + }; + + var extensionSourceMap = new Dictionary>(); + extensionSourceMap.Add("_none_", sources); + + var settings = new Mock().Object; + var logger = new Mock().Object; + + this.discovererEnumerator.LoadTests(extensionSourceMap, settings, logger); + + Assert.IsTrue(DllTestDiscoverer.IsDiscoverTestCalled); + Assert.IsFalse(SingletonTestDiscoverer.IsDiscoverTestCalled); + + // Also validate that the right set of arguments were passed on to the discoverer. + CollectionAssert.AreEqual(new List { sources[1] }, DllTestDiscoverer.Sources.ToList()); + Assert.AreEqual(settings, DllTestDiscoverer.DiscoveryContext.RunSettings); + Assert.AreEqual(logger, DllTestDiscoverer.MessageLogger); + Assert.IsNotNull(DllTestDiscoverer.DiscoverySink); + } + finally + { + this.ResetDiscoverers(); + } + } + + [TestMethod] + public void LoadTestsShouldCallIntoOtherDiscoverersEvenIfDiscoveryInOneFails() + { + try + { + TestPluginCacheTests.SetupMockExtensions( + new string[] { typeof(DiscovererEnumeratorTests).GetTypeInfo().Assembly.Location }, + () => { }); + + var sources = new List + { + "test1.cs", + typeof(DiscoveryResultCacheTests).GetTypeInfo().Assembly.Location + }; + + var extensionSourceMap = new Dictionary>(); + extensionSourceMap.Add("_none_", sources); + + var settings = new Mock().Object; + var mocklogger = new Mock(); + + this.discovererEnumerator.LoadTests(extensionSourceMap, settings, mocklogger.Object); + + Assert.IsTrue(DllTestDiscoverer.IsDiscoverTestCalled); + Assert.IsTrue(NotImplementedTestDiscoverer.IsDiscoverTestCalled); + + // Also validate that the right set of arguments were passed on to the discoverer. + CollectionAssert.AreEqual(new List { sources[1] }, DllTestDiscoverer.Sources.ToList()); + Assert.AreEqual(settings, DllTestDiscoverer.DiscoveryContext.RunSettings); + Assert.AreEqual(mocklogger.Object, DllTestDiscoverer.MessageLogger); + Assert.IsNotNull(DllTestDiscoverer.DiscoverySink); + + // Check if we log the failure. + var message = string.Format( + CultureInfo.CurrentUICulture, + "An exception occurred while test discoverer '{0}' was loading tests. Exception: {1}", + typeof(NotImplementedTestDiscoverer).Name, + "The method or operation is not implemented."); + + mocklogger.Verify(l => l.SendMessage(TestMessageLevel.Error, message), Times.Once); + } + finally + { + this.ResetDiscoverers(); + } + } + + [TestMethod] + public void LoadTestsShouldCallIntoTheAdapterWithTheRightTestCaseSink() + { + try + { + TestPluginCacheTests.SetupMockExtensions( + new string[] { typeof(DiscovererEnumeratorTests).GetTypeInfo().Assembly.Location }, + () => { }); + + var sources = new List + { + typeof(DiscoveryResultCacheTests).GetTypeInfo().Assembly.Location + }; + + var extensionSourceMap = new Dictionary>(); + extensionSourceMap.Add("_none_", sources); + + var settings = new Mock().Object; + var logger = new Mock().Object; + + this.discovererEnumerator.LoadTests(extensionSourceMap, settings, logger); + + Assert.IsTrue(DllTestDiscoverer.IsDiscoverTestCalled); + Assert.AreEqual(1, this.discoveryResultCache.Tests.Count); + } + finally + { + this.ResetDiscoverers(); + } + } + + [TestMethod] + public void LoadTestsShouldIterateOverAllExtensionsInTheMapAndDiscoverTests() + { + try + { + TestPluginCacheTests.SetupMockExtensions( + new string[] { typeof(DiscovererEnumeratorTests).GetTypeInfo().Assembly.Location }, + () => { }); + + var dllsources = new List + { + typeof(DiscoveryResultCacheTests).GetTypeInfo().Assembly.Location, + typeof(DiscoveryResultCacheTests).GetTypeInfo().Assembly.Location + }; + var jsonsources = new List + { + "test1.json", + "test2.json" + }; + + var extensionSourceMap = new Dictionary>(); + extensionSourceMap.Add(typeof(DiscovererEnumeratorTests).GetTypeInfo().Assembly.Location, jsonsources); + extensionSourceMap.Add("_none_", dllsources); + + var settings = new Mock().Object; + var logger = new Mock().Object; + + this.discovererEnumerator.LoadTests(extensionSourceMap, settings, logger); + + Assert.IsTrue(DllTestDiscoverer.IsDiscoverTestCalled); + Assert.IsTrue(JsonTestDiscoverer.IsDiscoverTestCalled); + + // Also validate that the right set of arguments were passed on to the discoverer. + CollectionAssert.AreEqual(dllsources, DllTestDiscoverer.Sources.ToList()); + Assert.AreEqual(settings, DllTestDiscoverer.DiscoveryContext.RunSettings); + Assert.AreEqual(logger, DllTestDiscoverer.MessageLogger); + Assert.IsNotNull(DllTestDiscoverer.DiscoverySink); + + CollectionAssert.AreEqual(jsonsources, JsonTestDiscoverer.Sources.ToList()); + Assert.AreEqual(settings, JsonTestDiscoverer.DiscoveryContext.RunSettings); + Assert.AreEqual(logger, JsonTestDiscoverer.MessageLogger); + Assert.IsNotNull(JsonTestDiscoverer.DiscoverySink); + } + finally + { + this.ResetDiscoverers(); + } + } + + private void ResetDiscoverers() + { + DllTestDiscoverer.Reset(); + JsonTestDiscoverer.Reset(); + NotImplementedTestDiscoverer.Reset(); + } + + #region implementation + + /// + /// Placing this before others so that at runtime this would be the first to be discovered as a discoverer. + /// + [FileExtension(".csv")] + [DefaultExecutorUri("discoverer://csvdiscoverer")] + private class SingletonTestDiscoverer : ITestDiscoverer + { + private SingletonTestDiscoverer() + { + } + + public static bool IsDiscoverTestCalled { get; private set; } + + public void DiscoverTests(IEnumerable sources, IDiscoveryContext discoveryContext, IMessageLogger logger, ITestCaseDiscoverySink discoverySink) + { + IsDiscoverTestCalled = true; + } + + public static void Reset() + { + IsDiscoverTestCalled = false; + } + } + + [FileExtension(".cs")] + [DefaultExecutorUri("discoverer://csvdiscoverer")] + private class NotImplementedTestDiscoverer : ITestDiscoverer + { + public static bool IsDiscoverTestCalled { get; private set; } + + public void DiscoverTests(IEnumerable sources, IDiscoveryContext discoveryContext, IMessageLogger logger, ITestCaseDiscoverySink discoverySink) + { + IsDiscoverTestCalled = true; + throw new NotImplementedException(); + } + + public static void Reset() + { + IsDiscoverTestCalled = false; + } + } + + [FileExtension(".dll")] + [DefaultExecutorUri("discoverer://dlldiscoverer")] + private class DllTestDiscoverer : ITestDiscoverer + { + public static bool IsDiscoverTestCalled { get; private set; } + + public static IEnumerable Sources { get; private set; } + + public static IDiscoveryContext DiscoveryContext { get; private set; } + + public static IMessageLogger MessageLogger { get; private set; } + + public static ITestCaseDiscoverySink DiscoverySink { get; private set; } + + public void DiscoverTests(IEnumerable sources, IDiscoveryContext discoveryContext, IMessageLogger logger, ITestCaseDiscoverySink discoverySink) + { + IsDiscoverTestCalled = true; + Sources = sources; + DiscoveryContext = discoveryContext; + MessageLogger = logger; + DiscoverySink = discoverySink; + + var testCase = new TestCase("A.C.M", new Uri("executor://dllexecutor"), "A"); + discoverySink.SendTestCase(testCase); + } + + public static void Reset() + { + IsDiscoverTestCalled = false; + } + } + + [FileExtension(".json")] + [DefaultExecutorUri("discoverer://jsondiscoverer")] + private class JsonTestDiscoverer : ITestDiscoverer + { + public static bool IsDiscoverTestCalled { get; private set; } + + public static IEnumerable Sources { get; private set; } + + public static IDiscoveryContext DiscoveryContext { get; private set; } + + public static IMessageLogger MessageLogger { get; private set; } + + public static ITestCaseDiscoverySink DiscoverySink { get; private set; } + + public void DiscoverTests(IEnumerable sources, IDiscoveryContext discoveryContext, IMessageLogger logger, ITestCaseDiscoverySink discoverySink) + { + IsDiscoverTestCalled = true; + Sources = sources; + DiscoveryContext = discoveryContext; + MessageLogger = logger; + DiscoverySink = discoverySink; + } + + public static void Reset() + { + IsDiscoverTestCalled = false; + } + } + + #endregion + } +} diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Discovery/DiscoveryManagerTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Discovery/DiscoveryManagerTests.cs new file mode 100644 index 0000000000..acaf5554e7 --- /dev/null +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Discovery/DiscoveryManagerTests.cs @@ -0,0 +1,149 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.CrossPlatEngine.UnitTests.Discovery +{ + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + using System.Reflection; + + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Discovery; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + using Moq; + + using TestPlatform.Common.UnitTests.ExtensionFramework; + + [TestClass] + public class DiscoveryManagerTests + { + private DiscoveryManager discoveryManager; + + [TestInitialize] + public void TestInit() + { + this.discoveryManager = new DiscoveryManager(); + } + + [TestCleanup] + public void TestCleanup() + { + TestDiscoveryExtensionManager.Destroy(); + TestPluginCache.Instance = null; + } + + #region Initialize tests + + [TestMethod] + public void InitializeShouldUpdateAdditionalExtenions() + { + var testableTestPluginCache = TestPluginCacheTests.SetupMockPathUtilities(); + + // Stub the default extensions folder. + testableTestPluginCache.DoesDirectoryExistSetter = false; + + this.discoveryManager.Initialize( + new string[] { typeof(TestPluginCacheTests).GetTypeInfo().Assembly.Location }); + + var allDiscoverers = TestDiscoveryExtensionManager.Create().Discoverers; + + Assert.IsNotNull(allDiscoverers); + Assert.IsTrue(allDiscoverers.Count() > 0); + } + + #endregion + + #region DiscoverTests tests + + [TestMethod] + public void DiscoverTestsShouldLogIfTheSourceDoesNotExist() + { + var criteria = new DiscoveryCriteria(new List { "imaginary.dll" }, 100, null); + var mockLogger = new Mock(); + + this.discoveryManager.DiscoverTests(criteria, mockLogger.Object); + + var errorMessage = string.Format(CultureInfo.CurrentCulture, "Could not find file {0}.", "imaginary.dll"); + mockLogger.Verify( + l => + l.HandleLogMessage( + Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging.TestMessageLevel.Warning, + errorMessage), + Times.Once); + } + + [TestMethod] + public void DiscoverTestsShouldLogIfThereAreNoValidSources() + { + var sources = new List { "imaginary.dll" }; + var criteria = new DiscoveryCriteria(sources, 100, null); + var mockLogger = new Mock(); + + this.discoveryManager.DiscoverTests(criteria, mockLogger.Object); + + var sourcesString = string.Join(",", sources.ToArray()); + var errorMessage = string.Format(CultureInfo.CurrentCulture, Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Resources.NoValidSourceFoundForDiscovery, sourcesString); + mockLogger.Verify( + l => + l.HandleLogMessage( + Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging.TestMessageLevel.Warning, + errorMessage), + Times.Once); + } + + [TestMethod] + public void DiscoverTestsShouldLogIfTheSameSourceIsSpecifiedTwice() + { + TestPluginCacheTests.SetupMockExtensions( + new string[] { typeof(DiscovererEnumeratorTests).GetTypeInfo().Assembly.Location }, + () => { }); + + var sources = new List + { + typeof(DiscoveryManagerTests).GetTypeInfo().Assembly.Location, + typeof(DiscoveryManagerTests).GetTypeInfo().Assembly.Location + }; + + var criteria = new DiscoveryCriteria(sources, 100, null); + var mockLogger = new Mock(); + + this.discoveryManager.DiscoverTests(criteria, mockLogger.Object); + + var errorMessage = string.Format(CultureInfo.CurrentCulture, Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Resources.DuplicateSource, sources[0]); + mockLogger.Verify( + l => + l.HandleLogMessage( + Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging.TestMessageLevel.Warning, + errorMessage), + Times.Once); + } + + [TestMethod] + public void DiscoverTestsShouldDiscoverTestsInTheSpecifiedSource() + { + TestPluginCacheTests.SetupMockExtensions( + new string[] { typeof(DiscovererEnumeratorTests).GetTypeInfo().Assembly.Location }, + () => { }); + + var sources = new List + { + typeof(DiscoveryManagerTests).GetTypeInfo().Assembly.Location + }; + + var criteria = new DiscoveryCriteria(sources, 1, null); + var mockLogger = new Mock(); + + this.discoveryManager.DiscoverTests(criteria, mockLogger.Object); + + // Assert that the tests are passed on via the handletestruncomplete event. + mockLogger.Verify(l => l.HandleDiscoveryComplete(1, It.IsAny>(), false), Times.Once); + } + + #endregion + } +} diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Discovery/DiscoveryResultCacheTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Discovery/DiscoveryResultCacheTests.cs new file mode 100644 index 0000000000..21d02fa8ba --- /dev/null +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Discovery/DiscoveryResultCacheTests.cs @@ -0,0 +1,123 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.CrossPlatEngine.UnitTests.Discovery +{ + using System; + + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Discovery; + using System.Collections; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + + [TestClass] + public class DiscoveryResultCacheTests + { + [TestMethod] + public void DiscoveryResultCacheConstructorShouldInitializeDiscoveredTestsList() + { + var cache = new DiscoveryResultCache(1000, TimeSpan.FromHours(1), (tests) => { }); + + Assert.IsNotNull(cache.Tests); + Assert.AreEqual(0, cache.Tests.Count); + } + + [TestMethod] + public void DiscoveryResultCacheConstructorShouldInitializeTotalDiscoveredTests() + { + var cache = new DiscoveryResultCache(1000, TimeSpan.FromHours(1), (tests) => { }); + + Assert.AreEqual(0, cache.TotalDiscoveredTests); + } + + [TestMethod] + public void AddTestShouldAddATestCaseToTheList() + { + var cache = new DiscoveryResultCache(1000, TimeSpan.FromHours(1), (tests) => { }); + + var testCase = new TestCase("A.C.M", new Uri("executor://unittest"), "A"); + cache.AddTest(testCase); + + Assert.AreEqual(1, cache.Tests.Count); + Assert.AreEqual(testCase, cache.Tests[0]); + } + + [TestMethod] + public void AddTestShouldIncreaseDiscoveredTestsCount() + { + var cache = new DiscoveryResultCache(1000, TimeSpan.FromHours(1), (tests) => { }); + + var testCase = new TestCase("A.C.M", new Uri("executor://unittest"), "A"); + cache.AddTest(testCase); + + Assert.AreEqual(1, cache.TotalDiscoveredTests); + } + + [TestMethod] + public void AddTestShouldReportTestCasesIfMaxCacheSizeIsMet() + { + ICollection reportedTestCases = null; + var cache = new DiscoveryResultCache(2, TimeSpan.FromHours(1), (tests) => { reportedTestCases = tests; }); + + var testCase1 = new TestCase("A.C.M", new Uri("executor://unittest"), "A"); + var testCase2 = new TestCase("A.C.M2", new Uri("executor://unittest"), "A"); + cache.AddTest(testCase1); + cache.AddTest(testCase2); + + Assert.IsNotNull(reportedTestCases); + Assert.AreEqual(2, reportedTestCases.Count); + CollectionAssert.AreEqual(new List { testCase1, testCase2 }, reportedTestCases.ToList()); + } + + [TestMethod] + public void AddTestShouldResetTestListOnceCacheSizeIsMet() + { + var cache = new DiscoveryResultCache(2, TimeSpan.FromHours(1), (tests) => { }); + + var testCase1 = new TestCase("A.C.M", new Uri("executor://unittest"), "A"); + var testCase2 = new TestCase("A.C.M2", new Uri("executor://unittest"), "A"); + cache.AddTest(testCase1); + cache.AddTest(testCase2); + + Assert.IsNotNull(cache.Tests); + Assert.AreEqual(0, cache.Tests.Count); + + // Validate that total tests is no reset though. + Assert.AreEqual(2, cache.TotalDiscoveredTests); + } + + [TestMethod] + public void AddTestShouldReportTestCasesIfCacheTimeoutIsMet() + { + ICollection reportedTestCases = null; + var cache = new DiscoveryResultCache(100, TimeSpan.FromMilliseconds(10), (tests) => { reportedTestCases = tests; }); + + var testCase = new TestCase("A.C.M", new Uri("executor://unittest"), "A"); + Task.Delay(20).Wait(); + cache.AddTest(testCase); + + Assert.IsNotNull(reportedTestCases); + Assert.AreEqual(1, reportedTestCases.Count); + Assert.AreEqual(testCase, reportedTestCases.ToArray()[0]); + } + + [TestMethod] + public void AddTestShouldResetTestListIfCacheTimeoutIsMet() + { + ICollection reportedTestCases = null; + var cache = new DiscoveryResultCache(100, TimeSpan.FromMilliseconds(10), (tests) => { reportedTestCases = tests; }); + + var testCase = new TestCase("A.C.M", new Uri("executor://unittest"), "A"); + Task.Delay(20).Wait(); + cache.AddTest(testCase); + + Assert.IsNotNull(cache.Tests); + Assert.AreEqual(0, cache.Tests.Count); + + // Validate that total tests is no reset though. + Assert.AreEqual(1, cache.TotalDiscoveredTests); + } + } +} diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Discovery/TestCaseDiscoverySinkTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Discovery/TestCaseDiscoverySinkTests.cs new file mode 100644 index 0000000000..5a15e72218 --- /dev/null +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Discovery/TestCaseDiscoverySinkTests.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.CrossPlatEngine.UnitTests.Discovery +{ + using System; + + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Discovery; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + using Moq; + + [TestClass] + public class TestCaseDiscoverySinkTests + { + [TestMethod] + public void SendTestCaseShouldNotThrowIfCacheIsNull() + { + var sink = new TestCaseDiscoverySink(null); + + var testCase = new TestCase("A.C.M", new Uri("executor://unittest"), "A"); + + // This should not throw. + sink.SendTestCase(testCase); + } + + [TestMethod] + public void SendTestCaseShouldSendTestCasesToCache() + { + var cache = new DiscoveryResultCache(1000, TimeSpan.FromHours(1), (tests) => { }); + var sink = new TestCaseDiscoverySink(cache); + + var testCase = new TestCase("A.C.M", new Uri("executor://unittest"), "A"); + + sink.SendTestCase(testCase); + + // Assert that the cache has the test case. + Assert.IsNotNull(cache.Tests); + Assert.AreEqual(1, cache.Tests.Count); + Assert.AreEqual(testCase, cache.Tests[0]); + } + } +} diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Execution/BaseRunTestsTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Execution/BaseRunTestsTests.cs new file mode 100644 index 0000000000..b29711328a --- /dev/null +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Execution/BaseRunTestsTests.cs @@ -0,0 +1,602 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.CrossPlatEngine.UnitTests.Execution +{ + using System; + using System.Collections.Generic; + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.Utilities; + using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Adapter; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Execution; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.ClientProtocol; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; + + using TestPlatform.CrossPlatEngine.UnitTests.TestableImplementations; + using Moq; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using System.Collections.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using System.IO; + using Common.UnitTests.ExtensionFramework; + using System.Reflection; + //using Microsoft.Extensions.PlatformAbstractions; + using System.Linq; + + [TestClass] + public class BaseRunTestsTests + { + private TestableTestRunCache testableTestRunCache; + private TestExecutionContext testExecutionContext; + private Mock mockTestRunEventsHandler; + + private TestableBaseRunTests runTestsInstance; + + private const string BaseRunTestsExecutorUri = "executor://BaseRunTestsExecutor/"; + private const string BadBaseRunTestsExecutorUri = "executor://BadBaseRunTestsExecutor/"; + + [TestInitialize] + public void TestInit() + { + this.testableTestRunCache = new TestableTestRunCache(); + this.testExecutionContext = new TestExecutionContext( + 100, + TimeSpan.MaxValue, + inIsolation: false, + keepAlive: false, + areTestCaseLevelEventsRequired: false, + isDebug: false, + testCaseFilter: null); + this.mockTestRunEventsHandler = new Mock(); + + this.runTestsInstance = new TestableBaseRunTests( + testableTestRunCache, + null, + testExecutionContext, + null, + this.mockTestRunEventsHandler.Object); + } + + #region Constructor tests + + [TestMethod] + public void ConstructorShouldInitializeRunContext() + { + var runContext = this.runTestsInstance.GetRunContext; + Assert.IsNotNull(runContext); + Assert.AreEqual(false, runContext.KeepAlive); + Assert.AreEqual(false, runContext.InIsolation); + Assert.AreEqual(false, runContext.IsDataCollectionEnabled); + Assert.AreEqual(false, runContext.IsBeingDebugged); + } + + [TestMethod] + public void ConstructorShouldInitializeFrameworkHandle() + { + var frameworkHandle = this.runTestsInstance.GetFrameworkHandle; + Assert.IsNotNull(frameworkHandle); + } + + [TestMethod] + public void ConstructorShouldInitializeExecutorUrisThatRanTests() + { + var executorUris = this.runTestsInstance.GetExecutorUrisThatRanTests; + Assert.IsNotNull(executorUris); + } + + #endregion + + #region RunTests tests + + [TestMethod] + public void RunTestsShouldRaiseTestRunCompleteBeforeThrowingExceptionOnAnException() + { + TestRunCompleteEventArgs receivedCompleteArgs = null; + // Setup mocks. + this.runTestsInstance.GetExecutorUriExtensionMapCallback = (fh, rc) => { throw new NotImplementedException(); }; + this.mockTestRunEventsHandler.Setup( + treh => + treh.HandleTestRunComplete( + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny>())) + .Callback( + (TestRunCompleteEventArgs complete, + TestRunChangedEventArgs stats, + ICollection attachments, + ICollection executorUris) => + { + receivedCompleteArgs = complete; + }); + + Assert.ThrowsException(() => this.runTestsInstance.RunTests()); + + Assert.IsNotNull(receivedCompleteArgs); + Assert.IsTrue(receivedCompleteArgs.IsAborted); + } + + [TestMethod] + public void RunTestsShouldNotThrowIfExceptionIsAFileNotFoundException() + { + TestRunCompleteEventArgs receivedCompleteArgs = null; + // Setup mocks. + this.runTestsInstance.GetExecutorUriExtensionMapCallback = (fh, rc) => { throw new FileNotFoundException(); }; + this.mockTestRunEventsHandler.Setup( + treh => + treh.HandleTestRunComplete( + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny>())) + .Callback( + (TestRunCompleteEventArgs complete, + TestRunChangedEventArgs stats, + ICollection attachments, + ICollection executorUris) => + { + receivedCompleteArgs = complete; + }); + + // This should not throw. + this.runTestsInstance.RunTests(); + + Assert.IsNotNull(receivedCompleteArgs); + Assert.IsTrue(receivedCompleteArgs.IsAborted); + } + + [TestMethod] + public void RunTestsShouldNotThrowIfExceptionIsAnArgumentException() + { + TestRunCompleteEventArgs receivedCompleteArgs = null; + // Setup mocks. + this.runTestsInstance.GetExecutorUriExtensionMapCallback = (fh, rc) => { throw new ArgumentException(); }; + this.mockTestRunEventsHandler.Setup( + treh => + treh.HandleTestRunComplete( + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny>())) + .Callback( + (TestRunCompleteEventArgs complete, + TestRunChangedEventArgs stats, + ICollection attachments, + ICollection executorUris) => + { + receivedCompleteArgs = complete; + }); + + // This should not throw. + this.runTestsInstance.RunTests(); + + Assert.IsNotNull(receivedCompleteArgs); + Assert.IsTrue(receivedCompleteArgs.IsAborted); + } + + [TestMethod] + public void RunTestsShouldThrowIfExecutorUriExtensionMapIsNull() + { + // Setup mocks. + this.runTestsInstance.GetExecutorUriExtensionMapCallback = (fh, rc) => { return null; }; + + // This should not throw. + Assert.ThrowsException(() => this.runTestsInstance.RunTests()); + } + + [TestMethod] + public void RunTestsShouldInvokeTheTestExecutorIfAdapterAssemblyIsKnown() + { + var assemblyLocation = typeof (BaseRunTestsTests).GetTypeInfo().Assembly.Location; + var executorUriExtensionMap = new List> + { + new Tuple(new Uri(BaseRunTestsExecutorUri), assemblyLocation) + }; + LazyExtension receivedExecutor = null; + + // Setup mocks. + this.runTestsInstance.GetExecutorUriExtensionMapCallback = (fh, rc) => { return executorUriExtensionMap; }; + this.runTestsInstance.InvokeExecutorCallback = + (executor, executorUriExtensionTuple, runContext, frameworkHandle) => + { + receivedExecutor = executor; + }; + + this.runTestsInstance.RunTests(); + + Assert.IsNotNull(receivedExecutor); + Assert.AreEqual(BaseRunTestsExecutorUri, receivedExecutor.Metadata.ExtensionUri); + } + + [TestMethod] + public void RunTestsShouldInvokeTheTestExecutorIfAdapterAssemblyIsUnknown() + { + var executorUriExtensionMap = new List> + { + new Tuple(new Uri(BaseRunTestsExecutorUri), Microsoft.VisualStudio.TestPlatform.ObjectModel.Constants.UnspecifiedAdapterPath) + }; + LazyExtension receivedExecutor = null; + + // Setup mocks. + this.runTestsInstance.GetExecutorUriExtensionMapCallback = (fh, rc) => { return executorUriExtensionMap; }; + this.runTestsInstance.InvokeExecutorCallback = + (executor, executorUriExtensionTuple, runContext, frameworkHandle) => + { + receivedExecutor = executor; + }; + TestPluginCacheTests.SetupMockExtensions( + new string[] { typeof(BaseRunTestsTests).GetTypeInfo().Assembly.Location }, + () => { }); + + this.runTestsInstance.RunTests(); + + Assert.IsNotNull(receivedExecutor); + Assert.AreEqual(BaseRunTestsExecutorUri, receivedExecutor.Metadata.ExtensionUri); + } + + [TestMethod] + public void RunTestsShouldReportAWarningIfExecutorUriIsNotDefinedInExtensionAssembly() + { + var assemblyLocation = typeof(BaseRunTestsTests).GetTypeInfo().Assembly.Location; + var executorUriExtensionMap = new List> + { + new Tuple(new Uri("executor://nonexistent/"), assemblyLocation) + }; + LazyExtension receivedExecutor = null; + + // Setup mocks. + this.runTestsInstance.GetExecutorUriExtensionMapCallback = (fh, rc) => { return executorUriExtensionMap; }; + this.runTestsInstance.InvokeExecutorCallback = + (executor, executorUriExtensionTuple, runContext, frameworkHandle) => + { + receivedExecutor = executor; + }; + + this.runTestsInstance.RunTests(); + + var expectedWarningMessageFormat = + "Could not find test executor with URI '{0}'. Make sure that the test executor is installed and supports .net runtime version {1}."; + //var runtimeVersion = string.Concat(PlatformServices.Default.Runtime.RuntimeType, " ", + // PlatformServices.Default.Runtime.RuntimeVersion); + var runtimeVersion = " "; + + var expectedWarningMessage = string.Format(expectedWarningMessageFormat, "executor://nonexistent/", + runtimeVersion); + this.mockTestRunEventsHandler.Verify( + treh => treh.HandleLogMessage(TestMessageLevel.Warning, expectedWarningMessage), Times.Once); + + // Should not have been called. + Assert.IsNull(receivedExecutor); + } + + [TestMethod] + public void RunTestsShouldNotAddExecutorUriToExecutorUriListIfNoTestsAreRun() + { + var assemblyLocation = typeof(BaseRunTestsTests).GetTypeInfo().Assembly.Location; + var executorUriExtensionMap = new List> + { + new Tuple(new Uri(BaseRunTestsExecutorUri), assemblyLocation) + }; + + // Setup mocks. + this.runTestsInstance.GetExecutorUriExtensionMapCallback = (fh, rc) => { return executorUriExtensionMap; }; + + this.runTestsInstance.RunTests(); + + Assert.AreEqual(0, this.runTestsInstance.GetExecutorUrisThatRanTests.Count); + } + + [TestMethod] + public void RunTestsShouldAddExecutorUriToExecutorUriListIfExecutorHasRunTests() + { + var assemblyLocation = typeof(BaseRunTestsTests).GetTypeInfo().Assembly.Location; + var executorUriExtensionMap = new List> + { + new Tuple(new Uri(BaseRunTestsExecutorUri), assemblyLocation) + }; + + // Setup mocks. + this.runTestsInstance.GetExecutorUriExtensionMapCallback = (fh, rc) => { return executorUriExtensionMap; }; + this.runTestsInstance.InvokeExecutorCallback = + (executor, executorUriExtensionTuple, runContext, frameworkHandle) => + { + this.testableTestRunCache.TotalExecutedTests += 1; + }; + + this.runTestsInstance.RunTests(); + + var expectedUris = new string[] {BaseRunTestsExecutorUri.ToLower()}; + CollectionAssert.AreEqual(expectedUris, this.runTestsInstance.GetExecutorUrisThatRanTests.ToArray()); + } + + [TestMethod] + public void RunTestsShouldReportWarningIfExecutorThrowsAnException() + { + var assemblyLocation = typeof(BaseRunTestsTests).GetTypeInfo().Assembly.Location; + var executorUriExtensionMap = new List> + { + new Tuple(new Uri(BaseRunTestsExecutorUri), assemblyLocation) + }; + + // Setup mocks. + this.runTestsInstance.GetExecutorUriExtensionMapCallback = (fh, rc) => { return executorUriExtensionMap; }; + this.runTestsInstance.InvokeExecutorCallback = + (executor, executorUriExtensionTuple, runContext, frameworkHandle) => + { + throw new ArgumentException("Test influenced."); + }; + + this.runTestsInstance.RunTests(); + + var messageFormat = "An exception occurred while invoking executor '{0}': {1}"; + var message = string.Format(messageFormat, BaseRunTestsExecutorUri.ToLower(), "Test influenced."); + this.mockTestRunEventsHandler.Verify(treh => treh.HandleLogMessage(TestMessageLevel.Error, message), + Times.Once); + + // Also validate that a test run complete is called. + this.mockTestRunEventsHandler.Verify( + treh => treh.HandleTestRunComplete(It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny>()), Times.Once); + } + + [TestMethod] + public void RunTestsShouldNotFailOtherExecutorsRunIfOneExecutorThrowsAnException() + { + var assemblyLocation = typeof(BaseRunTestsTests).GetTypeInfo().Assembly.Location; + var executorUriExtensionMap = new List> + { + new Tuple(new Uri(BadBaseRunTestsExecutorUri), assemblyLocation), + new Tuple(new Uri(BaseRunTestsExecutorUri), assemblyLocation) + }; + + // Setup mocks. + this.runTestsInstance.GetExecutorUriExtensionMapCallback = (fh, rc) => { return executorUriExtensionMap; }; + this.runTestsInstance.InvokeExecutorCallback = + (executor, executorUriExtensionTuple, runContext, frameworkHandle) => + { + if (string.Equals(BadBaseRunTestsExecutorUri, executor.Metadata.ExtensionUri)) + { + throw new Exception(); + } + else + { + this.testableTestRunCache.TotalExecutedTests++; + } + }; + + this.runTestsInstance.RunTests(); + + var expectedUris = new string[] { BaseRunTestsExecutorUri.ToLower() }; + CollectionAssert.AreEqual(expectedUris, this.runTestsInstance.GetExecutorUrisThatRanTests.ToArray()); + } + + [TestMethod] + public void RunTestsShouldIterateThroughAllExecutors() + { + var assemblyLocation = typeof(BaseRunTestsTests).GetTypeInfo().Assembly.Location; + var executorUriExtensionMap = new List> + { + new Tuple(new Uri(BadBaseRunTestsExecutorUri), assemblyLocation), + new Tuple(new Uri(BaseRunTestsExecutorUri), assemblyLocation) + }; + + // Setup mocks. + this.runTestsInstance.GetExecutorUriExtensionMapCallback = (fh, rc) => { return executorUriExtensionMap; }; + this.runTestsInstance.InvokeExecutorCallback = + (executor, executorUriExtensionTuple, runContext, frameworkHandle) => + { + this.testableTestRunCache.TotalExecutedTests++; + }; + + this.runTestsInstance.RunTests(); + + var expectedUris = new string[] { BadBaseRunTestsExecutorUri.ToLower(), BaseRunTestsExecutorUri.ToLower() }; + CollectionAssert.AreEqual(expectedUris, this.runTestsInstance.GetExecutorUrisThatRanTests.ToArray()); + } + + [TestMethod] + public void RunTestsShouldRaiseTestRunComplete() + { + TestRunCompleteEventArgs receivedRunCompleteArgs = null; + TestRunChangedEventArgs receivedRunStatusArgs = null; + ICollection receivedattachments = null; + ICollection receivedExecutorUris = null; + + var assemblyLocation = typeof(BaseRunTestsTests).GetTypeInfo().Assembly.Location; + var executorUriExtensionMap = new List> + { + new Tuple(new Uri(BadBaseRunTestsExecutorUri), assemblyLocation), + new Tuple(new Uri(BaseRunTestsExecutorUri), assemblyLocation) + }; + + // Setup mocks. + this.runTestsInstance.GetExecutorUriExtensionMapCallback = (fh, rc) => { return executorUriExtensionMap; }; + this.runTestsInstance.InvokeExecutorCallback = + (executor, executorUriExtensionTuple, runContext, frameworkHandle) => + { + var tr = new Microsoft.VisualStudio.TestPlatform.ObjectModel.TestResult(new TestCase("A.C.M", new Uri("default://dummy/"), "A")); + this.testableTestRunCache.TestResultList.Add(tr); + this.testableTestRunCache.TotalExecutedTests++; + }; + this.mockTestRunEventsHandler.Setup( + treh => + treh.HandleTestRunComplete( + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny>())) + .Callback( + (TestRunCompleteEventArgs complete, + TestRunChangedEventArgs stats, + ICollection attachments, + ICollection executorUris) => + { + receivedRunCompleteArgs = complete; + receivedRunStatusArgs = stats; + receivedattachments = attachments; + receivedExecutorUris = executorUris; + }); + + // Act. + this.runTestsInstance.RunTests(); + + // Test run complete assertions. + Assert.IsNotNull(receivedRunCompleteArgs); + Assert.IsNull(receivedRunCompleteArgs.Error); + Assert.IsFalse(receivedRunCompleteArgs.IsAborted); + Assert.AreEqual(this.testableTestRunCache.TestRunStatistics, receivedRunCompleteArgs.TestRunStatistics); + + // Test run changed event assertions + Assert.IsNotNull(receivedRunStatusArgs); + Assert.AreEqual(this.testableTestRunCache.TestRunStatistics, receivedRunStatusArgs.TestRunStatistics); + Assert.IsNotNull(receivedRunStatusArgs.NewTestResults); + Assert.IsTrue(receivedRunStatusArgs.NewTestResults.Count() > 0); + Assert.IsNull(receivedRunStatusArgs.ActiveTests); + + // Attachments + Assert.IsNotNull(receivedattachments); + + // Executor Uris + var expectedUris = new string[] { BadBaseRunTestsExecutorUri.ToLower(), BaseRunTestsExecutorUri.ToLower() }; + CollectionAssert.AreEqual(expectedUris, receivedExecutorUris.ToArray()); + } + + [TestMethod] + public void RunTestsShouldNotifyItsImplementersOfAnyExceptionThrownByTheExecutors() + { + bool? isExceptionThrown = null; + var assemblyLocation = typeof(BaseRunTestsTests).GetTypeInfo().Assembly.Location; + var executorUriExtensionMap = new List> + { + new Tuple(new Uri(BaseRunTestsExecutorUri), assemblyLocation) + }; + + // Setup mocks. + this.runTestsInstance.GetExecutorUriExtensionMapCallback = (fh, rc) => { return executorUriExtensionMap; }; + this.runTestsInstance.InvokeExecutorCallback = + (executor, executorUriExtensionTuple, runContext, frameworkHandle) => + { + throw new Exception(); + }; + this.runTestsInstance.BeforeRaisingTestRunCompleteCallback = (isEx) => { isExceptionThrown = isEx; }; + + this.runTestsInstance.RunTests(); + + Assert.IsTrue(isExceptionThrown.HasValue && isExceptionThrown.Value); + } + + #endregion + + #region Testable Implementation + + private class TestableBaseRunTests : BaseRunTests + { + public TestableBaseRunTests( + ITestRunCache testRunCache, + string runSettings, + TestExecutionContext testExecutionContext, + ITestCaseEventsHandler testCaseEventsHandler, + ITestRunEventsHandler testRunEventsHandler) + : base(testRunCache, runSettings, testExecutionContext, testCaseEventsHandler, testRunEventsHandler) + { + } + + public Action BeforeRaisingTestRunCompleteCallback { get; set; } + + public Func>> GetExecutorUriExtensionMapCallback { get; set; } + + public + Action + , Tuple, RunContext, + IFrameworkHandle> InvokeExecutorCallback { get; set; } + + /// + /// Gets the run settings. + /// + public string GetRunSettings => this.RunSettings; + + /// + /// Gets the test execution context. + /// + public TestExecutionContext GetTestExecutionContext => this.TestExecutionContext; + + /// + /// Gets the test run events handler. + /// + public ITestRunEventsHandler GetTestRunEventsHandler => this.TestRunEventsHandler; + + /// + /// Gets the test run cache. + /// + public ITestRunCache GetTestRunCache => this.TestRunCache; + + public bool GetIsCancellationRequested => this.IsCancellationRequested; + + public RunContext GetRunContext => this.RunContext; + + public FrameworkHandle GetFrameworkHandle => this.FrameworkHandle; + + public ICollection GetExecutorUrisThatRanTests => this.ExecutorUrisThatRanTests; + + protected override void BeforeRaisingTestRunComplete(bool exceptionsHitDuringRunTests) + { + this.BeforeRaisingTestRunCompleteCallback?.Invoke(exceptionsHitDuringRunTests); + } + + protected override IEnumerable> GetExecutorUriExtensionMap(IFrameworkHandle testExecutorFrameworkHandle, RunContext runContext) + { + return this.GetExecutorUriExtensionMapCallback?.Invoke(testExecutorFrameworkHandle, runContext); + } + + protected override void InvokeExecutor(LazyExtension executor, Tuple executorUriExtensionTuple, RunContext runContext, IFrameworkHandle frameworkHandle) + { + this.InvokeExecutorCallback?.Invoke(executor, executorUriExtensionTuple, runContext, frameworkHandle); + } + } + + [ExtensionUri(BaseRunTestsExecutorUri)] + private class TestExecutor : ITestExecutor + { + public void Cancel() + { + + } + + public void RunTests(IEnumerable sources, IRunContext runContext, IFrameworkHandle frameworkHandle) + { + + } + + public void RunTests(IEnumerable tests, IRunContext runContext, IFrameworkHandle frameworkHandle) + { + + } + } + + [ExtensionUri(BadBaseRunTestsExecutorUri)] + private class BadTestExecutor : ITestExecutor + { + public void Cancel() + { + throw new NotImplementedException(); + } + + public void RunTests(IEnumerable sources, IRunContext runContext, IFrameworkHandle frameworkHandle) + { + throw new NotImplementedException(); + } + + public void RunTests(IEnumerable tests, IRunContext runContext, IFrameworkHandle frameworkHandle) + { + throw new NotImplementedException(); + } + } + + #endregion + + } +} diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Execution/ExecutionManagerTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Execution/ExecutionManagerTests.cs new file mode 100644 index 0000000000..2bb168fa9b --- /dev/null +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Execution/ExecutionManagerTests.cs @@ -0,0 +1,178 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.CrossPlatEngine.UnitTests.Execution +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + + using Common.UnitTests.ExtensionFramework; + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework; + using Microsoft.VisualStudio.TestPlatform.Common.SettingsProvider; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Execution; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.ClientProtocol; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + using Moq; + + using static RunTestsWithSourcesTests; + + [TestClass] + public class ExecutionManagerTests + { + private ExecutionManager executionManager; + + [TestInitialize] + public void TestInit() + { + this.executionManager = new ExecutionManager(); + + TestPluginCache.Instance = null; + } + + [TestCleanup] + public void TestCleanup() + { + RunTestWithSourcesExecutor.RunTestsWithSourcesCallback = null; + RunTestWithSourcesExecutor.RunTestsWithTestsCallback = null; + + TestDiscoveryExtensionManager.Destroy(); + TestExecutorExtensionManager.Destroy(); + SettingsProviderExtensionManager.Destroy(); + } + + [TestMethod] + public void InitializeShouldLoadAndInitializeAllExtension() + { + var assemblyLocation = typeof(TestDiscoveryExtensionManagerTests).GetTypeInfo().Assembly.Location; + this.executionManager.Initialize(new List { assemblyLocation }); + + Assert.IsNotNull(TestPluginCache.Instance.TestExtensions); + + // Discoverers + Assert.IsTrue(TestPluginCache.Instance.TestExtensions.TestDiscoverers.Count > 0); + var allDiscoverers = TestDiscoveryExtensionManager.Create().Discoverers; + + foreach (var discoverer in allDiscoverers) + { + Assert.IsTrue(discoverer.IsExtensionCreated); + } + + // Executors + Assert.IsTrue(TestPluginCache.Instance.TestExtensions.TestExecutors.Count > 0); + var allExecutors = TestExecutorExtensionManager.Create().TestExtensions; + + foreach (var executor in allExecutors) + { + Assert.IsTrue(executor.IsExtensionCreated); + } + + // Settings Providers + Assert.IsTrue(TestPluginCache.Instance.TestExtensions.TestSettingsProviders.Count > 0); + var settingsProviders = SettingsProviderExtensionManager.Create().SettingsProvidersMap.Values; + + foreach (var provider in settingsProviders) + { + Assert.IsTrue(provider.IsExtensionCreated); + } + } + + [TestMethod] + public void StartTestRunShouldRunTestsInTheProvidedSources() + { + var assemblyLocation = typeof(ExecutionManagerTests).GetTypeInfo().Assembly.Location; + + var adapterSourceMap = new Dictionary>(); + adapterSourceMap.Add(assemblyLocation, new List { assemblyLocation }); + + var testExecutionContext = new TestExecutionContext( + 1, + TimeSpan.MaxValue, + inIsolation: false, + keepAlive: false, + areTestCaseLevelEventsRequired: false, + isDebug: false, + testCaseFilter: null); + + var mockTestRunEventsHandler = new Mock(); + + var isExecutorCalled = false; + RunTestWithSourcesExecutor.RunTestsWithSourcesCallback = (s, rc, fh) => + { + isExecutorCalled = true; + var tr = + new Microsoft.VisualStudio.TestPlatform.ObjectModel.TestResult( + new Microsoft.VisualStudio.TestPlatform.ObjectModel.TestCase( + "A.C.M", + new Uri("e://d/"), + "A.dll")); + fh.RecordResult(tr); + }; + + this.executionManager.StartTestRun(adapterSourceMap, null, testExecutionContext, null, mockTestRunEventsHandler.Object); + + Assert.IsTrue(isExecutorCalled); + mockTestRunEventsHandler.Verify( + treh => treh.HandleTestRunComplete(It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny>()), Times.Once); + + // Also verify that run stats are passed through. + mockTestRunEventsHandler.Verify(treh => treh.HandleTestRunStatsChange(It.IsAny()), Times.Once); + } + + [TestMethod] + public void StartTestRunShouldRunTestsForTheProvidedTests() + { + var assemblyLocation = typeof(ExecutionManagerTests).GetTypeInfo().Assembly.Location; + + var tests = new List + { + new TestCase("A.C.M1", new Uri(RunTestsWithSourcesTestsExecutorUri), assemblyLocation) + }; + + var testExecutionContext = new TestExecutionContext( + 1, + TimeSpan.MaxValue, + inIsolation: false, + keepAlive: false, + areTestCaseLevelEventsRequired: false, + isDebug: false, + testCaseFilter: null); + + var mockTestRunEventsHandler = new Mock(); + + var isExecutorCalled = false; + RunTestWithSourcesExecutor.RunTestsWithTestsCallback = (s, rc, fh) => + { + isExecutorCalled = true; + var tr = + new Microsoft.VisualStudio.TestPlatform.ObjectModel.TestResult( + new Microsoft.VisualStudio.TestPlatform.ObjectModel.TestCase( + "A.C.M", + new Uri(RunTestsWithSourcesTestsExecutorUri), + "A.dll")); + fh.RecordResult(tr); + }; + TestPluginCacheTests.SetupMockExtensions(new string[] { assemblyLocation }, () => { }); + + + this.executionManager.StartTestRun(tests, null, testExecutionContext, null, mockTestRunEventsHandler.Object); + + Assert.IsTrue(isExecutorCalled); + mockTestRunEventsHandler.Verify( + treh => treh.HandleTestRunComplete(It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny>()), Times.Once); + + // Also verify that run stats are passed through. + mockTestRunEventsHandler.Verify(treh => treh.HandleTestRunStatsChange(It.IsAny()), Times.Once); + } + } +} diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Execution/InProcDataCollectionExtensionManagerTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Execution/InProcDataCollectionExtensionManagerTests.cs new file mode 100644 index 0000000000..6684da3607 --- /dev/null +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Execution/InProcDataCollectionExtensionManagerTests.cs @@ -0,0 +1,61 @@ + +namespace TestPlatform.CrossPlatEngine.UnitTests.Execution +{ + using System; + using System.Collections.ObjectModel; + using System.Reflection; + using System.Runtime.Loader; + + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.EventHandlers; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Execution; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.InProcDataCollector; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + using Moq; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + + [TestClass] + public class InProcDataCollectionExtensionManagerTests + { + private Mock mockInProcDataCollectionHelper; + + private Mock mockTestCaseEvents; + + private TestCaseEventsHandler testCasesEventsHandler; + + [TestInitialize] + public void InitializeTests() + { + this.mockInProcDataCollectionHelper = new Mock(null); + this.mockTestCaseEvents = new Mock(); + this.testCasesEventsHandler = new TestCaseEventsHandler(this.mockInProcDataCollectionHelper.Object, this.mockTestCaseEvents.Object); + } + + [TestMethod] + public void InProcDataCollectionTestCaseEventHandlerCallingTriggerTestCaseStart() + { + this.testCasesEventsHandler.SendTestCaseStart(null); + this.mockInProcDataCollectionHelper.Verify(x => x.TriggerTestCaseStart(null), Times.Once); + } + + [TestMethod] + public void InProcDataCollectionTestCaseEventHandlerCallingTriggerTestCaseEnd() + { + this.testCasesEventsHandler.SendTestCaseEnd(null, TestOutcome.Passed); + this.mockInProcDataCollectionHelper.Verify(x => x.TriggerTestCaseEnd(null, TestOutcome.Passed), Times.Once); + } + + [TestMethod] + public void InProcDataCollectionTestCaseEventHandlerCallingTestCaseEventsFromClients() + { + this.testCasesEventsHandler.SendTestCaseStart(null); + this.testCasesEventsHandler.SendTestCaseEnd(null, TestOutcome.Passed); + this.testCasesEventsHandler.SendTestResult(null); + + this.mockTestCaseEvents.Verify(x => x.SendTestCaseStart(null), Times.Once); + this.mockTestCaseEvents.Verify(x => x.SendTestCaseEnd(null, TestOutcome.Passed), Times.Once); + this.mockTestCaseEvents.Verify(x => x.SendTestResult(null), Times.Once); + } + } +} diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Execution/RunTestsWithSourcesTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Execution/RunTestsWithSourcesTests.cs new file mode 100644 index 0000000000..99834bf260 --- /dev/null +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Execution/RunTestsWithSourcesTests.cs @@ -0,0 +1,265 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.CrossPlatEngine.UnitTests.Execution +{ + using System; + using System.Collections.Generic; + + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Execution; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.ClientProtocol; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Moq; + using TestableImplementations; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Adapter; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.Utilities; + using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; + using System.Linq; + using System.Reflection; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + + [TestClass] + public class RunTestsWithSourcesTests + { + private TestableTestRunCache testableTestRunCache; + private TestExecutionContext testExecutionContext; + private Mock mockTestRunEventsHandler; + + private TestableRunTestsWithSources runTestsInstance; + + internal const string RunTestsWithSourcesTestsExecutorUri = "executor://RunTestWithSourcesDiscoverer/"; + + [TestInitialize] + public void TestInit() + { + this.testableTestRunCache = new TestableTestRunCache(); + this.testExecutionContext = new TestExecutionContext( + 100, + TimeSpan.MaxValue, + inIsolation: false, + keepAlive: false, + areTestCaseLevelEventsRequired: false, + isDebug: false, + testCaseFilter: null); + this.mockTestRunEventsHandler = new Mock(); + } + + [TestCleanup] + public void TestCleanup() + { + RunTestWithSourcesExecutor.RunTestsWithSourcesCallback = null; + } + + [TestMethod] + public void BeforeRaisingTestRunCompleteShouldWarnIfNoTestsAreRun() + { + var adapterSourceMap = new Dictionary>(); + adapterSourceMap.Add("a", new List {"a", "aa"}); + adapterSourceMap.Add("b", new List { "b", "ab" }); + + var executorUriVsSourceList = new Dictionary, IEnumerable>(); + executorUriVsSourceList.Add(new Tuple(new Uri("e://d/"), "A.dll"), new List {"s1.dll "}); + + this.runTestsInstance = new TestableRunTestsWithSources( + adapterSourceMap, + testableTestRunCache, + null, + testExecutionContext, + null, + this.mockTestRunEventsHandler.Object, + executorUriVsSourceList); + + this.runTestsInstance.CallBeforeRaisingTestRunComplete(false); + + var messageFormat = + "No test is available in {0}. Make sure that installed test discoverers & executors, platform & framework version settings are appropriate and try again."; + var message = string.Format(messageFormat, "a aa b ab"); + this.mockTestRunEventsHandler.Verify(treh => treh.HandleLogMessage(TestMessageLevel.Warning, message), + Times.Once); + } + + [TestMethod] + public void GetExecutorUriExtensionMapShouldReturnEmptyOnInvalidSources() + { + var adapterSourceMap = new Dictionary>(); + adapterSourceMap.Add("a", new List { "a", "aa" }); + + this.runTestsInstance = new TestableRunTestsWithSources( + adapterSourceMap, + testableTestRunCache, + null, + testExecutionContext, + null, + this.mockTestRunEventsHandler.Object); + + var executorUris = this.runTestsInstance.CallGetExecutorUriExtensionMap(new Mock().Object, new RunContext()); + + Assert.IsNotNull(executorUris); + Assert.AreEqual(0, executorUris.Count()); + } + + [TestMethod] + public void GetExecutorUriExtensionMapShouldReturnDefaultExecutorUrisForTheDiscoverersDefined() + { + var assemblyLocation = typeof (RunTestsWithSourcesTests).GetTypeInfo().Assembly.Location; + + var adapterSourceMap = new Dictionary>(); + adapterSourceMap.Add("a", new List {"a", "aa"}); + adapterSourceMap.Add(assemblyLocation, new List {assemblyLocation}); + + this.runTestsInstance = new TestableRunTestsWithSources( + adapterSourceMap, + testableTestRunCache, + null, + testExecutionContext, + null, + this.mockTestRunEventsHandler.Object); + + var executorUris = this.runTestsInstance.CallGetExecutorUriExtensionMap( + new Mock().Object, new RunContext()); + + Assert.IsNotNull(executorUris); + CollectionAssert.Contains(executorUris.ToArray(), + new Tuple(new Uri("executor://RunTestWithSourcesDiscoverer"), assemblyLocation)); + } + + [TestMethod] + public void InvokeExecutorShouldInvokeTestExecutorWithTheSources() + { + var adapterSourceMap = new Dictionary>(); + adapterSourceMap.Add("a", new List { "a", "aa" }); + adapterSourceMap.Add("b", new List { "b", "ab" }); + + var executorUriVsSourceList = new Dictionary, IEnumerable>(); + var executorUriExtensionTuple = new Tuple(new Uri("e://d/"), "A.dll"); + executorUriVsSourceList.Add(executorUriExtensionTuple, new List { "s1.dll " }); + + this.runTestsInstance = new TestableRunTestsWithSources( + adapterSourceMap, + testableTestRunCache, + null, + testExecutionContext, + null, + this.mockTestRunEventsHandler.Object, + executorUriVsSourceList); + + var testExecutor = new RunTestWithSourcesExecutor(); + var extension = new LazyExtension(testExecutor, new TestExecutorMetadata("e://d/")); + IEnumerable receivedSources = null; + RunTestWithSourcesExecutor.RunTestsWithSourcesCallback = (sources, rc, fh) => { receivedSources = sources; }; + + this.runTestsInstance.CallInvokeExecutor(extension, executorUriExtensionTuple, null, null); + + Assert.IsNotNull(receivedSources); + CollectionAssert.AreEqual(new List {"s1.dll "}, receivedSources.ToList()); + } + + [TestMethod] + public void RunTestsShouldRunTestsForTheSourcesSpecified() + { + var assemblyLocation = typeof(RunTestsWithSourcesTests).GetTypeInfo().Assembly.Location; + + var adapterSourceMap = new Dictionary>(); + adapterSourceMap.Add("a", new List { "a", "aa" }); + adapterSourceMap.Add(assemblyLocation, new List { assemblyLocation }); + + this.runTestsInstance = new TestableRunTestsWithSources( + adapterSourceMap, + testableTestRunCache, + null, + testExecutionContext, + null, + this.mockTestRunEventsHandler.Object); + + bool isExecutorCalled = false; + RunTestWithSourcesExecutor.RunTestsWithSourcesCallback = (s, rc, fh) => { isExecutorCalled = true; }; + + this.runTestsInstance.RunTests(); + + Assert.IsTrue(isExecutorCalled); + this.mockTestRunEventsHandler.Verify( + treh => treh.HandleTestRunComplete(It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny>()), Times.Once); + } + + #region Testable Implemetations + + private class TestableRunTestsWithSources : RunTestsWithSources + { + public TestableRunTestsWithSources(Dictionary> adapterSourceMap, + ITestRunCache testRunCache, string runSettings, TestExecutionContext testExecutionContext, + ITestCaseEventsHandler testCaseEventsHandler, ITestRunEventsHandler testRunEventsHandler) + : base( + adapterSourceMap, testRunCache, runSettings, testExecutionContext, testCaseEventsHandler, + testRunEventsHandler) + { + } + + internal TestableRunTestsWithSources(Dictionary> adapterSourceMap, + ITestRunCache testRunCache, string runSettings, TestExecutionContext testExecutionContext, + ITestCaseEventsHandler testCaseEventsHandler, ITestRunEventsHandler testRunEventsHandler, Dictionary, IEnumerable> executorUriVsSourceList) + : base( + adapterSourceMap, testRunCache, runSettings, testExecutionContext, testCaseEventsHandler, + testRunEventsHandler, executorUriVsSourceList) + { + } + + public void CallBeforeRaisingTestRunComplete(bool exceptionsHitDuringRunTests) + { + this.BeforeRaisingTestRunComplete(exceptionsHitDuringRunTests); + } + + public IEnumerable> CallGetExecutorUriExtensionMap( + IFrameworkHandle testExecutorFrameworkHandle, RunContext runContext) + { + return this.GetExecutorUriExtensionMap(testExecutorFrameworkHandle, runContext); + } + + public void CallInvokeExecutor(LazyExtension executor, + Tuple executorUriExtensionTuple, RunContext runContext, IFrameworkHandle frameworkHandle) + { + this.InvokeExecutor(executor, executorUriExtensionTuple, runContext, frameworkHandle); + } + } + + [FileExtension(".dll")] + [DefaultExecutorUri(RunTestsWithSourcesTestsExecutorUri)] + private class RunTestWithSourcesDiscoverer : ITestDiscoverer + { + public void DiscoverTests(IEnumerable sources, IDiscoveryContext discoveryContext, IMessageLogger logger, ITestCaseDiscoverySink discoverySink) + { + throw new NotImplementedException(); + } + } + + [ExtensionUri(RunTestsWithSourcesTestsExecutorUri)] + internal class RunTestWithSourcesExecutor : ITestExecutor + { + public static Action, IRunContext, IFrameworkHandle> RunTestsWithSourcesCallback { get; set; } + public static Action, IRunContext, IFrameworkHandle> RunTestsWithTestsCallback { get; set; } + + public void Cancel() + { + throw new NotImplementedException(); + } + + public void RunTests(IEnumerable sources, IRunContext runContext, IFrameworkHandle frameworkHandle) + { + RunTestsWithSourcesCallback?.Invoke(sources, runContext, frameworkHandle); + } + + public void RunTests(IEnumerable tests, IRunContext runContext, IFrameworkHandle frameworkHandle) + { + RunTestsWithTestsCallback?.Invoke(tests, runContext, frameworkHandle); + } + } + + #endregion + } +} diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Execution/RunTestsWithTestsTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Execution/RunTestsWithTestsTests.cs new file mode 100644 index 0000000000..862999f10d --- /dev/null +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Execution/RunTestsWithTestsTests.cs @@ -0,0 +1,176 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.CrossPlatEngine.UnitTests.Execution +{ + using System; + using System.Collections.Generic; + using System.Linq; + + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework; + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.Utilities; + using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Adapter; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Execution; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.ClientProtocol; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + using Moq; + + using TestPlatform.CrossPlatEngine.UnitTests.TestableImplementations; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + [TestClass] + public class RunTestsWithTestsTests + { + private TestableTestRunCache testableTestRunCache; + private TestExecutionContext testExecutionContext; + private Mock mockTestRunEventsHandler; + + private TestableRunTestsWithTests runTestsInstance; + + private const string RunTestsWithSourcesTestsExecutorUri = "executor://RunTestWithSourcesDiscoverer/"; + + [TestInitialize] + public void TestInit() + { + this.testableTestRunCache = new TestableTestRunCache(); + this.testExecutionContext = new TestExecutionContext( + 100, + TimeSpan.MaxValue, + inIsolation: false, + keepAlive: false, + areTestCaseLevelEventsRequired: false, + isDebug: false, + testCaseFilter: null); + this.mockTestRunEventsHandler = new Mock(); + } + + [TestMethod] + public void GetExecutorUriExtensionMapShouldReturnExecutorUrisMapForTestCasesWithSameExecutorUri() + { + var tests = new List + { + new TestCase("A.C.M1", new Uri("e://d"), "s.dll"), + new TestCase("A.C.M2", new Uri("e://d"), "s.dll") + }; + + this.runTestsInstance = new TestableRunTestsWithTests( + tests, + testableTestRunCache, + null, + testExecutionContext, + null, + this.mockTestRunEventsHandler.Object); + + var map = this.runTestsInstance.CallGetExecutorUriExtensionMap(new Mock().Object, new RunContext()); + + var expectedMap = new List> + { + new Tuple(new Uri("e://d"), + Microsoft.VisualStudio.TestPlatform.ObjectModel.Constants.UnspecifiedAdapterPath) + }; + + CollectionAssert.AreEqual(expectedMap, map.ToList()); + } + + [TestMethod] + public void GetExecutorUriExtensionMapShouldReturnExecutorUrisMapForTestCasesWithDifferentExecutorUri() + { + var tests = new List + { + new TestCase("A.C.M1", new Uri("e://d"), "s.dll"), + new TestCase("A.C.M2", new Uri("e://d2"), "s.dll") + }; + + this.runTestsInstance = new TestableRunTestsWithTests( + tests, + testableTestRunCache, + null, + testExecutionContext, + null, + this.mockTestRunEventsHandler.Object); + + var map = this.runTestsInstance.CallGetExecutorUriExtensionMap(new Mock().Object, new RunContext()); + + var expectedMap = new List> + { + new Tuple(new Uri("e://d"), + Microsoft.VisualStudio.TestPlatform.ObjectModel.Constants.UnspecifiedAdapterPath), + new Tuple(new Uri("e://d2"), + Microsoft.VisualStudio.TestPlatform.ObjectModel.Constants.UnspecifiedAdapterPath) + }; + + CollectionAssert.AreEqual(expectedMap, map.ToList()); + } + + [TestMethod] + public void InvokeExecutorShouldInvokeTestExecutorWithTheTests() + { + var tests = new List + { + new TestCase("A.C.M1", new Uri("e://d"), "s.dll") + }; + + var executorUriVsTestList = new Dictionary, List>(); + var executorUriExtensionTuple = new Tuple(new Uri("e://d/"), "A.dll"); + executorUriVsTestList.Add(executorUriExtensionTuple, tests); + + this.runTestsInstance = new TestableRunTestsWithTests( + tests, + testableTestRunCache, + null, + testExecutionContext, + null, + this.mockTestRunEventsHandler.Object, + executorUriVsTestList); + + var testExecutor = new RunTestsWithSourcesTests.RunTestWithSourcesExecutor(); + var extension = new LazyExtension(testExecutor, new TestExecutorMetadata("e://d/")); + IEnumerable receivedTests = null; + RunTestsWithSourcesTests.RunTestWithSourcesExecutor.RunTestsWithTestsCallback = (t, rc, fh) => { receivedTests = t; }; + + this.runTestsInstance.CallInvokeExecutor(extension, executorUriExtensionTuple, null, null); + + Assert.IsNotNull(receivedTests); + CollectionAssert.AreEqual(tests, receivedTests.ToList()); + } + + #region Testable Implemetations + + private class TestableRunTestsWithTests : RunTestsWithTests + { + public TestableRunTestsWithTests(IEnumerable testCases, + ITestRunCache testRunCache, string runSettings, TestExecutionContext testExecutionContext, + ITestCaseEventsHandler testCaseEventsHandler, ITestRunEventsHandler testRunEventsHandler) + : base( + testCases, testRunCache, runSettings, testExecutionContext, testCaseEventsHandler, + testRunEventsHandler) + { + } + + internal TestableRunTestsWithTests(IEnumerable testCases, ITestRunCache testRunCache, string runSettings, TestExecutionContext testExecutionContext, ITestCaseEventsHandler testCaseEventsHandler, ITestRunEventsHandler testRunEventsHandler, Dictionary, List> executorUriVsTestList) + : base( + testCases, testRunCache, runSettings, testExecutionContext, testCaseEventsHandler, + testRunEventsHandler, executorUriVsTestList) + { + } + + public IEnumerable> CallGetExecutorUriExtensionMap( + IFrameworkHandle testExecutorFrameworkHandle, RunContext runContext) + { + return this.GetExecutorUriExtensionMap(testExecutorFrameworkHandle, runContext); + } + + public void CallInvokeExecutor(LazyExtension executor, + Tuple executorUriExtensionTuple, RunContext runContext, IFrameworkHandle frameworkHandle) + { + this.InvokeExecutor(executor, executorUriExtensionTuple, runContext, frameworkHandle); + } + } + + #endregion + + } +} diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Execution/TestRunCacheTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Execution/TestRunCacheTests.cs new file mode 100644 index 0000000000..a4dbc780b1 --- /dev/null +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Execution/TestRunCacheTests.cs @@ -0,0 +1,409 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.CrossPlatEngine.UnitTests.Execution +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + using System.Threading; + + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Execution; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + [TestClass] + public class TestRunCacheBehaviors + { + #region OnTestStarted tests + + [TestMethod] + public void OnTestStartedShouldAddToInProgressTests() + { + var tester = new TestCacheTester { ExpectedCacheSize = int.MaxValue }; + + var cache = new TestRunCache(int.MaxValue, TimeSpan.MaxValue, tester.CacheHitOnSize); + + var tr = this.GetTestResult(0); + cache.OnTestStarted(tr.TestCase); + + CollectionAssert.Contains(cache.InProgressTests.ToList(), tr.TestCase); + } + + [TestMethod] + public void OnTestStartedShouldAddMultipleInProgressTestsTillCacheHit() + { + long cacheSize = 10; + var tester = new TestCacheTester { ExpectedCacheSize = cacheSize }; + + var cache = new TestRunCache(cacheSize, TimeSpan.MaxValue, tester.CacheHitOnSize); + for (var i = 0; i < (cacheSize - 1); i++) + { + var tr = this.GetTestResult(i); + cache.OnTestStarted(tr.TestCase); + + Assert.AreEqual(i, cache.InProgressTests.Count - 1); + } + } + + //[TestMethod] + //public void OnTestStartedShouldReportInProgressTestsForLongRunningUnitTest() + //{ + // var cacheTimeout = new TimeSpan(0, 0, 0, 3, 0); + // var tester = new TestCacheTester { ExpectedCacheSize = int.MaxValue }; + + // var cache = new TestRunCache(int.MaxValue, cacheTimeout, tester.CacheHitOnTimerLimit); + + // var tr = this.GetTestResult(0); + // cache.OnTestStarted(tr.TestCase); + + // Assert.AreEqual(0, tester.TotalInProgressTestsReceived); + + // Assert.AreEqual(1, tester.TotalInProgressTestsReceived); + //} + + [TestMethod] + public void OnTestStartedShouldReportResultsOnCacheHit() + { + long cacheSize = 2; + var tester = new TestCacheTester { ExpectedCacheSize = cacheSize }; + + var cache = new TestRunCache(cacheSize, TimeSpan.MaxValue, tester.CacheHitOnSize); + for (var i = 0; i < cacheSize; i++) + { + var tr = this.GetTestResult(i); + cache.OnTestStarted(tr.TestCase); + } + + Assert.AreEqual(1, tester.CacheHitCount); + Assert.AreEqual(0, cache.TotalExecutedTests); + Assert.AreEqual(0, cache.TestResults.Count); + Assert.AreEqual(0, cache.InProgressTests.Count); + Assert.AreEqual(2, tester.TotalInProgressTestsReceived); + } + + #endregion + + #region OnNewTestResult tests + + [TestMethod] + public void OnNewTestResultShouldAddToTotalExecutedTests() + { + long cacheSize = 10; + var tester = new TestCacheTester { ExpectedCacheSize = cacheSize }; + + var cache = new TestRunCache(cacheSize, TimeSpan.MaxValue, tester.CacheHitOnSize); + for (var i = 0; i < 2; i++) + { + var tr = this.GetTestResult(i); + cache.OnNewTestResult(tr); + } + + Assert.AreEqual(2, cache.TotalExecutedTests); + } + + [TestMethod] + public void OnNewTestResultShouldAddToTestResultCache() + { + long cacheSize = 10; + var tester = new TestCacheTester { ExpectedCacheSize = cacheSize }; + + var cache = new TestRunCache(cacheSize, TimeSpan.MaxValue, tester.CacheHitOnSize); + for (var i = 0; i < 2; i++) + { + var tr = this.GetTestResult(i); + cache.OnNewTestResult(tr); + CollectionAssert.Contains(cache.TestResults.ToList(), tr); + } + } + + [TestMethod] + public void OnNewTestResultShouldUpdateRunStats() + { + long cacheSize = 10; + var tester = new TestCacheTester { ExpectedCacheSize = cacheSize }; + + var cache = new TestRunCache(cacheSize, TimeSpan.MaxValue, tester.CacheHitOnSize); + for (var i = 0; i < 2; i++) + { + var tr = this.GetTestResult(i); + tr.Outcome = TestOutcome.Passed; + cache.OnNewTestResult(tr); + } + + Assert.AreEqual(2, cache.TestRunStatistics.ExecutedTests); + Assert.AreEqual(2, cache.TestRunStatistics.Stats[TestOutcome.Passed]); + } + + [TestMethod] + public void OnNewTestResultShouldRemoveTestCaseFromInProgressList() + { + long cacheSize = 10; + var tester = new TestCacheTester { ExpectedCacheSize = cacheSize }; + + var cache = new TestRunCache(cacheSize, TimeSpan.MaxValue, tester.CacheHitOnSize); + for (var i = 0; i < 2; i++) + { + var tr = this.GetTestResult(i); + cache.OnTestStarted(tr.TestCase); + cache.OnNewTestResult(tr); + } + + Assert.AreEqual(0, cache.InProgressTests.Count); + } + + [TestMethod] + public void OnNewTestResultShouldReportTestResultsWhenMaxCacheSizeIsHit() + { + long cacheSize = 10; + var tester = new TestCacheTester { ExpectedCacheSize = cacheSize }; + + var cache = new TestRunCache(cacheSize, TimeSpan.MaxValue, tester.CacheHitOnSize); + for (var i = 0; i < cacheSize; i++) + { + var tr = this.GetTestResult(i); + cache.OnNewTestResult(tr); + } + + Assert.AreEqual(1, tester.CacheHitCount); + Assert.AreEqual(cacheSize, cache.TotalExecutedTests); + Assert.AreEqual(0, cache.TestResults.Count); + } + + [TestMethod] + public void OnNewTestResultShouldNotFireIfMaxCacheSizeIsNotHit() + { + long cacheSize = 10; + var tester = new TestCacheTester { ExpectedCacheSize = cacheSize }; + + var cache = new TestRunCache(cacheSize, TimeSpan.MaxValue, tester.CacheHitOnSize); + var executedTests = cacheSize - 1; + for (var i = 0; i < executedTests; i++) + { + var tr = this.GetTestResult(i); + cache.OnNewTestResult(tr); + } + + Assert.AreEqual(0, tester.CacheHitCount); + Assert.AreEqual(executedTests, cache.TotalExecutedTests); + Assert.AreEqual(executedTests, cache.TestResults.Count); + } + + [TestMethod] + public void OnNewTestResultShouldReportResultsMultipleTimes() + { + long cacheSize = 10; + var tester = new TestCacheTester { ExpectedCacheSize = cacheSize }; + + var cache = new TestRunCache(cacheSize, TimeSpan.MaxValue, tester.CacheHitOnSize); + long executedTests = 45; + + for (var i = 0; i < executedTests; i++) + { + var tr = this.GetTestResult(i); + cache.OnNewTestResult(tr); + } + + Assert.AreEqual(4, tester.CacheHitCount); + Assert.AreEqual(executedTests, cache.TotalExecutedTests); + Assert.AreEqual(5, cache.TestResults.Count); + } + + #endregion + + #region OnTestCompletion tests + + [TestMethod] + public void OnTestCompletionShouldNotThrowIfCompletedTestIsNull() + { + long cacheSize = 10; + var tester = new TestCacheTester { ExpectedCacheSize = cacheSize }; + + var cache = new TestRunCache(cacheSize, TimeSpan.MaxValue, tester.CacheHitOnSize); + + Assert.IsFalse(cache.OnTestCompletion(null)); + } + + [TestMethod] + public void OnTestCompletionShouldReturnFalseIfInProgressTestsIsEmpty() + { + long cacheSize = 10; + var tester = new TestCacheTester { ExpectedCacheSize = cacheSize }; + + var cache = new TestRunCache(cacheSize, TimeSpan.MaxValue, tester.CacheHitOnSize); + + Assert.IsFalse(cache.OnTestCompletion(this.GetTestResult(0).TestCase)); + } + + [TestMethod] + public void OnTestCompletionShouldUpdateInProgressList() + { + long cacheSize = 2; + var tester = new TestCacheTester { ExpectedCacheSize = cacheSize }; + + var cache = new TestRunCache(cacheSize, TimeSpan.MaxValue, tester.CacheHitOnSize); + for (int i = 0; i < cacheSize; i++) + { + var tr = this.GetTestResult(i); + cache.OnTestStarted(tr.TestCase); + Assert.IsTrue(cache.OnTestCompletion(tr.TestCase)); + + Assert.AreEqual(0, cache.InProgressTests.Count); + } + } + + [TestMethod] + public void OnTestCompletionShouldUpdateInProgressListWhenTestHasSameId() + { + long cacheSize = 2; + var tester = new TestCacheTester { ExpectedCacheSize = cacheSize }; + + var cache = new TestRunCache(cacheSize, TimeSpan.MaxValue, tester.CacheHitOnSize); + for (var i = 0; i < cacheSize; i++) + { + var tr = this.GetTestResult(i); + cache.OnTestStarted(tr.TestCase); + + var clone = new TestCase( + tr.TestCase.FullyQualifiedName, + tr.TestCase.ExecutorUri, + tr.TestCase.Source); + clone.Id = tr.TestCase.Id; + + Assert.IsTrue(cache.OnTestCompletion(clone)); + + Assert.AreEqual(0, cache.InProgressTests.Count); + } + } + + [TestMethod] + public void OnTestCompleteShouldNotRemoveTestCaseFromInProgressListForUnrelatedTestResult() + { + long cacheSize = 10; + var tester = new TestCacheTester { ExpectedCacheSize = cacheSize }; + + var cache = new TestRunCache(cacheSize, TimeSpan.MaxValue, tester.CacheHitOnSize); + + var tr1 = this.GetTestResult(0); + cache.OnTestStarted(tr1.TestCase); + + var tr2 = this.GetTestResult(1); + Assert.IsFalse(cache.OnTestCompletion(tr2.TestCase)); + + Assert.AreEqual(1, cache.InProgressTests.Count); + } + + #endregion + + #region GetLastChunk tests + + [TestMethod] + public void GetLastChunkShouldReturnTestResultsInCache() + { + long cacheSize = 10; + var tester = new TestCacheTester { ExpectedCacheSize = cacheSize }; + + var cache = new TestRunCache(cacheSize, TimeSpan.MaxValue, tester.CacheHitOnSize); + List pushedTestResults = new List(); + + for (var i = 0; i < 2; i++) + { + var tr = this.GetTestResult(i); + cache.OnNewTestResult(tr); + pushedTestResults.Add(tr); + } + + var testResultsInCache = cache.GetLastChunk(); + CollectionAssert.AreEqual(pushedTestResults, testResultsInCache.ToList()); + } + + [TestMethod] + public void GetLastChunkShouldResetTestResultsInCache() + { + long cacheSize = 10; + var tester = new TestCacheTester { ExpectedCacheSize = cacheSize }; + + var cache = new TestRunCache(cacheSize, TimeSpan.MaxValue, tester.CacheHitOnSize); + + for (var i = 0; i < 2; i++) + { + var tr = this.GetTestResult(i); + cache.OnNewTestResult(tr); + } + + cache.GetLastChunk(); + Assert.AreEqual(0, cache.TestResults.Count); + } + + #endregion + + #region TestRunStasts tests + + [TestMethod] + public void TestRunStatsShouldReturnCurrentStats() + { + long cacheSize = 10; + var tester = new TestCacheTester { ExpectedCacheSize = cacheSize }; + + var cache = new TestRunCache(cacheSize, TimeSpan.MaxValue, tester.CacheHitOnSize); + + for (var i = 0; i < cacheSize; i++) + { + var tr = this.GetTestResult(i); + if (i < 5) + { + tr.Outcome = TestOutcome.Passed; + } + else + { + tr.Outcome = TestOutcome.Failed; + } + + cache.OnNewTestResult(tr); + } + + var stats = cache.TestRunStatistics; + + Assert.AreEqual(cacheSize, stats.ExecutedTests); + Assert.AreEqual(5, stats.Stats[TestOutcome.Passed]); + Assert.AreEqual(5, stats.Stats[TestOutcome.Failed]); + } + + #endregion + + #region Helpers + + private Microsoft.VisualStudio.TestPlatform.ObjectModel.TestResult GetTestResult(int index) + { + var tc = new TestCase("Test" + index, new Uri("executor://dummy"), "DummySourceFileName"); + var testResult = new Microsoft.VisualStudio.TestPlatform.ObjectModel.TestResult(tc); + testResult.TestCase.Id = Guid.NewGuid(); + + return testResult; + } + + private class TestCacheTester + { + public long ExpectedCacheSize { get; set; } + + public int CacheHitCount { get; set; } + + public int TotalInProgressTestsReceived { get; set; } + + public void CacheHitOnSize(TestRunStatistics stats, ICollection results, ICollection tests) + { + Assert.AreEqual(this.ExpectedCacheSize, results.Count + tests.Count); + this.CacheHitCount++; + this.TotalInProgressTestsReceived += tests.Count; + } + + public void CacheHitOnTimerLimit(ICollection results, ICollection tests) + { + this.CacheHitCount++; + this.TotalInProgressTestsReceived += tests.Count; + } + } + + #endregion + } +} diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Hosting/DefaultTestHostManagerTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Hosting/DefaultTestHostManagerTests.cs new file mode 100644 index 0000000000..72efa73c79 --- /dev/null +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Hosting/DefaultTestHostManagerTests.cs @@ -0,0 +1,304 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.CrossPlatEngine.UnitTests.Hosting +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Reflection; + + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.Interfaces; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Hosting; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces; + using Moq; + + [TestClass] + public class DefaultTestHostManagerTests + { + private DefaultTestHostManager testHostManager; + + /// + /// The mock process helper. + /// + /// Doing this only because mocks currently does not support internalVisibleTo on signed assemblies yet for .Net Core. + private MockProcessHelper mockProcessHelper; + + [TestInitialize] + public void TestInit() + { + this.mockProcessHelper = new MockProcessHelper(); + } + + [TestMethod] + public void ConstructorShouldSetX86ProcessForX86Architecture() + { + this.testHostManager = new DefaultTestHostManager(Architecture.X86, this.mockProcessHelper); + + // Setup mocks. + var processPath = string.Empty; + var times = 0; + + this.mockProcessHelper.LaunchProcessInvoker = (path, args, wd) => + { + times++; + processPath = path; + return Process.GetCurrentProcess(); + }; + + this.testHostManager.LaunchTestHost(new Dictionary(), new List()); + + var expectedProcessPath = + Path.Combine( + Path.GetDirectoryName(typeof(DefaultTestHostManagerTests).GetTypeInfo().Assembly.Location), + "testhost.x86.exe"); + + Assert.AreEqual(expectedProcessPath, processPath); + Assert.AreEqual(1, times); + } + + [TestMethod] + public void ConstructorShouldSetX64ProcessForX64Architecture() + { + this.testHostManager = new DefaultTestHostManager(Architecture.X64, this.mockProcessHelper); + + // Setup mocks. + var processPath = string.Empty; + var times = 0; + + this.mockProcessHelper.LaunchProcessInvoker = (path, args, wd) => + { + times++; + processPath = path; + return Process.GetCurrentProcess(); + }; + + this.testHostManager.LaunchTestHost(new Dictionary(), new List()); + + var expectedProcessPath = + Path.Combine( + Path.GetDirectoryName(typeof(DefaultTestHostManagerTests).GetTypeInfo().Assembly.Location), + "testhost.exe"); + + Assert.AreEqual(expectedProcessPath, processPath); + Assert.AreEqual(1, times); + } + + [TestMethod] + public void LaunchTestHostShouldLaunchProcessWithOneArgument() + { + this.testHostManager = new DefaultTestHostManager(Architecture.X64, this.mockProcessHelper); + + // Setup mocks. + var cliargs = string.Empty; + var times = 0; + + this.mockProcessHelper.LaunchProcessInvoker = (path, args, wd) => + { + times++; + cliargs = args; + return Process.GetCurrentProcess(); + }; + + var arguments = new List { "-p" }; + this.testHostManager.LaunchTestHost(new Dictionary(), arguments); + + var commandLineArgumentsString = string.Join(" ", arguments); + + Assert.AreEqual(commandLineArgumentsString, cliargs); + Assert.AreEqual(1, times); + } + + [TestMethod] + public void LaunchTestHostShouldLaunchProcessWithMultipleArguments() + { + this.testHostManager = new DefaultTestHostManager(Architecture.X64, this.mockProcessHelper); + + // Setup mocks. + var cliargs = string.Empty; + var times = 0; + + this.mockProcessHelper.LaunchProcessInvoker = (path, args, wd) => + { + times++; + cliargs = args; + return Process.GetCurrentProcess(); + }; + + var arguments = new List { "-p", "23453" }; + this.testHostManager.LaunchTestHost(new Dictionary(), arguments); + + var commandLineArgumentsString = string.Join(" ", arguments); + + Assert.AreEqual(commandLineArgumentsString, cliargs); + Assert.AreEqual(1, times); + } + + [TestMethod] + public void LaunchTestHostShouldLaunchProcessWithCurrentWorkingDirectory() + { + this.testHostManager = new DefaultTestHostManager(Architecture.X64, this.mockProcessHelper); + + // Setup mocks. + var pwd = string.Empty; + var times = 0; + + this.mockProcessHelper.LaunchProcessInvoker = (path, args, wd) => + { + times++; + pwd = wd; + return Process.GetCurrentProcess(); + }; + + this.testHostManager.LaunchTestHost(new Dictionary(), new List()); + + var workingDirectory = Directory.GetCurrentDirectory(); + + Assert.AreEqual(workingDirectory, pwd); + Assert.AreEqual(1, times); + } + + [TestMethod] + public void LaunchTestHostShouldReturnTestHostProcessId() + { + this.testHostManager = new DefaultTestHostManager(Architecture.X64, this.mockProcessHelper); ; + + // Setup mocks. + this.mockProcessHelper.LaunchProcessInvoker = (path, args, wd) => + { + return Process.GetCurrentProcess(); + }; + + var processID = this.testHostManager.LaunchTestHost(new Dictionary(), new List()); + + Assert.AreEqual(Process.GetCurrentProcess().Id, processID); + } + + [TestMethod] + public void LaunchTestHostShouldLaunchDotnetExeIfRunningUnderDotnetCLIContext() + { + this.testHostManager = new DefaultTestHostManager(Architecture.X64, this.mockProcessHelper); + + string processPath = null; + + // Setup mocks. + this.mockProcessHelper.LaunchProcessInvoker = (path, args, wd) => + { + processPath = path; + return Process.GetCurrentProcess(); + }; + var currentProcessPath = "c:\\temp\\dotnet.exe"; + this.mockProcessHelper.CurrentProcessName = currentProcessPath; + + this.testHostManager.LaunchTestHost(new Dictionary(), new List()); + + Assert.AreEqual(currentProcessPath, processPath); + } + + [TestMethod] + public void LaunchTestHostShouldPassTestHostAssemblyInArgumentsIfRunningUnderDotnetCLIContext() + { + this.testHostManager = new DefaultTestHostManager(Architecture.X64, this.mockProcessHelper); + + string arguments = null; + + // Setup mocks. + this.mockProcessHelper.LaunchProcessInvoker = (path, args, wd) => + { + arguments = args; + return Process.GetCurrentProcess(); + }; + this.mockProcessHelper.CurrentProcessName = "c:\\temp\\dotnet.exe"; + + this.testHostManager.LaunchTestHost(new Dictionary(), new List()); + + var testhostAssemblyPath = + Path.Combine( + Path.GetDirectoryName(typeof(DefaultTestHostManager).GetTypeInfo().Assembly.Location), + "testhost.dll"); + + StringAssert.Contains(arguments, testhostAssemblyPath); + } + + [TestMethod] + public void LaunchTestHostShouldSetWorkingDirectoryToDotnetExeDirectoryIfRunningUnderDotnetCLIContext() + { + this.testHostManager = new DefaultTestHostManager(Architecture.X64, this.mockProcessHelper); + + string workingDirectory = null; + + // Setup mocks. + this.mockProcessHelper.LaunchProcessInvoker = (path, args, wd) => + { + workingDirectory = wd; + return Process.GetCurrentProcess(); + }; + var currentProcessPath = "c:\\temp\\dotnet.exe"; + this.mockProcessHelper.CurrentProcessName = currentProcessPath; + + this.testHostManager.LaunchTestHost(new Dictionary(), new List()); + + Assert.AreEqual(Path.GetDirectoryName(currentProcessPath), workingDirectory); + } + + [TestMethod] + public void PropertiesShouldReturnEmptyDictionary() + { + this.testHostManager = new DefaultTestHostManager(Architecture.X64, this.mockProcessHelper); + + Assert.AreEqual(0, this.testHostManager.Properties.Count); + } + + [TestMethod] + public void LaunchTestHostShouldUseCustomHostIfSet() + { + this.testHostManager = new DefaultTestHostManager(Architecture.X64, this.mockProcessHelper); + var mockCustomLauncher = new Mock(); + this.testHostManager.SetCustomLauncher(mockCustomLauncher.Object); + + var isProcessHelperCalled = false; + var processToReturn = Process.GetCurrentProcess(); + // Setup mocks. + this.mockProcessHelper.LaunchProcessInvoker = (path, args, wd) => + { + isProcessHelperCalled = true; + return processToReturn; + }; + + mockCustomLauncher.Setup(mc => mc.LaunchTestHost(It.IsAny())).Returns(processToReturn.Id); + + var processID = this.testHostManager.LaunchTestHost(new Dictionary(), new List()); + + Assert.IsFalse(isProcessHelperCalled, "ProcessHelper must not be called if custom launcher is set."); + mockCustomLauncher.Verify(mc => mc.LaunchTestHost(It.IsAny()), Times.Once, "Custom launcher must be called if set."); + } + + #region implementations + + private class MockProcessHelper : IProcessHelper + { + public MockProcessHelper() + { + this.CurrentProcessName = "testhost.exe"; + } + + public Func LaunchProcessInvoker { get; set; } + + public string CurrentProcessName { get; set; } + + public string GetCurrentProcessFileName() + { + return this.CurrentProcessName; + } + + public Process LaunchProcess(string processPath, string arguments, string workingDirectory) + { + return this.LaunchProcessInvoker?.Invoke(processPath, arguments, workingDirectory); + } + } + + #endregion + } +} diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Microsoft.TestPlatform.CrossPlatEngine.UnitTests.xproj b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Microsoft.TestPlatform.CrossPlatEngine.UnitTests.xproj new file mode 100644 index 0000000000..59a15f74ab --- /dev/null +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Microsoft.TestPlatform.CrossPlatEngine.UnitTests.xproj @@ -0,0 +1,22 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 48c34b69-c0ff-4b10-b8d3-47c9a539b109 + TestPlatform.CrossPlatEngine.UnitTests + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\ + v4.5.2 + + + 2.0 + + + + + + \ No newline at end of file diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Properties/AssemblyInfo.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..54390bb4ee --- /dev/null +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +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: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("TestPlatform.CrossPlatEngine.UnitTests")] +[assembly: AssemblyTrademark("")] + +// 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("48c34b69-c0ff-4b10-b8d3-47c9a539b109")] diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/TestEngineTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/TestEngineTests.cs new file mode 100644 index 0000000000..320c78073e --- /dev/null +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/TestEngineTests.cs @@ -0,0 +1,97 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.CrossPlatEngine.UnitTests +{ + using System.Collections.Generic; + + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.Parallel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class TestEngineTests + { + private ITestEngine testEngine; + + public TestEngineTests() + { + this.testEngine = new TestEngine(); + } + + [TestMethod] + public void GetDiscoveryManagerShouldReturnANonNullInstance() + { + Assert.IsNotNull(this.testEngine.GetDiscoveryManager()); + } + + + [TestMethod] + public void GetExecutionManagerShouldReturnANonNullInstance() + { + var testRunCriteria = new TestRunCriteria(new List() { "1.dll" }, 100); + Assert.IsNotNull(this.testEngine.GetExecutionManager(testRunCriteria)); + } + + [TestMethod] + public void GetExecutionManagerShouldReturnDefaultExecutionManagerIfParallelDisabled() + { + string settingXml = @""; + var testRunCriteria = new TestRunCriteria(new List() { "1.dll" }, 100, false, settingXml); + Assert.IsNotNull(this.testEngine.GetExecutionManager(testRunCriteria)); + Assert.IsInstanceOfType(this.testEngine.GetExecutionManager(testRunCriteria), typeof(ProxyExecutionManager)); + } + + [TestMethod] + public void GetExecutionManagerWithSingleSourceShouldReturnDefaultExecutionManagerEvenIfParallelEnabled() + { + string settingXml = @"2"; + var testRunCriteria = new TestRunCriteria(new List() { "1.dll" }, 100, false, settingXml); + Assert.IsNotNull(this.testEngine.GetExecutionManager(testRunCriteria)); + Assert.IsInstanceOfType(this.testEngine.GetExecutionManager(testRunCriteria), typeof(ProxyExecutionManager)); + } + + [TestMethod] + public void GetExecutionManagerShouldReturnParallelExecutionManagerIfParallelEnabled() + { + string settingXml = @"2"; + var testRunCriteria = new TestRunCriteria(new List() { "1.dll", "2.dll" }, 100, false, settingXml); + Assert.IsNotNull(this.testEngine.GetExecutionManager(testRunCriteria)); + Assert.IsInstanceOfType(this.testEngine.GetExecutionManager(testRunCriteria), typeof(ParallelProxyExecutionManager)); + } + + [TestMethod] + public void GetExcecutionManagerShouldReturnExectuionManagerWithDataCollectionIfDataCollectionIsEnabled() + { + var settingXml = @""; + var testRunCriteria = new TestRunCriteria(new List() { "1.dll" }, 100, false, settingXml); + var result = this.testEngine.GetExecutionManager(testRunCriteria); + Assert.IsNotNull(result); + Assert.IsInstanceOfType(result, typeof(ProxyExecutionManagerWithDataCollection)); + } + + [TestMethod] + public void GetExtensionManagerShouldReturnANonNullInstance() + { + Assert.IsNotNull(this.testEngine.GetExtensionManager()); + } + + + [TestMethod] + public void GetDefaultTestHostManagerReturnsANonNullInstance() + { + Assert.IsNotNull(this.testEngine.GetDefaultTestHostManager(Architecture.X86)); + } + + [TestMethod] + public void GetDefaultTestHostManagerReturnsANewInstanceEverytime() + { + var instance1 = this.testEngine.GetDefaultTestHostManager(Architecture.X86); + var instance2 = this.testEngine.GetDefaultTestHostManager(Architecture.X86); + Assert.AreNotEqual(instance1, instance2); + } + } +} diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/TestExtensionManagerTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/TestExtensionManagerTests.cs new file mode 100644 index 0000000000..234ddb1ef3 --- /dev/null +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/TestExtensionManagerTests.cs @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.CrossPlatEngine.UnitTests +{ + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class TestExtensionManagerTests + { + private ITestExtensionManager testExtensionManager; + + public TestExtensionManagerTests() + { + this.testExtensionManager = new TestExtensionManager(); + } + + [TestMethod] + public void UseAdditionalExtensionsShouldUpdateAdditionalExtensionsInCache() + { + TestPluginCache.Instance = null; + var extensions = new List { typeof(TestExtensionManagerTests).GetTypeInfo().Assembly.Location }; + + try + { + this.testExtensionManager.UseAdditionalExtensions(extensions, true); + + Assert.IsTrue(TestPluginCache.Instance.LoadOnlyWellKnownExtensions); + CollectionAssert.AreEqual(extensions, TestPluginCache.Instance.PathToAdditionalExtensions.ToList()); + } + finally + { + TestPluginCache.Instance = null; + } + } + } +} diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/TestHostManagerFactoryTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/TestHostManagerFactoryTests.cs new file mode 100644 index 0000000000..9be071f488 --- /dev/null +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/TestHostManagerFactoryTests.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.CrossPlatEngine.UnitTests +{ + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class TestHostManagerFactoryTests + { + private TestHostManagerFactory testHostManagerFactory; + + [TestInitialize] + public void TestInit() + { + this.testHostManagerFactory = new TestHostManagerFactory(); + } + + [TestMethod] + public void GetDiscoveryManagerShouldReturnADiscoveryManagerInstance() + { + Assert.IsNotNull(this.testHostManagerFactory.GetDiscoveryManager()); + } + + [TestMethod] + public void GetDiscoveryManagerShouldCacheTheDiscoveryManagerInstance() + { + Assert.AreEqual(this.testHostManagerFactory.GetDiscoveryManager(), this.testHostManagerFactory.GetDiscoveryManager()); + } + + [TestMethod] + public void GetDiscoveryManagerShouldReturnAnExecutionManagerInstance() + { + Assert.IsNotNull(this.testHostManagerFactory.GetExecutionManager()); + } + + [TestMethod] + public void GetDiscoveryManagerShouldCacheTheExecutionManagerInstance() + { + Assert.AreEqual(this.testHostManagerFactory.GetExecutionManager(), this.testHostManagerFactory.GetExecutionManager()); + } + } +} diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/TestableImplementations/TestableTestRunCache.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/TestableImplementations/TestableTestRunCache.cs new file mode 100644 index 0000000000..17e2e4c51f --- /dev/null +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/TestableImplementations/TestableTestRunCache.cs @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.CrossPlatEngine.UnitTests.TestableImplementations +{ + using System; + using System.Collections.Generic; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Execution; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + + public class TestableTestRunCache : ITestRunCache + { + public TestableTestRunCache() + { + this.TestStartedList = new List(); + this.TestCompletedList = new List(); + this.TestResultList = new List(); + } + + // use the below three to fill in data to the testable cache. + public List TestStartedList { get; private set; } + + public List TestCompletedList { get; private set; } + + public List TestResultList { get; private set; } + + public ICollection InProgressTests { get; set; } + + + // Use the TestResultList instead to fill in data. This is just to avoid confusion. + public ICollection TestResults { get; set; } + + public TestRunStatistics TestRunStatistics { get; set; } + + public long TotalExecutedTests { get; set; } + + public ICollection GetLastChunk() + { + return this.TestResultList; + } + + public void OnNewTestResult(TestResult testResult) + { + this.TestResultList.Add(testResult); + } + + public bool OnTestCompletion(TestCase testCase) + { + this.TestCompletedList.Add(testCase); + + return false; + } + + public void OnTestStarted(TestCase testCase) + { + this.TestStartedList.Add(testCase); + } + } +} diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/project.json b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/project.json new file mode 100644 index 0000000000..b89ee5ead5 --- /dev/null +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/project.json @@ -0,0 +1,38 @@ +{ + "version": "15.0.0-*", + + "buildOptions": { + "delaySign": true, + "keyFile": "../../scripts/key.snk", + "warningsAsErrors": true + }, + + "dependencies": { + "Microsoft.NETCore.App": { + "type": "platform", + "version": "1.0.0-rc2-3002702" + }, + "dotnet-test-mstest": { + "version": "1.0.1-preview", + "exclude": "compile" + }, + "MSTest.TestFramework": "1.0.0-preview", + "moq.netcore": "4.4.0-beta8", + "System.Diagnostics.TraceSource": "4.0.0-rc2-24015", + "Microsoft.TestPlatform.CrossPlatEngine": "15.0.0-*", + "Microsoft.TestPlatform.ObjectModel": "15.0.0-*", + "Microsoft.TestPlatform.Common": "15.0.0-*", + "Microsoft.TestPlatform.Common.UnitTests": "15.0.0-*" + }, + + "frameworks": { + "netcoreapp1.0": { + "imports": [ + "dnxcore50", + "portable-net45+win8" + ] + } + }, + + "testRunner": "mstest" +} \ No newline at end of file diff --git a/test/Microsoft.TestPlatform.End2EndTests/End2EndTests.cs b/test/Microsoft.TestPlatform.End2EndTests/End2EndTests.cs new file mode 100644 index 0000000000..8778002e53 --- /dev/null +++ b/test/Microsoft.TestPlatform.End2EndTests/End2EndTests.cs @@ -0,0 +1,138 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestingMSTest +{ + using System.IO; + + using Microsoft.VisualStudio.TestTools.UnitTesting; + + using TestPlatform.TestUtilities; + + [TestClass] + public class End2EndTests:VsTestConsoleTestBase + { +#if DEBUG + private const string TestAssemblyRelativePath = @"Samples\SampleUnitTestProject\bin\Debug\SampleUnitTestProject.dll"; +#else + private const string TestAssemblyRelativePath = @"Samples\SampleUnitTestProject\bin\Release\SampleUnitTestProject.dll"; +#endif + private const string TestAdapterRelativePath = @"Samples\packages\MSTest.TestAdapter.1.0.0-preview\build"; + + private string testAssembly; + + private string testAdapter; + + [TestInitialize] + public void InitializeTests() + { + this.testAssembly = GetSampleTestAssembly(); + this.testAdapter = GetTestAdapterPath(); + } + + + + [TestMethod] + [TestCategory("EndToEnd")] + public void RunAllTestExecution() + { + this.InvokeVsTestForExecution(this.testAssembly, this.testAdapter); + this.ValidateSummaryStatus(1, 1, 1); + this.ValidatePassedTests("SampleUnitTestProject.UnitTest1.PassingTest"); + this.ValidateFailedTests("SampleUnitTestProject.UnitTest1.FailingTest"); + this.ValidateSkippedTests("SampleUnitTestProject.UnitTest1.SkippingTest"); + } + + [TestMethod] + [TestCategory("EndToEnd")] + public void DiscoverAllTests() + { + this.InvokeVsTestForDiscovery(this.testAssembly, this.testAdapter); + var listOfTests = new string[] { "SampleUnitTestProject.UnitTest1.PassingTest", "SampleUnitTestProject.UnitTest1.FailingTest", "SampleUnitTestProject.UnitTest1.SkippingTest" }; + this.ValidateDiscoveredTests(listOfTests); + } + + [TestMethod] + [TestCategory("EndToEnd")] + public void RunSelectedTests() + { + var arguments = PrepareArguments(this.testAssembly, this.testAdapter, string.Empty); + arguments = string.Concat(arguments, " /Tests:PassingTest"); + this.InvokeVsTest(arguments); + this.ValidateSummaryStatus(1, 0, 0); + this.ValidatePassedTests("SampleUnitTestProject.UnitTest1.PassingTest"); + } + + [TestMethod] + [TestCategory("EndToEnd")] + public void RunAllWithTestImpactSettings() + { + var runSettings = GetInProcDataCollectionRunsettignsFile(); + + this.InvokeVsTestForExecution(this.testAssembly, this.testAdapter, runSettings); + this.ValidateSummaryStatus(1, 1, 1); + this.ValidatePassedTests("SampleUnitTestProject.UnitTest1.PassingTest"); + this.ValidateFailedTests("SampleUnitTestProject.UnitTest1.FailingTest"); + this.ValidateSkippedTests("SampleUnitTestProject.UnitTest1.SkippingTest"); + + ValidateInProcDataCollectionOutput(); + } + +#region PrivateMethods + private static string GetSampleTestAssembly() + { + var currentDirectoryInfo = new DirectoryInfo(Directory.GetCurrentDirectory()); + + var testAssembly = Path.Combine(currentDirectoryInfo.Parent?.FullName, TestAssemblyRelativePath); + + return testAssembly; + } + private static string GetTestAdapterPath() + { + var currentDirectoryInfo = new DirectoryInfo(Directory.GetCurrentDirectory()); + + var testAdapterPath = Path.Combine(currentDirectoryInfo.Parent?.FullName, TestAdapterRelativePath); + + return testAdapterPath; + } + + private static string GetInProcDataCollectionRunsettignsFile() + { + var runSettings = Path.Combine(Path.GetDirectoryName(GetSampleTestAssembly()), "runsettingstest.runsettings"); +#if DEBUG + var realtiveInProcPath = @"Samples\TestImpactListener.Tests\bin\Debug\TestImpactListener.Tests.dll"; +#else + var realtiveInProcPath = @"Samples\TestImpactListener.Tests\bin\Release\TestImpactListener.Tests.dll"; +#endif + var currentDirectoryInfo = new DirectoryInfo(Directory.GetCurrentDirectory()); + var inprocasm = Path.Combine(currentDirectoryInfo.Parent?.FullName, realtiveInProcPath); + var fileContents = @" + + + + + 4312 + + + + + "; + + fileContents = string.Format(fileContents, "'" + inprocasm + "'"); + File.WriteAllText(runSettings, fileContents); + + return runSettings; + } + + private static void ValidateInProcDataCollectionOutput() + { + var fileName = Path.Combine(Path.GetTempPath(), "inproctest.txt"); + Assert.IsTrue(File.Exists(fileName)); + var actual = File.ReadAllText(fileName); + var expected = @"TestSessionStart : 4312 TestCaseStart : PassingTest TestCaseEnd : PassingTest TestCaseStart : FailingTest TestCaseEnd : FailingTest TestCaseStart : SkippingTest TestCaseEnd : SkippingTest TestSessionEnd"; + actual = actual.Replace(" ", "").Replace("\r\n", ""); + expected = expected.Replace(" ", "").Replace("\r\n", ""); + Assert.AreEqual(expected, actual); + } +#endregion + } +} diff --git a/test/Microsoft.TestPlatform.End2EndTests/Microsoft.TestPlatform.End2EndTests.xproj b/test/Microsoft.TestPlatform.End2EndTests/Microsoft.TestPlatform.End2EndTests.xproj new file mode 100644 index 0000000000..8a89f794a5 --- /dev/null +++ b/test/Microsoft.TestPlatform.End2EndTests/Microsoft.TestPlatform.End2EndTests.xproj @@ -0,0 +1,22 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + c1497516-acb5-49af-a676-51db65ff8252 + TestingMSTest + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\ + v4.5.2 + + + 2.0 + + + + + + \ No newline at end of file diff --git a/test/Microsoft.TestPlatform.End2EndTests/Properties/AssemblyInfo.cs b/test/Microsoft.TestPlatform.End2EndTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..a91bf84dca --- /dev/null +++ b/test/Microsoft.TestPlatform.End2EndTests/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +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: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("TestingMSTest")] +[assembly: AssemblyTrademark("")] + +// 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("c1497516-acb5-49af-a676-51db65ff8252")] diff --git a/test/Microsoft.TestPlatform.End2EndTests/project.json b/test/Microsoft.TestPlatform.End2EndTests/project.json new file mode 100644 index 0000000000..35ebca1030 --- /dev/null +++ b/test/Microsoft.TestPlatform.End2EndTests/project.json @@ -0,0 +1,35 @@ +{ + "version": "1.0.0-*", + + + + "dependencies": { + "dotnet-test-mstest": { + "version": "1.0.1-preview", + "exclude": "compile" + }, + + "MSTest.TestFramework": "1.0.0-preview", + "TestPlatform.TestUtilities": "1.0.0-*" + + }, + + + "frameworks": { + "netcoreapp1.0": { + "imports": [ + "dnxcore50", + "portable-net45+win8" + ], + "dependencies": { + "Microsoft.NETCore.App": { + "type": "platform", + "version": "1.0.0-rc2-3002702" + } + } + }, + "net461": { } + }, + + "testRunner": "mstest" + } diff --git a/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests.xproj b/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests.xproj new file mode 100644 index 0000000000..3c365b8f37 --- /dev/null +++ b/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests.xproj @@ -0,0 +1,22 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + ab12d4b6-bbe7-4a69-9839-3fcd0c77a04f + Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\ + v4.5.2 + + + 2.0 + + + + + + \ No newline at end of file diff --git a/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/Properties/AssemblyInfo.cs b/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..b966c9647c --- /dev/null +++ b/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +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: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests")] +[assembly: AssemblyTrademark("")] + +// 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("ab12d4b6-bbe7-4a69-9839-3fcd0c77a04f")] diff --git a/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/TrxLoggerTests.cs b/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/TrxLoggerTests.cs new file mode 100644 index 0000000000..260570c6ea --- /dev/null +++ b/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/TrxLoggerTests.cs @@ -0,0 +1,418 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests +{ + using System; + + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Microsoft.VisualStudio.TestPlatform.Extensions.TrxLogger; + using VisualStudio.TestPlatform.ObjectModel.Client; + using Moq; + using System.Collections.Generic; + using VisualStudio.TestPlatform.ObjectModel.Logging; + using ObjectModel = Microsoft.VisualStudio.TestPlatform.ObjectModel; + using TrxLoggerObjectModel = Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel; + using System.Globalization; + using VisualStudio.TestPlatform.ObjectModel; + using System.Collections.ObjectModel; + using Utility; + using System.Linq; + + [TestClass] + public class TrxLoggerTests + { + private Mock events; + private TrxLogger trxLogger; + + [TestInitialize] + public void Initialize() + { + this.events = new Mock(); + + this.trxLogger = new TrxLogger(); + this.trxLogger.Initialize(this.events.Object, "dummy"); + } + + [TestMethod] + public void InitializeShouldThrowExceptionIfEventsIsNull() + { + Assert.ThrowsException( + () => + { + this.trxLogger.Initialize(null, "dummy"); + }); + } + + [TestMethod] + public void InitializeShouldNotThrowExceptionIfEventsIsNotNull() + { + var events = new Mock(); + this.trxLogger.Initialize(events.Object, "dummy"); + } + + [TestMethod] + public void InitializeShouldThrowExceptionIfTestRunDirectoryIsEmptyOrNull() + { + Assert.ThrowsException( + () => + { + var events = new Mock(); + this.trxLogger.Initialize(events.Object, null); + }); + } + + [TestMethod] + public void InitializeShouldNotThrowExceptionIfTestRunDirectoryIsNeitherEmptyNorNull() + { + var events = new Mock(); + this.trxLogger.Initialize(events.Object, "dummy"); + } + + [TestMethod] + public void TestMessageHandlerShouldThrowExceptionIfEventArgsIsNull() + { + Assert.ThrowsException(() => + { + this.trxLogger.TestMessageHandler(new object(), default(TestRunMessageEventArgs)); + }); + } + + [TestMethod] + public void TestMessageHandlerShouldAddMessageWhenItIsInformation() + { + string message = "The information to test"; + TestRunMessageEventArgs trme = new TestRunMessageEventArgs(TestMessageLevel.Informational, message); + this.trxLogger.TestMessageHandler(new object(), trme); + + Assert.IsTrue(this.trxLogger.GetRunLevelInformationalMessage().Contains(message)); + } + + [TestMethod] + public void TestMessageHandlerShouldAddMessageInListIfItIsWarning() + { + string message = "The information to test"; + TestRunMessageEventArgs trme = new TestRunMessageEventArgs(TestMessageLevel.Warning, message); + this.trxLogger.TestMessageHandler(new object(), trme); + this.trxLogger.TestMessageHandler(new object(), trme); + + Assert.AreEqual(this.trxLogger.GetRunLevelErrorsAndWarnings().Count, 2); + } + + [TestMethod] + public void TestMessageHandlerShouldAddMessageInListIfItIsError() + { + string message = "The information to test"; + TestRunMessageEventArgs trme = new TestRunMessageEventArgs(TestMessageLevel.Error, message); + this.trxLogger.TestMessageHandler(new object(), trme); + + Assert.AreEqual(this.trxLogger.GetRunLevelErrorsAndWarnings().Count, 1); + } + + [TestMethod] + public void TestResultHandlerShouldCaptureStartTimeInSummaryAsTestMethodStartTime() + { + DateTime actualTime = new DateTime(2008, 6, 19, 7, 0, 0); + actualTime = DateTime.SpecifyKind(actualTime, DateTimeKind.Utc); + + ObjectModel.TestCase testCase = new ObjectModel.TestCase("dummy string", new Uri("some://uri"), "DummySourceFileName"); + ObjectModel.TestResult testResult = new ObjectModel.TestResult(testCase); + + testResult.StartTime = (DateTimeOffset)actualTime; + + Mock e = new Mock(testResult); + + trxLogger.TestResultHandler(new object(), e.Object); + + Assert.AreEqual((DateTimeOffset)actualTime, this.trxLogger.LoggerTestRun.Started); + } + + [TestMethod] + public void TestResultHandlerKeepingTheTrackOfPassedAndFailedTests() + { + ObjectModel.TestCase passTestCase1 = new ObjectModel.TestCase("Pass1", new Uri("some://uri"), "DummySourceFileName"); + ObjectModel.TestCase passTestCase2 = new ObjectModel.TestCase("Pass2", new Uri("some://uri"), "DummySourceFileName"); + ObjectModel.TestCase failTestCase1 = new ObjectModel.TestCase("Fail1", new Uri("some://uri"), "DummySourceFileName"); + ObjectModel.TestCase skipTestCase1 = new ObjectModel.TestCase("Skip1", new Uri("some://uri"), "DummySourceFileName"); + + ObjectModel.TestResult passResult1 = new ObjectModel.TestResult(passTestCase1); + passResult1.Outcome = ObjectModel.TestOutcome.Passed; + + ObjectModel.TestResult passResult2 = new ObjectModel.TestResult(passTestCase2); + passResult2.Outcome = ObjectModel.TestOutcome.Passed; + + ObjectModel.TestResult failResult1 = new ObjectModel.TestResult(failTestCase1); + failResult1.Outcome = ObjectModel.TestOutcome.Failed; + + ObjectModel.TestResult skipResult1 = new ObjectModel.TestResult(skipTestCase1); + skipResult1.Outcome = ObjectModel.TestOutcome.Skipped; + + Mock pass1 = new Mock(passResult1); + Mock pass2 = new Mock(passResult2); + Mock fail1 = new Mock(failResult1); + Mock skip1 = new Mock(skipResult1); + + + this.trxLogger.TestResultHandler(new object(), pass1.Object); + this.trxLogger.TestResultHandler(new object(), pass2.Object); + this.trxLogger.TestResultHandler(new object(), fail1.Object); + this.trxLogger.TestResultHandler(new object(), skip1.Object); + + + Assert.AreEqual(this.trxLogger.PassedTestCount, 2, "Passed Tests"); + Assert.AreEqual(this.trxLogger.FailedTestCount, 1, "Failed Tests"); + } + + [TestMethod] + public void TestResultHandlerKeepingTheTrackOfTotalTests() + { + ObjectModel.TestCase passTestCase1 = new ObjectModel.TestCase("Pass1", new Uri("some://uri"), "DummySourceFileName"); + ObjectModel.TestCase passTestCase2 = new ObjectModel.TestCase("Pass2", new Uri("some://uri"), "DummySourceFileName"); + ObjectModel.TestCase failTestCase1 = new ObjectModel.TestCase("Fail1", new Uri("some://uri"), "DummySourceFileName"); + ObjectModel.TestCase skipTestCase1 = new ObjectModel.TestCase("Skip1", new Uri("some://uri"), "DummySourceFileName"); + + ObjectModel.TestResult passResult1 = new ObjectModel.TestResult(passTestCase1); + passResult1.Outcome = ObjectModel.TestOutcome.Passed; + + ObjectModel.TestResult passResult2 = new ObjectModel.TestResult(passTestCase2); + passResult2.Outcome = ObjectModel.TestOutcome.Passed; + + ObjectModel.TestResult failResult1 = new ObjectModel.TestResult(failTestCase1); + failResult1.Outcome = ObjectModel.TestOutcome.Failed; + + ObjectModel.TestResult skipResult1 = new ObjectModel.TestResult(skipTestCase1); + skipResult1.Outcome = ObjectModel.TestOutcome.Skipped; + + Mock pass1 = new Mock(passResult1); + Mock pass2 = new Mock(passResult2); + Mock fail1 = new Mock(failResult1); + Mock skip1 = new Mock(skipResult1); + + + this.trxLogger.TestResultHandler(new object(), pass1.Object); + this.trxLogger.TestResultHandler(new object(), pass2.Object); + this.trxLogger.TestResultHandler(new object(), fail1.Object); + this.trxLogger.TestResultHandler(new object(), skip1.Object); + + + Assert.AreEqual(this.trxLogger.TotalTestCount, 4, "Passed Tests"); + } + + [TestMethod] + public void TestResultHandlerLockingAMessageForSkipTest() + { + ObjectModel.TestCase skipTestCase1 = new ObjectModel.TestCase("Skip1", new Uri("some://uri"), "DummySourceFileName"); + + ObjectModel.TestResult skipResult1 = new ObjectModel.TestResult(skipTestCase1); + skipResult1.Outcome = ObjectModel.TestOutcome.Skipped; + + Mock skip1 = new Mock(skipResult1); + + this.trxLogger.TestResultHandler(new object(), skip1.Object); + + string expectedMessage = String.Format(CultureInfo.CurrentCulture, TrxResource.MessageForSkippedTests, "Skip1"); + + Assert.AreEqual(String.Compare(this.trxLogger.GetRunLevelInformationalMessage(), expectedMessage, true), 0); + } + + [TestMethod] + public void TestResultHandlerShouldCreateOneTestResultForEachTestCase() + { + ObjectModel.TestCase testCase1 = new ObjectModel.TestCase("TestCase1", new Uri("some://uri"), "DummySourceFileName"); + ObjectModel.TestCase testCase2 = new ObjectModel.TestCase("TestCase2", new Uri("some://uri"), "DummySourceFileName"); + + ObjectModel.TestResult result1 = new ObjectModel.TestResult(testCase1); + result1.Outcome = ObjectModel.TestOutcome.Skipped; + + ObjectModel.TestResult result2 = new ObjectModel.TestResult(testCase2); + result2.Outcome = ObjectModel.TestOutcome.Failed; + + Mock resultEventArg1 = new Mock(result1); + Mock resultEventArg2 = new Mock(result2); + + this.trxLogger.TestResultHandler(new object(), resultEventArg1.Object); + this.trxLogger.TestResultHandler(new object(), resultEventArg2.Object); + + Assert.AreEqual(this.trxLogger.TestResultCount, 2, "TestResultHandler is not creating test result entry for each test case"); + } + + [TestMethod] + public void TestResultHandlerShouldCreateOneTestEntryForEachTestCase() + { + ObjectModel.TestCase testCase1 = new ObjectModel.TestCase("TestCase1", new Uri("some://uri"), "DummySourceFileName"); + ObjectModel.TestCase testCase2 = new ObjectModel.TestCase("TestCase2", new Uri("some://uri"), "DummySourceFileName"); + + ObjectModel.TestResult result1 = new ObjectModel.TestResult(testCase1); + result1.Outcome = ObjectModel.TestOutcome.Skipped; + + ObjectModel.TestResult result2 = new ObjectModel.TestResult(testCase2); + result2.Outcome = ObjectModel.TestOutcome.Passed; + + Mock resultEventArg1 = new Mock(result1); + Mock resultEventArg2 = new Mock(result2); + + this.trxLogger.TestResultHandler(new object(), resultEventArg1.Object); + this.trxLogger.TestResultHandler(new object(), resultEventArg2.Object); + + Assert.AreEqual(this.trxLogger.TestEntryCount, 2, "TestResultHandler is not creating test result entry for each test case"); + } + + [TestMethod] + public void TestResultHandlerShouldCreateOneUnitTestElementForEachTestCase() + { + ObjectModel.TestCase testCase1 = new ObjectModel.TestCase("TestCase1", new Uri("some://uri"), "DummySourceFileName"); + ObjectModel.TestCase testCase2 = new ObjectModel.TestCase("TestCase2", new Uri("some://uri"), "DummySourceFileName"); + + ObjectModel.TestResult result1 = new ObjectModel.TestResult(testCase1); + + ObjectModel.TestResult result2 = new ObjectModel.TestResult(testCase2); + result2.Outcome = ObjectModel.TestOutcome.Failed; + + Mock resultEventArg1 = new Mock(result1); + Mock resultEventArg2 = new Mock(result2); + + this.trxLogger.TestResultHandler(new object(), resultEventArg1.Object); + this.trxLogger.TestResultHandler(new object(), resultEventArg2.Object); + + Assert.AreEqual(this.trxLogger.UnitTestElementCount, 2, "TestResultHandler is not creating test result entry for each test case"); + } + + [TestMethod] + public void OutcomeOfRunWillBeFailIfAnyTestsFails() + { + ObjectModel.TestCase passTestCase1 = new ObjectModel.TestCase("Pass1", new Uri("some://uri"), "DummySourceFileName"); + ObjectModel.TestCase passTestCase2 = new ObjectModel.TestCase("Pass2", new Uri("some://uri"), "DummySourceFileName"); + ObjectModel.TestCase failTestCase1 = new ObjectModel.TestCase("Fail1", new Uri("some://uri"), "DummySourceFileName"); + ObjectModel.TestCase skipTestCase1 = new ObjectModel.TestCase("Skip1", new Uri("some://uri"), "DummySourceFileName"); + + ObjectModel.TestResult passResult1 = new ObjectModel.TestResult(passTestCase1); + passResult1.Outcome = ObjectModel.TestOutcome.Passed; + + ObjectModel.TestResult passResult2 = new ObjectModel.TestResult(passTestCase2); + passResult2.Outcome = ObjectModel.TestOutcome.Passed; + + ObjectModel.TestResult failResult1 = new ObjectModel.TestResult(failTestCase1); + failResult1.Outcome = ObjectModel.TestOutcome.Failed; + + ObjectModel.TestResult skipResult1 = new ObjectModel.TestResult(skipTestCase1); + skipResult1.Outcome = ObjectModel.TestOutcome.Skipped; + + Mock pass1 = new Mock(passResult1); + Mock pass2 = new Mock(passResult2); + Mock fail1 = new Mock(failResult1); + Mock skip1 = new Mock(skipResult1); + + this.trxLogger.TestResultHandler(new object(), pass1.Object); + this.trxLogger.TestResultHandler(new object(), pass2.Object); + this.trxLogger.TestResultHandler(new object(), fail1.Object); + this.trxLogger.TestResultHandler(new object(), skip1.Object); + + var testRunCompleteEventArgs = new TestRunCompleteEventArgs(null, false, false, null, new Collection(), new TimeSpan(1, 0, 0, 0)); + + try + { + // Intentionally making it null so that it will not create actual trx file + TrxLogger.TrxFileDirectory = null; + this.trxLogger.TestRunCompleteHandler(new object(), testRunCompleteEventArgs); + } + catch (ArgumentNullException) + { + } + + Assert.AreEqual(TrxLoggerObjectModel.TestOutcome.Failed, this.trxLogger.TestResultOutcome); + } + + [TestMethod] + public void OutcomeOfRunWillBeCompletedIfNoTestsFails() + { + ObjectModel.TestCase passTestCase1 = new ObjectModel.TestCase("Pass1", new Uri("some://uri"), "DummySourceFileName"); + ObjectModel.TestCase passTestCase2 = new ObjectModel.TestCase("Pass2", new Uri("some://uri"), "DummySourceFileName"); + ObjectModel.TestCase skipTestCase1 = new ObjectModel.TestCase("Skip1", new Uri("some://uri"), "DummySourceFileName"); + + ObjectModel.TestResult passResult1 = new ObjectModel.TestResult(passTestCase1); + passResult1.Outcome = ObjectModel.TestOutcome.Passed; + + ObjectModel.TestResult passResult2 = new ObjectModel.TestResult(passTestCase2); + passResult2.Outcome = ObjectModel.TestOutcome.Passed; + + ObjectModel.TestResult skipResult1 = new ObjectModel.TestResult(skipTestCase1); + skipResult1.Outcome = ObjectModel.TestOutcome.Skipped; + + Mock pass1 = new Mock(passResult1); + Mock pass2 = new Mock(passResult2); + Mock skip1 = new Mock(skipResult1); + + this.trxLogger.TestResultHandler(new object(), pass1.Object); + this.trxLogger.TestResultHandler(new object(), pass2.Object); + this.trxLogger.TestResultHandler(new object(), skip1.Object); + + var testRunCompleteEventArgs = new TestRunCompleteEventArgs(null, false, false, null, new Collection(), new TimeSpan(1, 0, 0, 0)); + + try + { + // Intentionally making it null so that it will not create actual trx file + TrxLogger.TrxFileDirectory = null; + this.trxLogger.TestRunCompleteHandler(new object(), testRunCompleteEventArgs); + } + catch (ArgumentNullException) + { + } + + Assert.AreEqual(TrxLoggerObjectModel.TestOutcome.Completed, this.trxLogger.TestResultOutcome); + } + + /// + /// Unit test for reading TestCategories from the TestCase which is part of test result. + /// + [TestMethod] + public void GetCustomPropertyValueFromTestCaseShouldReadCategoyrAttributesFromTestCase() + { + ObjectModel.TestCase testCase1 = new ObjectModel.TestCase("TestCase1", new Uri("some://uri"), "DummySourceFileName"); + TestProperty testProperty = TestProperty.Register("MSTestDiscoverer.TestCategory", "String array property", string.Empty, string.Empty, typeof(string[]), null, TestPropertyAttributes.Hidden, typeof(TestObject)); + + testCase1.SetPropertyValue(testProperty, new[] { "ClassLevel", "AsmLevel" }); + + List listCategoriesActual = Converter.GetCustomPropertyValueFromTestCase(testCase1, "MSTestDiscoverer.TestCategory"); + + List listCategoriesExpected = new List(); + listCategoriesExpected.Add("ClassLevel"); + listCategoriesExpected.Add("AsmLevel"); + + CollectionAssert.AreEqual(listCategoriesExpected, listCategoriesActual); + } + + /// + /// Unit test for assigning or populating test categories read to the unit test element. + /// + [TestMethod] + public void GetQToolsTestElementFromTestCaseShouldAssignTestCategoryOfUnitTestElement() + { + ObjectModel.TestCase testCase = new ObjectModel.TestCase("TestCase1", new Uri("some://uri"), "DummySourceFileName"); + ObjectModel.TestResult result = new ObjectModel.TestResult(testCase); + TestProperty testProperty = TestProperty.Register("MSTestDiscoverer.TestCategory", "String array property", string.Empty, string.Empty, typeof(string[]), null, TestPropertyAttributes.Hidden, typeof(TestObject)); + + testCase.SetPropertyValue(testProperty, new[] { "AsmLevel", "ClassLevel", "MethodLevel" }); + + TrxLoggerObjectModel.UnitTestElement unitTestElement = Converter.GetQToolsTestElementFromTestCase(result); + + object[] expected = new[] { "MethodLevel", "ClassLevel", "AsmLevel" }; + + CollectionAssert.AreEqual(expected, unitTestElement.TestCategories.ToArray().OrderByDescending(x =>x.ToString()).ToArray()); + } + + /// + /// Unit test for regression when there's no test categories. + /// + [TestMethod] + public void GetQToolsTestElementFromTestCaseShouldNotFailWhenThereIsNoTestCategoreis() + { + ObjectModel.TestCase testCase = new ObjectModel.TestCase("TestCase1", new Uri("some://uri"), "DummySourceFileName"); + ObjectModel.TestResult result = new ObjectModel.TestResult(testCase); + + TrxLoggerObjectModel.UnitTestElement unitTestElement = Converter.GetQToolsTestElementFromTestCase(result); + + object[] expected = Enumerable.Empty().ToArray(); + + CollectionAssert.AreEqual(expected, unitTestElement.TestCategories.ToArray()); + } + } +} diff --git a/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/project.json b/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/project.json new file mode 100644 index 0000000000..bc0601c04b --- /dev/null +++ b/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/project.json @@ -0,0 +1,37 @@ +{ + "version": "15.0.0-*", + + "buildOptions": { + "delaySign": true, + "keyFile": "../../scripts/key.snk", + "warningsAsErrors": true + }, + + "dependencies": { + "Microsoft.NETCore.App": { + "type": "platform", + "version": "1.0.0-rc2-3002702" + }, + "dotnet-test-mstest": { + "version": "1.0.1-preview", + "exclude": "compile" + }, + "MSTest.TestFramework": "1.0.0-preview", + "moq.netcore": "4.4.0-beta8", + "System.Diagnostics.TraceSource": "4.0.0-rc2-24015", + "Microsoft.TestPlatform.Client": "15.0.0-*", + "Microsoft.TestPlatform.Extensions.TrxLogger": "15.0.0-*", + "Microsoft.TestPlatform.ObjectModel": "15.0.0-*" + }, + + "frameworks": { + "netcoreapp1.0": { + "imports": [ + "dnxcore50", + "portable-net45+win8" + ] + } + }, + + "testRunner": "mstest" +} \ No newline at end of file diff --git a/test/Microsoft.TestPlatform.ObjectModel.UnitTests/Client/BaseTestRunCriteriaTests.cs b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/Client/BaseTestRunCriteriaTests.cs new file mode 100644 index 0000000000..ca94904d4e --- /dev/null +++ b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/Client/BaseTestRunCriteriaTests.cs @@ -0,0 +1,66 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.ObjectModel.UnitTests.Client +{ + using System; + + using Microsoft.VisualStudio.TestTools.UnitTesting; + using VisualStudio.TestPlatform.ObjectModel.Client; + [TestClass] + public class BaseTestRunCriteriaTests + { + [TestMethod] + public void ConstructorShouldThrowIfFrequencyOfRunStatsChangeIsZero() + { + var isExceptionThrown = false; + + try + { + var criteria = new BaseTestRunCriteria(frequencyOfRunStatsChangeEvent: 0); + } + catch (ArgumentOutOfRangeException ex) + { + isExceptionThrown = true; + StringAssert.Contains(ex.Message, "Notification frequency need to be a positive value."); + } + + Assert.IsTrue(isExceptionThrown); + } + + [TestMethod] + public void ConstructorShouldThrowIfFrequencyOfRunStatsChangeIsLesssThanZero() + { + var isExceptionThrown = false; + + try + { + var criteria = new BaseTestRunCriteria(frequencyOfRunStatsChangeEvent: -10); + } + catch (ArgumentOutOfRangeException ex) + { + isExceptionThrown = true; + StringAssert.Contains(ex.Message, "Notification frequency need to be a positive value."); + } + + Assert.IsTrue(isExceptionThrown); + } + + [TestMethod] + public void ConstructorShouldThrowIfRunStatsChangeEventTimeoutIsMinimumTimeSpanValue() + { + var isExceptionThrown = false; + + try + { + var criteria = new BaseTestRunCriteria(frequencyOfRunStatsChangeEvent: 1, keepAlive: false, testSettings: null, runStatsChangeEventTimeout: TimeSpan.MinValue); + } + catch (ArgumentOutOfRangeException ex) + { + isExceptionThrown = true; + StringAssert.Contains(ex.Message, "Notification timeout must be greater than zero."); + } + + Assert.IsTrue(isExceptionThrown); + } + } +} diff --git a/test/Microsoft.TestPlatform.ObjectModel.UnitTests/Client/TestRunCriteriaTests.cs b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/Client/TestRunCriteriaTests.cs new file mode 100644 index 0000000000..63c3ea4ea4 --- /dev/null +++ b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/Client/TestRunCriteriaTests.cs @@ -0,0 +1,178 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.ObjectModel.UnitTests +{ + using System; + using System.Collections.Generic; + using System.Linq; + + using Microsoft.VisualStudio.TestTools.UnitTesting; + + using VisualStudio.TestPlatform.ObjectModel; + using VisualStudio.TestPlatform.ObjectModel.Client; + + [TestClass] + public class TestRunCriteriaTests + { + #region Constructor tests. + + [TestMethod] + public void ConstructorForSourcesShouldInitializeAdapterSourceMap() + { + var sources = new List { "s1.dll", "s2.dll" }; + var testRunCriteria = new TestRunCriteria(sources, frequencyOfRunStatsChangeEvent: 10); + + Assert.IsNotNull(testRunCriteria.AdapterSourceMap); + CollectionAssert.AreEqual(new List { "_none_" }, testRunCriteria.AdapterSourceMap.Keys); + CollectionAssert.AreEqual(sources, testRunCriteria.AdapterSourceMap.Values.First().ToList()); + } + + [TestMethod] + public void ConstructorForSourcesWithBaseTestRunCriteriaShouldInitializeAdapterSourceMap() + { + var sources = new List { "s1.dll", "s2.dll" }; + var testRunCriteria = new TestRunCriteria(sources, new BaseTestRunCriteria(10)); + + Assert.IsNotNull(testRunCriteria.AdapterSourceMap); + CollectionAssert.AreEqual(new List { "_none_" }, testRunCriteria.AdapterSourceMap.Keys); + CollectionAssert.AreEqual(sources, testRunCriteria.AdapterSourceMap.Values.First().ToList()); + } + + [TestMethod] + public void ConstructorForSourcesWithAdapterSourceMapShouldInitializeSourceMap() + { + var adapterSourceMap = new Dictionary>(); + var sourceSet1 = new List { "s1.dll", "s2.dll" }; + var sourceSet2 = new List { "s1.json", "s2.json" }; + adapterSourceMap.Add("dummyadapter1", sourceSet1); + adapterSourceMap.Add("dummyadapter2", sourceSet2); + + var testRunCriteria = new TestRunCriteria(adapterSourceMap, 10, false, null, TimeSpan.MaxValue, null); + + Assert.IsNotNull(testRunCriteria.AdapterSourceMap); + CollectionAssert.AreEqual(new List { "dummyadapter1", "dummyadapter2" }, testRunCriteria.AdapterSourceMap.Keys); + CollectionAssert.AreEqual(sourceSet1, testRunCriteria.AdapterSourceMap.Values.First().ToList()); + CollectionAssert.AreEqual(sourceSet2, testRunCriteria.AdapterSourceMap.Values.ToArray()[1].ToList()); + } + + #endregion + + #region Sources tests. + + [TestMethod] + public void SourcesShouldEnumerateThroughAllSourcesInTheAdapterSourceMap() + { + var adapterSourceMap = new Dictionary>(); + var sourceSet1 = new List { "s1.dll", "s2.dll" }; + var sourceSet2 = new List { "s1.json", "s2.json" }; + adapterSourceMap.Add("dummyadapter1", sourceSet1); + adapterSourceMap.Add("dummyadapter2", sourceSet2); + + var testRunCriteria = new TestRunCriteria(adapterSourceMap, 10, false, null, TimeSpan.MaxValue, null); + + var expectedSourceSet = new List(sourceSet1); + expectedSourceSet.AddRange(sourceSet2); + CollectionAssert.AreEqual(expectedSourceSet, testRunCriteria.Sources.ToList()); + } + + [TestMethod] + public void SourcesShouldReturnNullIfAdapterSourceMapIsNull() + { + var testRunCriteria = + new TestRunCriteria( + new List { new TestCase("A.C.M", new Uri("excutor://dummy"), "s.dll") }, + frequencyOfRunStatsChangeEvent: 10); + + Assert.IsNull(testRunCriteria.Sources); + } + + #endregion + + #region HasSpecificSources tests + + [TestMethod] + public void HasSpecificSourcesReturnsFalseIfSourcesAreNotSpecified() + { + var testRunCriteria = + new TestRunCriteria( + new List { new TestCase("A.C.M", new Uri("excutor://dummy"), "s.dll") }, + frequencyOfRunStatsChangeEvent: 10); + + Assert.IsFalse(testRunCriteria.HasSpecificSources); + } + + [TestMethod] + public void HasSpecificSourcesReturnsTrueIfSourcesAreSpecified() + { + var sources = new List { "s1.dll", "s2.dll" }; + var testRunCriteria = new TestRunCriteria(sources, frequencyOfRunStatsChangeEvent: 10); + + Assert.IsTrue(testRunCriteria.HasSpecificSources); + } + + #endregion + + #region HasSpecificTests tests + + [TestMethod] + public void HasSpecificTestsReturnsTrueIfTestsAreSpecified() + { + var testRunCriteria = + new TestRunCriteria( + new List { new TestCase("A.C.M", new Uri("excutor://dummy"), "s.dll") }, + frequencyOfRunStatsChangeEvent: 10); + + Assert.IsTrue(testRunCriteria.HasSpecificTests); + } + + [TestMethod] + public void HasSpecificTestsReturnsFalseIfSourcesAreSpecified() + { + var sources = new List { "s1.dll", "s2.dll" }; + var testRunCriteria = new TestRunCriteria(sources, frequencyOfRunStatsChangeEvent: 10); + + Assert.IsFalse(testRunCriteria.HasSpecificTests); + } + + #endregion + + #region TestCaseFilter tests + + [TestMethod] + public void TestCaseFilterSetterShouldThrowIftestCriteriaIsBasedOnTests() + { + var testRunCriteria = + new TestRunCriteria( + new List { new TestCase("A.C.M", new Uri("excutor://dummy"), "s.dll") }, + frequencyOfRunStatsChangeEvent: 10); + + var isExceptionthrown = false; + try + { + testRunCriteria.TestCaseFilter = "foo"; + } + catch (InvalidOperationException ex) + { + isExceptionthrown = true; + Assert.AreEqual( + "Cannot specify TestCaseFilter for specific tests run. FilterCriteria is only for run with sources.", + ex.Message); + } + + Assert.IsTrue(isExceptionthrown); + } + + [TestMethod] + public void TestCaseFilterSetterShouldSetFilterCriteriaForSources() + { + var sources = new List { "s1.dll", "s2.dll" }; + var testRunCriteria = new TestRunCriteria(sources, frequencyOfRunStatsChangeEvent: 10); + + testRunCriteria.TestCaseFilter = "foo"; + + Assert.AreEqual("foo", testRunCriteria.TestCaseFilter); + } + + #endregion + } +} diff --git a/test/Microsoft.TestPlatform.ObjectModel.UnitTests/Microsoft.TestPlatform.ObjectModel.UnitTests.xproj b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/Microsoft.TestPlatform.ObjectModel.UnitTests.xproj new file mode 100644 index 0000000000..5239d6c62b --- /dev/null +++ b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/Microsoft.TestPlatform.ObjectModel.UnitTests.xproj @@ -0,0 +1,22 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + fc30d066-c891-40c0-b94a-d7dbb50ebf8f + Microsoft.TestPlatform.ObjectModel.UnitTests + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\ + v4.5.2 + + + 2.0 + + + + + + \ No newline at end of file diff --git a/test/Microsoft.TestPlatform.ObjectModel.UnitTests/Properties/AssemblyInfo.cs b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..ac195ead2f --- /dev/null +++ b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +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: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Microsoft.TestPlatform.ObjectModel.UnitTests")] +[assembly: AssemblyTrademark("")] + +// 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("fc30d066-c891-40c0-b94a-d7dbb50ebf8f")] diff --git a/test/Microsoft.TestPlatform.ObjectModel.UnitTests/RunSettings/InProcDataCollectionRunSettingsTests.cs b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/RunSettings/InProcDataCollectionRunSettingsTests.cs new file mode 100644 index 0000000000..6e15e6e2c6 --- /dev/null +++ b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/RunSettings/InProcDataCollectionRunSettingsTests.cs @@ -0,0 +1,159 @@ + +namespace Microsoft.TestPlatform.ObjectModel.UnitTests.RunSettings +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + + using Microsoft.TestPlatform.ObjectModel.UnitTests.Utilities; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class InProcDataCollectionRunSettingsTests + { + + private string settingsXml = @" + + + + + 4312 + + + + + "; + + private Collection inProcDCSettings; + + [TestInitialize] + public void InitializeTests() + { + InProcDataCollectionUtilities.ReadInProcDataCollectionRunSettings(this.settingsXml); + this.inProcDCSettings = InProcDataCollectionUtilities.GetInProcDataCollectorSettings(); + Assert.IsNotNull(this.inProcDCSettings); + Assert.AreEqual(this.inProcDCSettings.Count, 1); + } + + [TestMethod] + public void InProcDataCollectorIsReadingUri() + { + Assert.IsTrue(string.Equals(this.inProcDCSettings[0].Uri.ToString(), "InProcDataCollector://Microsoft/TestImpact/1.0", StringComparison.OrdinalIgnoreCase)); + } + + [TestMethod] + public void InProcDataCollectorIsReadingFriendlyName() + { + Assert.IsTrue(string.Equals(this.inProcDCSettings[0].FriendlyName, "Test Impact", StringComparison.OrdinalIgnoreCase)); + } + + [TestMethod] + public void InProcDataCollectorIsReadingAssemblyQualifiedName() + { + Assert.IsTrue(string.Equals(this.inProcDCSettings[0].AssemblyQualifiedName.ToString(), "TestImpactListener.Tests, Version=1.0.0.0, Culture=neutral, PublicKeyToken=7ccb7239ffde675a", StringComparison.OrdinalIgnoreCase)); + } + + [TestMethod] + public void InProcDataCollectorIsReadingCodeBase() + { + Assert.IsTrue(string.Equals(this.inProcDCSettings[0].CodeBase.ToString(), @"E:\repos\MSTest\src\managed\TestPlatform\TestImpactListener.Tests\bin\Debug\TestImpactListener.Tests.dll", StringComparison.OrdinalIgnoreCase)); + } + + [TestMethod] + public void InProcDataCollectorIsReadingConfiguration() + { + Assert.IsTrue(string.Equals(this.inProcDCSettings[0].Configuration.OuterXml.ToString(), @"4312", StringComparison.OrdinalIgnoreCase)); + } + + [TestMethod] + public void InProcDataCollectorIsReadingMultipleDataCollector() + { + var multiSettingsXml = @" + + + + + 4312 + + + + + 4312 + + + + + "; + InProcDataCollectionUtilities.ReadInProcDataCollectionRunSettings(multiSettingsXml); + var inProcDCSettings = InProcDataCollectionUtilities.GetInProcDataCollectorSettings(); + Assert.IsNotNull(inProcDCSettings); + Assert.AreEqual(inProcDCSettings.Count, 2); + } + + [TestMethod] + public void InProcDataCollectorWillThrowExceptionForInavlidAttributes() + { + var invalidSettingsXml = @" + + + + + 4312 + + + + + "; + + Assert.ThrowsException( + () => InProcDataCollectionUtilities.ReadInProcDataCollectionRunSettings(invalidSettingsXml)); + } + + [TestMethod] + public void InProcDataCollectorReadingWhenDataCollectorsArePresent() + { + string settingsXml = @" + + + + + + + + .*CPPUnitTestFramework.* + + + + + + + + + + + + 4312 + + + + + "; + + InProcDataCollectionUtilities.ReadInProcDataCollectionRunSettings(settingsXml); + var inProcDCSettings = InProcDataCollectionUtilities.GetInProcDataCollectorSettings(); + Assert.IsNotNull(inProcDCSettings); + Assert.AreEqual(inProcDCSettings.Count, 1); + } + + [TestMethod] + public void InProcDataCollectionSettingsToXMLCheck() + { + string actualXML = + @"4312"; + InProcDataCollectionUtilities.ReadInProcDataCollectionRunSettings(this.settingsXml); + Assert.IsTrue(string.Equals(InProcDataCollectionUtilities.InProcDataCollectionRunSettings.ToXml().OuterXml.Trim().Replace(" ", ""), actualXML.Trim().Replace(" ", ""), StringComparison.OrdinalIgnoreCase)); + } + } +} diff --git a/test/Microsoft.TestPlatform.ObjectModel.UnitTests/Utilities/XmlRunSettingsUtilitiesTests.cs b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/Utilities/XmlRunSettingsUtilitiesTests.cs new file mode 100644 index 0000000000..774c7a09b7 --- /dev/null +++ b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/Utilities/XmlRunSettingsUtilitiesTests.cs @@ -0,0 +1,359 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.ObjectModel.UnitTests.Utilities +{ + using System; + using System.Collections.Generic; + using System.Xml; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class XmlRunSettingsUtilitiesTests + { + #region private variables + + private readonly string runSettingsXmlWithDataCollectors = @" + + + + + + + + + + .*CPPUnitTestFramework.* + + + + + + + +"; + + private readonly string runSettingsXmlWithDataCollectorsDisabled = @" + + + + + + + + + + .*CPPUnitTestFramework.* + + + + + + + +"; + + private readonly string runSettingsXmlWithIncorrectDataCollectorSettings = @" + + + + + + + + + + .*CPPUnitTestFramework.* + + + + + + + +"; + + #endregion + + #region GetTestRunParameters tests + + [TestMethod] + public void GetTestRunParametersReturnsEmptyDictionaryOnNullRunSettings() + { + Dictionary trp = XmlRunSettingsUtilities.GetTestRunParameters(null); + Assert.IsNotNull(trp); + Assert.AreEqual(0, trp.Count); + } + + [TestMethod] + public void GetTestRunParametersReturnsEmptyDictionaryWhenNoTestRunParameters() + { + string settingsXml = + @" + + + .\TestResults + x86 + Framework40 + + "; + + Dictionary trp = XmlRunSettingsUtilities.GetTestRunParameters(settingsXml); + Assert.IsNotNull(trp); + Assert.AreEqual(0, trp.Count); + } + + [TestMethod] + public void GetTestRunParametersReturnsEmptyDictionaryForEmptyTestRunParametersNode() + { + string settingsXml = + @" + + + .\TestResults + x86 + Framework40 + + + + "; + + Dictionary trp = XmlRunSettingsUtilities.GetTestRunParameters(settingsXml); + Assert.IsNotNull(trp); + Assert.AreEqual(0, trp.Count); + } + + [TestMethod] + public void GetTestRunParametersReturns1EntryOn1TestRunParameter() + { + string settingsXml = + @" + + + .\TestResults + x86 + Framework40 + + + + + "; + + Dictionary trp = XmlRunSettingsUtilities.GetTestRunParameters(settingsXml); + Assert.IsNotNull(trp); + Assert.AreEqual(1, trp.Count); + + // Verify Parameter Values. + Assert.IsTrue(trp.ContainsKey("webAppUrl")); + Assert.AreEqual(trp["webAppUrl"], "http://localhost"); + } + + [TestMethod] + public void GetTestRunParametersReturns3EntryOn3TestRunParameter() + { + string settingsXml = + @" + + + .\TestResults + x86 + Framework40 + + + + + + + "; + + Dictionary trp = XmlRunSettingsUtilities.GetTestRunParameters(settingsXml); + Assert.IsNotNull(trp); + Assert.AreEqual(3, trp.Count); + + // Verify Parameter Values. + Assert.IsTrue(trp.ContainsKey("webAppUrl")); + Assert.AreEqual(trp["webAppUrl"], "http://localhost"); + Assert.IsTrue(trp.ContainsKey("webAppUserName")); + Assert.AreEqual(trp["webAppUserName"], "Admin"); + Assert.IsTrue(trp.ContainsKey("webAppPassword")); + Assert.AreEqual(trp["webAppPassword"], "Password"); + } + + [TestMethod] + public void GetTestRunParametersThrowsWhenTRPNodeHasAttributes() + { + string settingsXml = + @" + + + .\TestResults + x86 + Framework40 + + + + + "; + + Assert.ThrowsException(() => XmlRunSettingsUtilities.GetTestRunParameters(settingsXml)); + } + + [TestMethod] + public void GetTestRunParametersThrowsWhenTRPNodeHasNonParameterTypeChildNodes() + { + string settingsXml = + @" + + + .\TestResults + x86 + Framework40 + + + + x86 + + "; + + Assert.ThrowsException(() => XmlRunSettingsUtilities.GetTestRunParameters(settingsXml)); + } + + [TestMethod] + public void GetTestRunParametersIgnoresMalformedKeyValues() + { + string settingsXml = + @" + + + .\TestResults + x86 + Framework40 + + + + + "; + + Dictionary trp = XmlRunSettingsUtilities.GetTestRunParameters(settingsXml); + Assert.IsNotNull(trp); + Assert.AreEqual(0, trp.Count); + } + + [TestMethod] + public void GetInProcDataCollectionRunSettingsFromSettings() + { + string settingsXml= @" + + + + + 4312 + + + + + "; + var inProcDCRunSettings = XmlRunSettingsUtilities.GetInProcDataCollectionRunSettings(settingsXml); + Assert.IsNotNull(inProcDCRunSettings); + Assert.AreEqual(inProcDCRunSettings.DataCollectorSettingsList.Count, 1); + } + + [TestMethod] + public void GetInProcDataCollectionRunSettingsThrowsExceptionWhenXMLNotValid() + { + string settingsXml = @" + + + + + 4312 + + + + + "; + + Assert.ThrowsException( + () => XmlRunSettingsUtilities.GetInProcDataCollectionRunSettings(settingsXml)); + } + #endregion + + #region CreateDefaultRunSettings tests + + [TestMethod] + public void CreateDefaultRunSettingsShouldReturnABasicRunSettings() + { + var defaultRunSettings = XmlRunSettingsUtilities.CreateDefaultRunSettings().CreateNavigator().OuterXml; + var expectedRunSettings = + "\r\n \r\n \r\n \r\n"; + + Assert.AreEqual(expectedRunSettings, defaultRunSettings); + } + + #endregion + + #region IsDataCollectionEnabled tests + + [TestMethod] + public void IsDataCollectionEnabledShouldReturnFalseIfRunSettingsIsNull() + { + Assert.IsFalse(XmlRunSettingsUtilities.IsDataCollectionEnabled(null)); + } + + [TestMethod] + public void IsDataCollectionEnabledShouldReturnFalseIfDataCollectionNodeIsNotPresent() + { + Assert.IsFalse(XmlRunSettingsUtilities.IsDataCollectionEnabled("")); + } + + [TestMethod] + public void IsDataCollectionEnabledShouldReturnFalseIfDataCollectionIsDisabled() + { + Assert.IsFalse(XmlRunSettingsUtilities.IsDataCollectionEnabled(this.runSettingsXmlWithDataCollectorsDisabled)); + } + + + [TestMethod] + public void IsDataCollectionEnabledShouldReturnTrueIfDataCollectionIsEnabled() + { + Assert.IsTrue(XmlRunSettingsUtilities.IsDataCollectionEnabled(this.runSettingsXmlWithDataCollectors)); + } + + #endregion + + #region GetDataCollectionRunSettings tests + + [TestMethod] + public void GetDataCollectionRunSettingsShouldReturnNullIfSettingsIsNull() + { + Assert.IsNull(XmlRunSettingsUtilities.GetDataCollectionRunSettings(null)); + } + + [TestMethod] + public void GetDataCollectionRunSettingsShouldReturnNullOnNoDataCollectorSettings() + { + Assert.IsNull(XmlRunSettingsUtilities.GetDataCollectionRunSettings("")); + } + + [TestMethod] + public void GetDataCollectionRunSettingsShouldReturnDataCollectorRunSettings() + { + Assert.IsNotNull(XmlRunSettingsUtilities.GetDataCollectionRunSettings(this.runSettingsXmlWithDataCollectors)); + } + + [TestMethod] + public void GetDataCollectionRunSettingsShouldReturnDataCollectorRunSettingsEvenIfDisabled() + { + Assert.IsNotNull(XmlRunSettingsUtilities.GetDataCollectionRunSettings(this.runSettingsXmlWithDataCollectorsDisabled)); + } + + [TestMethod] + public void GetDataCollectionRunSettingsShouldThrowOnMalformedDataCollectorSettings() + { + Assert.ThrowsException(() => XmlRunSettingsUtilities.GetDataCollectionRunSettings(this.runSettingsXmlWithIncorrectDataCollectorSettings)); + } + + #endregion + } +} diff --git a/test/Microsoft.TestPlatform.ObjectModel.UnitTests/project.json b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/project.json new file mode 100644 index 0000000000..fed3a2b673 --- /dev/null +++ b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/project.json @@ -0,0 +1,35 @@ +{ + "version": "15.0.0-*", + + "buildOptions": { + "delaySign": true, + "keyFile": "../../scripts/key.snk", + "warningsAsErrors": true + }, + + "dependencies": { + "Microsoft.NETCore.App": { + "type": "platform", + "version": "1.0.0-rc2-3002702" + }, + "dotnet-test-mstest": { + "version": "1.0.1-preview", + "exclude": "compile" + }, + "MSTest.TestFramework": "1.0.0-preview", + "moq.netcore": "4.4.0-beta8", + "System.Diagnostics.TraceSource": "4.0.0-rc2-24015", + "Microsoft.TestPlatform.ObjectModel": "15.0.0-*" + }, + + "frameworks": { + "netcoreapp1.0": { + "imports": [ + "dnxcore50", + "portable-net45+win8" + ] + } + }, + + "testRunner": "mstest" +} \ No newline at end of file diff --git a/test/Microsoft.TestPlatform.Utilities.UnitTests/ClientUtilitiesTests.cs b/test/Microsoft.TestPlatform.Utilities.UnitTests/ClientUtilitiesTests.cs new file mode 100644 index 0000000000..61ab121b6a --- /dev/null +++ b/test/Microsoft.TestPlatform.Utilities.UnitTests/ClientUtilitiesTests.cs @@ -0,0 +1,162 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.Utilities.Tests +{ + using System; + using System.IO; + using System.Reflection; + using System.Xml; + + using Microsoft.VisualStudio.TestPlatform.Utilities; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class ClientUtilitiesTests + { + [TestMethod] + public void FixRelativePathsInRunSettingsShouldThrowIfDocumentIsNull() + { + Assert.ThrowsException(() => ClientUtilities.FixRelativePathsInRunSettings(null, "c:\\temp")); + } + + [TestMethod] + public void FixRelativePathsInRunSettingsShouldThrowIfPathIsNullOrEmpty() + { + Assert.ThrowsException(() => ClientUtilities.FixRelativePathsInRunSettings(new XmlDocument(), null)); + Assert.ThrowsException(() => ClientUtilities.FixRelativePathsInRunSettings(new XmlDocument(), "")); + } + + [TestMethod] + public void FixRelativePathsInRunSettingsShouldNotModifyAnEmptyRunSettings() + { + var runSettingsXML = ""; + + var doc = new XmlDocument(); + doc.LoadXml(runSettingsXML); + + var currentAssemblyLocation = typeof(ClientUtilitiesTests).GetTypeInfo().Assembly.Location; + + ClientUtilities.FixRelativePathsInRunSettings(doc, currentAssemblyLocation); + + var finalSettingsXml = doc.OuterXml; + + Assert.AreEqual(runSettingsXML, finalSettingsXml); + } + + [TestMethod] + public void FixRelativePathsInRunSettingsShouldModifyRelativeTestSettingsFilePath() + { + var runSettingsXML = "..\\remote.testsettings"; + + var doc = new XmlDocument(); + doc.LoadXml(runSettingsXML); + + var currentAssemblyLocation = typeof(ClientUtilitiesTests).GetTypeInfo().Assembly.Location; + + ClientUtilities.FixRelativePathsInRunSettings(doc, currentAssemblyLocation); + + var finalSettingsXml = doc.OuterXml; + + var expectedPath = Path.GetFullPath(Path.Combine( + Path.GetDirectoryName(currentAssemblyLocation), "..\\remote.testsettings")); + var expectedSettingsXml = string.Concat( + "", + expectedPath, + ""); + + Assert.AreEqual(expectedSettingsXml, finalSettingsXml); + } + + [TestMethod] + public void FixRelativePathsInRunSettingsShouldNotModifyAbsoluteTestSettingsFilePath() + { + var runSettingsXML = "C:\\temp\\remote.testsettings"; + + var doc = new XmlDocument(); + doc.LoadXml(runSettingsXML); + + var currentAssemblyLocation = typeof(ClientUtilitiesTests).GetTypeInfo().Assembly.Location; + + ClientUtilities.FixRelativePathsInRunSettings(doc, currentAssemblyLocation); + + var finalSettingsXml = doc.OuterXml; + + Assert.AreEqual(runSettingsXML, finalSettingsXml); + } + + [TestMethod] + public void FixRelativePathsInRunSettingsShouldNotModifyEmptyTestSettingsFilePath() + { + var runSettingsXML = ""; + + var doc = new XmlDocument(); + doc.LoadXml(runSettingsXML); + + var currentAssemblyLocation = typeof(ClientUtilitiesTests).GetTypeInfo().Assembly.Location; + + ClientUtilities.FixRelativePathsInRunSettings(doc, currentAssemblyLocation); + + var finalSettingsXml = doc.OuterXml; + + Assert.AreEqual(runSettingsXML, finalSettingsXml); + } + + [TestMethod] + public void FixRelativePathsInRunSettingsShouldModifyRelativeResultsDirectory() + { + var runSettingsXML = "..\\results"; + + var doc = new XmlDocument(); + doc.LoadXml(runSettingsXML); + + var currentAssemblyLocation = typeof(ClientUtilitiesTests).GetTypeInfo().Assembly.Location; + + ClientUtilities.FixRelativePathsInRunSettings(doc, currentAssemblyLocation); + + var finalSettingsXml = doc.OuterXml; + + var expectedPath = Path.GetFullPath(Path.Combine( + Path.GetDirectoryName(currentAssemblyLocation), "..\\results")); + var expectedSettingsXml = string.Concat( + "", + expectedPath, + ""); + + Assert.AreEqual(expectedSettingsXml, finalSettingsXml); + } + + [TestMethod] + public void FixRelativePathsInRunSettingsShouldNotModifyAbsoluteResultsDirectory() + { + var runSettingsXML = "C:\\temp\\results"; + + var doc = new XmlDocument(); + doc.LoadXml(runSettingsXML); + + var currentAssemblyLocation = typeof(ClientUtilitiesTests).GetTypeInfo().Assembly.Location; + + ClientUtilities.FixRelativePathsInRunSettings(doc, currentAssemblyLocation); + + var finalSettingsXml = doc.OuterXml; + + Assert.AreEqual(runSettingsXML, finalSettingsXml); + } + + [TestMethod] + public void FixRelativePathsInRunSettingsShouldNotModifyEmptyResultsDirectory() + { + var runSettingsXML = ""; + + var doc = new XmlDocument(); + doc.LoadXml(runSettingsXML); + + var currentAssemblyLocation = typeof(ClientUtilitiesTests).GetTypeInfo().Assembly.Location; + + ClientUtilities.FixRelativePathsInRunSettings(doc, currentAssemblyLocation); + + var finalSettingsXml = doc.OuterXml; + + Assert.AreEqual(runSettingsXML, finalSettingsXml); + } + } +} diff --git a/test/Microsoft.TestPlatform.Utilities.UnitTests/CodeCoverageDataAdapterUtilitiesTests.cs b/test/Microsoft.TestPlatform.Utilities.UnitTests/CodeCoverageDataAdapterUtilitiesTests.cs new file mode 100644 index 0000000000..b0d436e684 --- /dev/null +++ b/test/Microsoft.TestPlatform.Utilities.UnitTests/CodeCoverageDataAdapterUtilitiesTests.cs @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.Utilities.Tests +{ + using System.Xml; + + using Microsoft.VisualStudio.TestPlatform.Utilities; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class CodeCoverageDataAdapterUtilitiesTests + { + [TestMethod] + public void UpdateWithCodeCoverageSettingsIfNotConfiguredShouldNotUpdateIfStaticCCIsAlreadySpecified() + { + var runSettingsXml = @" + + + + + + + "; + + var xmlDocument = new XmlDocument(); + xmlDocument.LoadXml(runSettingsXml); + + CodeCoverageDataAdapterUtilities.UpdateWithCodeCoverageSettingsIfNotConfigured( + xmlDocument.ToXPathNavigable()); + + var expectedRunSettings = + ""; + Assert.AreEqual(expectedRunSettings, xmlDocument.OuterXml); + } + + [TestMethod] + public void UpdateWithCodeCoverageSettingsIfNotConfiguredShouldNotUpdateIfDynamicCCIsAlreadySpecified() + { + var runSettingsXml = @" + + + + + + + "; + + var xmlDocument = new XmlDocument(); + xmlDocument.LoadXml(runSettingsXml); + + CodeCoverageDataAdapterUtilities.UpdateWithCodeCoverageSettingsIfNotConfigured( + xmlDocument.ToXPathNavigable()); + + var expectedRunSettings = + ""; + Assert.AreEqual(expectedRunSettings, xmlDocument.OuterXml); + } + } +} diff --git a/test/Microsoft.TestPlatform.Utilities.UnitTests/ExceptionUtilities.cs b/test/Microsoft.TestPlatform.Utilities.UnitTests/ExceptionUtilities.cs new file mode 100644 index 0000000000..5ac3b18c04 --- /dev/null +++ b/test/Microsoft.TestPlatform.Utilities.UnitTests/ExceptionUtilities.cs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.Utilities.Tests +{ + using System; + + using Microsoft.VisualStudio.TestTools.UnitTesting; + + /// + /// This only exists because there is an issue with MSTest v2 and ThrowsException with a message API. + /// Move to Assert.ThrowException() with a message once the bug is fixed. + /// + public static class ExceptionUtilities + { + public static void ThrowsException(Action action , string format, params string[] args) + { + var isExceptionThrown = false; + + try + { + action(); + } + catch (Exception ex) + { + Assert.AreEqual(typeof(T), ex.GetType()); + isExceptionThrown = true; + var message = string.Format(format, args); + StringAssert.Contains(ex.Message, message); + } + + Assert.IsTrue(isExceptionThrown, "No Exception Thrown"); + } + } +} diff --git a/test/Microsoft.TestPlatform.Utilities.UnitTests/InferRunSettingsHelperTests.cs b/test/Microsoft.TestPlatform.Utilities.UnitTests/InferRunSettingsHelperTests.cs new file mode 100644 index 0000000000..8ff6a8c8a0 --- /dev/null +++ b/test/Microsoft.TestPlatform.Utilities.UnitTests/InferRunSettingsHelperTests.cs @@ -0,0 +1,209 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.Utilities.UnitTests +{ + using System; + using System.Xml; + using System.Xml.XPath; + + using Microsoft.TestPlatform.Utilities.Tests; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities; + using Microsoft.VisualStudio.TestPlatform.Utilities; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class InferRunSettingsHelperTests + { + [TestMethod] + public void UpdateRunSettingsShouldThrowIfRunSettingsNodeDoesNotExist() + { + var settings = @""; + var navigator = this.GetNavigator(settings); + + Action action = () => InferRunSettingsHelper.UpdateRunSettingsWithUserProvidedSwitches(navigator, Architecture.X86, FrameworkVersion.Framework45, "temp"); + + ExceptionUtilities.ThrowsException( + action, + "An error occurred while loading the settings. Error: {0}.", + "Could not find 'RunSettings' node."); + } + + [TestMethod] + public void UpdateRunSettingsShouldThrowIfPlatformNodeIsInvalid() + { + var settings = @"foo"; + var navigator = this.GetNavigator(settings); + + Action action = () => InferRunSettingsHelper.UpdateRunSettingsWithUserProvidedSwitches(navigator, Architecture.X86, FrameworkVersion.Framework45, "temp"); + + ExceptionUtilities.ThrowsException( + action, + "An error occurred while loading the settings. Error: {0}.", + string.Format("Invalid setting '{0}'. Invalid value '{1}' specified for '{2}'", "RunConfiguration", "foo", "TargetPlatform")); + } + + [TestMethod] + public void UpdateRunSettingsShouldThrowIfFrameworkNodeIsInvalid() + { + var settings = @"foo"; + var navigator = this.GetNavigator(settings); + + Action action = () => InferRunSettingsHelper.UpdateRunSettingsWithUserProvidedSwitches(navigator, Architecture.X86, FrameworkVersion.Framework45, "temp"); + + ExceptionUtilities.ThrowsException( + action, + "An error occurred while loading the settings. Error: {0}.", + string.Format("Invalid setting '{0}'. Invalid value '{1}' specified for '{2}'", "RunConfiguration", "foo", "TargetFrameworkVersion")); + } + + [TestMethod] + public void UpdateRunSettingsShouldUpdateWithPlatformSettings() + { + var settings = @""; + var navigator = this.GetNavigator(settings); + + InferRunSettingsHelper.UpdateRunSettingsWithUserProvidedSwitches(navigator, Architecture.X86, FrameworkVersion.Framework45, "temp"); + + var xml = navigator.OuterXml; + + StringAssert.Contains(xml, "X86"); + } + + [TestMethod] + public void UpdateRunSettingsShouldUpdateWithFrameworkSettings() + { + var settings = @""; + var navigator = this.GetNavigator(settings); + + InferRunSettingsHelper.UpdateRunSettingsWithUserProvidedSwitches(navigator, Architecture.X86, FrameworkVersion.Framework45, "temp"); + + var xml = navigator.OuterXml; + + StringAssert.Contains(xml, "Framework45"); + } + + [TestMethod] + public void UpdateRunSettingsShouldUpdateWithResultsDirectorySettings() + { + var settings = @""; + var navigator = this.GetNavigator(settings); + + InferRunSettingsHelper.UpdateRunSettingsWithUserProvidedSwitches(navigator, Architecture.X86, FrameworkVersion.Framework45, "temp"); + + var xml = navigator.OuterXml; + + StringAssert.Contains(xml, "temp"); + } + + [TestMethod] + public void UpdateRunSettingsShouldNotUpdatePlatformIfRunSettingsAlreadyHasIt() + { + var settings = @"X86"; + var navigator = this.GetNavigator(settings); + + InferRunSettingsHelper.UpdateRunSettingsWithUserProvidedSwitches(navigator, Architecture.X64, FrameworkVersion.Framework45, "temp"); + + var xml = navigator.OuterXml; + + StringAssert.Contains(xml, "X86"); + } + + [TestMethod] + public void UpdateRunSettingsShouldNotUpdateFrameworkIfRunSettingsAlreadyHasIt() + { + var settings = @"Framework40"; + var navigator = this.GetNavigator(settings); + + InferRunSettingsHelper.UpdateRunSettingsWithUserProvidedSwitches(navigator, Architecture.X64, FrameworkVersion.Framework45, "temp"); + + var xml = navigator.OuterXml; + + StringAssert.Contains(xml, "Framework40"); + } + + [TestMethod] + public void UpdateRunSettingsShouldNotUpdateResultsDirectoryIfRunSettingsAlreadyHasIt() + { + var settings = @"someplace"; + var navigator = this.GetNavigator(settings); + + InferRunSettingsHelper.UpdateRunSettingsWithUserProvidedSwitches(navigator, Architecture.X64, FrameworkVersion.Framework45, "temp"); + + var xml = navigator.OuterXml; + + StringAssert.Contains(xml, "someplace"); + } + + [TestMethod] + public void UpdateRunSettingsShouldNotUpdatePlatformOrFrameworkOrResultsDirectoryIfRunSettingsAlreadyHasIt() + { + var settings = @"X86Framework40someplace"; + var navigator = this.GetNavigator(settings); + + InferRunSettingsHelper.UpdateRunSettingsWithUserProvidedSwitches(navigator, Architecture.X64, FrameworkVersion.Framework45, "temp"); + + var xml = navigator.OuterXml; + + StringAssert.Contains(xml, "X86"); + StringAssert.Contains(xml, "Framework40"); + StringAssert.Contains(xml, "someplace"); + } + + [TestMethod] + public void UpdateRunSettingsWithAnEmptyRunSettingsShouldAddValuesSpecifiedInRunConfiguration() + { + var settings = @""; + var navigator = this.GetNavigator(settings); + + InferRunSettingsHelper.UpdateRunSettingsWithUserProvidedSwitches(navigator, Architecture.X64, FrameworkVersion.Framework45, "temp"); + + var xml = navigator.OuterXml; + + StringAssert.Contains(xml, "X64"); + StringAssert.Contains(xml, "Framework45"); + StringAssert.Contains(xml, "temp"); + } + + [TestMethod] + public void UpdateRunSettingsShouldReturnBackACompleteRunSettings() + { + var settings = @""; + var navigator = this.GetNavigator(settings); + + InferRunSettingsHelper.UpdateRunSettingsWithUserProvidedSwitches(navigator, Architecture.X64, FrameworkVersion.Framework45, "temp"); + + var xml = navigator.OuterXml; + var expectedRunSettings = "\r\n \r\n temp\r\n X64\r\n Framework45\r\n \r\n"; + + Assert.AreEqual(expectedRunSettings, xml); + } + + [TestMethod] + public void UpdateRunSettingsShouldThrowIfArchitectureSetIsIncompatibleWithCurrentSystemArchitecture() + { + var settings = @""; + var navigator = this.GetNavigator(settings); + + Action action = () => InferRunSettingsHelper.UpdateRunSettingsWithUserProvidedSwitches(navigator, Architecture.ARM, FrameworkVersion.Framework45, "temp"); + + ExceptionUtilities.ThrowsException( + action, + "Incompatible Target platform settings '{0}' with system architecture '{1}'.", + "ARM", + XmlRunSettingsUtilities.OSArchitecture.ToString()); + } + + #region private methods + + private XPathNavigator GetNavigator(string settingsXml) + { + var doc = new XmlDocument(); + doc.LoadXml(settingsXml); + + return doc.CreateNavigator(); + } + + #endregion + } +} diff --git a/test/Microsoft.TestPlatform.Utilities.UnitTests/MSTestSettingsUtilitiesTests.cs b/test/Microsoft.TestPlatform.Utilities.UnitTests/MSTestSettingsUtilitiesTests.cs new file mode 100644 index 0000000000..48d33e243b --- /dev/null +++ b/test/Microsoft.TestPlatform.Utilities.UnitTests/MSTestSettingsUtilitiesTests.cs @@ -0,0 +1,117 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.Utilities.Tests +{ + using System; + using System.IO; + using System.Xml; + using System.Xml.XPath; + + using Microsoft.VisualStudio.TestPlatform.Utilities; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using VisualStudio.TestPlatform.ObjectModel; + + [TestClass] + public class MSTestSettingsUtilitiesTests + { + #region IsLegacyTestSettingsFile tests + + [TestMethod] + public void IsLegacyTestSettingsFileShouldReturnTrueIfTestSettingsExtension() + { + Assert.IsTrue(MSTestSettingsUtilities.IsLegacyTestSettingsFile("C:\\temp\\t.testsettings")); + } + + [TestMethod] + public void IsLegacyTestSettingsFileShouldReturnTrueIfTestRunConfigExtension() + { + Assert.IsTrue(MSTestSettingsUtilities.IsLegacyTestSettingsFile("C:\\temp\\t.testrunConfig")); + } + + [TestMethod] + public void IsLegacyTestSettingsFileShouldReturnTrueIfVSMDIExtension() + { + Assert.IsTrue(MSTestSettingsUtilities.IsLegacyTestSettingsFile("C:\\temp\\t.vsmdi")); + } + + #endregion + + #region Import tests + + [TestMethod] + public void ImportShouldThrowIfNotLegacySettingsFile() + { + var defaultRunSettingsXml = ""; + var xmlDocument = new XmlDocument(); + xmlDocument.LoadXml(defaultRunSettingsXml); + + Action action = + () => + MSTestSettingsUtilities.Import( + "C:\\temp\\r.runsettings", + xmlDocument.ToXPathNavigable(), + Architecture.X86, + FrameworkVersion.Framework45); + ExceptionUtilities.ThrowsException(action, "Unexpected settings file specified."); + } + + [TestMethod] + public void ImportShouldThrowIfDefaultRunSettingsIsIncorrect() + { + var defaultRunSettingsXml = ""; + var xmlDocument = new XmlDocument(); + xmlDocument.LoadXml(defaultRunSettingsXml); + + Action action = + () => + MSTestSettingsUtilities.Import( + "C:\\temp\\r.testsettings", + xmlDocument.ToXPathNavigable(), + Architecture.X86, + FrameworkVersion.Framework45); + ExceptionUtilities.ThrowsException(action, "Could not find 'RunSettings' node."); + } + + [TestMethod] + public void ImportShouldEmbedTestSettingsInformation() + { + var defaultRunSettingsXml = ""; + var xmlDocument = new XmlDocument(); + xmlDocument.LoadXml(defaultRunSettingsXml); + var finalxPath = MSTestSettingsUtilities.Import( + "C:\\temp\\r.testsettings", + xmlDocument.ToXPathNavigable(), + Architecture.X86, + FrameworkVersion.Framework45); + + var finalSettingsXml = finalxPath.CreateNavigator().OuterXml; + + var expectedSettingsXml = + "\r\n \r\n C:\\temp\\r.testsettings\r\n true\r\n \r\n \r\n"; + + Assert.AreEqual(expectedSettingsXml, finalSettingsXml); + } + + [TestMethod] + public void ImportShouldEmbedTestSettingsAndDefaultRunConfigurationInformation() + { + var defaultRunSettingsXml = ""; + var xmlDocument = new XmlDocument(); + xmlDocument.LoadXml(defaultRunSettingsXml); + var finalxPath = MSTestSettingsUtilities.Import( + "C:\\temp\\r.testsettings", + xmlDocument.ToXPathNavigable(), + Architecture.X86, + FrameworkVersion.Framework45); + + var finalSettingsXml = finalxPath.CreateNavigator().OuterXml; + + var expectedSettingsXml = + "\r\n \r\n X86\r\n Framework45\r\n \r\n \r\n C:\\temp\\r.testsettings\r\n true\r\n \r\n"; + + Assert.AreEqual(expectedSettingsXml, finalSettingsXml); + } + + #endregion + } +} diff --git a/test/Microsoft.TestPlatform.Utilities.UnitTests/Microsoft.TestPlatform.Utilities.UnitTests.xproj b/test/Microsoft.TestPlatform.Utilities.UnitTests/Microsoft.TestPlatform.Utilities.UnitTests.xproj new file mode 100644 index 0000000000..21cac64fec --- /dev/null +++ b/test/Microsoft.TestPlatform.Utilities.UnitTests/Microsoft.TestPlatform.Utilities.UnitTests.xproj @@ -0,0 +1,22 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 78a40fa0-835f-453e-8883-95eaac5f348d + Microsoft.TestPlatform.Utilities.UnitTests + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\ + v4.5.2 + + + 2.0 + + + + + + \ No newline at end of file diff --git a/test/Microsoft.TestPlatform.Utilities.UnitTests/Properties/AssemblyInfo.cs b/test/Microsoft.TestPlatform.Utilities.UnitTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..380aad5a61 --- /dev/null +++ b/test/Microsoft.TestPlatform.Utilities.UnitTests/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +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: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Microsoft.TestPlatform.Utilities.Tests")] +[assembly: AssemblyTrademark("")] + +// 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("78a40fa0-835f-453e-8883-95eaac5f348d")] diff --git a/test/Microsoft.TestPlatform.Utilities.UnitTests/XmlUtilitiesTests.cs b/test/Microsoft.TestPlatform.Utilities.UnitTests/XmlUtilitiesTests.cs new file mode 100644 index 0000000000..e30d9894b7 --- /dev/null +++ b/test/Microsoft.TestPlatform.Utilities.UnitTests/XmlUtilitiesTests.cs @@ -0,0 +1,176 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.Utilities.UnitTests +{ + using System; + using System.Xml; + using System.Xml.XPath; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.Utilities; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class XmlUtilitiesTests + { + #region GetNodeXml tests + + [TestMethod] + public void GetNodeXmlShouldThrowIfNavigatorIsNull() + { + Assert.ThrowsException(() => XmlUtilities.GetNodeXml(null, @"/RunSettings/RunConfiguration")); + } + + [TestMethod] + public void GetNodeXmlShouldThrowIfXPathIsNull() + { + var settingsXml = @""; + var navigator = this.GetNavigator(settingsXml); + + Assert.ThrowsException(() => XmlUtilities.GetNodeXml(navigator, null)); + } + + [TestMethod] + public void GetNodeXmlShouldThrowIfXPathIsInvalid() + { + var settingsXml = @""; + var navigator = this.GetNavigator(settingsXml); + + Assert.ThrowsException(() => XmlUtilities.GetNodeXml(navigator, @"Rs\r")); + } + + [TestMethod] + public void GetNodeXmlShouldReturnNullIfNodeDoesNotExist() + { + var settingsXml = @""; + var navigator = this.GetNavigator(settingsXml); + + Assert.IsNull(XmlUtilities.GetNodeXml(navigator, @"/RunSettings/RunConfiguration")); + } + + [TestMethod] + public void GetNodeXmlShouldReturnNodeValue() + { + var settingsXml = @"abc"; + var navigator = this.GetNavigator(settingsXml); + + Assert.AreEqual("abc", XmlUtilities.GetNodeXml(navigator, @"/RunSettings/RC")); + } + + #endregion + + #region IsValidNodeXmlValue tests + + [TestMethod] + public void IsValidNodeXmlValueShouldReturnFalseOnArgumentException() + { + Func validator = (string xml) => + { + Enum.Parse(typeof(Architecture), xml); + return true; + }; + + Assert.IsFalse(XmlUtilities.IsValidNodeXmlValue("foo", validator)); + } + + [TestMethod] + public void IsValidNodeXmlValueShouldReturnFalseIfValidatorReturnsFalse() + { + Func validator = (string xml) => + { + return false; + }; + + Assert.IsFalse(XmlUtilities.IsValidNodeXmlValue("foo", validator)); + } + + [TestMethod] + public void IsValidNodeXmlValueShouldReturnTrueIfValidatorReturnsTrue() + { + Func validator = (string xml) => + { + return true; + }; + + Assert.IsTrue(XmlUtilities.IsValidNodeXmlValue("foo", validator)); + } + + #endregion + + #region AppendOrModifyChild tests + + [TestMethod] + public void AppendOrModifyChildShouldModifyExistingNode() + { + var settingsXml = @"abc"; + var navigator = this.GetNavigator(settingsXml); + + navigator.MoveToChild("RunSettings", string.Empty); + + XmlUtilities.AppendOrModifyChild(navigator, @"/RunSettings/RC", "RC", "ab"); + + var rcNavigator = navigator.SelectSingleNode(@"/RunSettings/RC"); + Assert.IsNotNull(rcNavigator); + Assert.AreEqual("ab", rcNavigator.InnerXml); + } + + [TestMethod] + public void AppendOrModifyChildShouldAppendANewNode() + { + var settingsXml = @""; + var navigator = this.GetNavigator(settingsXml); + + navigator.MoveToChild("RunSettings", string.Empty); + + XmlUtilities.AppendOrModifyChild(navigator, @"/RunSettings/RC", "RC", "abc"); + + var rcNavigator = navigator.SelectSingleNode(@"/RunSettings/RC"); + Assert.IsNotNull(rcNavigator); + Assert.AreEqual("abc", rcNavigator.InnerXml); + } + + [TestMethod] + public void AppendOrModifyChildShouldNotModifyExistingXmlIfInnerXmlPassedInIsNull() + { + var settingsXml = @"abc"; + var navigator = this.GetNavigator(settingsXml); + + navigator.MoveToChild("RunSettings", string.Empty); + + XmlUtilities.AppendOrModifyChild(navigator, @"/RunSettings/RC", "RC", null); + + var rcNavigator = navigator.SelectSingleNode(@"/RunSettings/RC"); + Assert.IsNotNull(rcNavigator); + Assert.AreEqual("abc", rcNavigator.InnerXml); + } + + [TestMethod] + public void AppendOrModifyChildShouldCreateAnEmptyNewNodeIfInnerXmlPassedInIsNull() + { + var settingsXml = @""; + var navigator = this.GetNavigator(settingsXml); + + navigator.MoveToChild("RunSettings", string.Empty); + + XmlUtilities.AppendOrModifyChild(navigator, @"/RunSettings/RC", "RC", null); + + var rcNavigator = navigator.SelectSingleNode(@"/RunSettings/RC"); + Assert.IsNotNull(rcNavigator); + Assert.AreEqual(string.Empty, rcNavigator.InnerXml); + } + + #endregion + + #region private methods + + private XPathNavigator GetNavigator(string settingsXml) + { + var doc = new XmlDocument(); + doc.LoadXml(settingsXml); + + return doc.CreateNavigator(); + } + + #endregion + } +} diff --git a/test/Microsoft.TestPlatform.Utilities.UnitTests/project.json b/test/Microsoft.TestPlatform.Utilities.UnitTests/project.json new file mode 100644 index 0000000000..8dfbcbfa47 --- /dev/null +++ b/test/Microsoft.TestPlatform.Utilities.UnitTests/project.json @@ -0,0 +1,33 @@ +{ + "version": "15.0.0-*", + + "buildOptions": { + "delaySign": true, + "keyFile": "../../scripts/key.snk", + "warningsAsErrors": true + }, + + "dependencies": { + "Microsoft.NETCore.App": { + "type": "platform", + "version": "1.0.0-rc2-3002702" + }, + "dotnet-test-mstest": { + "version": "1.0.1-preview", + "exclude": "compile" + }, + "MSTest.TestFramework": "1.0.0-preview", + "Microsoft.TestPlatform.Utilities": "15.0.0-*" + }, + + "frameworks": { + "netcoreapp1.0": { + "imports": [ + "dnxcore50", + "portable-net45+win8" + ] + } + }, + + "testRunner": "mstest" +} \ No newline at end of file diff --git a/test/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.UnitTests/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.UnitTests.xproj b/test/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.UnitTests/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.UnitTests.xproj new file mode 100644 index 0000000000..daa5d271d5 --- /dev/null +++ b/test/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.UnitTests/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.UnitTests.xproj @@ -0,0 +1,22 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 7da16d44-71ea-436f-a201-573861dc21ea + Microsoft.TestPlatform.VsTestConsole.TranslationLayer.UnitTests + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\ + v4.5.2 + + + 2.0 + + + + + + \ No newline at end of file diff --git a/test/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.UnitTests/Properties/AssemblyInfo.cs b/test/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.UnitTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..f026a1cb21 --- /dev/null +++ b/test/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.UnitTests/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +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: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Microsoft.TestPlatform.VsTestConsole.TranslationLayer.UnitTests")] +[assembly: AssemblyTrademark("")] + +// 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("7da16d44-71ea-436f-a201-573861dc21ea")] diff --git a/test/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.UnitTests/VsTestConsoleRequestSenderTests.cs b/test/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.UnitTests/VsTestConsoleRequestSenderTests.cs new file mode 100644 index 0000000000..66c59798f4 --- /dev/null +++ b/test/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.UnitTests/VsTestConsoleRequestSenderTests.cs @@ -0,0 +1,1004 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.VsTestConsole.TranslationLayer.UnitTests +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Linq.Expressions; + using System.Threading; + using System.Threading.Tasks; + + using Microsoft.TestPlatform.VsTestConsole.TranslationLayer.Interfaces; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + using Moq; + + using Newtonsoft.Json.Linq; + using Newtonsoft.Json; + using VisualStudio.TestPlatform.ObjectModel.Client.Interfaces; + + [TestClass] + public class VsTestConsoleRequestSenderTests + { + private ITranslationLayerRequestSender requestSender; + + private Mock mockCommunicationManager; + + private int WaitTimeout = 2000; + + [TestInitialize] + public void TestInit() + { + this.mockCommunicationManager = new Mock(); + this.requestSender = new VsTestConsoleRequestSender(mockCommunicationManager.Object, JsonDataSerializer.Instance); + } + + #region Communication Tests + + [TestMethod] + public void InitializeCommunicationShouldSucceed() + { + this.InitializeCommunication(); + + this.mockCommunicationManager.Verify(cm => cm.HostServer(), Times.Once); + this.mockCommunicationManager.Verify(cm => cm.AcceptClientAsync(), Times.Once); + + this.mockCommunicationManager.Verify(cm => cm.WaitForClientConnection(Timeout.Infinite), Times.Once); + + this.mockCommunicationManager.Verify(cm => cm.ReceiveMessage(), Times.Exactly(2)); + this.mockCommunicationManager.Verify(cm => cm.SendMessage(MessageType.VersionCheck), Times.Once); + } + + [TestMethod] + public void InitializeCommunicationShouldReturnInvalidPortNumberIfHostServerFails() + { + this.mockCommunicationManager.Setup(cm => cm.HostServer()).Throws(new Exception("Fail")); + + var portOutput = this.requestSender.InitializeCommunication(); + Assert.IsTrue(portOutput < 0, "Negative port number must be returned if Hosting Server fails."); + + var connectionSuccess = this.requestSender.WaitForRequestHandlerConnection(WaitTimeout); + Assert.IsFalse(connectionSuccess, "Connection must fail as server fai;ed to host."); + + this.mockCommunicationManager.Verify(cm => cm.HostServer(), Times.Once); + this.mockCommunicationManager.Verify(cm => cm.AcceptClientAsync(), Times.Never); + + this.mockCommunicationManager.Verify(cm => cm.WaitForClientConnection(Timeout.Infinite), Times.Never); + + this.mockCommunicationManager.Verify(cm => cm.ReceiveMessage(), Times.Never); + } + + [TestMethod] + public void InitializeCommunicationShouldFailConnectionIfMessageReceiveFailed() + { + var dummyPortInput = 123; + this.mockCommunicationManager.Setup(cm => cm.HostServer()).Returns(dummyPortInput); + this.mockCommunicationManager.Setup(cm => cm.AcceptClientAsync()).Callback(() => { }); + + this.mockCommunicationManager.Setup(cm => cm.WaitForClientConnection(Timeout.Infinite)) + .Callback((int timeout) => Task.Delay(200).Wait()); + + this.mockCommunicationManager.Setup(cm => cm.ReceiveMessage()).Throws(new Exception("Fail")); + + var portOutput = this.requestSender.InitializeCommunication(); + // Hosting server didn't server, so port number should still be valid + Assert.AreEqual(dummyPortInput, portOutput, "Port number must return without changes."); + + // Connection must not succeed as handshake failed + var connectionSuccess = this.requestSender.WaitForRequestHandlerConnection(WaitTimeout); + Assert.IsFalse(connectionSuccess, "Connection must fail if handshake failed."); + + this.mockCommunicationManager.Verify(cm => cm.HostServer(), Times.Once); + this.mockCommunicationManager.Verify(cm => cm.AcceptClientAsync(), Times.Once); + + this.mockCommunicationManager.Verify(cm => cm.WaitForClientConnection(Timeout.Infinite), Times.Once); + } + + [TestMethod] + public void InitializeCommunicationShouldFailConnectionIfSessionConnectedDidNotComeFirst() + { + var dummyPortInput = 123; + this.mockCommunicationManager.Setup(cm => cm.HostServer()).Returns(dummyPortInput); + this.mockCommunicationManager.Setup(cm => cm.AcceptClientAsync()).Callback(() => { }); + + this.mockCommunicationManager.Setup(cm => cm.WaitForClientConnection(Timeout.Infinite)) + .Callback((int timeout) => Task.Delay(200).Wait()); + + var discoveryMessage = new Message() { MessageType = MessageType.StartDiscovery }; + + this.mockCommunicationManager.Setup(cm => cm.ReceiveMessage()).Returns(discoveryMessage); + + var portOutput = this.requestSender.InitializeCommunication(); + Assert.AreEqual(dummyPortInput, portOutput, "Port number must return without changes."); + var connectionSuccess = this.requestSender.WaitForRequestHandlerConnection(WaitTimeout); + Assert.IsFalse(connectionSuccess, "Connection must fail if version check failed."); + + this.mockCommunicationManager.Verify(cm => cm.HostServer(), Times.Once); + this.mockCommunicationManager.Verify(cm => cm.AcceptClientAsync(), Times.Once); + + this.mockCommunicationManager.Verify(cm => cm.WaitForClientConnection(Timeout.Infinite), Times.Once); + + this.mockCommunicationManager.Verify(cm => cm.ReceiveMessage(), Times.Once); + this.mockCommunicationManager.Verify(cm => cm.SendMessage(It.IsAny()), Times.Never); + } + + [TestMethod] + public void InitializeCommunicationShouldFailConnectionIfSendMessageFailed() + { + var dummyPortInput = 123; + this.mockCommunicationManager.Setup(cm => cm.HostServer()).Returns(dummyPortInput); + this.mockCommunicationManager.Setup(cm => cm.AcceptClientAsync()).Callback(() => { }); + + this.mockCommunicationManager.Setup(cm => cm.WaitForClientConnection(Timeout.Infinite)) + .Callback((int timeout) => Task.Delay(200).Wait()); + + var sessionConnected = new Message() { MessageType = MessageType.SessionConnected }; + + this.mockCommunicationManager.Setup(cm => cm.ReceiveMessage()).Returns(sessionConnected); + this.mockCommunicationManager.Setup(cm => cm.SendMessage(MessageType.VersionCheck)).Throws(new Exception("Fail")); + + var portOutput = this.requestSender.InitializeCommunication(); + Assert.AreEqual(dummyPortInput, portOutput, "Port number must return without changes."); + var connectionSuccess = this.requestSender.WaitForRequestHandlerConnection(WaitTimeout); + Assert.IsFalse(connectionSuccess, "Connection must fail if version check failed."); + + this.mockCommunicationManager.Verify(cm => cm.HostServer(), Times.Once); + this.mockCommunicationManager.Verify(cm => cm.AcceptClientAsync(), Times.Once); + + this.mockCommunicationManager.Verify(cm => cm.WaitForClientConnection(Timeout.Infinite), Times.Once); + + this.mockCommunicationManager.Verify(cm => cm.ReceiveMessage(), Times.Once); + this.mockCommunicationManager.Verify(cm => cm.SendMessage(It.IsAny()), Times.Once); + } + + [TestMethod] + public void InitializeCommunicationShouldFailConnectionIfVersionIsWrong() + { + var dummyPortInput = 123; + this.mockCommunicationManager.Setup(cm => cm.HostServer()).Returns(dummyPortInput); + this.mockCommunicationManager.Setup(cm => cm.AcceptClientAsync()).Callback(() => { }); + + this.mockCommunicationManager.Setup(cm => cm.WaitForClientConnection(Timeout.Infinite)) + .Callback((int timeout) => Task.Delay(200).Wait()); + + var sessionConnected = new Message() { MessageType = MessageType.SessionConnected }; + // Give wrong version + var versionCheck = new Message() { MessageType = MessageType.VersionCheck, Payload = JToken.FromObject("2") }; + + Action changedMessage = () => + { + this.mockCommunicationManager.Setup(cm => cm.ReceiveMessage()).Returns(versionCheck); + }; + + this.mockCommunicationManager.Setup(cm => cm.ReceiveMessage()).Returns(sessionConnected); + this.mockCommunicationManager.Setup(cm => cm.SendMessage(MessageType.VersionCheck)).Callback(changedMessage); + + var portOutput = this.requestSender.InitializeCommunication(); + Assert.AreEqual(dummyPortInput, portOutput, "Port number must return without changes."); + var connectionSuccess = this.requestSender.WaitForRequestHandlerConnection(WaitTimeout); + Assert.IsFalse(connectionSuccess, "Connection must fail if version check failed."); + + this.mockCommunicationManager.Verify(cm => cm.HostServer(), Times.Once); + this.mockCommunicationManager.Verify(cm => cm.AcceptClientAsync(), Times.Once); + + this.mockCommunicationManager.Verify(cm => cm.WaitForClientConnection(Timeout.Infinite), Times.Once); + + this.mockCommunicationManager.Verify(cm => cm.ReceiveMessage(), Times.Exactly(2)); + this.mockCommunicationManager.Verify(cm => cm.SendMessage(MessageType.VersionCheck), Times.Once); + } + + #endregion + + #region Discovery Tests + + [TestMethod] + public void DiscoverTestsShouldCompleteWithZeroTests() + { + this.InitializeCommunication(); + + var mockHandler = new Mock(); + + var payload = new DiscoveryCompletePayload() { TotalTests = 0, LastDiscoveredTests = null, IsAborted = false }; + var discoveryComplete = new Message() { MessageType = MessageType.DiscoveryComplete, + Payload = JToken.FromObject(payload) }; + this.mockCommunicationManager.Setup(cm => cm.ReceiveMessage()).Returns(discoveryComplete); + + this.requestSender.DiscoverTests(new List() { "1.dll" }, null, mockHandler.Object); + + mockHandler.Verify(mh => mh.HandleDiscoveryComplete(0, null, false), Times.Once, "Discovery Complete must be called"); + mockHandler.Verify(mh => mh.HandleDiscoveredTests(It.IsAny>()), Times.Never, "DiscoveredTests must not be called"); + mockHandler.Verify(mh => mh.HandleLogMessage(It.IsAny(), It.IsAny()), Times.Never, "TestMessage event must not be called"); + } + + [TestMethod] + public void DiscoverTestsShouldCompleteWithSingleTest() + { + this.InitializeCommunication(); + + var mockHandler = new Mock(); + + var testCase = new TestCase("hello", new Uri("world://how"), "1.dll"); + var testCaseList = new List() { testCase }; + var testsFound = new Message() + { + MessageType = MessageType.TestCasesFound, + Payload = JToken.FromObject(testCaseList) + }; + + var payload = new DiscoveryCompletePayload() { TotalTests = 1, LastDiscoveredTests = null, IsAborted = false }; + var discoveryComplete = new Message() + { + MessageType = MessageType.DiscoveryComplete, + Payload = JToken.FromObject(payload) + }; + + this.mockCommunicationManager.Setup(cm => cm.ReceiveMessage()).Returns(testsFound); + mockHandler.Setup(mh => mh.HandleDiscoveredTests(It.IsAny>())).Callback( + () => this.mockCommunicationManager.Setup(cm => cm.ReceiveMessage()).Returns(discoveryComplete)); + + this.requestSender.DiscoverTests(new List() { "1.dll" }, null, mockHandler.Object); + + mockHandler.Verify(mh => mh.HandleDiscoveryComplete(1, null, false), Times.Once, "Discovery Complete must be called"); + mockHandler.Verify(mh => mh.HandleDiscoveredTests(It.IsAny>()), Times.Once, "DiscoveredTests must be called"); + mockHandler.Verify(mh => mh.HandleLogMessage(It.IsAny(), It.IsAny()), Times.Never, "TestMessage event must not be called"); + } + + [TestMethod] + public void DiscoverTestsShouldReportBackTestsWithTraitsInTestsFoundMessage() + { + this.InitializeCommunication(); + + var mockHandler = new Mock(); + + var testCase = new TestCase("hello", new Uri("world://how"), "1.dll"); + testCase.Traits.Add(new Trait("a", "b")); + + List receivedTestCases = null; + var testCaseList = new List() { testCase }; + var testsFound = new Message() + { + MessageType = MessageType.TestCasesFound, + Payload = JToken.FromObject(testCaseList, JsonSerializer.Create( + new JsonSerializerSettings + { + TypeNameHandling = + TypeNameHandling.Auto + })) + }; + + var payload = new DiscoveryCompletePayload() { TotalTests = 1, LastDiscoveredTests = null, IsAborted = false }; + var discoveryComplete = new Message() + { + MessageType = MessageType.DiscoveryComplete, + Payload = JToken.FromObject(payload, JsonSerializer.Create( + new JsonSerializerSettings + { + TypeNameHandling = + TypeNameHandling.Auto + })) + }; + + this.mockCommunicationManager.Setup(cm => cm.ReceiveMessage()).Returns(testsFound); + mockHandler.Setup(mh => mh.HandleDiscoveredTests(It.IsAny>())) + .Callback( + (IEnumerable tests) => + { + receivedTestCases = tests?.ToList(); + this.mockCommunicationManager.Setup(cm => cm.ReceiveMessage()).Returns(discoveryComplete); + }); + + this.requestSender.DiscoverTests(new List() { "1.dll" }, null, mockHandler.Object); + + Assert.IsNotNull(receivedTestCases); + Assert.AreEqual(1, receivedTestCases.Count); + + // Verify that the traits are passed through properly. + var traits = receivedTestCases.ToArray()[0].Traits; + Assert.IsNotNull(traits); + Assert.AreEqual(traits.ToArray()[0].Name, "a"); + Assert.AreEqual(traits.ToArray()[0].Value, "b"); + } + + [TestMethod] + public void DiscoverTestsShouldReportBackTestsWithTraitsInDiscoveryCompleteMessage() + { + this.InitializeCommunication(); + + var mockHandler = new Mock(); + + var testCase = new TestCase("hello", new Uri("world://how"), "1.dll"); + testCase.Traits.Add(new Trait("a", "b")); + + List receivedTestCases = null; + var testCaseList = new List() { testCase }; + + var payload = new DiscoveryCompletePayload() { TotalTests = 1, LastDiscoveredTests = testCaseList, IsAborted = false }; + var discoveryComplete = new Message() + { + MessageType = MessageType.DiscoveryComplete, + Payload = JToken.FromObject(payload, JsonSerializer.Create( + new JsonSerializerSettings + { + TypeNameHandling = + TypeNameHandling.Auto + })) + }; + + this.mockCommunicationManager.Setup(cm => cm.ReceiveMessage()).Returns(discoveryComplete); + mockHandler.Setup(mh => mh.HandleDiscoveryComplete(It.IsAny(), It.IsAny>(), It.IsAny())) + .Callback( + (long totalTests, IEnumerable tests, bool isAborted) => + { + receivedTestCases = tests?.ToList(); + }); + + this.requestSender.DiscoverTests(new List() { "1.dll" }, null, mockHandler.Object); + + Assert.IsNotNull(receivedTestCases); + Assert.AreEqual(1, receivedTestCases.Count); + + // Verify that the traits are passed through properly. + var traits = receivedTestCases.ToArray()[0].Traits; + Assert.IsNotNull(traits); + Assert.AreEqual(traits.ToArray()[0].Name, "a"); + Assert.AreEqual(traits.ToArray()[0].Value, "b"); + } + + [TestMethod] + public void DiscoverTestsShouldCompleteWithTestMessage() + { + this.InitializeCommunication(); + + var mockHandler = new Mock(); + + var testCase = new TestCase("hello", new Uri("world://how"), "1.dll"); + var testCaseList = new List() { testCase }; + var testsFound = new Message() + { + MessageType = MessageType.TestCasesFound, + Payload = JToken.FromObject(testCaseList) + }; + + var payload = new DiscoveryCompletePayload() { TotalTests = 1, LastDiscoveredTests = null, IsAborted = false }; + var discoveryComplete = new Message() + { + MessageType = MessageType.DiscoveryComplete, + Payload = JToken.FromObject(payload) + }; + + var mpayload = new TestMessagePayload() { MessageLevel = TestMessageLevel.Informational, Message = "Hello" }; + var message = new Message() + { + MessageType = MessageType.TestMessage, + Payload = JToken.FromObject(mpayload) + }; + + this.mockCommunicationManager.Setup(cm => cm.ReceiveMessage()).Returns(testsFound); + mockHandler.Setup(mh => mh.HandleDiscoveredTests(It.IsAny>())).Callback( + () => this.mockCommunicationManager.Setup(cm => cm.ReceiveMessage()).Returns(message)); + mockHandler.Setup(mh => mh.HandleLogMessage(It.IsAny(), It.IsAny())).Callback( + () => this.mockCommunicationManager.Setup(cm => cm.ReceiveMessage()).Returns(discoveryComplete)); + + this.requestSender.DiscoverTests(new List() { "1.dll" }, null, mockHandler.Object); + + mockHandler.Verify(mh => mh.HandleDiscoveryComplete(1, null, false), Times.Once, "Discovery Complete must be called"); + mockHandler.Verify(mh => mh.HandleDiscoveredTests(It.IsAny>()), Times.Once, "DiscoveredTests must be called"); + mockHandler.Verify(mh => mh.HandleLogMessage(TestMessageLevel.Informational, "Hello"), Times.Once, "TestMessage event must be called"); + } + + #endregion + + #region RunTests + + [TestMethod] + public void StartTestRunShouldCompleteWithZeroTests() + { + this.InitializeCommunication(); + + var mockHandler = new Mock(); + + var dummyCompleteArgs = new TestRunCompleteEventArgs(null, false, false, null, null, TimeSpan.FromMilliseconds(1)); + var dummyLastRunArgs = new TestRunChangedEventArgs(null, null, null); + + var payload = new TestRunCompletePayload() + { + ExecutorUris = null, + LastRunTests = dummyLastRunArgs, + RunAttachments = null, + TestRunCompleteArgs = dummyCompleteArgs + }; + + var runComplete = new Message() + { + MessageType = MessageType.ExecutionComplete, + Payload = JToken.FromObject(payload) + }; + this.mockCommunicationManager.Setup(cm => cm.ReceiveMessage()).Returns(runComplete); + + this.requestSender.StartTestRun(new List() { "1.dll" }, null, mockHandler.Object); + + mockHandler.Verify(mh => mh.HandleTestRunComplete(It.IsAny(), + It.IsAny(), null, null), Times.Once, "Run Complete must be called"); + mockHandler.Verify(mh => mh.HandleTestRunStatsChange(It.IsAny()), Times.Never, "RunChangedArgs must not be called"); + mockHandler.Verify(mh => mh.HandleLogMessage(It.IsAny(), It.IsAny()), Times.Never, "TestMessage event must not be called"); + } + + [TestMethod] + public void StartTestRunShouldCompleteWithSingleTestAndMessage() + { + this.InitializeCommunication(); + + var mockHandler = new Mock(); + + var testCase = new TestCase("hello", new Uri("world://how"), "1.dll"); + var testResult = new VisualStudio.TestPlatform.ObjectModel.TestResult(testCase); + testResult.Outcome = TestOutcome.Passed; + + var dummyCompleteArgs = new TestRunCompleteEventArgs(null, false, false, null, null, TimeSpan.FromMilliseconds(1)); + var dummyLastRunArgs = new TestRunChangedEventArgs(null, null, null); + + var testsChangedArgs = new TestRunChangedEventArgs(null, + new List() { testResult }, null); + + var testsPayload = new Message() + { + MessageType = MessageType.TestRunStatsChange, + Payload = JToken.FromObject(testsChangedArgs) + }; + + var payload = new TestRunCompletePayload() + { + ExecutorUris = null, + LastRunTests = dummyLastRunArgs, + RunAttachments = null, + TestRunCompleteArgs = dummyCompleteArgs + }; + + var runComplete = new Message() + { + MessageType = MessageType.ExecutionComplete, + Payload = JToken.FromObject(payload) + }; + + var mpayload = new TestMessagePayload() { MessageLevel = TestMessageLevel.Informational, Message = "Hello" }; + var message = new Message() + { + MessageType = MessageType.TestMessage, + Payload = JToken.FromObject(mpayload) + }; + + + this.mockCommunicationManager.Setup(cm => cm.ReceiveMessage()).Returns(testsPayload); + + mockHandler.Setup(mh => mh.HandleTestRunStatsChange(It.IsAny())).Callback( + (testRunChangedArgs) => + { + Assert.IsTrue(testRunChangedArgs.NewTestResults != null && testsChangedArgs.NewTestResults.Count() > 0, "TestResults must be passed properly"); + this.mockCommunicationManager.Setup(cm => cm.ReceiveMessage()).Returns(message); + }); + + mockHandler.Setup(mh => mh.HandleLogMessage(It.IsAny(), It.IsAny())).Callback( + () => + { + this.mockCommunicationManager.Setup(cm => cm.ReceiveMessage()).Returns(runComplete); + }); + + this.requestSender.StartTestRun(new List() { "1.dll" }, null, mockHandler.Object); + + mockHandler.Verify(mh => mh.HandleTestRunComplete(It.IsAny(), + It.IsAny(), null, null), Times.Once, "Run Complete must be called"); + mockHandler.Verify(mh => mh.HandleTestRunStatsChange(It.IsAny()), Times.Once, "RunChangedArgs must be called"); + mockHandler.Verify(mh => mh.HandleLogMessage(It.IsAny(), It.IsAny()), Times.Once, "TestMessage event must be called"); + } + + [TestMethod] + public void StartTestRunWithCustomHostShouldComplete() + { + this.InitializeCommunication(); + + var mockHandler = new Mock(); + + var testCase = new TestCase("hello", new Uri("world://how"), "1.dll"); + var testResult = new VisualStudio.TestPlatform.ObjectModel.TestResult(testCase); + testResult.Outcome = TestOutcome.Passed; + + var dummyCompleteArgs = new TestRunCompleteEventArgs(null, false, false, null, null, TimeSpan.FromMilliseconds(1)); + var dummyLastRunArgs = new TestRunChangedEventArgs(null, null, null); + + var testsChangedArgs = new TestRunChangedEventArgs(null, + new List() { testResult }, null); + + var testsPayload = new Message() + { + MessageType = MessageType.TestRunStatsChange, + Payload = JToken.FromObject(testsChangedArgs) + }; + + var payload = new TestRunCompletePayload() + { + ExecutorUris = null, + LastRunTests = dummyLastRunArgs, + RunAttachments = null, + TestRunCompleteArgs = dummyCompleteArgs + }; + + var runComplete = new Message() + { + MessageType = MessageType.ExecutionComplete, + Payload = JToken.FromObject(payload) + }; + + var mpayload = new TestMessagePayload() { MessageLevel = TestMessageLevel.Informational, Message = "Hello" }; + var message = new Message() + { + MessageType = MessageType.TestMessage, + Payload = JToken.FromObject(mpayload) + }; + + var runprocessInfoPayload = new Message() + { + MessageType = MessageType.CustomTestHostLaunch, + Payload = JToken.FromObject(new TestProcessStartInfo()) + }; + + + mockHandler.Setup(mh => mh.HandleTestRunStatsChange(It.IsAny())).Callback( + (testRunChangedArgs) => + { + Assert.IsTrue(testRunChangedArgs.NewTestResults != null && testsChangedArgs.NewTestResults.Count() > 0, "TestResults must be passed properly"); + this.mockCommunicationManager.Setup(cm => cm.ReceiveMessage()).Returns(message); + }); + + mockHandler.Setup(mh => mh.HandleLogMessage(It.IsAny(), It.IsAny())).Callback( + () => + { + this.mockCommunicationManager.Setup(cm => cm.ReceiveMessage()).Returns(runComplete); + }); + + var mockLauncher = new Mock(); + mockLauncher.Setup(ml => ml.LaunchTestHost(It.IsAny())).Callback + (() => this.mockCommunicationManager.Setup(cm => cm.ReceiveMessage()).Returns(testsPayload)); + + this.mockCommunicationManager.Setup(cm => cm.ReceiveMessage()).Returns(runprocessInfoPayload); + + this.requestSender.StartTestRunWithCustomHost(new List() { "1.dll" }, null, mockHandler.Object, mockLauncher.Object); + + mockHandler.Verify(mh => mh.HandleTestRunComplete(It.IsAny(), + It.IsAny(), null, null), Times.Once, "Run Complete must be called"); + mockHandler.Verify(mh => mh.HandleTestRunStatsChange(It.IsAny()), Times.Once, "RunChangedArgs must be called"); + mockHandler.Verify(mh => mh.HandleLogMessage(It.IsAny(), It.IsAny()), Times.Once, "TestMessage event must be called"); + mockLauncher.Verify(ml => ml.LaunchTestHost(It.IsAny()), Times.Once, "Custom TestHostLauncher must be called"); + } + + [TestMethod] + public void StartTestRunWithSelectedTestsShouldCompleteWithZeroTests() + { + this.InitializeCommunication(); + + var mockHandler = new Mock(); + + var dummyCompleteArgs = new TestRunCompleteEventArgs(null, false, false, null, null, TimeSpan.FromMilliseconds(1)); + var dummyLastRunArgs = new TestRunChangedEventArgs(null, null, null); + + var payload = new TestRunCompletePayload() + { + ExecutorUris = null, + LastRunTests = dummyLastRunArgs, + RunAttachments = null, + TestRunCompleteArgs = dummyCompleteArgs + }; + + var runComplete = new Message() + { + MessageType = MessageType.ExecutionComplete, + Payload = JToken.FromObject(payload) + }; + this.mockCommunicationManager.Setup(cm => cm.ReceiveMessage()).Returns(runComplete); + + this.requestSender.StartTestRun(new List(), null, mockHandler.Object); + + mockHandler.Verify(mh => mh.HandleTestRunComplete(It.IsAny(), + It.IsAny(), null, null), Times.Once, "Run Complete must be called"); + mockHandler.Verify(mh => mh.HandleTestRunStatsChange(It.IsAny()), Times.Never, "RunChangedArgs must not be called"); + mockHandler.Verify(mh => mh.HandleLogMessage(It.IsAny(), It.IsAny()), Times.Never, "TestMessage event must not be called"); + } + + [TestMethod] + public void StartTestRunWithSelectedTestsShouldCompleteWithSingleTestAndMessage() + { + this.InitializeCommunication(); + + var mockHandler = new Mock(); + + var testCase = new TestCase("hello", new Uri("world://how"), "1.dll"); + var testResult = new VisualStudio.TestPlatform.ObjectModel.TestResult(testCase); + testResult.Outcome = TestOutcome.Passed; + + var testCaseList = new List() { testCase }; + + var dummyCompleteArgs = new TestRunCompleteEventArgs(null, false, false, null, null, TimeSpan.FromMilliseconds(1)); + var dummyLastRunArgs = new TestRunChangedEventArgs(null, null, null); + + var testsChangedArgs = new TestRunChangedEventArgs(null, + new List() { testResult }, null); + + var testsPayload = new Message() + { + MessageType = MessageType.TestRunStatsChange, + Payload = JToken.FromObject(testsChangedArgs) + }; + + var payload = new TestRunCompletePayload() + { + ExecutorUris = null, + LastRunTests = dummyLastRunArgs, + RunAttachments = null, + TestRunCompleteArgs = dummyCompleteArgs + }; + + var runComplete = new Message() + { + MessageType = MessageType.ExecutionComplete, + Payload = JToken.FromObject(payload) + }; + + var mpayload = new TestMessagePayload() { MessageLevel = TestMessageLevel.Informational, Message = "Hello" }; + var message = new Message() + { + MessageType = MessageType.TestMessage, + Payload = JToken.FromObject(mpayload) + }; + + + this.mockCommunicationManager.Setup(cm => cm.ReceiveMessage()).Returns(testsPayload); + + mockHandler.Setup(mh => mh.HandleTestRunStatsChange(It.IsAny())).Callback( + (testRunChangedArgs) => + { + Assert.IsTrue(testRunChangedArgs.NewTestResults != null && testsChangedArgs.NewTestResults.Count() > 0, "TestResults must be passed properly"); + this.mockCommunicationManager.Setup(cm => cm.ReceiveMessage()).Returns(message); + }); + + mockHandler.Setup(mh => mh.HandleLogMessage(It.IsAny(), It.IsAny())).Callback( + () => + { + this.mockCommunicationManager.Setup(cm => cm.ReceiveMessage()).Returns(runComplete); + }); + + this.requestSender.StartTestRun(testCaseList, null, mockHandler.Object); + + mockHandler.Verify(mh => mh.HandleTestRunComplete(It.IsAny(), + It.IsAny(), null, null), Times.Once, "Run Complete must be called"); + mockHandler.Verify(mh => mh.HandleTestRunStatsChange(It.IsAny()), Times.Once, "RunChangedArgs must be called"); + mockHandler.Verify(mh => mh.HandleLogMessage(It.IsAny(), It.IsAny()), Times.Once, "TestMessage event must be called"); + } + + + [TestMethod] + public void StartTestRunWithSelectedTestsHavingTraitsShouldReturnTestRunCompleteWithTraitsIntact() + { + this.InitializeCommunication(); + + var mockHandler = new Mock(); + + var testCase = new TestCase("hello", new Uri("world://how"), "1.dll"); + testCase.Traits.Add(new Trait("a", "b")); + + var testResult = new VisualStudio.TestPlatform.ObjectModel.TestResult(testCase); + testResult.Outcome = TestOutcome.Passed; + + var testCaseList = new List() { testCase }; + + TestRunChangedEventArgs receivedChangeEventArgs = null; + var dummyCompleteArgs = new TestRunCompleteEventArgs(null, false, false, null, null, TimeSpan.FromMilliseconds(1)); + var dummyLastRunArgs = new TestRunChangedEventArgs(null, new List { testResult }, null); + + var payload = new TestRunCompletePayload() + { + ExecutorUris = null, + LastRunTests = dummyLastRunArgs, + RunAttachments = null, + TestRunCompleteArgs = dummyCompleteArgs + }; + + var runComplete = new Message() + { + MessageType = MessageType.ExecutionComplete, + Payload = + JToken.FromObject( + payload, + JsonSerializer.Create( + new JsonSerializerSettings + { + TypeNameHandling = + TypeNameHandling.Auto + })) + }; + + this.mockCommunicationManager.Setup(cm => cm.ReceiveMessage()).Returns(runComplete); + + mockHandler.Setup(mh => mh.HandleTestRunComplete( + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny>())) + .Callback( + (TestRunCompleteEventArgs complete, + TestRunChangedEventArgs stats, + ICollection attachments, + ICollection executorUris) => + { + receivedChangeEventArgs = stats; + }); + + this.requestSender.StartTestRun(testCaseList, null, mockHandler.Object); + + Assert.IsNotNull(receivedChangeEventArgs); + Assert.IsTrue(receivedChangeEventArgs.NewTestResults.Count() > 0); + + // Verify that the traits are passed through properly. + var traits = receivedChangeEventArgs.NewTestResults.ToArray()[0].TestCase.Traits; + Assert.IsNotNull(traits); + Assert.AreEqual(traits.ToArray()[0].Name, "a"); + Assert.AreEqual(traits.ToArray()[0].Value, "b"); + } + + [TestMethod] + public void StartTestRunWithSelectedTestsHavingTraitsShouldReturnTestRunStatsWithTraitsIntact() + { + this.InitializeCommunication(); + + var mockHandler = new Mock(); + + var testCase = new TestCase("hello", new Uri("world://how"), "1.dll"); + testCase.Traits.Add(new Trait("a", "b")); + + var testResult = new VisualStudio.TestPlatform.ObjectModel.TestResult(testCase); + testResult.Outcome = TestOutcome.Passed; + + var testCaseList = new List() { testCase }; + + TestRunChangedEventArgs receivedChangeEventArgs = null; + var dummyCompleteArgs = new TestRunCompleteEventArgs(null, false, false, null, null, TimeSpan.FromMilliseconds(1)); + var dummyLastRunArgs = new TestRunChangedEventArgs(null, null, null); + + var testsChangedArgs = new TestRunChangedEventArgs( + null, + new List() { testResult }, + null); + + var testsRunStatsPayload = new Message() + { + MessageType = MessageType.TestRunStatsChange, + Payload = JToken.FromObject( + testsChangedArgs, + JsonSerializer.Create( + new JsonSerializerSettings + { + TypeNameHandling = + TypeNameHandling.Auto + })) + }; + + var testRunCompletepayload = new TestRunCompletePayload() + { + ExecutorUris = null, + LastRunTests = dummyLastRunArgs, + RunAttachments = null, + TestRunCompleteArgs = dummyCompleteArgs + }; + + var runComplete = new Message() + { + MessageType = MessageType.ExecutionComplete, + Payload = JToken.FromObject( + testRunCompletepayload, + JsonSerializer.Create( + new JsonSerializerSettings + { + TypeNameHandling = + TypeNameHandling.Auto + })) + }; + + this.mockCommunicationManager.Setup(cm => cm.ReceiveMessage()).Returns(testsRunStatsPayload); + + mockHandler.Setup(mh => mh.HandleTestRunStatsChange( + It.IsAny())) + .Callback( + (TestRunChangedEventArgs stats) => + { + receivedChangeEventArgs = stats; + this.mockCommunicationManager.Setup(cm => cm.ReceiveMessage()).Returns(runComplete); + }); + + this.requestSender.StartTestRun(testCaseList, null, mockHandler.Object); + + Assert.IsNotNull(receivedChangeEventArgs); + Assert.IsTrue(receivedChangeEventArgs.NewTestResults.Count() > 0); + + // Verify that the traits are passed through properly. + var traits = receivedChangeEventArgs.NewTestResults.ToArray()[0].TestCase.Traits; + Assert.IsNotNull(traits); + Assert.AreEqual(traits.ToArray()[0].Name, "a"); + Assert.AreEqual(traits.ToArray()[0].Value, "b"); + } + + [TestMethod] + public void StartTestRunWithSelectedTestsAndCustomHostShouldComplete() + { + this.InitializeCommunication(); + + var mockHandler = new Mock(); + + var testCase = new TestCase("hello", new Uri("world://how"), "1.dll"); + var testResult = new VisualStudio.TestPlatform.ObjectModel.TestResult(testCase); + testResult.Outcome = TestOutcome.Passed; + + var testCaseList = new List() { testCase }; + + var dummyCompleteArgs = new TestRunCompleteEventArgs(null, false, false, null, null, TimeSpan.FromMilliseconds(1)); + var dummyLastRunArgs = new TestRunChangedEventArgs(null, null, null); + + var testsChangedArgs = new TestRunChangedEventArgs(null, + new List() { testResult }, null); + + var testsPayload = new Message() + { + MessageType = MessageType.TestRunStatsChange, + Payload = JToken.FromObject(testsChangedArgs) + }; + + var payload = new TestRunCompletePayload() + { + ExecutorUris = null, + LastRunTests = dummyLastRunArgs, + RunAttachments = null, + TestRunCompleteArgs = dummyCompleteArgs + }; + + var runComplete = new Message() + { + MessageType = MessageType.ExecutionComplete, + Payload = JToken.FromObject(payload) + }; + + var mpayload = new TestMessagePayload() { MessageLevel = TestMessageLevel.Informational, Message = "Hello" }; + var message = new Message() + { + MessageType = MessageType.TestMessage, + Payload = JToken.FromObject(mpayload) + }; + + + var runprocessInfoPayload = new Message() + { + MessageType = MessageType.CustomTestHostLaunch, + Payload = JToken.FromObject(new TestProcessStartInfo()) + }; + + mockHandler.Setup(mh => mh.HandleTestRunStatsChange(It.IsAny())).Callback( + (testRunChangedArgs) => + { + Assert.IsTrue(testRunChangedArgs.NewTestResults != null && testsChangedArgs.NewTestResults.Count() > 0, "TestResults must be passed properly"); + this.mockCommunicationManager.Setup(cm => cm.ReceiveMessage()).Returns(message); + }); + + mockHandler.Setup(mh => mh.HandleLogMessage(It.IsAny(), It.IsAny())).Callback( + () => + { + this.mockCommunicationManager.Setup(cm => cm.ReceiveMessage()).Returns(runComplete); + }); + + var mockLauncher = new Mock(); + mockLauncher.Setup(ml => ml.LaunchTestHost(It.IsAny())).Callback + (() => this.mockCommunicationManager.Setup(cm => cm.ReceiveMessage()).Returns(testsPayload)); + + this.mockCommunicationManager.Setup(cm => cm.ReceiveMessage()).Returns(runprocessInfoPayload); + + this.requestSender.StartTestRunWithCustomHost(testCaseList, null, mockHandler.Object, mockLauncher.Object); + + mockHandler.Verify(mh => mh.HandleTestRunComplete(It.IsAny(), + It.IsAny(), null, null), Times.Once, "Run Complete must be called"); + mockHandler.Verify(mh => mh.HandleTestRunStatsChange(It.IsAny()), Times.Once, "RunChangedArgs must be called"); + mockHandler.Verify(mh => mh.HandleLogMessage(It.IsAny(), It.IsAny()), Times.Once, "TestMessage event must be called"); + mockLauncher.Verify(ml => ml.LaunchTestHost(It.IsAny()), Times.Once, "Custom TestHostLauncher must be called"); + } + + [TestMethod] + public void StartTestRunWithCustomHostInParallelShouldCallCustomHostMultipleTimes() + { + var mockLauncher = new Mock(); + var mockHandler = new Mock(); + + IEnumerable sources = new List { "1.dll" }; + + var p1 = new TestProcessStartInfo() { FileName = "X" }; + var p2 = new TestProcessStartInfo() { FileName = "Y" }; + + var message1 = new Message() + { + MessageType = MessageType.CustomTestHostLaunch, + Payload = JToken.FromObject(p1) + }; + + var message2 = new Message() + { + MessageType = MessageType.CustomTestHostLaunch, + Payload = JToken.FromObject(p2) + }; + + var dummyCompleteArgs = new TestRunCompleteEventArgs(null, false, false, null, null, TimeSpan.FromMilliseconds(1)); + + var completepayload = new TestRunCompletePayload() + { + ExecutorUris = null, + LastRunTests = null, + RunAttachments = null, + TestRunCompleteArgs = dummyCompleteArgs + }; + var runComplete = new Message() + { + MessageType = MessageType.ExecutionComplete, + Payload = JToken.FromObject(completepayload) + }; + + this.mockCommunicationManager.Setup(cm => cm.ReceiveMessage()).Returns(message1); + mockLauncher.Setup(ml => ml.LaunchTestHost(It.IsAny())) + .Callback((startInfo) => + { + if(startInfo.FileName.Equals(p1.FileName)) + { + this.mockCommunicationManager.Setup(cm => cm.ReceiveMessage()).Returns(message2); + } + else if (startInfo.FileName.Equals(p2.FileName)) + { + this.mockCommunicationManager.Setup(cm => cm.ReceiveMessage()).Returns(runComplete); + } + }); + + this.requestSender.StartTestRunWithCustomHost(sources, null, mockHandler.Object, mockLauncher.Object); + + mockLauncher.Verify(ml => ml.LaunchTestHost(It.IsAny()), Times.Exactly(2)); + } + + #endregion + + #region private methods + + private void InitializeCommunication() + { + var dummyPortInput = 123; + this.mockCommunicationManager.Setup(cm => cm.HostServer()).Returns(dummyPortInput); + this.mockCommunicationManager.Setup(cm => cm.AcceptClientAsync()).Callback(() => { }); + + this.mockCommunicationManager.Setup(cm => cm.WaitForClientConnection(Timeout.Infinite)) + .Callback((int timeout) => Task.Delay(200).Wait()); + + var sessionConnected = new Message() { MessageType = MessageType.SessionConnected }; + var versionCheck = new Message() { MessageType = MessageType.VersionCheck, Payload = JToken.FromObject("1") }; + + Action changedMessage = () => + { + this.mockCommunicationManager.Setup(cm => cm.ReceiveMessage()).Returns(versionCheck); + }; + + this.mockCommunicationManager.Setup(cm => cm.ReceiveMessage()).Returns(sessionConnected); + this.mockCommunicationManager.Setup(cm => cm.SendMessage(MessageType.VersionCheck)).Callback(changedMessage); + + var portOutput = this.requestSender.InitializeCommunication(); + Assert.AreEqual(dummyPortInput, portOutput, "Port number must return without changes."); + var connectionSuccess = this.requestSender.WaitForRequestHandlerConnection(WaitTimeout); + Assert.IsTrue(connectionSuccess, "Connection must succeed."); + } + + #endregion + } +} diff --git a/test/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.UnitTests/VsTestConsoleWrapperTests.cs b/test/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.UnitTests/VsTestConsoleWrapperTests.cs new file mode 100644 index 0000000000..4e4a370dbb --- /dev/null +++ b/test/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.UnitTests/VsTestConsoleWrapperTests.cs @@ -0,0 +1,401 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.VsTestConsole.TranslationLayer.UnitTests +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + + using Microsoft.TestPlatform.VsTestConsole.TranslationLayer.Interfaces; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces; + + using Moq; + + [TestClass] + public class VsTestConsoleWrapperTests + { + private IVsTestConsoleWrapper consoleWrapper; + + private MockTranslationLayerSender mockSender; + + private MockProcessManager mockProcessManager; + + [TestInitialize] + public void TestInit() + { + this.mockSender = new MockTranslationLayerSender(); + this.mockProcessManager = new MockProcessManager(); + this.consoleWrapper = new VsTestConsoleWrapper(mockSender, mockProcessManager); + } + + [TestMethod] + public void StartSessionShouldStartVsTestConsole() + { + var inputPort = 123; + this.mockSender.SetupPort(inputPort); + + var startProcessCalled = false; + this.mockProcessManager.VerifyArgs = (args) => + { + startProcessCalled = true; + Assert.IsTrue(args.Length > 0 && args[0].Contains(inputPort + ""), "Wrong Port fed to process manager"); + }; + + this.consoleWrapper.StartSession(); + + Assert.IsTrue(startProcessCalled, "Start Process must be called"); + } + + + [TestMethod] + public void StartSessionShouldThrowExceptionOnBadPort() + { + var inputPort = -1; + this.mockSender.SetupPort(inputPort); + + Assert.ThrowsException(() => this.consoleWrapper.StartSession()); + } + + [TestMethod] + public void InitializeExtensionsShouldSucceed() + { + this.mockSender.SetConnectionResult(true); + + bool initExtCalled = false; + Action, bool> assertPaths = (paths, loadOnlyWellKnownExtensions) => + { + initExtCalled = true; + Assert.IsTrue(paths != null && paths.Count() == 2, "Extension Paths must be set correctly."); + }; + + this.mockSender.SetInitExtFunc(assertPaths); + + this.consoleWrapper.InitializeExtensions(new List() { "Hello", "World" }); + Assert.IsTrue(initExtCalled, "Initialize Extensions must be called"); + } + + + [TestMethod] + public void InitializeExtensionsShouldThrowExceptionOnBadConnection() + { + this.mockSender.SetConnectionResult(false); + bool initExtCalled = false; + Action, bool> assertPaths = (paths, loadOnlyWellKnownExtensions) => + { + initExtCalled = true; + }; + + Assert.ThrowsException(() => this.consoleWrapper.InitializeExtensions(new List() { "Hello", "World" })); + + Assert.IsFalse(initExtCalled, "Initialize Extensions must NOT be called if connection failed"); + } + + + [TestMethod] + public void DiscoverTestsShouldSucceed() + { + this.mockSender.SetConnectionResult(true); + + bool discoverTestsCalled = false; + Action, string, ITestDiscoveryEventsHandler> assertSources = + (paths, settings, handler) => + { + discoverTestsCalled = true; + Assert.IsTrue(paths != null && paths.Count() == 2, "Sources must be set correctly."); + Assert.IsNotNull(handler, "TestDiscoveryEventsHandler must be set correctly."); + }; + + this.mockSender.SetupDiscoverTests(assertSources); + + this.consoleWrapper.DiscoverTests(new List() { "Hello", "World" }, null, new Mock().Object); + + Assert.IsTrue(discoverTestsCalled, "Discover Tests must be called on translation layer"); + } + + [TestMethod] + public void DiscoverTestsShouldThrowExceptionOnBadConnection() + { + this.mockSender.SetConnectionResult(false); + + bool discoverTestsCalled = false; + Action, string, ITestDiscoveryEventsHandler> assertSources = + (paths, settings, handler) => + { + discoverTestsCalled = true; + }; + + Assert.ThrowsException(() => this.consoleWrapper.DiscoverTests(new List() { "Hello", "World" }, null, new Mock().Object)); + + Assert.IsFalse(discoverTestsCalled, "Discover Tests must NOT be called on translation layer when connection is bad."); + } + + [TestMethod] + public void RunTestsWithSourcesShouldSucceed() + { + this.mockSender.SetConnectionResult(true); + + bool runTestsCalled = false; + Action, string, ITestRunEventsHandler> assertSources = + (sources, settings, handler) => + { + runTestsCalled = true; + Assert.IsTrue(sources != null && sources.Count() == 2, "Sources must be set correctly."); + Assert.IsTrue(!string.IsNullOrEmpty(settings), "RunSettings must be set correctly."); + Assert.IsNotNull(handler, "TestRunEventsHandler must be set correctly."); + }; + + this.mockSender.SetupRunTestsWithSources(assertSources); + + this.consoleWrapper.RunTests(new List() { "Hello", "World" }, "RunSettings", new Mock().Object); + + Assert.IsTrue(runTestsCalled, "Run Tests must be called on translation layer"); + } + + [TestMethod] + public void RunTestsWithSourcesAndCustomHostShouldSucceed() + { + this.mockSender.SetConnectionResult(true); + + bool runTestsCalled = false; + Action, string, ITestRunEventsHandler, ITestHostLauncher> assertSources = + (sources, settings, handler, customLauncher) => + { + runTestsCalled = true; + Assert.IsTrue(sources != null && sources.Count() == 2, "Sources must be set correctly."); + Assert.IsTrue(!string.IsNullOrEmpty(settings), "RunSettings must be set correctly."); + Assert.IsNotNull(handler, "TestRunEventsHandler must be set correctly."); + Assert.IsNotNull(customLauncher, "Custom Launcher must be set correctly."); + }; + + this.mockSender.SetupRunTestsWithSourcesAndCustomHost(assertSources); + + this.consoleWrapper.RunTestsWithCustomTestHost(new List() { "Hello", "World" }, "RunSettings", + new Mock().Object, new Mock().Object); + + Assert.IsTrue(runTestsCalled, "Run Tests must be called on translation layer"); + } + + [TestMethod] + public void RunTestsWithSelectedTestsShouldSucceed() + { + this.mockSender.SetConnectionResult(true); + + bool runTestsCalled = false; + Action, string, ITestRunEventsHandler> assertTests = + (tests, settings, handler) => + { + runTestsCalled = true; + Assert.IsTrue(tests != null && tests.Count() == 2, "TestCases must be set correctly."); + Assert.IsTrue(!string.IsNullOrEmpty(settings), "RunSettings must be set correctly."); + Assert.IsNotNull(handler, "TestRunEventsHandler must be set correctly."); + }; + + this.mockSender.SetupRunTestsWithSelectedTests(assertTests); + + var testCases = new List(); + testCases.Add(new TestCase("a.b.c", new Uri("d://uri"), "a.dll")); + testCases.Add(new TestCase("d.e.f", new Uri("g://uri"), "d.dll")); + + this.consoleWrapper.RunTests(testCases, "RunSettings", new Mock().Object); + + Assert.IsTrue(runTestsCalled, "Run Tests must be called on translation layer"); + } + + [TestMethod] + public void RunTestsWithSelectedTestsAndCustomLauncherShouldSucceed() + { + this.mockSender.SetConnectionResult(true); + + bool runTestsCalled = false; + Action, string, ITestRunEventsHandler, ITestHostLauncher> assertTests = + (tests, settings, handler, customLauncher) => + { + runTestsCalled = true; + Assert.IsTrue(tests != null && tests.Count() == 2, "TestCases must be set correctly."); + Assert.IsTrue(!string.IsNullOrEmpty(settings), "RunSettings must be set correctly."); + Assert.IsNotNull(handler, "TestRunEventsHandler must be set correctly."); + Assert.IsNotNull(customLauncher, "Custom Launcher must be set correctly."); + }; + + this.mockSender.SetupRunTestsWithSelectedTestsAndCustomHost(assertTests); + + var testCases = new List(); + testCases.Add(new TestCase("a.b.c", new Uri("d://uri"), "a.dll")); + testCases.Add(new TestCase("d.e.f", new Uri("g://uri"), "d.dll")); + + this.consoleWrapper.RunTestsWithCustomTestHost(testCases, "RunSettings", + new Mock().Object, new Mock().Object); + + Assert.IsTrue(runTestsCalled, "Run Tests must be called on translation layer"); + } + + [TestMethod] + public void EndSessionShouldSucceed() + { + this.consoleWrapper.EndSession(); + + Assert.IsTrue(this.mockSender.IsCloseCalled, "Close method must be called on sender"); + Assert.IsTrue(this.mockSender.IsSessionEnded, "SessionEnd method must be called on sender"); + } + + + private class MockTranslationLayerSender : ITranslationLayerRequestSender + { + public bool IsCloseCalled = false; + public bool IsSessionEnded = false; + + public void Close() + { + IsCloseCalled = true; + } + + public void DiscoverTests(IEnumerable sources, string runSettings, ITestDiscoveryEventsHandler discoveryEventsHandler) + { + this.discoverFunc(sources, runSettings, discoveryEventsHandler); + } + + public void Dispose() + { + + } + + public void EndSession() + { + IsSessionEnded = true; + } + + public int InitializeCommunication() + { + return port; + } + + public void InitializeExtensions(IEnumerable pathToAdditionalExtensions) + { + this.initExtFunc(pathToAdditionalExtensions, false); + } + + public void StartTestRun(IEnumerable testCases, string runSettings, ITestRunEventsHandler runEventsHandler) + { + this.runTestsWithSelectedTestsFunc.Invoke(testCases, runSettings, runEventsHandler); + } + + public void StartTestRun(IEnumerable sources, string runSettings, ITestRunEventsHandler runEventsHandler) + { + this.runTestsWithSourcesFunc.Invoke(sources, runSettings, runEventsHandler); + } + + public void StartTestRunWithCustomHost(IEnumerable sources, string runSettings, + ITestRunEventsHandler runEventsHandler, ITestHostLauncher customTestHostLauncher) + { + this.runTestsWithSourcesAndCustomLauncherFunc(sources, runSettings, runEventsHandler, customTestHostLauncher); + } + + public void StartTestRunWithCustomHost(IEnumerable testCases, string runSettings, + ITestRunEventsHandler runEventsHandler, ITestHostLauncher customTestHostLauncher) + { + this.runTestsWithSelectedTestsAndCustomHostFunc(testCases, runSettings, runEventsHandler, customTestHostLauncher); + } + + public bool WaitForRequestHandlerConnection(int connectionTimeout) + { + return this.connectionResult; + } + + private int port; + + internal void SetupPort(int inputPort) + { + this.port = inputPort; + } + + private bool connectionResult; + + internal void SetConnectionResult(bool connectionResult) + { + this.connectionResult = connectionResult; + } + + private Action, bool> initExtFunc; + + internal void SetInitExtFunc(Action, bool> initExtFunc) + { + this.initExtFunc = initExtFunc; + } + + private Action, string, ITestDiscoveryEventsHandler> discoverFunc; + + internal void SetupDiscoverTests(Action, string, ITestDiscoveryEventsHandler> discoverFunc) + { + this.discoverFunc = discoverFunc; + } + + private Action, string, ITestRunEventsHandler> runTestsWithSourcesFunc; + + internal void SetupRunTestsWithSources(Action, string, ITestRunEventsHandler> runTestsFunc) + { + this.runTestsWithSourcesFunc = runTestsFunc; + } + + + private Action, string, ITestRunEventsHandler, ITestHostLauncher> runTestsWithSourcesAndCustomLauncherFunc; + + internal void SetupRunTestsWithSourcesAndCustomHost(Action, string, ITestRunEventsHandler, ITestHostLauncher> runTestsFunc) + { + this.runTestsWithSourcesAndCustomLauncherFunc = runTestsFunc; + } + + private Action, string, ITestRunEventsHandler> runTestsWithSelectedTestsFunc; + + internal void SetupRunTestsWithSelectedTests(Action, string, ITestRunEventsHandler> runTestsFunc) + { + this.runTestsWithSelectedTestsFunc = runTestsFunc; + } + + private Action, string, ITestRunEventsHandler, ITestHostLauncher> runTestsWithSelectedTestsAndCustomHostFunc; + + internal void SetupRunTestsWithSelectedTestsAndCustomHost(Action, string, ITestRunEventsHandler, ITestHostLauncher> runTestsFunc) + { + this.runTestsWithSelectedTestsAndCustomHostFunc = runTestsFunc; + } + + public void CancelTestRun() + { + throw new NotImplementedException(); + } + + public void AbortTestRun() + { + throw new NotImplementedException(); + } + } + + + private class MockProcessManager : IProcessManager + { + public Action VerifyArgs; + + public bool IsProcessInitialized() + { + return true; + } + + public void ShutdownProcess() + { + + } + + public void StartProcess(string[] args) + { + if(VerifyArgs != null) + { + VerifyArgs(args); + } + } + } + } +} diff --git a/test/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.UnitTests/project.json b/test/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.UnitTests/project.json new file mode 100644 index 0000000000..15b0f62b70 --- /dev/null +++ b/test/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.UnitTests/project.json @@ -0,0 +1,37 @@ +{ + "version": "15.0.0-*", + + "buildOptions": { + "delaySign": true, + "keyFile": "../../scripts/key.snk", + "warningsAsErrors": true + }, + + "dependencies": { + "Microsoft.NETCore.App": { + "type": "platform", + "version": "1.0.0-rc2-3002702" + }, + "dotnet-test-mstest": { + "version": "1.0.1-preview", + "exclude": "compile" + }, + "MSTest.TestFramework": "1.0.0-preview", + "moq.netcore": "4.4.0-beta8", + "System.Diagnostics.TraceSource": "4.0.0-rc2-24015", + "Microsoft.TestPlatform.VsTestConsole.TranslationLayer": "15.0.0-*", + "Microsoft.TestPlatform.CommunicationUtilities": "15.0.0-*", + "Microsoft.TestPlatform.ObjectModel": "15.0.0-*" + }, + + "frameworks": { + "netcoreapp1.0": { + "imports": [ + "dnxcore50", + "portable-net45+win8" + ] + } + }, + + "testRunner": "mstest" +} diff --git a/test/Samples/SampleUnitTestProject/Properties/AssemblyInfo.cs b/test/Samples/SampleUnitTestProject/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..66b0fac7e5 --- /dev/null +++ b/test/Samples/SampleUnitTestProject/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +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("SampleUnitTestProject")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("SampleUnitTestProject")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[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("37c76c3d-947e-48a6-af68-2d7c9ec35f6f")] + +// 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/test/Samples/SampleUnitTestProject/SampleUnitTestProject.csproj b/test/Samples/SampleUnitTestProject/SampleUnitTestProject.csproj new file mode 100644 index 0000000000..be29ca25d4 --- /dev/null +++ b/test/Samples/SampleUnitTestProject/SampleUnitTestProject.csproj @@ -0,0 +1,103 @@ + + + + + + Debug + AnyCPU + {37C76C3D-947E-48A6-AF68-2D7C9EC35F6F} + Library + Properties + SampleUnitTestProject + SampleUnitTestProject + v4.5.2 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\MSTest.TestFramework.1.0.0-preview\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll + True + + + ..\packages\MSTest.TestFramework.1.0.0-preview\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll + True + + + + + + + + + + + + + + + + + + Designer + + + + + + + False + + + False + + + False + + + False + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use 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/test/Samples/SampleUnitTestProject/UnitTest1.cs b/test/Samples/SampleUnitTestProject/UnitTest1.cs new file mode 100644 index 0000000000..41f772c5d3 --- /dev/null +++ b/test/Samples/SampleUnitTestProject/UnitTest1.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace SampleUnitTestProject +{ + using Microsoft.VisualStudio.TestTools.UnitTesting; + + /// + /// The unit test 1. + /// + [TestClass] + public class UnitTest1 + { + /// + /// The passing test. + /// + [TestMethod] + public void PassingTest() + { + Assert.AreEqual(2, 2); + } + + /// + /// The failing test. + /// + [TestMethod] + public void FailingTest() + { + Assert.AreEqual(2, 3); + } + + /// + /// The skipping test. + /// + [Ignore] + [TestMethod] + public void SkippingTest() + { + } + } +} diff --git a/test/Samples/SampleUnitTestProject/packages.config b/test/Samples/SampleUnitTestProject/packages.config new file mode 100644 index 0000000000..8b1e1f4e26 --- /dev/null +++ b/test/Samples/SampleUnitTestProject/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/test/Samples/TestImpactListener.Tests/Class1.cs b/test/Samples/TestImpactListener.Tests/Class1.cs new file mode 100644 index 0000000000..0c14633f92 --- /dev/null +++ b/test/Samples/TestImpactListener.Tests/Class1.cs @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestImpactListener.Tests +{ + using System; + using System.IO; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollector.InProcDataCollector; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.InProcDataCollector; + + /// + /// The ti listener tests. + /// + public class TIListenerTests : InProcDataCollection + { + private string fileName; + + /// + /// Initializes a new instance of the class. + /// + public TIListenerTests() + { + this.fileName = Path.Combine(Path.GetTempPath(), "inproctest.txt"); + } + + /// + /// The test session start. + /// + /// + /// The test session start args. + /// + public void TestSessionStart(TestSessionStartArgs testSessionStartArgs) + { + Console.WriteLine(testSessionStartArgs.Configuration); + File.WriteAllText(this.fileName, "TestSessionStart : " + testSessionStartArgs.Configuration + "\r\n"); + } + + /// + /// The test case start. + /// + /// + /// The test case start args. + /// + public void TestCaseStart(TestCaseStartArgs testCaseStartArgs) + { + Console.WriteLine( + "TestCase Name : {0}, TestCase ID:{1}", + testCaseStartArgs.TestCase.DisplayName, + testCaseStartArgs.TestCase.Id); + File.AppendAllText(this.fileName, "TestCaseStart : " + testCaseStartArgs.TestCase.DisplayName + "\r\n"); + } + + /// + /// The test case end. + /// + /// + /// The test case end args. + /// + public void TestCaseEnd(TestCaseEndArgs testCaseEndArgs) + { + Console.WriteLine("TestCase Name:{0}, TestCase ID:{1}, OutCome:{2}", testCaseEndArgs.TestCase.DisplayName, testCaseEndArgs.TestCase.Id, testCaseEndArgs.TestOutcome); + File.AppendAllText(this.fileName, "TestCaseEnd : " + testCaseEndArgs.TestCase.DisplayName + "\r\n"); + } + + /// + /// The test session end. + /// + /// + /// The test session end args. + /// + public void TestSessionEnd(TestSessionEndArgs testSessionEndArgs) + { + Console.WriteLine("TestSession Ended"); + File.AppendAllText(this.fileName, "TestSessionEnd"); + } + } +} diff --git a/test/Samples/TestImpactListener.Tests/Properties/AssemblyInfo.cs b/test/Samples/TestImpactListener.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..10249b2b80 --- /dev/null +++ b/test/Samples/TestImpactListener.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +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("TestImpactListener.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("TestImpactListener.Tests")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[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("f060289c-6e1a-4b21-9eb5-7f111a79449c")] + +// 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/test/Samples/TestImpactListener.Tests/TITestDllKey.snk b/test/Samples/TestImpactListener.Tests/TITestDllKey.snk new file mode 100644 index 0000000000000000000000000000000000000000..1c70857c3474f30956685c092e0d4e898613502d GIT binary patch literal 596 zcmV-a0;~N80ssI2Bme+XQ$aES1ONa50096y*I+959mwklIz+1c8IDUyiLi4wQmlKDSI`@Fr=H>*I0K| z4!JH*S{$*(+^pTVu1U1sj}2$K00xc7mLn}7=Rr~I>Ire00}Z>p+y*3fV1t=?g_MH> zn!Rd5STC_hMsx))y}ZlX?er12|{t+l-xd;9hm zL4$oHA3Y%_)r+@jY&MI zP0#j1#61`)^y^?>TTZpKKg1<#aeS5P2@Tb05t~~_@da>Er(qxsp?c6sjqRu4`g;D5 zsuer-H8mcI%?B1uz13sqnUGKW*)AK;lEks`Wjl=s>Jx+9*+8pQWCJ=eV(FiW-D~*L z`)(X1^|{&Pt-t2|pdb}ecu#=Fz~((@{DM0KyS3Mw^)*h~+2@IBcaqZ)}G`^;E zp``<)|5PPu;=hY=FW{&4^>z}yv%#|&-=>Tq7$q4Ya*(d{NhE1I1dVrJt^sC!R&n*R i8riknC`)zOF2!>qV>qEhrLdRg)%0n!Q_HF+CCmtiY#i?Z literal 0 HcmV?d00001 diff --git a/test/Samples/TestImpactListener.Tests/TestImpactListener.Tests.csproj b/test/Samples/TestImpactListener.Tests/TestImpactListener.Tests.csproj new file mode 100644 index 0000000000..96836bfb34 --- /dev/null +++ b/test/Samples/TestImpactListener.Tests/TestImpactListener.Tests.csproj @@ -0,0 +1,67 @@ + + + + + Debug + AnyCPU + {F060289C-6E1A-4B21-9EB5-7F111A79449C} + Library + Properties + TestImpactListener.Tests + TestImpactListener.Tests + v4.6.1 + 512 + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + true + + + TITestDllKey.snk + + + + ..\artifacts\src\Microsoft.TestPlatform.ObjectModel\bin\Debug\net461\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/Samples/TestImpactListener.Tests/app.config b/test/Samples/TestImpactListener.Tests/app.config new file mode 100644 index 0000000000..5e95024db8 --- /dev/null +++ b/test/Samples/TestImpactListener.Tests/app.config @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/TestPlatform.CommandLine.UnitTests/Processors/TestAdapterPathArgumentProcessorTests.cs b/test/TestPlatform.CommandLine.UnitTests/Processors/TestAdapterPathArgumentProcessorTests.cs new file mode 100644 index 0000000000..45045ed2d3 --- /dev/null +++ b/test/TestPlatform.CommandLine.UnitTests/Processors/TestAdapterPathArgumentProcessorTests.cs @@ -0,0 +1,218 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Reflection; + using Microsoft.VisualStudio.TestPlatform.CommandLine.Processors; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + using Moq; + using ObjectModel.Client; + using Utilities; + [TestClass] + public class TestAdapterPathArgumentProcessorTests + { + [TestMethod] + public void GetMetadataShouldReturnListTestsArgumentProcessorCapabilities() + { + var processor = new TestAdapterPathArgumentProcessor(); + Assert.IsTrue(processor.Metadata.Value is TestAdapterPathArgumentProcessorCapabilities); + } + + [TestMethod] + public void GetExecuterShouldReturnListTestsArgumentProcessorCapabilities() + { + var processor = new TestAdapterPathArgumentProcessor(); + Assert.IsTrue(processor.Executor.Value is TestAdapterPathArgumentExecutor); + } + + #region ListTestsArgumentProcessorCapabilities tests + + [TestMethod] + public void CapabilitiesShouldReturnAppropriateProperties() + { + var capabilities = new TestAdapterPathArgumentProcessorCapabilities(); + Assert.AreEqual("/TestAdapterPath", capabilities.CommandName); + Assert.AreEqual("/TestAdapterPath\n This makes vstest.console.exe process use custom test adapters\n from a given path (if any) in the test run. \n Example /TestAdapterPath:", capabilities.HelpContentResourceName); + + Assert.AreEqual(HelpContentPriority.TestAdapterPathArgumentProcessorHelpPriority, capabilities.HelpPriority); + Assert.AreEqual(true, capabilities.IsAction); + Assert.AreEqual(ArgumentProcessorPriority.TestAdapterPath, capabilities.Priority); + + Assert.AreEqual(false, capabilities.AllowMultiple); + Assert.AreEqual(false, capabilities.AlwaysExecute); + Assert.AreEqual(false, capabilities.IsSpecialCommand); + } + + #endregion + + #region TestAdapterPathArgumentExecutor tests + + [TestMethod] + public void InitializeShouldThrowIfArgumentIsNull() + { + var mockTestPlatform = new Mock(); + var mockOutput = new Mock(); + var executor = new TestAdapterPathArgumentExecutor(CommandLineOptions.Instance, mockTestPlatform.Object, mockOutput.Object); + + var message = + @"The /TestAdapterPath parameter requires a value, which is path of a location containing custom test adapters. Example: /TestAdapterPath:c:\MyCustomAdapters"; + + var isExceptionThrown = false; + + try + { + executor.Initialize(null); + } + catch (Exception ex) + { + isExceptionThrown = true; + Assert.IsTrue(ex is CommandLineException); + Assert.AreEqual(message, ex.Message); + } + + Assert.IsTrue(isExceptionThrown); + } + + [TestMethod] + public void InitializeShouldThrowIfArgumentIsAWhiteSpace() + { + var mockTestPlatform = new Mock(); + var mockOutput = new Mock(); + var executor = new TestAdapterPathArgumentExecutor(CommandLineOptions.Instance, mockTestPlatform.Object, mockOutput.Object); + + var message = + @"The /TestAdapterPath parameter requires a value, which is path of a location containing custom test adapters. Example: /TestAdapterPath:c:\MyCustomAdapters"; + + var isExceptionThrown = false; + + try + { + executor.Initialize(" "); + } + catch (Exception ex) + { + isExceptionThrown = true; + Assert.IsTrue(ex is CommandLineException); + Assert.AreEqual(message, ex.Message); + } + + Assert.IsTrue(isExceptionThrown); + } + + [TestMethod] + public void InitializeShouldThrowIfPathDoesNotExist() + { + var mockTestPlatform = new Mock(); + var mockOutput = new Mock(); + var executor = new TestAdapterPathArgumentExecutor(CommandLineOptions.Instance, mockTestPlatform.Object, mockOutput.Object); + + var folder = "C:\\temp\\thisfolderdoesnotexist"; + + var message = string.Format( + @"The path '{0}' specified in the 'TestAdapterPath' is invalid. Error: {1}", + folder, + "The custom test adapter search path provided was not found, provide a valid path and try again."); + + var isExceptionThrown = false; + + try + { + executor.Initialize("\"" +folder + "\""); + } + catch (Exception ex) + { + isExceptionThrown = true; + Assert.IsTrue(ex is CommandLineException); + Assert.AreEqual(message, ex.Message); + } + + Assert.IsTrue(isExceptionThrown); + } + + [TestMethod] + public void InitializeShouldUpdateAdditionalExtensionsWithTestAdapterPath() + { + var mockTestPlatform = new Mock(); + var mockOutput = new Mock(); + var executor = new TestableTestAdapterPathArgumentExecutor(CommandLineOptions.Instance, mockTestPlatform.Object, mockOutput.Object); + + var currentAssemblyPath = typeof(TestAdapterPathArgumentExecutor).GetTypeInfo().Assembly.Location; + var currentFolder = Path.GetDirectoryName(currentAssemblyPath); + + executor.TestAdapters = (directory) => + { + if (string.Equals(directory, currentFolder)) + { + return new List + { + typeof(TestAdapterPathArgumentExecutor).GetTypeInfo() + .Assembly.Location + }; + } + + return new List { }; + }; + + + executor.Initialize(currentFolder); + + mockTestPlatform.Verify(tp => tp.UpdateExtensions(new List { currentAssemblyPath }, false), Times.Once); + } + + [TestMethod] + public void InitializeShouldReportIfNoTestAdaptersFoundInPath() + { + var mockTestPlatform = new Mock(); + var mockOutput = new Mock(); + var executor = new TestableTestAdapterPathArgumentExecutor(CommandLineOptions.Instance, mockTestPlatform.Object, mockOutput.Object); + + var currentAssemblyPath = typeof(TestAdapterPathArgumentExecutor).GetTypeInfo().Assembly.Location; + var currentFolder = Path.GetDirectoryName(currentAssemblyPath); + + executor.TestAdapters = (directory) => + { + return new List { }; + }; + + executor.Initialize(currentFolder); + + mockOutput.Verify( + o => + o.WriteLine( + string.Format( + "Warning: The path '{0}' specified in the 'TestAdapterPath' does not contain any test adapters, provide a valid path and try again.", + currentFolder), + OutputLevel.Warning)); + + } + + #endregion + + #region Testable implementations + + private class TestableTestAdapterPathArgumentExecutor : TestAdapterPathArgumentExecutor + { + internal TestableTestAdapterPathArgumentExecutor(CommandLineOptions options, ITestPlatform testPlatform, IOutput output) + : base(options, testPlatform, output) + { + } + + internal Func> TestAdapters { get; set; } + + internal override IEnumerable GetTestAdaptersFromDirectory(string directory) + { + if (this.TestAdapters != null) + { + return this.TestAdapters(directory); + } + return new List { }; + } + } + + #endregion + } +} diff --git a/test/TestPlatform.CrossPlatEngine.UnitTests/TestHostManagerFactoryTests.cs b/test/TestPlatform.CrossPlatEngine.UnitTests/TestHostManagerFactoryTests.cs new file mode 100644 index 0000000000..487f335f5e --- /dev/null +++ b/test/TestPlatform.CrossPlatEngine.UnitTests/TestHostManagerFactoryTests.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.CrossPlatEngine.UnitTests +{ + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class TestHostManagerFactoryTests + { + private TestHostManagerFactory testHostManagerFactory; + + [TestInitialize] + public void TestInit() + { + this.testHostManagerFactory = new TestHostManagerFactory(); + } + + [TestMethod] + public void GetDiscoveryManagerShouldReturnADiscoveryManagerInstance() + { + Assert.IsNotNull(this.testHostManagerFactory.GetDiscoveryManager()); + } + + [TestMethod] + public void GetDiscoveryManagerShouldCacheTheDiscoveryManagerInstance() + { + Assert.AreEqual(this.testHostManagerFactory.GetDiscoveryManager(), this.testHostManagerFactory.GetDiscoveryManager()); + } + } +} diff --git a/test/TestPlatform.TestUtilities/ExecutionManager.cs b/test/TestPlatform.TestUtilities/ExecutionManager.cs new file mode 100644 index 0000000000..54203f5444 --- /dev/null +++ b/test/TestPlatform.TestUtilities/ExecutionManager.cs @@ -0,0 +1,99 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.TestUtilities +{ + using System.Diagnostics; + using System.IO; + + /// + /// The execution manager. + /// + public static class ExecutionManager + { +#if DEBUG + private const string RelativeVsTestPath = @"artifacts\src\Microsoft.TestPlatform.VSIXCreator\bin\Debug\net461\win7-x64"; +#else + private const string RelativeVsTestPath = @"artifacts\src\Microsoft.TestPlatform.VSIXCreator\bin\Release\net461\win7-x64"; +#endif + + /// + /// Gets the executing location. + /// + public static string ExecutingLocation + { + get + { + return Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName); + } + } + + /// + /// The execute. + /// + /// + /// The args. + /// + /// + /// The standard out. + /// + /// + /// The standard error. + /// + public static void Execute(string args, out string stdOut, out string stdError) + { + stdError = string.Empty; + stdOut = string.Empty; + + using (Process vstestconsole = new Process()) + { + vstestconsole.StartInfo.FileName = GetVstestConsolePath(); + vstestconsole.StartInfo.Arguments = args; + vstestconsole.StartInfo.UseShellExecute = false; + vstestconsole.StartInfo.WorkingDirectory = GetBaseDirectory(); + vstestconsole.StartInfo.RedirectStandardError = true; + vstestconsole.StartInfo.RedirectStandardOutput = true; + vstestconsole.StartInfo.CreateNoWindow = true; + vstestconsole.Start(); + + stdError = vstestconsole.StandardError.ReadToEnd(); + stdOut = vstestconsole.StandardOutput.ReadToEnd(); + + vstestconsole.WaitForExit(); + } + } + + /// + /// The get VS test console path. + /// + /// + /// The . + /// + public static string GetVstestConsolePath() + { + var path = Path.Combine(GetBaseDirectory(), "vstest.console.exe"); + return path; + } + + /// + /// The get test adapter path. + /// + /// + /// The . + /// + public static string GetTestAdapterPath() + { + var path = Path.Combine(GetBaseDirectory(), "Adapter"); + return path; + } + + public static string GetBaseDirectory() + { + var baseDirectory = string.Empty; + + var directoryInfo = new DirectoryInfo(Directory.GetCurrentDirectory()); + baseDirectory = Path.Combine(directoryInfo.Parent?.Parent.FullName, RelativeVsTestPath); + + return baseDirectory; + } + } +} diff --git a/test/TestPlatform.TestUtilities/Properties/AssemblyInfo.cs b/test/TestPlatform.TestUtilities/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..ed20e131f5 --- /dev/null +++ b/test/TestPlatform.TestUtilities/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +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: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("TestPlatform.TestUtilities")] +[assembly: AssemblyTrademark("")] + +// 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("f52a4d48-90b3-4004-8c98-d2786cc9b965")] diff --git a/test/TestPlatform.TestUtilities/TestPlatform.TestUtilities.xproj b/test/TestPlatform.TestUtilities/TestPlatform.TestUtilities.xproj new file mode 100644 index 0000000000..77938e4991 --- /dev/null +++ b/test/TestPlatform.TestUtilities/TestPlatform.TestUtilities.xproj @@ -0,0 +1,21 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + f52a4d48-90b3-4004-8c98-d2786cc9b965 + TestPlatform.TestUtilities + .\obj + ..\..\artifacts\ + v4.5.2 + + + + 2.0 + + + diff --git a/test/TestPlatform.TestUtilities/VSTestConsoleTestBase.cs b/test/TestPlatform.TestUtilities/VSTestConsoleTestBase.cs new file mode 100644 index 0000000000..2dfa4e82df --- /dev/null +++ b/test/TestPlatform.TestUtilities/VSTestConsoleTestBase.cs @@ -0,0 +1,217 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.TestUtilities +{ + using System.Linq; + using System.Text.RegularExpressions; + + using Microsoft.VisualStudio.TestTools.UnitTesting; + + /// + /// The VS test console test base. + /// + public class VsTestConsoleTestBase + { + private const string TestSummaryStatusMessageFormat = "Total tests: {0}. Passed: {1}. Failed: {2}. Skipped: {3}"; + private string standardTestOutput = string.Empty; + + private string standardTestError = string.Empty; + + /// + /// The invoke VS test. + /// + /// + /// The arguments. + /// + public void InvokeVsTest(string arguments) + { + ExecutionManager.Execute(arguments, out this.standardTestOutput, out this.standardTestError); + this.FormatStandardOutCome(); + } + + /// + /// The invoke VS test. + /// + /// + /// The test assembly. + /// + /// + /// The test Adapter Path. + /// + /// + /// The run Settings. + /// + public void InvokeVsTestForExecution(string testAssembly, string testAdapterPath, string runSettings = "") + { + var arguments = PrepareArguments(testAssembly, testAdapterPath, runSettings); + this.InvokeVsTest(arguments); + } + + /// + /// The invoke VS test for discovery. + /// + /// + /// The test assembly. + /// + /// + /// The test adapter path. + /// + /// + /// The run Settings. + /// + public void InvokeVsTestForDiscovery(string testAssembly, string testAdapterPath, string runSettings = "") + { + var arguments = PrepareArguments(testAssembly, testAdapterPath, runSettings); + arguments = string.Concat(arguments, " /listtests"); + this.InvokeVsTest(arguments); + } + + /// + /// Validate if the overall Test count and results are matching. + /// + /// passed test count + /// failed test count + /// skipped test count + public void ValidateSummaryStatus(int passedTestsCount, int failedTestsCount, int skippedTestsCount) + { + var summaryStatus = string.Format( + TestSummaryStatusMessageFormat, + passedTestsCount + failedTestsCount + skippedTestsCount, + passedTestsCount, + failedTestsCount, + skippedTestsCount); + + Assert.IsTrue(this.standardTestOutput.Contains(summaryStatus), "The Test summary does not match. Expected: {0} Test Output: {1}", this.standardTestOutput, summaryStatus); + } + + /// + /// Validates if the test results have the specified set of passed tests. + /// + /// The set of passed tests. + /// Provide the full test name similar to this format SampleTest.TestCode.TestMethodPass. + public void ValidatePassedTests(params string[] passedTests) + { + foreach (var test in passedTests) + { + var flag = this.standardTestOutput.Contains("Passed " + test) + || this.standardTestOutput.Contains("Passed " + GetTestMethodName(test)); + Assert.IsTrue(flag, "Test {0} does not appear in passed tests list.", test); + } + } + + /// + /// Validates if the test results have the specified set of failed tests. + /// + /// The set of failed tests. + /// + /// Provide the full test name similar to this format SampleTest.TestCode.TestMethodFailed. + /// Also validates whether these tests have stack trace info. + /// + public void ValidateFailedTests(params string[] failedTests) + { + foreach (var test in failedTests) + { + var flag = this.standardTestOutput.Contains("Failed " + test) + || this.standardTestOutput.Contains("Failed " + GetTestMethodName(test)); + Assert.IsTrue(flag, "Test {0} does not appear in failed tests list.", test); + + // Verify stack information as well. + Assert.IsTrue(this.standardTestError.Contains(GetTestMethodName(test)), "No stack trace for failed test: {0}", test); + } + } + + /// + /// Validates if the test results have the specified set of skipped tests. + /// + /// The set of skipped tests. + /// Provide the full test name similar to this format SampleTest.TestCode.TestMethodSkipped. + public void ValidateSkippedTests(params string[] skippedTests) + { + foreach (var test in skippedTests) + { + var flag = this.standardTestOutput.Contains("Skipped " + test) + || this.standardTestOutput.Contains("Skipped " + GetTestMethodName(test)); + Assert.IsTrue(flag, "Test {0} does not appear in skipped tests list.", test); + } + } + + /// + /// The validate discovered tests. + /// + /// + /// The discovered tests list. + /// + public void ValidateDiscoveredTests(params string[] discoveredTestsList) + { + foreach (var test in discoveredTestsList) + { + var flag = this.standardTestOutput.Contains(test) + || this.standardTestOutput.Contains(GetTestMethodName(test)); + Assert.IsTrue(flag, "Test {0} does not appear in discovered tests list.", test); + } + } + + /// + /// The prepare arguments. + /// + /// + /// The test assembly. + /// + /// + /// The test adapter path. + /// + /// + /// The run settings. + /// + /// + /// The . + /// + public static string PrepareArguments(string testAssembly, string testAdapterPath, string runSettings) + { + string arguments; + if (string.IsNullOrWhiteSpace(runSettings)) + { + arguments = string.Concat("\"", testAssembly, "\"", " /testadapterpath:\"", testAdapterPath, "\""); + } + else + { + arguments = string.Concat( + "\"", + testAssembly, + "\"", + " /testadapterpath:\"", + testAdapterPath, + "\"", + " /settings:\"", + runSettings, + "\""); + } + + return arguments; + } + + /// + /// Gets the test method name from full name. + /// + /// test case complete name + /// just the test name + private static string GetTestMethodName(string testFullName) + { + string testMethodName = string.Empty; + + var splits = testFullName.Split('.'); + if (splits.Count() >= 3) + { + testMethodName = testFullName.Split('.')[2]; + } + + return testMethodName; + } + + private void FormatStandardOutCome() + { + this.standardTestError = Regex.Replace(this.standardTestError, @"\s+", " "); + this.standardTestOutput = Regex.Replace(this.standardTestOutput, @"\s+", " "); + } + } +} diff --git a/test/TestPlatform.TestUtilities/project.json b/test/TestPlatform.TestUtilities/project.json new file mode 100644 index 0000000000..3f6bc39b3e --- /dev/null +++ b/test/TestPlatform.TestUtilities/project.json @@ -0,0 +1,27 @@ +{ + "version": "1.0.0-*", + + "dependencies": { + "MSTest.TestFramework": "1.0.0-preview" + }, + + "frameworks": { + "netcoreapp1.0": { + "imports": [ + "dnxcore50", + "portable-net45+win8" + ], + "dependencies": { + "Microsoft.NETCore.App": { + "type": "platform", + "version": "1.0.0-rc2-3002702" + } + } + }, + "net461": { + "frameworkAssemblies": { + "System.Runtime": "" + } + } + } +} diff --git a/test/testhost.UnitTests/Properties/AssemblyInfo.cs b/test/testhost.UnitTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..94372a25cd --- /dev/null +++ b/test/testhost.UnitTests/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +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: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("TestPlatform.TestHost.UnitTests")] +[assembly: AssemblyTrademark("")] + +// 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("9e729143-86db-4acb-83c5-8dd40d27ffe7")] diff --git a/test/testhost.UnitTests/project.json b/test/testhost.UnitTests/project.json new file mode 100644 index 0000000000..7e62f5d035 --- /dev/null +++ b/test/testhost.UnitTests/project.json @@ -0,0 +1,34 @@ +{ + "version": "15.0.0-*", + + "buildOptions": { + "delaySign": true, + "keyFile": "../../scripts/key.snk", + "warningsAsErrors": true + }, + + "dependencies": { + "Microsoft.NETCore.App": { + "type": "platform", + "version": "1.0.0-rc2-3002702" + }, + "dotnet-test-mstest": { + "version": "1.0.1-preview", + "exclude": "compile" + }, + "MSTest.TestFramework": "1.0.0-preview", + "moq.netcore": "4.4.0-beta8", + "System.Diagnostics.TraceSource": "4.0.0-rc2-24015" + }, + + "frameworks": { + "netcoreapp1.0": { + "imports": [ + "dnxcore50", + "portable-net45+win8" + ] + } + }, + + "testRunner": "mstest" +} \ No newline at end of file diff --git a/test/testhost.UnitTests/testhost.UnitTests.xproj b/test/testhost.UnitTests/testhost.UnitTests.xproj new file mode 100644 index 0000000000..e611e7895c --- /dev/null +++ b/test/testhost.UnitTests/testhost.UnitTests.xproj @@ -0,0 +1,22 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 9e729143-86db-4acb-83c5-8dd40d27ffe7 + TestPlatform.TestHost.UnitTests + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\ + v4.5.2 + + + 2.0 + + + + + + \ No newline at end of file diff --git a/test/vstest.console.UnitTests/CommandLine/CommandLineOptionsTests.cs b/test/vstest.console.UnitTests/CommandLine/CommandLineOptionsTests.cs new file mode 100644 index 0000000000..1d1d3e503b --- /dev/null +++ b/test/vstest.console.UnitTests/CommandLine/CommandLineOptionsTests.cs @@ -0,0 +1,139 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.CommandLine +{ + using System; + using System.Collections.ObjectModel; + using System.Linq; + + using Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Implementations; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Moq; + using Utilities.Helpers.Interfaces; + + [TestClass] + public class CommandLineOptionsTests + { + [TestMethod] + public void CommandLineOptionsDefaultBatchSizeIsTen() + { + Assert.AreEqual(10, CommandLineOptions.Instance.BatchSize); + } + + [TestMethod] + public void CommandLineOptionsDefaultTestRunStatsEventTimeoutIsOnePointFiveSec() + { + var timeout = new TimeSpan(0, 0, 0, 1, 500); + Assert.AreEqual(timeout, CommandLineOptions.Instance.TestRunStatsEventTimeout); + } + + [TestMethod] + public void CommandLineOptionsGetForSourcesPropertyShouldReturnReadonlySourcesEnumerable() + { + Assert.IsTrue(CommandLineOptions.Instance.Sources is ReadOnlyCollection); + } + + [TestMethod] + public void CommandLineOptionsGetForHasPhoneContextPropertyIfTargetDeviceIsSetReturnsTrue() + { + Assert.IsFalse(CommandLineOptions.Instance.HasPhoneContext); + // Set some not null value + CommandLineOptions.Instance.TargetDevice = "TargetDevice"; + Assert.IsTrue(CommandLineOptions.Instance.HasPhoneContext); + } + + [TestMethod] + public void CommandLineOptionsAddSourceShouldThrowCommandLineExceptionForNullSource() + { + try + { + CommandLineOptions.Instance.AddSource(null); + } + catch (Exception ex) + { + Assert.IsTrue(ex is CommandLineException); + Assert.AreEqual("Cannot be null or empty", ex.Message); + } + } + + [TestMethod] + public void CommandLineOptionsAddSourceShouldThrowCommandLineExceptionForInvalidSource() + { + try + { + CommandLineOptions.Instance.AddSource("DummySource"); + } + catch (Exception ex) + { + Assert.IsTrue(ex is CommandLineException); + Assert.AreEqual("The test source file \"DummySource\" provided was not found.", ex.Message); + } + } + + [TestMethod] + public void CommandLineOptionsAddSourceShouldAddSourceThrowExceptionIfDuplicateSource() + { + var testFilePath = "DummyTestFile.txt"; + + // Setup mocks. + var mockFileHelper = new MockFileHelper(); + mockFileHelper.ExistsInvoker = (path) => + { + if (string.Equals(path, testFilePath)) + { + return true; + } + else + { + return false; + } + }; + + CommandLineOptions.Instance.Reset(); + CommandLineOptions.Instance.FileHelper = mockFileHelper; + CommandLineOptions.Instance.AddSource(testFilePath); + + var isExceptionThrown = false; + try + { + // Trying to add duplicate source + CommandLineOptions.Instance.AddSource(testFilePath); + } + catch (Exception ex) + { + isExceptionThrown = true; + Assert.IsTrue(ex is CommandLineException); + Assert.AreEqual("Duplicate source " + testFilePath + " specified.", ex.Message); + } + + Assert.IsTrue(isExceptionThrown); + } + + [TestMethod] + public void CommandLineOptionsAddSourceShouldAddSourceForValidSource() + { + string testFilePath = "DummyTestFile.txt"; + + // Setup mocks. + var mockFileHelper = new MockFileHelper(); + mockFileHelper.ExistsInvoker = (path) => + { + if (string.Equals(path, testFilePath)) + { + return true; + } + else + { + return false; + } + }; + + CommandLineOptions.Instance.Reset(); + CommandLineOptions.Instance.FileHelper = mockFileHelper; + CommandLineOptions.Instance.AddSource(testFilePath); + + // Check if the testsource is present in the TestSources + Assert.IsTrue(CommandLineOptions.Instance.Sources.Contains(testFilePath)); + } + } +} diff --git a/test/vstest.console.UnitTests/CommandLine/TestRunResultAggregatorTests.cs b/test/vstest.console.UnitTests/CommandLine/TestRunResultAggregatorTests.cs new file mode 100644 index 0000000000..7d09c90979 --- /dev/null +++ b/test/vstest.console.UnitTests/CommandLine/TestRunResultAggregatorTests.cs @@ -0,0 +1,121 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.CommandLine +{ + using System; + using System.Collections.Generic; + using System.Runtime.Serialization; + + using Client.Execution; + + using CrossPlatEngine.Execution; + + using Microsoft.VisualStudio.TestTools.UnitTesting; + + using Moq; + + using ObjectModel; + using ObjectModel.Client; + using ObjectModel.Logging; + + [TestClass] + public class TestRunResultAggregatorTests + { + TestRunResultAggregator resultAggregator = TestRunResultAggregator.Instance; + Mock mockTestRunRequest; + + [TestInitialize] + public void TestInit() + { + resultAggregator.Reset(); + mockTestRunRequest = new Mock(); + resultAggregator.RegisterTestRunEvents(mockTestRunRequest.Object); + } + + [TestCleanup] + public void TestCleanup() + { + resultAggregator.UnregisterTestRunEvents(mockTestRunRequest.Object); + } + + [TestMethod] + public void DefaultOutcomeIsPassed() + { + Assert.AreEqual(TestOutcome.Passed, resultAggregator.Outcome); + } + + [TestMethod] + public void MarkTestRunFailedSetsOutcomeToFailed() + { + resultAggregator.MarkTestRunFailed(); + Assert.AreEqual(TestOutcome.Failed, resultAggregator.Outcome); + } + + [TestMethod] + public void TestRunMessageHandlerForMessageLevelErrorSetsOutcomeToFailed() + { + var messageArgs = new TestRunMessageEventArgs(TestMessageLevel.Error, "bad stuff"); + mockTestRunRequest.Raise(tr => tr.TestRunMessage += null, messageArgs); + Assert.AreEqual(TestOutcome.Failed, resultAggregator.Outcome); + } + + [TestMethod] + public void TestRunCompletionHandlerForTestRunStatisticsNullSetsOutcomeToFailed() + { + var messageArgs = new TestRunCompleteEventArgs(null, false, false, null, null, new TimeSpan()); + mockTestRunRequest.Raise(tr => tr.OnRunCompletion += null, messageArgs); + Assert.AreEqual(TestOutcome.Failed, resultAggregator.Outcome); + } + + [TestMethod] + public void TestRunCompletionHandlerForTestRunStatsWithOneOrMoreFailingTestsSetsOutcomeToFailed() + { + var testOutcomeDict = new System.Collections.Generic.Dictionary(); + testOutcomeDict.Add(TestOutcome.Failed, 1); + var stats = new TestableTestRunStats(testOutcomeDict); + + var messageArgs = new TestRunCompleteEventArgs(stats, false, false, null, null, new TimeSpan()); + this.mockTestRunRequest.Raise(tr => tr.OnRunCompletion += null, messageArgs); + Assert.AreEqual(TestOutcome.Failed, resultAggregator.Outcome); + } + + #region implementation + + private class TestableTestRunStats : ITestRunStatistics + { + public TestableTestRunStats(Dictionary stats) + { + this.Stats = stats; + } + + public long ExecutedTests { get; set; } + + /// + /// Gets the test stats which is the test outcome versus its state. + /// + [DataMember] + public IDictionary Stats { get; private set; } + + /// + /// Gets the number of tests with a specified outcome. + /// + /// The test outcome. + /// The number of tests with this outcome. + public long this[TestOutcome testOutcome] + { + get + { + long count; + if (this.Stats.TryGetValue(testOutcome, out count)) + { + return count; + } + + return 0; + } + } + } + + #endregion + } +} diff --git a/test/vstest.console.UnitTests/ExceptionUtilities.cs b/test/vstest.console.UnitTests/ExceptionUtilities.cs new file mode 100644 index 0000000000..74ea7d2d23 --- /dev/null +++ b/test/vstest.console.UnitTests/ExceptionUtilities.cs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests +{ + using System; + + using Microsoft.VisualStudio.TestTools.UnitTesting; + + /// + /// This only exists because there is an issue with MSTest v2 and ThrowsException with a message API. + /// Move to Assert.ThrowException() with a message once the bug is fixed. + /// + public static class ExceptionUtilities + { + public static void ThrowsException(Action action , string format, params string[] args) + { + var isExceptionThrown = false; + + try + { + action(); + } + catch (Exception ex) + { + Assert.AreEqual(typeof(T), ex.GetType()); + isExceptionThrown = true; + var message = string.Format(format, args); + StringAssert.Contains(ex.Message, message); + } + + Assert.IsTrue(isExceptionThrown, "No Exception Thrown"); + } + } +} diff --git a/test/vstest.console.UnitTests/ExecutorUnitTests.cs b/test/vstest.console.UnitTests/ExecutorUnitTests.cs new file mode 100644 index 0000000000..db610b1860 --- /dev/null +++ b/test/vstest.console.UnitTests/ExecutorUnitTests.cs @@ -0,0 +1,85 @@ +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests +{ + using Implementations; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Processors; + using System; + using System.Collections.Generic; + using TestPlatform.CommandLine.Processors; + using TestPlatform.Utilities.Helpers.Interfaces; + using Utilities; + using System.Linq; + using System.Reflection; + + [TestClass] + public class ExecutorUnitTests + { + /// + /// Executor should Print splash screen first + /// + [TestMethod] + public void ExecutorPrintsSplashScreenTest() + { + var mockOutput = new MockOutput(); + var exitCode = new Executor(mockOutput).Execute("/?"); + + Assert.AreEqual(0, exitCode, "Exit code must be One for bad arguments"); + + // Verify that messages exist + Assert.IsTrue(mockOutput.Messages.Count > 0, "Executor must print atleast copyright info"); + Assert.IsNotNull(mockOutput.Messages.First().Message, "First Printed Message cannot be null or empty"); + + // Just check first 20 characters - don't need to check whole thing as assembly version is variable + Assert.IsTrue(mockOutput.Messages.First().Message.Contains( + Microsoft.VisualStudio.TestPlatform.CommandLine.Resources.MicrosoftCommandLineTitle.Substring(0, 20)), + "First Printed message must be Microsoft Copyright"); + } + + + /// + /// Executor should try find "project.json" if empty args given + /// + [TestMethod] + public void ExecutorEmptyArgsCallRunTestsProcessor() + { + var mockOutput = new MockOutput(); + var exitCode = new Executor(mockOutput).Execute(null); + + // Since no projectjsons exist in current folder it should fail + Assert.AreEqual(1, exitCode, "Exit code must be One for bad arguments"); + + //// Verify that messages exist + //Assert.IsTrue(mockOutput.Messages.Count > 0, "Executor must print atleast copyright info"); + //Assert.IsNotNull(mockOutput.Messages.First().Message, "First Printed Message cannot be null or empty"); + + //// Just check first 20 characters - don't need to check whole thing as assembly version is variable + //Assert.IsTrue(mockOutput.Messages.First().Message.Contains( + // Microsoft.VisualStudio.TestPlatform.CommandLine.Resources.MicrosoftCommandLineTitle.Substring(0, 20)), + // "First Printed message must be Microsoft Copyright"); + } + + + private class MockOutput : IOutput + { + public List Messages { get; set; } = new List(); + + public void Write(string message, OutputLevel level) + { + Messages.Add(new OutputMessage() { Message = message, Level = level }); + } + + public void WriteLine(string message, OutputLevel level) + { + Messages.Add(new OutputMessage() { Message = message, Level = level }); + } + } + + private class OutputMessage + { + public string Message { get; set; } + public OutputLevel Level { get; set; } + } + } + + +} diff --git a/test/vstest.console.UnitTests/Implementations/MockFileHelper.cs b/test/vstest.console.UnitTests/Implementations/MockFileHelper.cs new file mode 100644 index 0000000000..c4e78507c7 --- /dev/null +++ b/test/vstest.console.UnitTests/Implementations/MockFileHelper.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Implementations +{ + using System; + using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces; + + public class MockFileHelper : IFileHelper + { + public Func ExistsInvoker { get; set; } + + public bool Exists(string path) + { + if (this.ExistsInvoker != null) + { + return this.ExistsInvoker.Invoke(path); + } + + return false; + } + } +} diff --git a/test/vstest.console.UnitTests/Internal/ConsoleLoggerTests.cs b/test/vstest.console.UnitTests/Internal/ConsoleLoggerTests.cs new file mode 100644 index 0000000000..e6e75a2bb6 --- /dev/null +++ b/test/vstest.console.UnitTests/Internal/ConsoleLoggerTests.cs @@ -0,0 +1,303 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Internal +{ + using Microsoft.VisualStudio.TestPlatform.CommandLine.Internal; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.Utilities; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Moq; + using System; + using System.Collections.Generic; + using Microsoft.VisualStudio.TestPlatform.Common.Logging; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + using Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors; + using System.Threading; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using System.Globalization; + using System.Collections.ObjectModel; + using Resources = Microsoft.VisualStudio.TestPlatform.CommandLine.Resources; + + [TestClass] + public class ConsoleLoggerTests + { + private Mock testRunRequest; + private Mock events; + private Mock mockOutput; + private TestLoggerManager testLoggerManager; + private ConsoleLogger consoleLogger; + + [TestInitialize] + public void Initialize() + { + RunTestsArgumentProcessorTests.SetupMockExtensions(); + + // Setup Mocks and other dependencies + this.Setup(); + } + + [TestCleanup] + public void Cleanup() + { + DummyTestLoggerManager.Cleanup(); + } + + [TestMethod] + public void InitializeShouldThrowExceptionIfEventsIsNull() + { + Assert.ThrowsException(() => + { + this.consoleLogger.Initialize(null, null); + }); + } + + [TestMethod] + public void InitializeShouldNotThrowExceptionIfEventsIsNotNull() + { + var events = new Mock(); + this.consoleLogger.Initialize(events.Object, null); + } + + [TestMethod] + public void TestMessageHandlerShouldThrowExceptionIfEventArgsIsNull() + { + // Raise an event on mock object + Assert.ThrowsException(() => + { + this.testRunRequest.Raise(m => m.TestRunMessage += null, default(TestRunMessageEventArgs)); + }); + } + + [TestMethod] + public void TestMessageHandlerShouldWriteToConsoleIfTestRunEventsAreRaised() + { + int count = 0; + this.mockOutput.Setup(o => o.WriteLine(It.IsAny(), It.IsAny())).Callback( + (s, o) => { count++; }); + + // Raise events on mock object + this.testRunRequest.Raise(m => m.TestRunMessage += null, new TestRunMessageEventArgs(TestMessageLevel.Informational, "Informational123")); + this.testRunRequest.Raise(m => m.TestRunMessage += null, new TestRunMessageEventArgs(TestMessageLevel.Error, "Error123")); + this.testRunRequest.Raise(m => m.TestRunMessage += null, new TestRunMessageEventArgs(TestMessageLevel.Warning, "Warning123")); + + // Added this for synchronization + SpinWait.SpinUntil(() => count == 3, 300); + + this.mockOutput.Verify(o => o.WriteLine("Information: Informational123", OutputLevel.Information), Times.Once()); + this.mockOutput.Verify(o => o.WriteLine("Warning: Warning123", OutputLevel.Warning), Times.Once()); + this.mockOutput.Verify(o => o.WriteLine("Error: Error123", OutputLevel.Error), Times.Once()); + } + + [TestMethod] + public void TestResultHandlerShouldThowExceptionIfEventArgsIsNull() + { + var eventarg = default(TestRunChangedEventArgs); + + // Raise an event on mock object + Assert.ThrowsException(() => + { + testRunRequest.Raise(m => m.OnRunStatsChange += null, eventarg); + }); + } + + [TestMethod] + public void TestResultHandlerShouldWriteToConsoleIfTestResultEventsAreRaised() + { + var eventArgs = new TestRunChangedEventArgs(null, this.GetTestResultsObject(), null); + + // Raise an event on mock object + this.testRunRequest.Raise(m => m.OnRunStatsChange += null, eventArgs); + + this.mockOutput.Verify(o => o.WriteLine(string.Format(CultureInfo.CurrentCulture, Resources.PassedTestIndicator, "TestName"), OutputLevel.Information), Times.Once()); + this.mockOutput.Verify(o => o.WriteLine(string.Format(CultureInfo.CurrentCulture, Resources.FailedTestIndicator, "TestName"), OutputLevel.Information), Times.Once()); + this.mockOutput.Verify(o => o.WriteLine(string.Format(CultureInfo.CurrentCulture, Resources.SkippedTestIndicator, "TestName"), OutputLevel.Information), Times.Once()); + this.mockOutput.Verify(o => o.WriteLine(string.Format(CultureInfo.CurrentCulture, Resources.SkippedTestIndicator, "TestName"), OutputLevel.Information), Times.Once()); + this.mockOutput.Verify(o => o.WriteLine(string.Format(CultureInfo.CurrentCulture, Resources.SkippedTestIndicator, "TestName"), OutputLevel.Information), Times.Once()); + this.mockOutput.Verify(o => o.WriteLine(string.Format(CultureInfo.CurrentCulture, Resources.SkippedTestIndicator, "TestName"), OutputLevel.Information), Times.Once()); + } + + [TestMethod] + public void TestResulCompleteHandlerShouldThowExceptionIfEventArgsIsNull() + { + // Raise an event on mock object + Assert.ThrowsException(() => + { + this.testRunRequest.Raise(m => m.OnRunCompletion += null, default(TestRunCompleteEventArgs)); + }); + } + + [TestMethod] + public void TestRunCompleteHandlerShouldWriteToConsoleIfTestsPass() + { + // Raise an event on mock object raised to register test case count + var eventArgs = new TestRunChangedEventArgs(null, this.GetTestResultObject(TestOutcome.Passed), null); + this.testRunRequest.Raise(m => m.OnRunStatsChange += null, eventArgs); + + // Raise an event on mock object + this.testRunRequest.Raise(m => m.OnRunCompletion += null, new TestRunCompleteEventArgs(null, false, false, null, null, new TimeSpan(1, 0, 0, 0))); + + this.mockOutput.Verify(o => o.WriteLine(string.Format(CultureInfo.CurrentCulture, Resources.TestRunSummary, 1, 1, 0, 0), OutputLevel.Information), Times.Once()); + this.mockOutput.Verify(o => o.WriteLine(Resources.TestRunSuccessful, OutputLevel.Information), Times.Once()); + } + + [TestMethod] + public void TestRunCompleteHandlerShouldWriteToConsoleIfTestsFail() + { + // Raise an event on mock object raised to register test case count and mark Outcome as Outcome.Failed + var eventArgs = new TestRunChangedEventArgs(null, GetTestResultObject(TestOutcome.Failed), null); + this.testRunRequest.Raise(m => m.OnRunStatsChange += null, eventArgs); + + // Raise an event on mock object + this.testRunRequest.Raise(m => m.OnRunCompletion += null, new TestRunCompleteEventArgs(null, false, false, null, null, new TimeSpan(1, 0, 0, 0))); + + this.mockOutput.Verify(o => o.WriteLine(string.Format(CultureInfo.CurrentCulture, Resources.TestRunSummary, 1, 0, 1, 0), OutputLevel.Information), Times.Once()); + this.mockOutput.Verify(o => o.WriteLine(Resources.TestRunFailed, OutputLevel.Error), Times.Once()); + } + + [TestMethod] + public void PrintTimeHandlerShouldPrintElapsedTimeOnConsole() + { + // Raise an event on mock object raised to register test case count + var eventArgs = new TestRunChangedEventArgs(null, GetTestResultObject(TestOutcome.Passed), null); + this.testRunRequest.Raise(m => m.OnRunStatsChange += null, eventArgs); + + // Raise events on mock object + this.testRunRequest.Raise(m => m.OnRunCompletion += null, new TestRunCompleteEventArgs(null, false, false, null, null, new TimeSpan(1, 0, 0, 0))); + this.testRunRequest.Raise(m => m.OnRunCompletion += null, new TestRunCompleteEventArgs(null, false, false, null, null, new TimeSpan(0, 1, 0, 0))); + this.testRunRequest.Raise(m => m.OnRunCompletion += null, new TestRunCompleteEventArgs(null, false, false, null, null, new TimeSpan(0, 0, 1, 0))); + this.testRunRequest.Raise(m => m.OnRunCompletion += null, new TestRunCompleteEventArgs(null, false, false, null, null, new TimeSpan(0, 0, 0, 1))); + + // Verify PrintTimeSpan with different formats + this.mockOutput.Verify(o => o.WriteLine(string.Format(CultureInfo.CurrentCulture, Resources.ExecutionTimeFormatString, 1, Resources.Days), OutputLevel.Information), Times.Once()); + this.mockOutput.Verify(o => o.WriteLine(string.Format(CultureInfo.CurrentCulture, Resources.ExecutionTimeFormatString, 1, Resources.Hours), OutputLevel.Information), Times.Once()); + this.mockOutput.Verify(o => o.WriteLine(string.Format(CultureInfo.CurrentCulture, Resources.ExecutionTimeFormatString, 1, Resources.Minutes), OutputLevel.Information), Times.Once()); + this.mockOutput.Verify(o => o.WriteLine(string.Format(CultureInfo.CurrentCulture, Resources.ExecutionTimeFormatString, 1, Resources.Seconds), OutputLevel.Information), Times.Once()); + } + + [TestMethod] + public void DisplayFullInformationShouldWriteErrorMessageAndStackTraceToConsole() + { + var testresults = this.GetTestResultObject(TestOutcome.Failed); + testresults[0].ErrorMessage = "ErrorMessage"; + testresults[0].ErrorStackTrace = "ErrorStackTrace"; + + var eventArgs = new TestRunChangedEventArgs(null, testresults, null); + + // Raise an event on mock object + this.testRunRequest.Raise(m => m.OnRunStatsChange += null, eventArgs); + + this.mockOutput.Verify(o => o.WriteLine(string.Format(CultureInfo.CurrentCulture, "{0}{1}", Resources.TestMessageFormattingPrefix, "ErrorMessage"), OutputLevel.Error), Times.Once()); + this.mockOutput.Verify(o => o.Write(string.Format(CultureInfo.CurrentCulture, "{0}", "ErrorStackTrace"), OutputLevel.Error), Times.Once()); + this.mockOutput.Verify(o => o.WriteLine(Resources.ErrorMessageBanner, OutputLevel.Error), Times.Once()); + this.mockOutput.Verify(o => o.WriteLine(Resources.StacktraceBanner, OutputLevel.Error), Times.Once()); + } + + [TestMethod] + public void GetTestMessagesShouldWriteMessageAndStackTraceToConsole() + { + var count = 0; + this.mockOutput.Setup(o => o.WriteLine(It.IsAny(), It.IsAny())).Callback( + (s, o) => { count++; }); + + var testresults = this.GetTestResultObject(TestOutcome.Failed); + testresults[0].Messages.Add(new TestResultMessage(TestResultMessage.StandardOutCategory, "StandardOutCategory")); + testresults[0].Messages.Add(new TestResultMessage(TestResultMessage.StandardErrorCategory, "StandardErrorCategory")); + testresults[0].Messages.Add(new TestResultMessage(TestResultMessage.AdditionalInfoCategory, "AdditionalInfoCategory")); + testresults[0].Messages.Add(new TestResultMessage(TestResultMessage.AdditionalInfoCategory, "AnotherAdditionalInfoCategory")); + var eventArgs = new TestRunChangedEventArgs(null, testresults, null); + + // Raise an event on mock object + this.testRunRequest.Raise(m => m.OnRunStatsChange += null, eventArgs); + + // Added this for synchronization + SpinWait.SpinUntil(() => count == 3, 300); + + this.mockOutput.Verify(o => o.WriteLine(Resources.StdOutMessagesBanner, OutputLevel.Information), Times.Once()); + this.mockOutput.Verify(o => o.Write(" StandardOutCategory", OutputLevel.Information), Times.Once()); + + this.mockOutput.Verify(o => o.WriteLine(Resources.StdErrMessagesBanner, OutputLevel.Error), Times.Once()); + this.mockOutput.Verify(o => o.Write(" StandardErrorCategory", OutputLevel.Error), Times.Once()); + + this.mockOutput.Verify(o => o.WriteLine(Resources.AddnlInfoMessagesBanner, OutputLevel.Information), Times.Once()); + this.mockOutput.Verify(o => o.Write(" AdditionalInfoCategory AnotherAdditionalInfoCategory", OutputLevel.Information), Times.Once()); + } + + [TestMethod] + public void AttachmentInformationShouldBeWrittenToConsoleIfAttachmentsArePresent() + { + var attachmentSet = new AttachmentSet(new Uri("test://uri"), "myattachmentset"); + + var uriDataAttachment = new UriDataAttachment(new Uri("file://server/filename.ext"), "description"); + attachmentSet.Attachments.Add(uriDataAttachment); + + var uriDataAttachment1 = new UriDataAttachment(new Uri("file://server/filename1.ext"), "description"); + attachmentSet.Attachments.Add(uriDataAttachment1); + + var attachmentSetList = new List(); + attachmentSetList.Add(attachmentSet); + + var testRunCompleteEventArgs = new TestRunCompleteEventArgs(null, false, false, null, new Collection(attachmentSetList), new TimeSpan(1, 0, 0, 0)); + + // Raise an event on mock object raised to register test case count and mark Outcome as Outcome.Failed + this.testRunRequest.Raise(m => m.OnRunCompletion += null, testRunCompleteEventArgs); + this.mockOutput.Verify(o => o.WriteLine(string.Format(CultureInfo.CurrentCulture, Resources.AttachmentOutputFormat, uriDataAttachment.Uri.LocalPath), OutputLevel.Information), Times.Once()); + this.mockOutput.Verify(o => o.WriteLine(string.Format(CultureInfo.CurrentCulture, Resources.AttachmentOutputFormat, uriDataAttachment1.Uri.LocalPath), OutputLevel.Information), Times.Once()); + } + + /// + /// Setup Mocks and other dependencies + /// + private void Setup() + { + // mock for ITestRunRequest + this.testRunRequest = new Mock(); + this.events = new Mock(); + this.mockOutput = new Mock(); + + this.consoleLogger = new ConsoleLogger(this.mockOutput.Object); + this.consoleLogger.Initialize(this.events.Object, null); + + DummyTestLoggerManager.Cleanup(); + // Create Instance of TestLoggerManager + this.testLoggerManager = TestLoggerManager.Instance; + //Console.WriteLine(TestLoggerManager.Instance.GetHashCode()); + this.testLoggerManager.AddLogger(new Uri(ConsoleLogger.ExtensionUri), new Dictionary()); + this.testLoggerManager.EnableLogging(); + + // Register TestRunRequest object + this.testLoggerManager.RegisterTestRunEvents(this.testRunRequest.Object); + } + + private List GetTestResultsObject() + { + var testcase = new TestCase("TestName", new Uri("some://uri"), "TestSource"); + var testresult = new ObjectModel.TestResult(testcase); + testresult.Outcome = TestOutcome.Passed; + + var testresult1 = new ObjectModel.TestResult(testcase); + testresult1.Outcome = TestOutcome.Failed; + + var testresult2 = new ObjectModel.TestResult(testcase); + testresult2.Outcome = TestOutcome.None; + + var testresult3 = new ObjectModel.TestResult(testcase); + testresult3.Outcome = TestOutcome.NotFound; + + var testresult4 = new ObjectModel.TestResult(testcase); + testresult4.Outcome = TestOutcome.Skipped; + + var testresultList = new List { testresult, testresult1, testresult2, testresult3, testresult4 }; + + return testresultList; + } + + private List GetTestResultObject(TestOutcome outcome) + { + var testcase = new TestCase("TestName", new Uri("some://uri"), "TestSource"); + var testresult = new ObjectModel.TestResult(testcase); + testresult.Outcome = outcome; + var testresultList = new List { testresult }; + return testresultList; + } + } +} diff --git a/test/vstest.console.UnitTests/Processors/BuildPathArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/BuildPathArgumentProcessorTests.cs new file mode 100644 index 0000000000..7d69ff9303 --- /dev/null +++ b/test/vstest.console.UnitTests/Processors/BuildPathArgumentProcessorTests.cs @@ -0,0 +1,109 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors +{ + using Implementations; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Moq; + using Processors; + using System; + using TestPlatform.CommandLine.Processors; + using TestPlatform.Utilities.Helpers.Interfaces; + + [TestClass] + public class BuildBasePathArgumentProcessorTests + { + [TestMethod] + public void GetMetadataShouldReturnBuildBasePathArgumentProcessorCapabilities() + { + BuildBasePathArgumentProcessor processor = new BuildBasePathArgumentProcessor(); + Assert.IsTrue(processor.Metadata.Value is BuildBasePathArgumentProcessorCapabilities); + } + + [TestMethod] + public void GetExecuterShouldReturnBuildBasePathArgumentProcessorCapabilities() + { + BuildBasePathArgumentProcessor processor = new BuildBasePathArgumentProcessor(); + Assert.IsTrue(processor.Executor.Value is BuildBasePathArgumentExecutor); + } + + #region BuildBasePathArgumentProcessorCapabilitiesTests + + [TestMethod] + public void CapabilitiesShouldAppropriateProperties() + { + BuildBasePathArgumentProcessorCapabilities capabilities = new BuildBasePathArgumentProcessorCapabilities(); + Assert.AreEqual("/BuildBasePath", capabilities.CommandName); + Assert.AreEqual("/BuildBasePath:\n The directory containing the temporary outputs.", capabilities.HelpContentResourceName); + + Assert.AreEqual(HelpContentPriority.BuildBasePathArgumentProcessorHelpPriority, capabilities.HelpPriority); + Assert.AreEqual(false, capabilities.IsAction); + Assert.AreEqual(ArgumentProcessorPriority.Normal, capabilities.Priority); + + Assert.AreEqual(false, capabilities.AllowMultiple); + Assert.AreEqual(false, capabilities.AlwaysExecute); + Assert.AreEqual(false, capabilities.IsSpecialCommand); + } + + #endregion + + [TestMethod] + public void ExecuterInitializeWithNullOrEmptyBuildBasePathShouldThrowCommandLineException() + { + var options = CommandLineOptions.Instance; + BuildBasePathArgumentExecutor executor = new BuildBasePathArgumentExecutor(options); + + try + { + executor.Initialize(null); + } + catch (Exception ex) + { + Assert.IsTrue(ex is CommandLineException); + Assert.AreEqual(@"The BuildBasePath was not found, provide a valid path and try again.", ex.Message); + } + } + + [TestMethod] + public void ExecuterInitializeWithInvalidBuildBasePathShouldThrowCommandLineException() + { + var options = CommandLineOptions.Instance; + BuildBasePathArgumentExecutor executor = new BuildBasePathArgumentExecutor(options); + + try + { + executor.Initialize(@"C:\Foo.txt"); + } + catch (Exception ex) + { + Assert.IsTrue(ex is CommandLineException); + Assert.AreEqual(@"The BuildBasePath was not found, provide a valid path and try again.", ex.Message); + } + } + + [TestMethod] + public void ExecuterInitializeWithValidBuildBasePathShouldAddBuildBasePathToCommandLineOptions() + { + var options = CommandLineOptions.Instance; + BuildBasePathArgumentExecutor executor = new BuildBasePathArgumentExecutor(options); + string testBuildBasePath = @"C:\BuildDir"; + var mockFileHelper = new MockFileHelper(); + mockFileHelper.ExistsInvoker = (path) => + { + return string.Equals(path, testBuildBasePath); + }; + executor.FileHelper = mockFileHelper; + + executor.Initialize(testBuildBasePath); + Assert.AreEqual(testBuildBasePath, options.BuildBasePath); + } + + [TestMethod] + public void ExecutorExecuteReturnArgumentProcessorResultSuccess() + { + var executor = new BuildBasePathArgumentExecutor(CommandLineOptions.Instance); + var result = executor.Execute(); + Assert.AreEqual(ArgumentProcessorResult.Success, result); + } + } +} diff --git a/test/vstest.console.UnitTests/Processors/ConfigurationArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/ConfigurationArgumentProcessorTests.cs new file mode 100644 index 0000000000..6a8a1a008a --- /dev/null +++ b/test/vstest.console.UnitTests/Processors/ConfigurationArgumentProcessorTests.cs @@ -0,0 +1,98 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors +{ + using Microsoft.VisualStudio.TestTools.UnitTesting; + using System; + using TestPlatform.CommandLine.Processors; + + [TestClass] + public class ConfigurationArgumentProcessorTests + { + [TestMethod] + public void GetMetadataShouldReturnConfigurationArgumentProcessorCapabilities() + { + ConfigurationArgumentProcessor processor = new ConfigurationArgumentProcessor(); + Assert.IsTrue(processor.Metadata.Value is ConfigurationArgumentProcessorCapabilities); + } + + [TestMethod] + public void GetExecuterShouldReturnConfigurationArgumentProcessorCapabilities() + { + ConfigurationArgumentProcessor processor = new ConfigurationArgumentProcessor(); + Assert.IsTrue(processor.Executor.Value is ConfigurationArgumentExecutor); + } + + #region ConfigurationArgumentProcessorCapabilitiesTests + + [TestMethod] + public void CapabilitiesShouldAppropriateProperties() + { + ConfigurationArgumentProcessorCapabilities capabilities = new ConfigurationArgumentProcessorCapabilities(); + Assert.AreEqual("/Configuration", capabilities.CommandName); + Assert.AreEqual("/Configuration:\n The configuration the project is built for i.e. Debug/Release", capabilities.HelpContentResourceName); + + Assert.AreEqual(HelpContentPriority.ConfigurationArgumentProcessorHelpPriority, capabilities.HelpPriority); + Assert.AreEqual(false, capabilities.IsAction); + Assert.AreEqual(ArgumentProcessorPriority.Normal, capabilities.Priority); + + Assert.AreEqual(false, capabilities.AllowMultiple); + Assert.AreEqual(false, capabilities.AlwaysExecute); + Assert.AreEqual(false, capabilities.IsSpecialCommand); + } + + #endregion + + [TestMethod] + public void ExecuterInitializeWithNullOrEmptyConfigurationShouldThrowCommandLineException() + { + var options = CommandLineOptions.Instance; + ConfigurationArgumentExecutor executor = new ConfigurationArgumentExecutor(options); + + try + { + executor.Initialize(null); + } + catch (Exception ex) + { + Assert.IsTrue(ex is CommandLineException); + Assert.AreEqual("The given configuration is invalid.", ex.Message); + } + } + + [TestMethod] + public void ExecuterInitializeWithInvalidConfigurationShouldThrowCommandLineException() + { + var options = CommandLineOptions.Instance; + ConfigurationArgumentExecutor executor = new ConfigurationArgumentExecutor(options); + + try + { + executor.Initialize("Foo"); + } + catch (Exception ex) + { + Assert.IsTrue(ex is CommandLineException); + Assert.AreEqual("The given configuration is invalid.", ex.Message); + } + } + + [TestMethod] + public void ExecuterInitializeWithValidConfigurationShouldAddConfigurationToCommandLineOptions() + { + var options = CommandLineOptions.Instance; + ConfigurationArgumentExecutor executor = new ConfigurationArgumentExecutor(options); + + executor.Initialize("Debug"); + Assert.AreEqual("Debug", options.Configuration); + } + + [TestMethod] + public void ExecutorExecuteReturnArgumentProcessorResultSuccess() + { + var executor = new ConfigurationArgumentExecutor(CommandLineOptions.Instance); + var result = executor.Execute(); + Assert.AreEqual(ArgumentProcessorResult.Success, result); + } + } +} diff --git a/test/vstest.console.UnitTests/Processors/EnableLoggersArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/EnableLoggersArgumentProcessorTests.cs new file mode 100644 index 0000000000..c9ccea52ae --- /dev/null +++ b/test/vstest.console.UnitTests/Processors/EnableLoggersArgumentProcessorTests.cs @@ -0,0 +1,117 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors +{ + using Microsoft.VisualStudio.TestPlatform.CommandLine.Processors; + using Microsoft.VisualStudio.TestPlatform.Common.Logging; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using System; + using System.Collections.Generic; + + using Castle.DynamicProxy.Contributors; + + [TestClass] + public class EnableLoggersArgumentProcessorTests + { + [TestInitialize] + public void Initialize() + { + RunTestsArgumentProcessorTests.SetupMockExtensions(); + } + + [TestMethod] + public void GetMetadataShouldReturnEnableLoggerArgumentProcessorCapabilities() + { + EnableLoggerArgumentProcessor processor = new EnableLoggerArgumentProcessor(); + Assert.IsTrue(processor.Metadata.Value is EnableLoggerArgumentProcessorCapabilities); + } + + [TestMethod] + public void GetExecuterShouldReturnEnableLoggerArgumentExecutor() + { + EnableLoggerArgumentProcessor processor = new EnableLoggerArgumentProcessor(); + Assert.IsTrue(processor.Executor.Value is EnableLoggerArgumentExecutor); + } + + [TestMethod] + public void CapabilitiesShouldAppropriateProperties() + { + EnableLoggerArgumentProcessorCapabilities capabilities = new EnableLoggerArgumentProcessorCapabilities(); + Assert.AreEqual("/Logger", capabilities.CommandName); + Assert.AreEqual("/logger:\n Specify a logger for test results. For example, to log results into a \n Visual Studio Test Results File (TRX) use /logger:trx.\n To publish test results to Team Foundation Server, use TfsPublisher as shown below\n Example: /logger:TfsPublisher;\n Collection=;\n BuildName=;\n TeamProject=\n [;Platform=]\n [;Flavor=]\n [;RunTitle=]", capabilities.HelpContentResourceName); + + Assert.AreEqual(HelpContentPriority.EnableLoggerArgumentProcessorHelpPriority, capabilities.HelpPriority); + Assert.AreEqual(false, capabilities.IsAction); + Assert.AreEqual(ArgumentProcessorPriority.Logging, capabilities.Priority); + + Assert.AreEqual(true, capabilities.AllowMultiple); + Assert.AreEqual(false, capabilities.AlwaysExecute); + Assert.AreEqual(false, capabilities.IsSpecialCommand); + } + + + [TestMethod] + public void ExecutorInitializeWithNullOrEmptyArgumentsShouldThrowException() + { + var executor = new EnableLoggerArgumentExecutor(TestLoggerManager.Instance); + Assert.ThrowsException<CommandLineException>(() => + { + executor.Initialize(null); + }); + } + + [TestMethod] + public void ExecutorInitializeWithValidArgumentsShouldAddTestLoggerToTestLoggerManager() + { + RunTestsArgumentProcessorTests.SetupMockExtensions(); + var testloggerManager = new DummyTestLoggerManager(); + var executor = new EnableLoggerArgumentExecutor(testloggerManager); + + var countBefore = testloggerManager.GetInitializedLoggers.Count; + + executor.Initialize("TestLoggerExtension;Collection=http://localhost:8080/tfs/DefaultCollection;TeamProject=MyProject;BuildName=DailyBuild_20121130.1"); + var countAfter = testloggerManager.GetInitializedLoggers.Count; + Assert.IsTrue(countBefore == 0); + Assert.IsTrue(countAfter == 1); + } + + [TestMethod] + public void ExectorInitializeShouldThrowExceptionIfInvalidArgumentIsPassed() + { + var executor = new EnableLoggerArgumentExecutor(TestLoggerManager.Instance); + Assert.ThrowsException<CommandLineException>(() => + { + executor.Initialize("TestLoggerExtension;==;;;Collection=http://localhost:8080/tfs/DefaultCollection;TeamProject=MyProject;BuildName=DailyBuild_20121130.1"); + }); + } + + [TestMethod] + public void ExecutorExecuteShouldReturnArgumentProcessorResultSuccess() + { + var executor = new EnableLoggerArgumentExecutor(null); + var result = executor.Execute(); + Assert.AreEqual(ArgumentProcessorResult.Success, result); + } + } + + internal class DummyTestLoggerManager : TestLoggerManager + { + public DummyTestLoggerManager() + { + + } + + public HashSet<String> GetInitializedLoggers + { + get + { + return InitializedLoggers; + } + } + + public static void Cleanup() + { + Instance = null; + } + } +} diff --git a/test/vstest.console.UnitTests/Processors/EnableStaticLoggersArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/EnableStaticLoggersArgumentProcessorTests.cs new file mode 100644 index 0000000000..d0f476a277 --- /dev/null +++ b/test/vstest.console.UnitTests/Processors/EnableStaticLoggersArgumentProcessorTests.cs @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors +{ + using Microsoft.VisualStudio.TestPlatform.CommandLine.Processors; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class EnableStaticLoggerArgumentProcessorTests + { + [TestMethod] + public void GetMetadataShouldReturnEnableLoggerArgumentProcessorCapabilities() + { + EnableStaticLoggersArgumentProcessor processor = new EnableStaticLoggersArgumentProcessor(); + Assert.IsTrue(processor.Metadata.Value is EnableStaticLoggersArgumentProcessorCapabilities); + } + + [TestMethod] + public void GetExecutorShouldReturnEnableStaticLoggersArgumentExecutor() + { + var processor = new EnableStaticLoggersArgumentProcessor(); + Assert.IsTrue(processor.Executor.Value is EnableStaticLoggersArgumentExecutor); + } + + [TestMethod] + public void CapabilitiesShouldAppropriateProperties() + { + EnableStaticLoggersArgumentProcessorCapabilities capabilities = new EnableStaticLoggersArgumentProcessorCapabilities(); + Assert.AreEqual("/EnableStaticLoggers", capabilities.CommandName); + Assert.AreEqual(false, capabilities.AllowMultiple); + Assert.AreEqual(false, capabilities.IsAction); + Assert.AreEqual(ArgumentProcessorPriority.Logging, capabilities.Priority); + } + + public void ExecutorExecuteShouldReturnArgumentProcessorResultSuccess() + { + var executor = new EnableLoggerArgumentExecutor(null); + var result = executor.Execute(); + Assert.AreEqual(ArgumentProcessorResult.Success, result); + } + + // todo : Add test cases for NET451 and dotnet core + } +} diff --git a/test/vstest.console.UnitTests/Processors/HelpArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/HelpArgumentProcessorTests.cs new file mode 100644 index 0000000000..0f3fe457ec --- /dev/null +++ b/test/vstest.console.UnitTests/Processors/HelpArgumentProcessorTests.cs @@ -0,0 +1,98 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors +{ + using System.Collections.Generic; + + using Microsoft.VisualStudio.TestPlatform.CommandLine.Processors; + using Microsoft.VisualStudio.TestPlatform.Utilities; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class HelpArgumentProcessorTests + { + /// <summary> + /// The help argument processor get metadata should return help argument processor capabilities. + /// </summary> + [TestMethod] + public void GetMetadataShouldReturnHelpArgumentProcessorCapabilities() + { + HelpArgumentProcessor processor = new HelpArgumentProcessor(); + Assert.IsTrue(processor.Metadata.Value is HelpArgumentProcessorCapabilities); + } + + /// <summary> + /// The help argument processor get executer should return help argument processor capabilities. + /// </summary> + [TestMethod] + public void GetExecuterShouldReturnHelpArgumentProcessorCapabilities() + { + HelpArgumentProcessor processor = new HelpArgumentProcessor(); + Assert.IsTrue(processor.Executor.Value is HelpArgumentExecutor); + } + + #region HelpArgumentProcessorCapabilitiesTests + + [TestMethod] + public void CapabilitiesShouldAppropriateProperties() + { + HelpArgumentProcessorCapabilities capabilities = new HelpArgumentProcessorCapabilities(); + Assert.AreEqual("/?", capabilities.CommandName); + Assert.AreEqual("/?\n Display this usage message.", capabilities.HelpContentResourceName); + + Assert.AreEqual(HelpContentPriority.HelpArgumentProcessorHelpPriority, capabilities.HelpPriority); + Assert.AreEqual(false, capabilities.IsAction); + Assert.AreEqual(ArgumentProcessorPriority.Help, capabilities.Priority); + + Assert.AreEqual(true, capabilities.AllowMultiple); + Assert.AreEqual(false, capabilities.AlwaysExecute); + Assert.AreEqual(false, capabilities.IsSpecialCommand); + } + + #endregion + + [TestMethod] + public void ExecuterExecuteReturnArgumentProcessorResultAbort() + { + HelpArgumentExecutor executor = new HelpArgumentExecutor(); + var result = executor.Execute(); + Assert.AreEqual(ArgumentProcessorResult.Abort, result); + } + + [TestMethod] + public void ExecuterExecuteWritesAppropriateDataToConsole() + { + HelpArgumentExecutor executor = new HelpArgumentExecutor(); + var output = new DummyConsoleOutput(); + executor.Output = output; + var result = executor.Execute(); + Assert.IsTrue(output.Lines.Contains("Usage: vstest.console.exe [TestFileNames] [Options]")); + Assert.IsTrue(output.Lines.Contains("Options:")); + Assert.IsTrue(output.Lines.Contains("Description: Runs tests from the specified files.")); + Assert.IsTrue(output.Lines.Contains(" To run tests in the same process:\n >vstest.console.exe tests.dll \n To run tests in a separate process:\n >vstest.console.exe /inIsolation tests.dll\n To run tests with additional settings such as data collectors:\n >vstest.console.exe tests.dll /Settings:Local.RunSettings")); + } + } + + internal class DummyConsoleOutput : IOutput + { + /// <summary> + /// The lines. + /// </summary> + internal List<string> Lines; + + public DummyConsoleOutput() + { + this.Lines = new List<string>(); + } + + public void WriteLine(string message, OutputLevel level) + { + this.Lines.Add(message); + } + + public void Write(string message, OutputLevel level) + { + throw new System.NotImplementedException(); + } + } +} diff --git a/test/vstest.console.UnitTests/Processors/ListTestsArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/ListTestsArgumentProcessorTests.cs new file mode 100644 index 0000000000..a41924be93 --- /dev/null +++ b/test/vstest.console.UnitTests/Processors/ListTestsArgumentProcessorTests.cs @@ -0,0 +1,243 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Microsoft.VisualStudio.TestPlatform.Client; + using Microsoft.VisualStudio.TestPlatform.CommandLine.Processors; + using Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Implementations; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + using Moq; + using ObjectModel; + using ObjectModel.Client; + using TestPlatform.Utilities; + using Utilities; + using TestPlatformHelpers; + using Common.Logging; // <summary> + // Tests for ListTestsArgumentProcessor + // </summary> + [TestClass] + public class ListTestsArgumentProcessorTests + { + MockFileHelper mockFileHelper; + string dummyTestFilePath = "DummyTest.dll"; + + public ListTestsArgumentProcessorTests() + { + this.mockFileHelper = new MockFileHelper(); + this.mockFileHelper.ExistsInvoker = (path) => + { + if (string.Equals(path, this.dummyTestFilePath)) + { + return true; + } + else + { + return false; + } + }; + } + + /// <summary> + /// The help argument processor get metadata should return help argument processor capabilities. + /// </summary> + [TestMethod] + public void GetMetadataShouldReturnListTestsArgumentProcessorCapabilities() + { + ListTestsArgumentProcessor processor = new ListTestsArgumentProcessor(); + Assert.IsTrue(processor.Metadata.Value is ListTestsArgumentProcessorCapabilities); + } + + /// <summary> + /// The help argument processor get executer should return help argument processor capabilities. + /// </summary> + [TestMethod] + public void GetExecuterShouldReturnListTestsArgumentProcessorCapabilities() + { + ListTestsArgumentProcessor processor = new ListTestsArgumentProcessor(); + Assert.IsTrue(processor.Executor.Value is ListTestsArgumentExecutor); + } + + #region ListTestsArgumentProcessorCapabilitiesTests + + [TestMethod] + public void CapabilitiesShouldReturnAppropriateProperties() + { + ListTestsArgumentProcessorCapabilities capabilities = new ListTestsArgumentProcessorCapabilities(); + Assert.AreEqual("/ListTests", capabilities.CommandName); + Assert.AreEqual("/lt", capabilities.ShortCommandName); + Assert.AreEqual("/ListTests:<File Name>\n Lists discovered tests from the given test container.", capabilities.HelpContentResourceName); + + Assert.AreEqual(HelpContentPriority.ListTestsArgumentProcessorHelpPriority, capabilities.HelpPriority); + Assert.AreEqual(true, capabilities.IsAction); + Assert.AreEqual(ArgumentProcessorPriority.Normal, capabilities.Priority); + + Assert.AreEqual(false, capabilities.AllowMultiple); + Assert.AreEqual(false, capabilities.AlwaysExecute); + Assert.AreEqual(false, capabilities.IsSpecialCommand); + } + #endregion + + #region ListTestsArgumentExecutorTests + + [TestMethod] + public void ExecutorInitializeWithValidSourceShouldAddItToTestSources() + { + CommandLineOptions.Instance.Reset(); + + CommandLineOptions.Instance.FileHelper = this.mockFileHelper; + + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, new TestPlatform(), TestLoggerManager.Instance, TestRunResultAggregator.Instance); + ListTestsArgumentExecutor executor = new ListTestsArgumentExecutor( + CommandLineOptions.Instance, + null, + testRequestManager); + + executor.Initialize(this.dummyTestFilePath); + + Assert.IsTrue(Enumerable.Contains<string>(CommandLineOptions.Instance.Sources, this.dummyTestFilePath)); + } + + [TestMethod] + public void ExecutorExecuteForNoSourcesShouldReturnFail() + { + CommandLineOptions.Instance.Reset(); + + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, new TestPlatform(), TestLoggerManager.Instance, TestRunResultAggregator.Instance); + ListTestsArgumentExecutor executor = new ListTestsArgumentExecutor( + CommandLineOptions.Instance, + null, + testRequestManager); + + ArgumentProcessorResult argumentProcessorResult = executor.Execute(); + + Assert.AreEqual(ArgumentProcessorResult.Fail, argumentProcessorResult); + } + + [TestMethod] + public void ExecutorExecuteShouldCatchTestPlatformExceptionAndReturnFail() + { + var mockTestPlatform = new Mock<ITestPlatform>(); + var mockDiscoveryRequest = new Mock<IDiscoveryRequest>(); + + mockDiscoveryRequest.Setup(dr => dr.DiscoverAsync()).Throws(new TestPlatformException("DummyTestPlatformException")); + mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny<DiscoveryCriteria>())).Returns(mockDiscoveryRequest.Object); + + this.ResetAndAddSourceToCommandLineOptions(); + + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestLoggerManager.Instance, TestRunResultAggregator.Instance); + ListTestsArgumentExecutor listTestsArgumentExecutor = + new ListTestsArgumentExecutor( + CommandLineOptions.Instance, + null, + testRequestManager); + + ArgumentProcessorResult argumentProcessorResult = listTestsArgumentExecutor.Execute(); + Assert.AreEqual(ArgumentProcessorResult.Fail, argumentProcessorResult); + } + + [TestMethod] + public void ExecutorExecuteShouldCatchSettingsExceptionAndReturnFail() + { + var mockTestPlatform = new Mock<ITestPlatform>(); + var mockDiscoveryRequest = new Mock<IDiscoveryRequest>(); + + mockDiscoveryRequest.Setup(dr => dr.DiscoverAsync()).Throws(new SettingsException("DummySettingsException")); + mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny<DiscoveryCriteria>())).Returns(mockDiscoveryRequest.Object); + + this.ResetAndAddSourceToCommandLineOptions(); + + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestLoggerManager.Instance, TestRunResultAggregator.Instance); + ListTestsArgumentExecutor listTestsArgumentExecutor = + new ListTestsArgumentExecutor( + CommandLineOptions.Instance, + null, + testRequestManager); + + ArgumentProcessorResult argumentProcessorResult = listTestsArgumentExecutor.Execute(); + Assert.AreEqual(ArgumentProcessorResult.Fail, argumentProcessorResult); + } + + [TestMethod] + public void ExecutorExecuteShouldCatchInvalidOperationExceptionAndReturnFail() + { + var mockTestPlatform = new Mock<ITestPlatform>(); + var mockDiscoveryRequest = new Mock<IDiscoveryRequest>(); + + mockDiscoveryRequest.Setup(dr => dr.DiscoverAsync()).Throws(new InvalidOperationException("DummyInvalidOperationException")); + mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny<DiscoveryCriteria>())).Returns(mockDiscoveryRequest.Object); + + this.ResetAndAddSourceToCommandLineOptions(); + + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestLoggerManager.Instance, TestRunResultAggregator.Instance); + ListTestsArgumentExecutor listTestsArgumentExecutor = + new ListTestsArgumentExecutor( + CommandLineOptions.Instance, + null, + testRequestManager); + + ArgumentProcessorResult argumentProcessorResult = listTestsArgumentExecutor.Execute(); + Assert.AreEqual(ArgumentProcessorResult.Fail, argumentProcessorResult); + } + + [TestMethod] + public void ExecutorExecuteShouldThrowOtherExceptions() + { + var mockTestPlatform = new Mock<ITestPlatform>(); + var mockDiscoveryRequest = new Mock<IDiscoveryRequest>(); + + mockDiscoveryRequest.Setup(dr => dr.DiscoverAsync()).Throws(new Exception("DummyException")); + mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny<DiscoveryCriteria>())).Returns(mockDiscoveryRequest.Object); + + this.ResetAndAddSourceToCommandLineOptions(); + + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestLoggerManager.Instance, TestRunResultAggregator.Instance); + var executor = new ListTestsArgumentExecutor( + CommandLineOptions.Instance, + null, + testRequestManager); + + Assert.ThrowsException<Exception>(() => executor.Execute()); + } + + [TestMethod] + public void ExecutorExecuteShouldOutputDiscoveredTestsAndReturnSuccess() + { + var mockTestPlatform = new Mock<ITestPlatform>(); + var mockDiscoveryRequest = new Mock<IDiscoveryRequest>(); + var mockConsoleOutput = new Mock<IOutput>(); + + List<TestCase> list = new List<TestCase>(); + list.Add(new TestCase("Test1", new Uri("http://FooTestUri1"), "Source1")); + list.Add(new TestCase("Test2", new Uri("http://FooTestUri2"), "Source2")); + mockDiscoveryRequest.Setup(dr => dr.DiscoverAsync()).Raises(dr => dr.OnDiscoveredTests += null, new DiscoveredTestsEventArgs(list)); + + mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny<DiscoveryCriteria>())).Returns(mockDiscoveryRequest.Object); + + + this.ResetAndAddSourceToCommandLineOptions(); + + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestLoggerManager.Instance, TestRunResultAggregator.Instance); + new ListTestsArgumentExecutor(CommandLineOptions.Instance, null, testRequestManager, mockConsoleOutput.Object).Execute(); + + // Assert + mockDiscoveryRequest.Verify(dr => dr.DiscoverAsync(), Times.Once); + + mockConsoleOutput.Verify((IOutput co) => co.WriteLine(" Test1", OutputLevel.Information)); + mockConsoleOutput.Verify((IOutput co) => co.WriteLine(" Test2", OutputLevel.Information)); + } + + #endregion + + private void ResetAndAddSourceToCommandLineOptions() + { + CommandLineOptions.Instance.Reset(); + + CommandLineOptions.Instance.FileHelper = this.mockFileHelper; + CommandLineOptions.Instance.AddSource(this.dummyTestFilePath); + } + } +} diff --git a/test/vstest.console.UnitTests/Processors/OutputArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/OutputArgumentProcessorTests.cs new file mode 100644 index 0000000000..fed526bd74 --- /dev/null +++ b/test/vstest.console.UnitTests/Processors/OutputArgumentProcessorTests.cs @@ -0,0 +1,109 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors +{ + using Implementations; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Moq; + using Processors; + using System; + using TestPlatform.CommandLine.Processors; + using TestPlatform.Utilities.Helpers.Interfaces; + + [TestClass] + public class OutputArgumentProcessorTests + { + [TestMethod] + public void GetMetadataShouldReturnOutputArgumentProcessorCapabilities() + { + OutputArgumentProcessor processor = new OutputArgumentProcessor(); + Assert.IsTrue(processor.Metadata.Value is OutputArgumentProcessorCapabilities); + } + + [TestMethod] + public void GetExecuterShouldReturnOutputArgumentProcessorCapabilities() + { + OutputArgumentProcessor processor = new OutputArgumentProcessor(); + Assert.IsTrue(processor.Executor.Value is OutputArgumentExecutor); + } + + #region OutputArgumentProcessorCapabilitiesTests + + [TestMethod] + public void CapabilitiesShouldAppropriateProperties() + { + OutputArgumentProcessorCapabilities capabilities = new OutputArgumentProcessorCapabilities(); + Assert.AreEqual("/Output", capabilities.CommandName); + Assert.AreEqual("/Output:<Output>\n The directory containing the binaries to run.", capabilities.HelpContentResourceName); + + Assert.AreEqual(HelpContentPriority.OutputArgumentProcessorHelpPriority, capabilities.HelpPriority); + Assert.AreEqual(false, capabilities.IsAction); + Assert.AreEqual(ArgumentProcessorPriority.Normal, capabilities.Priority); + + Assert.AreEqual(false, capabilities.AllowMultiple); + Assert.AreEqual(false, capabilities.AlwaysExecute); + Assert.AreEqual(false, capabilities.IsSpecialCommand); + } + + #endregion + + [TestMethod] + public void ExecuterInitializeWithNullOrEmptyOutputShouldThrowCommandLineException() + { + var options = CommandLineOptions.Instance; + OutputArgumentExecutor executor = new OutputArgumentExecutor(options); + + try + { + executor.Initialize(null); + } + catch (Exception ex) + { + Assert.IsTrue(ex is CommandLineException); + Assert.AreEqual(@"The Output path was not found, provide a valid path and try again.", ex.Message); + } + } + + [TestMethod] + public void ExecuterInitializeWithInvalidOutputShouldThrowCommandLineException() + { + var options = CommandLineOptions.Instance; + OutputArgumentExecutor executor = new OutputArgumentExecutor(options); + + try + { + executor.Initialize(@"C:\Foo.txt"); + } + catch (Exception ex) + { + Assert.IsTrue(ex is CommandLineException); + Assert.AreEqual(@"The Output path was not found, provide a valid path and try again.", ex.Message); + } + } + + [TestMethod] + public void ExecuterInitializeWithValidOutputShouldAddOutputToCommandLineOptions() + { + var options = CommandLineOptions.Instance; + OutputArgumentExecutor executor = new OutputArgumentExecutor(options); + string testOutput = @"C:\OutputDir"; + var mockFileHelper = new MockFileHelper(); + mockFileHelper.ExistsInvoker = (path) => + { + return string.Equals(path, testOutput); + }; + executor.FileHelper = mockFileHelper; + + executor.Initialize(testOutput); + Assert.AreEqual(testOutput, options.Output); + } + + [TestMethod] + public void ExecutorExecuteReturnArgumentProcessorResultSuccess() + { + var executor = new OutputArgumentExecutor(CommandLineOptions.Instance); + var result = executor.Execute(); + Assert.AreEqual(ArgumentProcessorResult.Success, result); + } + } +} diff --git a/test/vstest.console.UnitTests/Processors/ParallelArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/ParallelArgumentProcessorTests.cs new file mode 100644 index 0000000000..3e6099d9f3 --- /dev/null +++ b/test/vstest.console.UnitTests/Processors/ParallelArgumentProcessorTests.cs @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors +{ + using Microsoft.VisualStudio.TestTools.UnitTesting; + using TestPlatform.CommandLine.Processors; + + [TestClass] + public class ParallelArgumentProcessorTests + { + [TestCleanup] + public void TestCleanup() + { + CommandLineOptions.Instance.Reset(); + } + + [TestMethod] + public void GetMetadataShouldReturnParallelArgumentProcessorCapabilities() + { + var processor = new ParallelArgumentProcessor(); + Assert.IsTrue(processor.Metadata.Value is ParallelArgumentProcessorCapabilities); + } + + [TestMethod] + public void GetExecuterShouldReturnPlatformArgumentProcessorCapabilities() + { + var processor = new ParallelArgumentProcessor(); + Assert.IsTrue(processor.Executor.Value is ParallelArgumentExecutor); + } + + #region ParallelArgumentProcessorCapabilities tests + + [TestMethod] + public void CapabilitiesShouldReturnAppropriateProperties() + { + var capabilities = new ParallelArgumentProcessorCapabilities(); + Assert.AreEqual("/Parallel", capabilities.CommandName); + Assert.AreEqual("/Parallel\nSpecifies that the tests be executed in parallel. By default up to all available cores on the machine may be used. The number of cores to use may be configured using a settings file.", capabilities.HelpContentResourceName); + + Assert.AreEqual(HelpContentPriority.ParallelArgumentProcessorHelpPriority, capabilities.HelpPriority); + Assert.AreEqual(false, capabilities.IsAction); + Assert.AreEqual(ArgumentProcessorPriority.AutoUpdateRunSettings, capabilities.Priority); + + Assert.AreEqual(false, capabilities.AllowMultiple); + Assert.AreEqual(false, capabilities.AlwaysExecute); + Assert.AreEqual(false, capabilities.IsSpecialCommand); + } + + #endregion + + #region ParallelArgumentExecutor Initialize tests + + [TestMethod] + public void InitializeShouldThrowIfArgumentIsNonNull() + { + var executor = new ParallelArgumentExecutor(CommandLineOptions.Instance); + + // Parallel should not have any values or arguments + ExceptionUtilities.ThrowsException<CommandLineException>( + () => executor.Initialize("123"), + "Argument " + 123 + " is not expected in the 'Parallel' command. Specify the command without the argument (Example: vstest.console.exe myTests.dll /Parallel) and try again."); + } + + [TestMethod] + public void InitializeShouldSetParallelValue() + { + var executor = new ParallelArgumentExecutor(CommandLineOptions.Instance); + executor.Initialize(null); + Assert.IsTrue(CommandLineOptions.Instance.Parallel, "Parallel option must be set to true."); + } + + #endregion + + #region ParallelArgumentExecutor Execute tests + + [TestMethod] + public void ExecuteShouldReturnSuccess() + { + var executor = new ParallelArgumentExecutor(CommandLineOptions.Instance); + + Assert.AreEqual(ArgumentProcessorResult.Success, executor.Execute()); + } + + #endregion + } +} diff --git a/test/vstest.console.UnitTests/Processors/PlatformArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/PlatformArgumentProcessorTests.cs new file mode 100644 index 0000000000..e3ecaa1924 --- /dev/null +++ b/test/vstest.console.UnitTests/Processors/PlatformArgumentProcessorTests.cs @@ -0,0 +1,127 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors +{ + using Microsoft.VisualStudio.TestTools.UnitTesting; + using TestPlatform.CommandLine.Processors; + + [TestClass] + public class PlatformArgumentProcessorTests + { + [TestCleanup] + public void TestCleanup() + { + CommandLineOptions.Instance.Reset(); + } + + [TestMethod] + public void GetMetadataShouldReturnPlatformArgumentProcessorCapabilities() + { + var processor = new PlatformArgumentProcessor(); + Assert.IsTrue(processor.Metadata.Value is PlatformArgumentProcessorCapabilities); + } + + [TestMethod] + public void GetExecuterShouldReturnPlatformArgumentProcessorCapabilities() + { + var processor = new PlatformArgumentProcessor(); + Assert.IsTrue(processor.Executor.Value is PlatformArgumentExecutor); + } + + #region PlatformArgumentProcessorCapabilities tests + + [TestMethod] + public void CapabilitiesShouldReturnAppropriateProperties() + { + var capabilities = new PlatformArgumentProcessorCapabilities(); + Assert.AreEqual("/Platform", capabilities.CommandName); + Assert.AreEqual("/Platform:<Platform type>\n Target platform architecture to be used for test execution. \n Valid values are x86, x64 and ARM.", capabilities.HelpContentResourceName); + + Assert.AreEqual(HelpContentPriority.PlatformArgumentProcessorHelpPriority, capabilities.HelpPriority); + Assert.AreEqual(false, capabilities.IsAction); + Assert.AreEqual(ArgumentProcessorPriority.AutoUpdateRunSettings, capabilities.Priority); + + Assert.AreEqual(false, capabilities.AllowMultiple); + Assert.AreEqual(false, capabilities.AlwaysExecute); + Assert.AreEqual(false, capabilities.IsSpecialCommand); + } + + #endregion + + #region PlatformArgumentExecutor Initialize tests + + [TestMethod] + public void InitializeShouldThrowIfArgumentIsNull() + { + var executor = new PlatformArgumentExecutor(CommandLineOptions.Instance); + + ExceptionUtilities.ThrowsException<CommandLineException>( + () => executor.Initialize(null), + "The /Platform argument requires the target platform type for the test run to be provided. Example: /Platform:x86"); + } + + [TestMethod] + public void InitializeShouldThrowIfArgumentIsEmpty() + { + var executor = new PlatformArgumentExecutor(CommandLineOptions.Instance); + + ExceptionUtilities.ThrowsException<CommandLineException>( + () => executor.Initialize(" "), + "The /Platform argument requires the target platform type for the test run to be provided. Example: /Platform:x86"); + } + + [TestMethod] + public void InitializeShouldThrowIfArgumentIsNotAnArchitecture() + { + var executor = new PlatformArgumentExecutor(CommandLineOptions.Instance); + + ExceptionUtilities.ThrowsException<CommandLineException>( + () => executor.Initialize("foo"), + "Invalid platform type:{0}. Valid platform types are x86, x64 and Arm.", + "foo"); + } + + [TestMethod] + public void InitializeShouldThrowIfArgumentIsNotASupportedArchitecture() + { + var executor = new PlatformArgumentExecutor(CommandLineOptions.Instance); + + ExceptionUtilities.ThrowsException<CommandLineException>( + () => executor.Initialize("AnyCPU"), + "Invalid platform type:{0}. Valid platform types are x86, x64 and Arm.", + "AnyCPU"); + } + + [TestMethod] + public void InitializeShouldSetCommandLineOptionsArchitecture() + { + var executor = new PlatformArgumentExecutor(CommandLineOptions.Instance); + + executor.Initialize("x64"); + Assert.AreEqual(ObjectModel.Architecture.X64, CommandLineOptions.Instance.TargetArchitecture); + } + + [TestMethod] + public void InitializeShouldNotConsiderCaseSensitivityOfTheArgumentPassed() + { + var executor = new PlatformArgumentExecutor(CommandLineOptions.Instance); + + executor.Initialize("ArM"); + Assert.AreEqual(ObjectModel.Architecture.ARM, CommandLineOptions.Instance.TargetArchitecture); + } + + #endregion + + #region PlatformArgumentExecutor Execute tests + + [TestMethod] + public void ExecuteShouldReturnSuccess() + { + var executor = new PlatformArgumentExecutor(CommandLineOptions.Instance); + + Assert.AreEqual(ArgumentProcessorResult.Success, executor.Execute()); + } + + #endregion + } +} diff --git a/test/vstest.console.UnitTests/Processors/PortArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/PortArgumentProcessorTests.cs new file mode 100644 index 0000000000..838f76040e --- /dev/null +++ b/test/vstest.console.UnitTests/Processors/PortArgumentProcessorTests.cs @@ -0,0 +1,130 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +using Microsoft.VisualStudio.TestPlatform.Client.DesignMode; +using Microsoft.VisualStudio.TestPlatform.Client.RequestHelper; +using Microsoft.VisualStudio.TestPlatform.CommandLine.Processors; +using Microsoft.VisualStudio.TestPlatform.CommandLine.TestPlatformHelpers; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using System; +using System.Threading; + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors +{ + [TestClass] + public class PortArgumentProcessorTests + { + [TestMethod] + public void GetMetadataShouldReturnPortArgumentProcessorCapabilities() + { + PortArgumentProcessor processor = new PortArgumentProcessor(); + Assert.IsTrue(processor.Metadata.Value is PortArgumentProcessorCapabilities); + } + + [TestMethod] + public void GetExecuterShouldReturnPortArgumentProcessorCapabilities() + { + PortArgumentProcessor processor = new PortArgumentProcessor(); + Assert.IsTrue(processor.Executor.Value is PortArgumentExecutor); + } + + #region PortArgumentProcessorCapabilitiesTests + + [TestMethod] + public void CapabilitiesShouldAppropriateProperties() + { + PortArgumentProcessorCapabilities capabilities = new PortArgumentProcessorCapabilities(); + Assert.AreEqual("/Port", capabilities.CommandName); + Assert.AreEqual("/Port:<Port>\n The Port for socket connection and receiving the event messages.", capabilities.HelpContentResourceName); + + Assert.AreEqual(HelpContentPriority.PortArgumentProcessorHelpPriority, capabilities.HelpPriority); + Assert.AreEqual(false, capabilities.IsAction); + Assert.AreEqual(ArgumentProcessorPriority.Normal, capabilities.Priority); + + Assert.AreEqual(false, capabilities.AllowMultiple); + Assert.AreEqual(false, capabilities.AlwaysExecute); + Assert.AreEqual(false, capabilities.IsSpecialCommand); + } + + #endregion + + [TestMethod] + public void ExecuterInitializeWithNullOrEmptyPortShouldThrowCommandLineException() + { + var executor = new PortArgumentExecutor(CommandLineOptions.Instance, TestRequestManager.Instance); + try + { + executor.Initialize(null); + } + catch (Exception ex) + { + Assert.IsTrue(ex is CommandLineException); + Assert.AreEqual("The /Port argument requires the port number which is an integer. Specify the port for socket connection and receiving the event messages.", ex.Message); + } + } + + [TestMethod] + public void ExecuterInitializeWithInvalidPortShouldThrowCommandLineException() + { + var executor = new PortArgumentExecutor(CommandLineOptions.Instance, TestRequestManager.Instance); + try + { + executor.Initialize("Foo"); + } + catch (Exception ex) + { + Assert.IsTrue(ex is CommandLineException); + Assert.AreEqual("The /Port argument requires the port number which is an integer. Specify the port for socket connection and receiving the event messages.", ex.Message); + } + } + + [TestMethod] + public void ExecuterInitializeWithValidPortShouldAddPortToCommandLineOptionsAndInitializeDesignModeManger() + { + var executor = new PortArgumentExecutor(CommandLineOptions.Instance, TestRequestManager.Instance); + int port = 2345; + executor.Initialize(port.ToString()); + Assert.AreEqual(port, CommandLineOptions.Instance.Port); + Assert.IsNotNull(DesignModeClient.Instance); + } + + [TestMethod] + public void ExecutorExecuteForValidConnectionReturnsArgumentProcessorResultSuccess() + { + var testDesignModeClient = new Mock<IDesignModeClient>(); + var testRequestManager = new Mock<ITestRequestManager>(); + + var executor = new PortArgumentExecutor(CommandLineOptions.Instance, testRequestManager.Object, + () => testDesignModeClient.Object); + + int port = 2345; + executor.Initialize(port.ToString()); + var result = executor.Execute(); + + testDesignModeClient.Verify(td => + td.ConnectToClientAndProcessRequests(port, testRequestManager.Object), Times.Once); + + Assert.AreEqual(ArgumentProcessorResult.Success, result); + } + + [TestMethod] + public void ExecutorExecuteForFailedConnectionReturnsArgumentProcessorResultFail() + { + var testRequestManager = new Mock<ITestRequestManager>(); + var testDesignModeClient = new Mock<IDesignModeClient>(); + + var executor = new PortArgumentExecutor(CommandLineOptions.Instance, testRequestManager.Object, + () => testDesignModeClient.Object); + + testDesignModeClient.Setup(td => td.ConnectToClientAndProcessRequests(It.IsAny<int>(), + It.IsAny<ITestRequestManager>())).Callback(() => { throw new TimeoutException(); }); + + int port = 2345; + executor.Initialize(port.ToString()); + var result = executor.Execute(); + + testDesignModeClient.Verify(td => td.ConnectToClientAndProcessRequests(port, testRequestManager.Object), Times.Once); + Assert.AreEqual(ArgumentProcessorResult.Fail, result); + } + } +} diff --git a/test/vstest.console.UnitTests/Processors/RunSettingsArgumentProcessortTests.cs b/test/vstest.console.UnitTests/Processors/RunSettingsArgumentProcessortTests.cs new file mode 100644 index 0000000000..527a94f4f3 --- /dev/null +++ b/test/vstest.console.UnitTests/Processors/RunSettingsArgumentProcessortTests.cs @@ -0,0 +1,260 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors +{ + using System; + using System.IO; + using System.Xml; + + using Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Implementations; + using Microsoft.VisualStudio.TestPlatform.Common; + using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + using Microsoft.VisualStudio.TestPlatform.CommandLine.Processors; + + [TestClass] + public class RunSettingsArgumentProcessortTests + { + [TestCleanup] + public void TestCleanup() + { + CommandLineOptions.Instance.Reset(); + } + + [TestMethod] + public void GetMetadataShouldReturnRunSettingsArgumentProcessorCapabilities() + { + var processor = new RunSettingsArgumentProcessor(); + Assert.IsTrue(processor.Metadata.Value is RunSettingsArgumentProcessorCapabilities); + } + + [TestMethod] + public void GetExecuterShouldReturnRunSettingsArgumentProcessorCapabilities() + { + var processor = new RunSettingsArgumentProcessor(); + Assert.IsTrue(processor.Executor.Value is RunSettingsArgumentExecutor); + } + + #region RunSettingsArgumentProcessorCapabilities tests + + [TestMethod] + public void CapabilitiesShouldReturnAppropriateProperties() + { + var capabilities = new RunSettingsArgumentProcessorCapabilities(); + Assert.AreEqual("/Settings", capabilities.CommandName); + Assert.AreEqual("/Settings:<Settings File>\n Settings to use when running tests.", capabilities.HelpContentResourceName); + + Assert.AreEqual(HelpContentPriority.RunSettingsArgumentProcessorHelpPriority, capabilities.HelpPriority); + Assert.AreEqual(false, capabilities.IsAction); + Assert.AreEqual(ArgumentProcessorPriority.RunSettings, capabilities.Priority); + + Assert.AreEqual(false, capabilities.AllowMultiple); + Assert.AreEqual(false, capabilities.AlwaysExecute); + Assert.AreEqual(false, capabilities.IsSpecialCommand); + } + + #endregion + + #region RunSettingsArgumentExecutor tests + + [TestMethod] + public void InitializeShouldThrowExceptionIfArgumentIsNull() + { + Action action = () => new RunSettingsArgumentExecutor(CommandLineOptions.Instance, null).Initialize(null); + + ExceptionUtilities.ThrowsException<CommandLineException>( + action, + "The /Settings parameter requires a settings file to be provided."); + } + + [TestMethod] + public void InitializeShouldThrowExceptionIfArgumentIsWhiteSpace() + { + Action action = () => new RunSettingsArgumentExecutor(CommandLineOptions.Instance, null).Initialize(" "); + + ExceptionUtilities.ThrowsException<CommandLineException>( + action, + "The /Settings parameter requires a settings file to be provided."); + } + + [TestMethod] + public void InitializeShouldThrowExceptionIfFileDoesNotExist() + { + var fileName = "C:\\Imaginary\\nonExistentFile.txt"; + + var executor = new RunSettingsArgumentExecutor(CommandLineOptions.Instance, null); + // Setup mocks. + var mockFileHelper = new MockFileHelper(); + mockFileHelper.ExistsInvoker = (string filePath) => { return false; }; + + executor.FileHelper = mockFileHelper; + + ExceptionUtilities.ThrowsException<CommandLineException>( + () => executor.Initialize(fileName), + "The Settings file '{0}' could not be found.", + fileName); + } + + [TestMethod] + public void InitializeShouldThrowIfRunSettingsSchemaDoesNotMatch() + { + // Arrange. + var fileName = "C:\\temp\\r.runsettings"; + var settingsXml = "<BadRunSettings></BadRunSettings>"; + + var settingsProvider = new TestableRunSettingsProvider(); + + CommandLineOptions.Instance.EnableCodeCoverage = true; + var executor = new TestableRunSettingsArgumentExecutor( + CommandLineOptions.Instance, + settingsProvider, + settingsXml); + + // Setup mocks. + var mockFileHelper = new MockFileHelper(); + mockFileHelper.ExistsInvoker = (string filePath) => { return true; }; + + executor.FileHelper = mockFileHelper; + + // Act and Assert. + ExceptionUtilities.ThrowsException<CommandLineException>( + () => executor.Initialize(fileName), + "Settings file provided do not confirm to required format."); + } + + [TestMethod] + public void InitializeShouldSetActiveRunSettings() + { + // Arrange. + var fileName = "C:\\temp\\r.runsettings"; + var settingsXml = "<RunSettings></RunSettings>"; + + var settingsProvider = new TestableRunSettingsProvider(); + + var executor = new TestableRunSettingsArgumentExecutor( + CommandLineOptions.Instance, + settingsProvider, + settingsXml); + + // Setup mocks. + var mockFileHelper = new MockFileHelper(); + mockFileHelper.ExistsInvoker = (string filePath) => { return true; }; + + executor.FileHelper = mockFileHelper; + + // Act. + executor.Initialize(fileName); + + // Assert. + Assert.IsNotNull(settingsProvider.ActiveRunSettings); + StringAssert.Contains(settingsProvider.ActiveRunSettings.SettingsXml, "<RunSettings>\r\n</RunSettings>"); + } + + [TestMethod] + public void InitializeShouldSetActiveRunSettingsForTestSettingsFiles() + { + // Arrange. + var fileName = "C:\\temp\\r.testsettings"; + var settingsXml = "<TestSettings></TestSettings>"; + + var settingsProvider = new TestableRunSettingsProvider(); + + var executor = new TestableRunSettingsArgumentExecutor( + CommandLineOptions.Instance, + settingsProvider, + settingsXml); + + // Setup mocks. + var mockFileHelper = new MockFileHelper(); + mockFileHelper.ExistsInvoker = (string filePath) => { return true; }; + + executor.FileHelper = mockFileHelper; + + // Act. + executor.Initialize(fileName); + + // Assert. + Assert.IsNotNull(settingsProvider.ActiveRunSettings); + StringAssert.Contains(settingsProvider.ActiveRunSettings.SettingsXml, "<RunSettings>\r\n <RunConfiguration>\r\n <TargetPlatform>X86</TargetPlatform>\r\n <TargetFrameworkVersion>Framework45</TargetFrameworkVersion>\r\n </RunConfiguration>\r\n <MSTest>\r\n <SettingsFile>C:\\temp\\r.testsettings</SettingsFile>\r\n <ForcedLegacyMode>true</ForcedLegacyMode>\r\n </MSTest>\r\n <DataCollectionRunSettings>\r\n <DataCollectors />\r\n </DataCollectionRunSettings>\r\n</RunSettings>"); + } + + [TestMethod] + public void InitializeShouldSetActiveRunSettingsWithCodeCoverageData() + { + // Arrange. + var fileName = "C:\\temp\\r.runsettings"; + var settingsXml = "<RunSettings></RunSettings>"; + + var settingsProvider = new TestableRunSettingsProvider(); + + CommandLineOptions.Instance.EnableCodeCoverage = true; + var executor = new TestableRunSettingsArgumentExecutor( + CommandLineOptions.Instance, + settingsProvider, + settingsXml); + + // Setup mocks. + var mockFileHelper = new MockFileHelper(); + mockFileHelper.ExistsInvoker = (string filePath) => { return true; }; + + executor.FileHelper = mockFileHelper; + + // Act. + executor.Initialize(fileName); + + // Assert. + Assert.IsNotNull(settingsProvider.ActiveRunSettings); + StringAssert.Contains(settingsProvider.ActiveRunSettings.SettingsXml, "<RunSettings>\r\n <DataCollectionRunSettings>\r\n <DataCollectors>\r\n <DataCollector uri=\"datacollector://microsoft/CodeCoverage/2.0\" assemblyQualifiedName=\"Microsoft.VisualStudio.Coverage.DynamicCoverageDataCollector, Microsoft.VisualStudio.TraceCollector, Version=15.0.0.0 , Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\" friendlyName=\"Code Coverage\">\r\n <Configuration>\r\n <CodeCoverage>\r\n <ModulePaths>\r\n <Exclude>\r\n <ModulePath>.*CPPUnitTestFramework.*</ModulePath>\r\n <ModulePath>.*vstest.console.*</ModulePath>\r\n <ModulePath>.*microsoft.intellitrace.*</ModulePath>\r\n <ModulePath>.*vstest.executionengine.*</ModulePath>\r\n <ModulePath>.*vstest.discoveryengine.*</ModulePath>\r\n <ModulePath>.*microsoft.teamfoundation.testplatform.*</ModulePath>\r\n <ModulePath>.*microsoft.visualstudio.testplatform.*</ModulePath>\r\n <ModulePath>.*microsoft.visualstudio.testwindow.*</ModulePath>\r\n <ModulePath>.*microsoft.visualstudio.mstest.*</ModulePath>\r\n <ModulePath>.*microsoft.visualstudio.qualitytools.*</ModulePath>\r\n <ModulePath>.*microsoft.vssdk.testhostadapter.*</ModulePath>\r\n <ModulePath>.*microsoft.vssdk.testhostframework.*</ModulePath>\r\n <ModulePath>.*qtagent32.*</ModulePath>\r\n <ModulePath>.*msvcr.*dll$</ModulePath>\r\n <ModulePath>.*msvcp.*dll$</ModulePath>\r\n <ModulePath>.*clr.dll$</ModulePath>\r\n <ModulePath>.*clr.ni.dll$</ModulePath>\r\n <ModulePath>.*clrjit.dll$</ModulePath>\r\n <ModulePath>.*clrjit.ni.dll$</ModulePath>\r\n <ModulePath>.*mscoree.dll$</ModulePath>\r\n <ModulePath>.*mscoreei.dll$</ModulePath>\r\n <ModulePath>.*mscoreei.ni.dll$</ModulePath>\r\n <ModulePath>.*mscorlib.dll$</ModulePath>\r\n <ModulePath>.*mscorlib.ni.dll$</ModulePath>\r\n </Exclude>\r\n </ModulePaths>\r\n <UseVerifiableInstrumentation>True</UseVerifiableInstrumentation>\r\n <AllowLowIntegrityProcesses>True</AllowLowIntegrityProcesses>\r\n <CollectFromChildProcesses>True</CollectFromChildProcesses>\r\n <CollectAspDotNet>false</CollectAspDotNet>\r\n <SymbolSearchPaths />\r\n <Functions>\r\n <Exclude>\r\n <Function>^std::.*</Function>\r\n <Function>^ATL::.*</Function>\r\n <Function>.*::__GetTestMethodInfo.*</Function>\r\n <Function>.*__CxxPureMSILEntry.*</Function>\r\n <Function>^Microsoft::VisualStudio::CppCodeCoverageFramework::.*</Function>\r\n <Function>^Microsoft::VisualStudio::CppUnitTestFramework::.*</Function>\r\n <Function>.*::YOU_CAN_ONLY_DESIGNATE_ONE_.*</Function>\r\n <Function>^__.*</Function>\r\n <Function>.*::__.*</Function>\r\n </Exclude>\r\n </Functions>\r\n <Attributes>\r\n <Exclude>\r\n <Attribute>^System.Diagnostics.DebuggerHiddenAttribute$</Attribute>\r\n <Attribute>^System.Diagnostics.DebuggerNonUserCodeAttribute$</Attribute>\r\n <Attribute>^System.Runtime.CompilerServices.CompilerGeneratedAttribute$</Attribute>\r\n <Attribute>^System.CodeDom.Compiler.GeneratedCodeAttribute$</Attribute>\r\n <Attribute>^System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute$</Attribute>\r\n </Exclude>\r\n </Attributes>\r\n <Sources>\r\n <Exclude>\r\n <Source>.*\\\\atlmfc\\\\.*</Source>\r\n <Source>.*\\\\vctools\\\\.*</Source>\r\n <Source>.*\\\\public\\\\sdk\\\\.*</Source>\r\n <Source>.*\\\\externalapis\\\\.*</Source>\r\n <Source>.*\\\\microsoft sdks\\\\.*</Source>\r\n <Source>.*\\\\vc\\\\include\\\\.*</Source>\r\n <Source>.*\\\\msclr\\\\.*</Source>\r\n <Source>.*\\\\ucrt\\\\.*</Source>\r\n </Exclude>\r\n </Sources>\r\n <CompanyNames />\r\n <PublicKeyTokens />\r\n </CodeCoverage>\r\n </Configuration>\r\n </DataCollector>\r\n </DataCollectors>\r\n </DataCollectionRunSettings>\r\n</RunSettings>"); + } + + #endregion + + #region Testable Implementations + + private class TestableRunSettingsArgumentExecutor : RunSettingsArgumentExecutor + { + private string runSettingsString; + + internal TestableRunSettingsArgumentExecutor( + CommandLineOptions commandLineOptions, + IRunSettingsProvider runSettingsManager, + string runSettings) + : base(commandLineOptions, runSettingsManager) + + { + this.runSettingsString = runSettings; + } + + protected override XmlReader GetReaderForFile(string runSettingsFile) + { + if (this.runSettingsString == null) + { + return null; + } + + var reader = new StringReader(this.runSettingsString); + var xmlReader = XmlReader.Create(reader, XmlRunSettingsUtilities.ReaderSettings); + + return xmlReader; + } + } + + private class TestableRunSettingsProvider : IRunSettingsProvider + { + public RunSettings ActiveRunSettings + { + get; + set; + } + + public void SetActiveRunSettings(RunSettings runSettings) + { + this.ActiveRunSettings = runSettings; + } + } + + #endregion + } +} diff --git a/test/vstest.console.UnitTests/Processors/RunSpecificTestsArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/RunSpecificTestsArgumentProcessorTests.cs new file mode 100644 index 0000000000..14fcfacf63 --- /dev/null +++ b/test/vstest.console.UnitTests/Processors/RunSpecificTestsArgumentProcessorTests.cs @@ -0,0 +1,271 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors +{ + using System; + using System.Collections.Generic; + + using Microsoft.VisualStudio.TestPlatform.Client; + using Microsoft.VisualStudio.TestPlatform.CommandLine.Processors; + using Microsoft.VisualStudio.TestPlatform.CommandLine.TestPlatformHelpers; + using Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Implementations; + using Microsoft.VisualStudio.TestPlatform.Common.Logging; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.Utilities; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + using Moq; + + [TestClass] + public class RunSpecificTestsArgumentProcessorTests + { + MockFileHelper mockFileHelper; + string dummyTestFilePath = "DummyTest.dll"; + + public RunSpecificTestsArgumentProcessorTests() + { + this.mockFileHelper = new MockFileHelper(); + this.mockFileHelper.ExistsInvoker = (path) => + { + return string.Equals(path, this.dummyTestFilePath); + }; + } + + [TestMethod] + public void GetMetadataShouldReturnRunSpecificTestsArgumentProcessorCapabilities() + { + RunSpecificTestsArgumentProcessor processor = new RunSpecificTestsArgumentProcessor(); + Assert.IsTrue(processor.Metadata.Value is RunSpecificTestsArgumentProcessorCapabilities); + } + + [TestMethod] + public void GetExecutorShouldReturnRunSpecificTestsArgumentProcessorCapabilities() + { + RunSpecificTestsArgumentProcessor processor = new RunSpecificTestsArgumentProcessor(); + Assert.IsTrue(processor.Executor.Value is RunSpecificTestsArgumentExecutor); + } + + #region RunSpecificTestsArgumentProcessorCapabilitiesTests + + [TestMethod] + public void CapabilitiesShouldReturnAppropriateProperties() + { + RunSpecificTestsArgumentProcessorCapabilities capabilities = new RunSpecificTestsArgumentProcessorCapabilities(); + Assert.AreEqual("/Tests", capabilities.CommandName); + StringAssert.Contains(capabilities.HelpContentResourceName,"/Tests:<Test Names>\n Run tests with names that match the provided values."); + + Assert.AreEqual(HelpContentPriority.RunSpecificTestsArgumentProcessorHelpPriority, capabilities.HelpPriority); + Assert.AreEqual(true, capabilities.IsAction); + Assert.AreEqual(ArgumentProcessorPriority.Normal, capabilities.Priority); + + Assert.AreEqual(false, capabilities.AllowMultiple); + Assert.AreEqual(false, capabilities.AlwaysExecute); + Assert.AreEqual(false, capabilities.IsSpecialCommand); + } + #endregion + + #region RunSpecificTestsArgumentExecutorTests + + [TestMethod] + public void ExecutorExecuteForNoSourcesShouldReturnFail() + { + CommandLineOptions.Instance.Reset(); + + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, new TestPlatform(), TestLoggerManager.Instance, TestRunResultAggregator.Instance); + RunSpecificTestsArgumentExecutor executor = new RunSpecificTestsArgumentExecutor(CommandLineOptions.Instance, null, testRequestManager); + ArgumentProcessorResult argumentProcessorResult = executor.Execute(); + Assert.AreEqual(ArgumentProcessorResult.Fail, argumentProcessorResult); + } + + [TestMethod] + public void ExecutorExecuteForValidSourceWithTestCaseFilterShouldReturnFail() + { + this.ResetAndAddSourceToCommandLineOptions(); + + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, new TestPlatform(), TestLoggerManager.Instance, TestRunResultAggregator.Instance); + var executor = new RunSpecificTestsArgumentExecutor(CommandLineOptions.Instance, null, testRequestManager); + CommandLineOptions.Instance.TestCaseFilterValue = "Filter"; + ArgumentProcessorResult argumentProcessorResult = executor.Execute(); + Assert.AreEqual(ArgumentProcessorResult.Fail, argumentProcessorResult); + } + + [TestMethod] + public void ExecutorExecuteShouldCatchTestPlatformExceptionThrownDuringDiscoveryAndReturnFail() + { + var mockTestPlatform = new Mock<ITestPlatform>(); + var mockTestRunRequest = new Mock<ITestRunRequest>(); + var mockDiscoveryRequest = new Mock<IDiscoveryRequest>(); + + mockDiscoveryRequest.Setup(dr => dr.DiscoverAsync()).Throws(new TestPlatformException("DummyTestPlatformException")); + mockTestPlatform.Setup(tp => tp.CreateTestRunRequest(It.IsAny<TestRunCriteria>())).Returns(mockTestRunRequest.Object); + mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny<DiscoveryCriteria>())).Returns(mockDiscoveryRequest.Object); + + this.ResetAndAddSourceToCommandLineOptions(); + + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestLoggerManager.Instance, TestRunResultAggregator.Instance); + var executor = new RunSpecificTestsArgumentExecutor(CommandLineOptions.Instance, null, testRequestManager); + + ArgumentProcessorResult argumentProcessorResult = executor.Execute(); + Assert.AreEqual(ArgumentProcessorResult.Fail, argumentProcessorResult); + } + + [TestMethod] + public void ExecutorExecuteShouldCatchInvalidOperationExceptionThrownDuringDiscoveryAndReturnFail() + { + var mockTestPlatform = new Mock<ITestPlatform>(); + var mockTestRunRequest = new Mock<ITestRunRequest>(); + var mockDiscoveryRequest = new Mock<IDiscoveryRequest>(); + + mockDiscoveryRequest.Setup(dr => dr.DiscoverAsync()).Throws(new InvalidOperationException("DummyInvalidOperationException")); + mockTestPlatform.Setup(tp => tp.CreateTestRunRequest(It.IsAny<TestRunCriteria>())).Returns(mockTestRunRequest.Object); + mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny<DiscoveryCriteria>())).Returns(mockDiscoveryRequest.Object); + + this.ResetAndAddSourceToCommandLineOptions(); + + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestLoggerManager.Instance, TestRunResultAggregator.Instance); + var executor = new RunSpecificTestsArgumentExecutor(CommandLineOptions.Instance, null, testRequestManager); + + ArgumentProcessorResult argumentProcessorResult = executor.Execute(); + Assert.AreEqual(ArgumentProcessorResult.Fail, argumentProcessorResult); + } + + [TestMethod] + public void ExecutorExecuteShouldCatchSettingsExceptionThrownDuringDiscoveryAndReturnFail() + { + var mockTestPlatform = new Mock<ITestPlatform>(); + var mockTestRunRequest = new Mock<ITestRunRequest>(); + var mockDiscoveryRequest = new Mock<IDiscoveryRequest>(); + + mockDiscoveryRequest.Setup(dr => dr.DiscoverAsync()).Throws(new SettingsException("DummySettingsException")); + mockTestPlatform.Setup(tp => tp.CreateTestRunRequest(It.IsAny<TestRunCriteria>())).Returns(mockTestRunRequest.Object); + mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny<DiscoveryCriteria>())).Returns(mockDiscoveryRequest.Object); + + this.ResetAndAddSourceToCommandLineOptions(); + + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestLoggerManager.Instance, TestRunResultAggregator.Instance); + var executor = new RunSpecificTestsArgumentExecutor(CommandLineOptions.Instance, null, testRequestManager); + + ArgumentProcessorResult argumentProcessorResult = executor.Execute(); + Assert.AreEqual(ArgumentProcessorResult.Fail, argumentProcessorResult); + } + + [TestMethod] + public void ExecutorExecuteShouldCatchTestPlatformExceptionThrownDuringExecutionAndReturnFail() + { + var mockTestPlatform = new Mock<ITestPlatform>(); + var mockTestRunRequest = new Mock<ITestRunRequest>(); + var mockDiscoveryRequest = new Mock<IDiscoveryRequest>(); + + List<TestCase> list = new List<TestCase>(); + list.Add(new TestCase("Test1", new Uri("http://FooTestUri1"), "Source1")); + list.Add(new TestCase("Test2", new Uri("http://FooTestUri2"), "Source2")); + mockDiscoveryRequest.Setup(dr => dr.DiscoverAsync()).Raises(dr => dr.OnDiscoveredTests += null, new DiscoveredTestsEventArgs(list)); + + mockTestRunRequest.Setup(dr => dr.ExecuteAsync()).Throws(new TestPlatformException("DummyTestPlatformException")); + mockTestPlatform.Setup(tp => tp.CreateTestRunRequest(It.IsAny<TestRunCriteria>())).Returns(mockTestRunRequest.Object); + mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny<DiscoveryCriteria>())).Returns(mockDiscoveryRequest.Object); + + this.ResetAndAddSourceToCommandLineOptions(); + + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestLoggerManager.Instance, TestRunResultAggregator.Instance); + var executor = new RunSpecificTestsArgumentExecutor(CommandLineOptions.Instance, null, testRequestManager); + + executor.Initialize("Test1"); + + ArgumentProcessorResult argumentProcessorResult = executor.Execute(); + Assert.AreEqual(ArgumentProcessorResult.Fail, argumentProcessorResult); + } + + [TestMethod] + public void ExecutorExecuteShouldCatchSettingsExceptionThrownDuringExecutionAndReturnFail() + { + var mockTestPlatform = new Mock<ITestPlatform>(); + var mockTestRunRequest = new Mock<ITestRunRequest>(); + var mockDiscoveryRequest = new Mock<IDiscoveryRequest>(); + + List<TestCase> list = new List<TestCase>(); + list.Add(new TestCase("Test1", new Uri("http://FooTestUri1"), "Source1")); + list.Add(new TestCase("Test2", new Uri("http://FooTestUri2"), "Source2")); + mockDiscoveryRequest.Setup(dr => dr.DiscoverAsync()).Raises(dr => dr.OnDiscoveredTests += null, new DiscoveredTestsEventArgs(list)); + + mockTestRunRequest.Setup(dr => dr.ExecuteAsync()).Throws(new SettingsException("DummySettingsException")); + mockTestPlatform.Setup(tp => tp.CreateTestRunRequest(It.IsAny<TestRunCriteria>())).Returns(mockTestRunRequest.Object); + mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny<DiscoveryCriteria>())).Returns(mockDiscoveryRequest.Object); + + this.ResetAndAddSourceToCommandLineOptions(); + + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestLoggerManager.Instance, TestRunResultAggregator.Instance); + var executor = new RunSpecificTestsArgumentExecutor(CommandLineOptions.Instance, null, testRequestManager); + + executor.Initialize("Test1"); + + ArgumentProcessorResult argumentProcessorResult = executor.Execute(); + Assert.AreEqual(ArgumentProcessorResult.Fail, argumentProcessorResult); + } + + [TestMethod] + public void ExecutorExecuteShouldCatchInvalidOperationExceptionThrownDuringExecutionAndReturnFail() + { + var mockTestPlatform = new Mock<ITestPlatform>(); + var mockTestRunRequest = new Mock<ITestRunRequest>(); + var mockDiscoveryRequest = new Mock<IDiscoveryRequest>(); + + List<TestCase> list = new List<TestCase>(); + list.Add(new TestCase("Test1", new Uri("http://FooTestUri1"), "Source1")); + list.Add(new TestCase("Test2", new Uri("http://FooTestUri2"), "Source2")); + mockDiscoveryRequest.Setup(dr => dr.DiscoverAsync()).Raises(dr => dr.OnDiscoveredTests += null, new DiscoveredTestsEventArgs(list)); + + mockTestRunRequest.Setup(dr => dr.ExecuteAsync()).Throws(new SettingsException("DummySettingsException")); + mockTestPlatform.Setup(tp => tp.CreateTestRunRequest(It.IsAny<TestRunCriteria>())).Returns(mockTestRunRequest.Object); + mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny<DiscoveryCriteria>())).Returns(mockDiscoveryRequest.Object); + + this.ResetAndAddSourceToCommandLineOptions(); + + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestLoggerManager.Instance, TestRunResultAggregator.Instance); + var executor = new RunSpecificTestsArgumentExecutor(CommandLineOptions.Instance, null, testRequestManager); + + executor.Initialize("Test1"); + + ArgumentProcessorResult argumentProcessorResult = executor.Execute(); + Assert.AreEqual(ArgumentProcessorResult.Fail, argumentProcessorResult); + } + + [TestMethod] + public void ExecutorExecuteShouldForValidSourcesAndValidSelectedTestsRunsTestsAndReturnSuccess() + { + var mockTestPlatform = new Mock<ITestPlatform>(); + var mockTestRunRequest = new Mock<ITestRunRequest>(); + var mockDiscoveryRequest = new Mock<IDiscoveryRequest>(); + + this.ResetAndAddSourceToCommandLineOptions(); + + List<TestCase> list = new List<TestCase>(); + list.Add(new TestCase("Test1", new Uri("http://FooTestUri1"), "Source1")); + mockDiscoveryRequest.Setup(dr => dr.DiscoverAsync()).Raises(dr => dr.OnDiscoveredTests += null, new DiscoveredTestsEventArgs(list)); + + mockTestPlatform.Setup(tp => tp.CreateTestRunRequest(It.IsAny<TestRunCriteria>())).Returns(mockTestRunRequest.Object); + mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny<DiscoveryCriteria>())).Returns(mockDiscoveryRequest.Object); + + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestLoggerManager.Instance, TestRunResultAggregator.Instance); + var executor = new RunSpecificTestsArgumentExecutor(CommandLineOptions.Instance, null, testRequestManager); + + executor.Initialize("Test1"); + + ArgumentProcessorResult argumentProcessorResult = executor.Execute(); + Assert.AreEqual(ArgumentProcessorResult.Success, argumentProcessorResult); + } + + #endregion + + private void ResetAndAddSourceToCommandLineOptions() + { + CommandLineOptions.Instance.Reset(); + CommandLineOptions.Instance.TestCaseFilterValue = null; + + CommandLineOptions.Instance.FileHelper = this.mockFileHelper; + + CommandLineOptions.Instance.AddSource(this.dummyTestFilePath); + } + } +} diff --git a/test/vstest.console.UnitTests/Processors/RunTestsArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/RunTestsArgumentProcessorTests.cs new file mode 100644 index 0000000000..b8019ad262 --- /dev/null +++ b/test/vstest.console.UnitTests/Processors/RunTestsArgumentProcessorTests.cs @@ -0,0 +1,314 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors +{ + using System; + using System.Collections.Generic; + using System.Reflection; + + using Microsoft.VisualStudio.TestPlatform.Client; + using Microsoft.VisualStudio.TestPlatform.CommandLine.Internal; + using Microsoft.VisualStudio.TestPlatform.CommandLine.Processors; + using Microsoft.VisualStudio.TestPlatform.CommandLine.TestPlatformHelpers; + using Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Implementations; + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework; + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.Utilities; + using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; + using Microsoft.VisualStudio.TestPlatform.Common.Logging; + using Microsoft.VisualStudio.TestPlatform.Common.Utilities; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + using Microsoft.VisualStudio.TestPlatform.Utilities; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + using Moq; + + /// <summary> + /// Tests for RunTestsArgumentProcessor + /// </summary> + [TestClass] + public class RunTestsArgumentProcessorTests + { + MockFileHelper mockFileHelper; + string dummyTestFilePath = "DummyTest.dll"; + + public RunTestsArgumentProcessorTests() + { + this.mockFileHelper = new MockFileHelper(); + this.mockFileHelper.ExistsInvoker = (path) => + { + if (string.Equals(path, this.dummyTestFilePath)) + { + return true; + } + else + { + return false; + } + }; + SetupMockExtensions(); + } + + [TestMethod] + public void GetMetadataShouldReturnRunTestsArgumentProcessorCapabilities() + { + RunTestsArgumentProcessor processor = new RunTestsArgumentProcessor(); + Assert.IsTrue(processor.Metadata.Value is RunTestsArgumentProcessorCapabilities); + } + + [TestMethod] + public void GetExecuterShouldReturnRunTestsArgumentProcessorCapabilities() + { + RunTestsArgumentProcessor processor = new RunTestsArgumentProcessor(); + Assert.IsTrue(processor.Executor.Value is RunTestsArgumentExecutor); + } + + #region RunTestsArgumentProcessorCapabilitiesTests + + [TestMethod] + public void CapabilitiesShouldReturnAppropriateProperties() + { + RunTestsArgumentProcessorCapabilities capabilities = new RunTestsArgumentProcessorCapabilities(); + Assert.AreEqual("/RunTests", capabilities.CommandName); + Assert.AreEqual("[TestFileNames]\n Run tests from the specified files. Separate multiple test file names\n by spaces.\n Examples: mytestproject.dll\n mytestproject.dll myothertestproject.exe", capabilities.HelpContentResourceName); + + Assert.AreEqual(HelpContentPriority.RunTestsArgumentProcessorHelpPriority, capabilities.HelpPriority); + Assert.AreEqual(true, capabilities.IsAction); + Assert.AreEqual(ArgumentProcessorPriority.Normal, capabilities.Priority); + + Assert.AreEqual(false, capabilities.AllowMultiple); + Assert.AreEqual(false, capabilities.AlwaysExecute); + Assert.AreEqual(true, capabilities.IsSpecialCommand); + } + #endregion + + #region RunTestsArgumentExecutorTests + + [TestMethod] + public void ExecutorExecuteForNoSourcesShouldReturnFail() + { + CommandLineOptions.Instance.Reset(); + + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, new TestPlatform(), TestLoggerManager.Instance, TestRunResultAggregator.Instance); + + var executor = new RunTestsArgumentExecutor(CommandLineOptions.Instance, null, testRequestManager); + + ArgumentProcessorResult argumentProcessorResult = executor.Execute(); + + Assert.AreEqual(ArgumentProcessorResult.Fail, argumentProcessorResult); + } + + [TestMethod] + public void ExecutorExecuteShouldCatchTestPlatformExceptionAndReturnFail() + { + var mockTestPlatform = new Mock<ITestPlatform>(); + var mockTestRunRequest = new Mock<ITestRunRequest>(); + + mockTestRunRequest.Setup(tr => tr.ExecuteAsync()).Throws(new TestPlatformException("DummyTestPlatformException")); + mockTestPlatform.Setup(tp => tp.CreateTestRunRequest(It.IsAny<TestRunCriteria>())).Returns(mockTestRunRequest.Object); + + this.ResetAndAddSourceToCommandLineOptions(); + + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestLoggerManager.Instance, TestRunResultAggregator.Instance); + var executor = new RunTestsArgumentExecutor(CommandLineOptions.Instance, null, testRequestManager); + + ArgumentProcessorResult argumentProcessorResult = executor.Execute(); + Assert.AreEqual(ArgumentProcessorResult.Fail, argumentProcessorResult); + } + + [TestMethod] + public void ExecutorExecuteShouldCatchSettingsExceptionAndReturnFail() + { + var mockTestPlatform = new Mock<ITestPlatform>(); + var mockTestRunRequest = new Mock<ITestRunRequest>(); + + mockTestRunRequest.Setup(tr => tr.ExecuteAsync()).Throws(new SettingsException("DummySettingsException")); + mockTestPlatform.Setup(tp => tp.CreateTestRunRequest(It.IsAny<TestRunCriteria>())).Returns(mockTestRunRequest.Object); + + this.ResetAndAddSourceToCommandLineOptions(); + + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestLoggerManager.Instance, TestRunResultAggregator.Instance); + var executor = new RunTestsArgumentExecutor(CommandLineOptions.Instance, null, testRequestManager); + + ArgumentProcessorResult argumentProcessorResult = executor.Execute(); + Assert.AreEqual(ArgumentProcessorResult.Fail, argumentProcessorResult); + } + + [TestMethod] + public void ExecutorExecuteShouldCatchInvalidOperationExceptionAndReturnFail() + { + var mockTestPlatform = new Mock<ITestPlatform>(); + var mockTestRunRequest = new Mock<ITestRunRequest>(); + + mockTestRunRequest.Setup(tr => tr.ExecuteAsync()).Throws(new InvalidOperationException("DummyInvalidOperationException")); + mockTestPlatform.Setup(tp => tp.CreateTestRunRequest(It.IsAny<TestRunCriteria>())).Returns(mockTestRunRequest.Object); + + this.ResetAndAddSourceToCommandLineOptions(); + + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestLoggerManager.Instance, TestRunResultAggregator.Instance); + var executor = new RunTestsArgumentExecutor(CommandLineOptions.Instance, null, testRequestManager); + + ArgumentProcessorResult argumentProcessorResult = executor.Execute(); + Assert.AreEqual(ArgumentProcessorResult.Fail, argumentProcessorResult); + } + + [TestMethod] + public void ExecutorExecuteShouldThrowOtherExceptions() + { + var mockTestPlatform = new Mock<ITestPlatform>(); + var mockTestRunRequest = new Mock<ITestRunRequest>(); + + mockTestRunRequest.Setup(tr => tr.ExecuteAsync()).Throws(new Exception("DummyException")); + mockTestPlatform.Setup(tp => tp.CreateTestRunRequest(It.IsAny<TestRunCriteria>())).Returns(mockTestRunRequest.Object); + + this.ResetAndAddSourceToCommandLineOptions(); + + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestLoggerManager.Instance, TestRunResultAggregator.Instance); + var executor = new RunTestsArgumentExecutor(CommandLineOptions.Instance, null, testRequestManager); + + Assert.ThrowsException<Exception>(() => executor.Execute()); + } + + [TestMethod] + public void ExecutorExecuteShouldForListOfTestsReturnSuccess() + { + var mockTestPlatform = new Mock<ITestPlatform>(); + var mockTestRunRequest = new Mock<ITestRunRequest>(); + var mockConsoleOutput = new Mock<IOutput>(); + + List<TestCase> list = new List<TestCase>(); + list.Add(new TestCase("Test1", new Uri("http://FooTestUri1"), "Source1")); + list.Add(new TestCase("Test2", new Uri("http://FooTestUri2"), "Source2")); + var mockTestRunStats = new Mock<ITestRunStatistics>(); + + var args = new TestRunCompleteEventArgs(mockTestRunStats.Object, false, false, null, null, new TimeSpan()); + + mockTestPlatform.Setup(tp => tp.CreateTestRunRequest(It.IsAny<TestRunCriteria>())).Returns(mockTestRunRequest.Object); + + this.ResetAndAddSourceToCommandLineOptions(); + + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestLoggerManager.Instance, TestRunResultAggregator.Instance); + var executor = new RunTestsArgumentExecutor(CommandLineOptions.Instance, null, testRequestManager); + + var result = executor.Execute(); + // Assert + mockTestRunRequest.Verify(tr => tr.ExecuteAsync(), Times.Once); + Assert.AreEqual(ArgumentProcessorResult.Success, result); + } + + #endregion + + private void ResetAndAddSourceToCommandLineOptions() + { + CommandLineOptions.Instance.Reset(); + + CommandLineOptions.Instance.FileHelper = this.mockFileHelper; + CommandLineOptions.Instance.AddSource(this.dummyTestFilePath); + } + + public static void SetupMockExtensions() + { + SetupMockExtensions(() => { }); + } + + public static void SetupMockExtensions(Action callback) + { + SetupMockExtensions(new string[] { typeof(RunTestsArgumentProcessorTests).GetTypeInfo().Assembly.Location, typeof(ConsoleLogger).GetTypeInfo().Assembly.Location }, callback); + } + + public static void SetupMockExtensions(string[] extensions, Action callback) + { + // Setup mocks. + var testableTestPluginCache = new TestableTestPluginCache(new Mock<IPathUtilities>().Object); + testableTestPluginCache.DoesDirectoryExistSetter = true; + + testableTestPluginCache.FilesInDirectory = (path, pattern) => + { + if (pattern.Equals("*.dll")) + { + callback.Invoke(); + return extensions; + } + return new string[] { }; + }; + + // Setup the testable instance. + TestPluginCache.Instance = testableTestPluginCache; + } + + [ExtensionUri("testlogger://logger")] + [FriendlyName("TestLoggerExtension")] + private class ValidLogger3 : ITestLogger + { + public void Initialize(TestLoggerEvents events, string testRunDirectory) + { + events.TestRunMessage += TestMessageHandler; + events.TestRunComplete += Events_TestRunComplete; + events.TestResult += Events_TestResult; + } + + private void Events_TestResult(object sender, TestResultEventArgs e) + { + } + + private void Events_TestRunComplete(object sender, TestRunCompleteEventArgs e) + { + + } + + private void TestMessageHandler(object sender, TestRunMessageEventArgs e) + { + } + } + } + + #region Testable implementation + + public class TestableTestPluginCache : TestPluginCache + { + public TestableTestPluginCache(IPathUtilities pathUtilities) + : base(pathUtilities) + { + } + + internal Func<string, string, string[]> FilesInDirectory + { + get; + set; + } + + public bool DoesDirectoryExistSetter + { + get; + set; + } + + public Func<IEnumerable<string>, TestExtensions> TestExtensionsSetter { get; set; } + + internal override bool DoesDirectoryExist(string path) + { + return this.DoesDirectoryExistSetter; + } + + internal override string[] GetFilesInDirectory(string path, string searchPattern) + { + return this.FilesInDirectory.Invoke(path, searchPattern); + } + + internal override TestExtensions GetTestExtensions(IEnumerable<string> extensions) + { + if (this.TestExtensionsSetter == null) + { + return base.GetTestExtensions(extensions); + } + else + { + return this.TestExtensionsSetter.Invoke(extensions); + } + } + } + + #endregion + +} diff --git a/test/vstest.console.UnitTests/Processors/TestAdapterPathArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/TestAdapterPathArgumentProcessorTests.cs new file mode 100644 index 0000000000..9c54638860 --- /dev/null +++ b/test/vstest.console.UnitTests/Processors/TestAdapterPathArgumentProcessorTests.cs @@ -0,0 +1,220 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Reflection; + + using Microsoft.VisualStudio.TestPlatform.CommandLine.Processors; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.Utilities; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + using Moq; + + [TestClass] + public class TestAdapterPathArgumentProcessorTests + { + [TestMethod] + public void GetMetadataShouldReturnTestAdapterPathArgumentProcessorCapabilities() + { + var processor = new TestAdapterPathArgumentProcessor(); + Assert.IsTrue(processor.Metadata.Value is TestAdapterPathArgumentProcessorCapabilities); + } + + [TestMethod] + public void GetExecuterShouldReturnTestAdapterPathArgumentProcessorCapabilities() + { + var processor = new TestAdapterPathArgumentProcessor(); + Assert.IsTrue(processor.Executor.Value is TestAdapterPathArgumentExecutor); + } + + #region TestAdapterPathArgumentProcessorCapabilities tests + + [TestMethod] + public void CapabilitiesShouldReturnAppropriateProperties() + { + var capabilities = new TestAdapterPathArgumentProcessorCapabilities(); + Assert.AreEqual("/TestAdapterPath", capabilities.CommandName); + Assert.AreEqual("/TestAdapterPath\n This makes vstest.console.exe process use custom test adapters\n from a given path (if any) in the test run. \n Example /TestAdapterPath:<pathToCustomAdapters>", capabilities.HelpContentResourceName); + + Assert.AreEqual(HelpContentPriority.TestAdapterPathArgumentProcessorHelpPriority, capabilities.HelpPriority); + Assert.AreEqual(false, capabilities.IsAction); + Assert.AreEqual(ArgumentProcessorPriority.TestAdapterPath, capabilities.Priority); + + Assert.AreEqual(false, capabilities.AllowMultiple); + Assert.AreEqual(false, capabilities.AlwaysExecute); + Assert.AreEqual(false, capabilities.IsSpecialCommand); + } + + #endregion + + #region TestAdapterPathArgumentExecutor tests + + [TestMethod] + public void InitializeShouldThrowIfArgumentIsNull() + { + var mockTestPlatform = new Mock<ITestPlatform>(); + var mockOutput = new Mock<IOutput>(); + var executor = new TestAdapterPathArgumentExecutor(CommandLineOptions.Instance, mockTestPlatform.Object, mockOutput.Object); + + var message = + @"The /TestAdapterPath parameter requires a value, which is path of a location containing custom test adapters. Example: /TestAdapterPath:c:\MyCustomAdapters"; + + var isExceptionThrown = false; + + try + { + executor.Initialize(null); + } + catch (Exception ex) + { + isExceptionThrown = true; + Assert.IsTrue(ex is CommandLineException); + Assert.AreEqual(message, ex.Message); + } + + Assert.IsTrue(isExceptionThrown); + } + + [TestMethod] + public void InitializeShouldThrowIfArgumentIsAWhiteSpace() + { + var mockTestPlatform = new Mock<ITestPlatform>(); + var mockOutput = new Mock<IOutput>(); + var executor = new TestAdapterPathArgumentExecutor(CommandLineOptions.Instance, mockTestPlatform.Object, mockOutput.Object); + + var message = + @"The /TestAdapterPath parameter requires a value, which is path of a location containing custom test adapters. Example: /TestAdapterPath:c:\MyCustomAdapters"; + + var isExceptionThrown = false; + + try + { + executor.Initialize(" "); + } + catch (Exception ex) + { + isExceptionThrown = true; + Assert.IsTrue(ex is CommandLineException); + Assert.AreEqual(message, ex.Message); + } + + Assert.IsTrue(isExceptionThrown); + } + + [TestMethod] + public void InitializeShouldThrowIfPathDoesNotExist() + { + var mockTestPlatform = new Mock<ITestPlatform>(); + var mockOutput = new Mock<IOutput>(); + var executor = new TestAdapterPathArgumentExecutor(CommandLineOptions.Instance, mockTestPlatform.Object, mockOutput.Object); + + var folder = "C:\\temp\\thisfolderdoesnotexist"; + + var message = string.Format( + @"The path '{0}' specified in the 'TestAdapterPath' is invalid. Error: {1}", + folder, + "The custom test adapter search path provided was not found, provide a valid path and try again."); + + var isExceptionThrown = false; + + try + { + executor.Initialize("\"" +folder + "\""); + } + catch (Exception ex) + { + isExceptionThrown = true; + Assert.IsTrue(ex is CommandLineException); + Assert.AreEqual(message, ex.Message); + } + + Assert.IsTrue(isExceptionThrown); + } + + [TestMethod] + public void InitializeShouldUpdateAdditionalExtensionsWithTestAdapterPath() + { + var mockTestPlatform = new Mock<ITestPlatform>(); + var mockOutput = new Mock<IOutput>(); + var executor = new TestableTestAdapterPathArgumentExecutor(CommandLineOptions.Instance, mockTestPlatform.Object, mockOutput.Object); + + var currentAssemblyPath = typeof(TestAdapterPathArgumentExecutor).GetTypeInfo().Assembly.Location; + var currentFolder = Path.GetDirectoryName(currentAssemblyPath); + + executor.TestAdapters = (directory) => + { + if (string.Equals(directory, currentFolder)) + { + return new List<string> + { + typeof(TestAdapterPathArgumentExecutor).GetTypeInfo() + .Assembly.Location + }; + } + + return new List<string> { }; + }; + + + executor.Initialize(currentFolder); + + mockTestPlatform.Verify(tp => tp.UpdateExtensions(new List<string> { currentAssemblyPath }, false), Times.Once); + } + + [TestMethod] + public void InitializeShouldReportIfNoTestAdaptersFoundInPath() + { + var mockTestPlatform = new Mock<ITestPlatform>(); + var mockOutput = new Mock<IOutput>(); + var executor = new TestableTestAdapterPathArgumentExecutor(CommandLineOptions.Instance, mockTestPlatform.Object, mockOutput.Object); + + var currentAssemblyPath = typeof(TestAdapterPathArgumentExecutor).GetTypeInfo().Assembly.Location; + var currentFolder = Path.GetDirectoryName(currentAssemblyPath); + + executor.TestAdapters = (directory) => + { + return new List<string> { }; + }; + + executor.Initialize(currentFolder); + + mockOutput.Verify( + o => + o.WriteLine( + string.Format( + "Warning: The path '{0}' specified in the 'TestAdapterPath' does not contain any test adapters, provide a valid path and try again.", + currentFolder), + OutputLevel.Warning)); + + } + + #endregion + + #region Testable implementations + + private class TestableTestAdapterPathArgumentExecutor : TestAdapterPathArgumentExecutor + { + internal TestableTestAdapterPathArgumentExecutor(CommandLineOptions options, ITestPlatform testPlatform, IOutput output) + : base(options, testPlatform, output) + { + } + + internal Func<string, IEnumerable<string>> TestAdapters { get; set; } + + internal override IEnumerable<string> GetTestAdaptersFromDirectory(string directory) + { + if (this.TestAdapters != null) + { + return this.TestAdapters(directory); + } + return new List<string> { }; + } + } + + #endregion + } +} diff --git a/test/vstest.console.UnitTests/Processors/TestCaseFilterArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/TestCaseFilterArgumentProcessorTests.cs new file mode 100644 index 0000000000..53ef8e10fd --- /dev/null +++ b/test/vstest.console.UnitTests/Processors/TestCaseFilterArgumentProcessorTests.cs @@ -0,0 +1,98 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors +{ + using Microsoft.VisualStudio.TestTools.UnitTesting; + using System; + using TestPlatform.CommandLine.Processors; + + [TestClass] + public class TestCaseFilterArgumentProcessorTests + { + [TestMethod] + public void GetMetadataShouldReturnTestCaseFilterArgumentProcessorCapabilities() + { + TestCaseFilterArgumentProcessor processor = new TestCaseFilterArgumentProcessor(); + Assert.IsTrue(processor.Metadata.Value is TestCaseFilterArgumentProcessorCapabilities); + } + + [TestMethod] + public void GetExecutorShouldReturnTestCaseFilterArgumentProcessorCapabilities() + { + TestCaseFilterArgumentProcessor processor = new TestCaseFilterArgumentProcessor(); + Assert.IsTrue(processor.Executor.Value is TestCaseFilterArgumentExecutor); + } + + #region TestCaseFilterArgumentProcessorCapabilitiesTests + + [TestMethod] + public void CapabilitiesShouldAppropriateProperties() + { + TestCaseFilterArgumentProcessorCapabilities capabilities = new TestCaseFilterArgumentProcessorCapabilities(); + Assert.AreEqual("/TestCaseFilter", capabilities.CommandName); + StringAssert.Contains(capabilities.HelpContentResourceName, "/TestCaseFilter:<Expression>\n Run tests that match the given expression.\n <Expression> is of the format <property>Operator<value>[|&<Expression>]"); + + Assert.AreEqual(HelpContentPriority.TestCaseFilterArgumentProcessorHelpPriority, capabilities.HelpPriority); + Assert.AreEqual(false, capabilities.IsAction); + Assert.AreEqual(ArgumentProcessorPriority.Normal, capabilities.Priority); + + Assert.AreEqual(false, capabilities.AllowMultiple); + Assert.AreEqual(false, capabilities.AlwaysExecute); + Assert.AreEqual(false, capabilities.IsSpecialCommand); + } + + #endregion + + [TestMethod] + public void ExecutorInitializeWithNullOrEmptyTestCaseFilterShouldThrowCommandLineException() + { + var options = CommandLineOptions.Instance; + TestCaseFilterArgumentExecutor executor = new TestCaseFilterArgumentExecutor(options); + + try + { + executor.Initialize(null); + } + catch (Exception ex) + { + Assert.IsTrue(ex is CommandLineException); + StringAssert.Contains(ex.Message, @"The /TestCaseFilter argument requires the filter value."); + } + } + + [TestMethod] + public void ExecutorInitializeWithInvalidTestCaseFilterShouldThrowCommandLineException() + { + var options = CommandLineOptions.Instance; + TestCaseFilterArgumentExecutor executor = new TestCaseFilterArgumentExecutor(options); + + try + { + executor.Initialize("Foo"); + } + catch (Exception ex) + { + Assert.IsTrue(ex is CommandLineException); + StringAssert.Contains(ex.Message, @"The /TestCaseFilter argument requires the filter value."); + } + } + + [TestMethod] + public void ExecutorInitializeWithValidTestCaseFilterShouldAddTestCaseFilterToCommandLineOptions() + { + var options = CommandLineOptions.Instance; + TestCaseFilterArgumentExecutor executor = new TestCaseFilterArgumentExecutor(options); + + executor.Initialize("Debug"); + Assert.AreEqual("Debug", options.TestCaseFilterValue); + } + + [TestMethod] + public void ExecutorExecutoreturnArgumentProcessorResultSuccess() + { + var executor = new TestCaseFilterArgumentExecutor(CommandLineOptions.Instance); + var result = executor.Execute(); + Assert.AreEqual(ArgumentProcessorResult.Success, result); + } + } +} diff --git a/test/vstest.console.UnitTests/Processors/TestSourceArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/TestSourceArgumentProcessorTests.cs new file mode 100644 index 0000000000..c2192f363d --- /dev/null +++ b/test/vstest.console.UnitTests/Processors/TestSourceArgumentProcessorTests.cs @@ -0,0 +1,121 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors +{ + using System; + using System.Linq; + + using Microsoft.VisualStudio.TestPlatform.CommandLine.Processors; + using Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Implementations; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + using Moq; + + // <summary> + // Tests for TestSourceArgumentProcessor + // </summary> + [TestClass] + public class TestSourceArgumentProcessorTests + { + /// <summary> + /// The help argument processor get metadata should return help argument processor capabilities. + /// </summary> + [TestMethod] + public void GetMetadataShouldReturnTestSourceArgumentProcessorCapabilities() + { + TestSourceArgumentProcessor processor = new TestSourceArgumentProcessor(); + Assert.IsTrue(processor.Metadata.Value is TestSourceArgumentProcessorCapabilities); + } + + /// <summary> + /// The help argument processor get executer should return help argument processor capabilities. + /// </summary> + [TestMethod] + public void GetExecuterShouldReturnTestSourceArgumentProcessorCapabilities() + { + TestSourceArgumentProcessor processor = new TestSourceArgumentProcessor(); + Assert.IsTrue(processor.Executor.Value is TestSourceArgumentExecutor); + } + + #region TestSourceArgumentProcessorCapabilitiesTests + + [TestMethod] + public void CapabilitiesShouldReturnAppropriateProperties() + { + TestSourceArgumentProcessorCapabilities capabilities = new TestSourceArgumentProcessorCapabilities(); + Assert.AreEqual("TestSource", capabilities.CommandName); + Assert.IsNull(capabilities.HelpContentResourceName); + + Assert.AreEqual(HelpContentPriority.None, capabilities.HelpPriority); + Assert.AreEqual(false, capabilities.IsAction); + Assert.AreEqual(ArgumentProcessorPriority.Normal, capabilities.Priority); + + Assert.AreEqual(true, capabilities.AllowMultiple); + Assert.AreEqual(false, capabilities.AlwaysExecute); + Assert.AreEqual(true, capabilities.IsSpecialCommand); + } + + #endregion + + #region TestSourceArgumentExecutorTests + + [TestMethod] + public void ExecuterInitializeWithInvalidSourceShouldThrowCommandLineException() + { + var options = CommandLineOptions.Instance; + TestSourceArgumentExecutor executor = new TestSourceArgumentExecutor(options); + + // This path is invalid + string testFilePath = "TestFile.txt"; + + try + { + executor.Initialize(testFilePath); + } + catch (Exception ex) + { + Assert.IsTrue(ex is CommandLineException); + Assert.AreEqual("The test source file \"" + testFilePath + "\" provided was not found.", ex.Message); + } + } + + [TestMethod] + public void ExecuterInitializeWithValidSourceShouldAddItToTestSources() + { + var testFilePath = "DummyTestFile.txt"; + var mockFileHelper = new MockFileHelper(); + mockFileHelper.ExistsInvoker = (path) => + { + if (string.Equals(path, testFilePath)) + { + return true; + } + else + { + return false; + } + }; + + var options = CommandLineOptions.Instance; + options.Reset(); + options.FileHelper = mockFileHelper; + var executor = new TestSourceArgumentExecutor(options); + + executor.Initialize(testFilePath); + + // Check if the testsource is present in the TestSources + Assert.IsTrue(options.Sources.Contains(testFilePath)); + } + + [TestMethod] + public void ExecutorExecuteReturnArgumentProcessorResultSuccess() + { + var options = CommandLineOptions.Instance; + var executor = new TestSourceArgumentExecutor(options); + var result = executor.Execute(); + Assert.AreEqual(ArgumentProcessorResult.Success, result); + } + + #endregion + } +} diff --git a/test/vstest.console.UnitTests/Processors/Utilities/ArgumentProcessorFactoryTests.cs b/test/vstest.console.UnitTests/Processors/Utilities/ArgumentProcessorFactoryTests.cs new file mode 100644 index 0000000000..5bb07d3c90 --- /dev/null +++ b/test/vstest.console.UnitTests/Processors/Utilities/ArgumentProcessorFactoryTests.cs @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors.Utilities +{ + using System; + using System.Collections.Generic; + using TestPlatform.CommandLine.Processors; + using TestTools.UnitTesting; + + [TestClass] + public class ArgumentProcessorFactoryTests + { + [TestMethod] + public void BuildCommadMapsForProcessorWithValidShortCommandNameAddsShortCommandNameToMap() + { + var configProcessor = new ConfigurationArgumentProcessor(); + IEnumerable<IArgumentProcessor> processors = new List<IArgumentProcessor>() + { + //Adding Processor which has a Short Command Name + configProcessor + }; + ArgumentProcessorFactory factory = ArgumentProcessorFactory.Create(processors); + + Assert.IsTrue(factory.CommandToProcessorMap.ContainsKey("/c")); + Assert.IsTrue(factory.CommandToProcessorMap.ContainsKey("/Configuration")); + Assert.IsTrue(factory.CommandToProcessorMap.ContainsValue(configProcessor)); + } + + [TestMethod] + public void BuildCommadMapsForProcessorWithIsSpecialCommandSetAddsProcessorToSpecialMap() + { + var sourceProcessor = new TestSourceArgumentProcessor(); + IEnumerable<IArgumentProcessor> processors = new List<IArgumentProcessor>(){sourceProcessor}; + ArgumentProcessorFactory factory = ArgumentProcessorFactory.Create(processors); + + Assert.IsTrue(factory.SpecialCommandToProcessorMap.ContainsKey("TestSource")); + Assert.IsTrue(factory.SpecialCommandToProcessorMap.ContainsValue(sourceProcessor)); + } + + [TestMethod] + public void BuildCommadMapsForMultipleProcessorAddsProcessorToAppropriateMaps() + { + var configProcessor = new ConfigurationArgumentProcessor(); + var sourceProcessor = new TestSourceArgumentProcessor(); + IEnumerable<IArgumentProcessor> processors = new List<IArgumentProcessor>() { sourceProcessor, configProcessor }; + ArgumentProcessorFactory factory = ArgumentProcessorFactory.Create(processors); + + Assert.IsTrue(factory.SpecialCommandToProcessorMap.ContainsKey("TestSource")); + Assert.IsTrue(factory.SpecialCommandToProcessorMap.ContainsValue(sourceProcessor)); + Assert.IsTrue(factory.CommandToProcessorMap.ContainsKey("/c")); + Assert.IsTrue(factory.CommandToProcessorMap.ContainsKey("/Configuration")); + Assert.IsTrue(factory.CommandToProcessorMap.ContainsValue(configProcessor)); + } + } +} + diff --git a/test/vstest.console.UnitTests/Processors/Utilities/JsonUtilitiesTests.cs b/test/vstest.console.UnitTests/Processors/Utilities/JsonUtilitiesTests.cs new file mode 100644 index 0000000000..78d93e6eda --- /dev/null +++ b/test/vstest.console.UnitTests/Processors/Utilities/JsonUtilitiesTests.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors.Utilities +{ + using System; + using System.Collections.Generic; + using System.Linq; + + using Microsoft.VisualStudio.TestTools.UnitTesting; + + using TestPlatform.CommandLine.Processors.Utilities; + + [TestClass] + public class JsonUtilitiesTests + { + [TestMethod] + public void GetTestRunnerAndAssemblyInfoForNonJsonSourcesReturnsSourceDictWithUndefinedTestRunnerKey() + { + string[] sources = { "foo.dll", "foo2.dll" }; + var resultDict = JsonUtilities.GetTestRunnerAndAssemblyInfo(sources); + + IEnumerable<string> result; + resultDict.TryGetValue("_none_", out result); + Assert.AreEqual("foo.dll", result.ToArray()[0]); + Assert.AreEqual("foo2.dll", result.ToArray()[1]); + } + + [TestMethod] + public void GetTestRunnerAndAssemblyInfoForInvalidJsonSourcesThrowsInvalidOperationException() + { + string[] sources = { "foo.json", "foo2.json" }; + Assert.ThrowsException<InvalidOperationException>(() => JsonUtilities.GetTestRunnerAndAssemblyInfo(sources)); + } + } +} diff --git a/test/vstest.console.UnitTests/Processors/Utilities/RunSettingsUtilitiesTests.cs b/test/vstest.console.UnitTests/Processors/Utilities/RunSettingsUtilitiesTests.cs new file mode 100644 index 0000000000..c75ed06128 --- /dev/null +++ b/test/vstest.console.UnitTests/Processors/Utilities/RunSettingsUtilitiesTests.cs @@ -0,0 +1,161 @@ +// Copyright(c) Microsoft.All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors.Utilities +{ + using System; + using System.IO; + + using Microsoft.VisualStudio.TestPlatform.CommandLine.Processors.Utilities; + using Microsoft.VisualStudio.TestPlatform.Common; + using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class RunSettingsUtilitiesTests + { + private const string DefaultRunSettingsTemplate = + "<RunSettings>\r\n <RunConfiguration>\r\n <ResultsDirectory>%ResultsDirectory%</ResultsDirectory>\r\n <TargetPlatform>X86</TargetPlatform>\r\n <TargetFrameworkVersion>Framework45</TargetFrameworkVersion>\r\n </RunConfiguration>\r\n</RunSettings>"; + + [TestCleanup] + public void TestCleanup() + { + CommandLineOptions.Instance.Reset(); + } + + [TestMethod] + public void GetRunSettingsShouldReturnDefaultRunSettingsIfProviderIsNull() + { + var runSettings = RunSettingsUtilities.GetRunSettings(null, null); + + Assert.AreEqual(this.GetDefaultRunSettings(), runSettings); + } + + [TestMethod] + public void GetRunSettingsShouldReturnDefaultRunSettingsIfActiveRunSettingsIsNull() + { + var settingsProvider = new TestableRunSettingsProvider(); + settingsProvider.SetActiveRunSettings(runSettings: null); + + var runSettings = RunSettingsUtilities.GetRunSettings(settingsProvider, null); + + Assert.AreEqual(this.GetDefaultRunSettings(), runSettings); + } + + [TestMethod] + public void GetRunSettingsShouldReturnRunSettingsFromTheProvider() + { + var settingsProvider = new TestableRunSettingsProvider(); + + var settings = "<RunSettings>\r\n <RunConfiguration>\r\n <RandomNumer>432423</RandomNumer>\r\n </RunConfiguration>\r\n</RunSettings>"; + settingsProvider.SetActiveRunSettings(settings); + + var receivedRunSettings = RunSettingsUtilities.GetRunSettings(settingsProvider, null); + + StringAssert.Contains(receivedRunSettings, "<RandomNumer>432423</RandomNumer>"); + } + + [TestMethod] + public void GetRunSettingsShouldReturnSettingsWithPlatformSpecifiedInCommandLineOptions() + { + var settingsProvider = new TestableRunSettingsProvider(); + settingsProvider.SetActiveRunSettings(runSettings: null); + + CommandLineOptions.Instance.TargetArchitecture = ObjectModel.Architecture.X64; + + var runSettings = RunSettingsUtilities.GetRunSettings(settingsProvider, CommandLineOptions.Instance); + + var defaultRunSettings = this.GetDefaultRunSettings(); + //Replace with the platform specified. + var expectedSettings = defaultRunSettings.Replace("X86", "X64"); + Assert.AreEqual(expectedSettings, runSettings); + } + + [TestMethod] + public void GetRunSettingsShouldReturnSettingsWithFrameworkSpecifiedInCommandLineOptions() + { + var settingsProvider = new TestableRunSettingsProvider(); + settingsProvider.SetActiveRunSettings(runSettings: null); + + CommandLineOptions.Instance.TargetFrameworkVersion = ObjectModel.FrameworkVersion.Framework35; + + var runSettings = RunSettingsUtilities.GetRunSettings(settingsProvider, CommandLineOptions.Instance); + + var defaultRunSettings = this.GetDefaultRunSettings(); + + //Replace with the framework specified. + var expectedSettings = defaultRunSettings.Replace("Framework45", "Framework35"); + Assert.AreEqual(expectedSettings, runSettings); + } + + [TestMethod] + public void GetRunSettingsShouldReturnSettingsWithoutParallelOptionWhenParallelIsOff() + { + var settingsProvider = new TestableRunSettingsProvider(); + settingsProvider.SetActiveRunSettings(runSettings: null); + + // Do not have to explicitly set - but for readability + CommandLineOptions.Instance.Parallel = false; + + var runSettings = RunSettingsUtilities.GetRunSettings(settingsProvider, CommandLineOptions.Instance); + + Assert.IsTrue(!runSettings.Contains("MaxCpuCount"), "MaxCpuCount must not be set if parallel setting is false."); + } + + + [TestMethod] + public void GetRunSettingsShouldReturnSettingsWithParallelOptionWhenParallelIsOn() + { + var settingsProvider = new TestableRunSettingsProvider(); + settingsProvider.SetActiveRunSettings(runSettings: null); + + CommandLineOptions.Instance.Parallel = true; + + var runSettings = RunSettingsUtilities.GetRunSettings(settingsProvider, CommandLineOptions.Instance); + StringAssert.Contains(runSettings, "<MaxCpuCount>0</MaxCpuCount>", "MaxCpuCount must be set to 0 if Parallel Enabled."); + } + + [TestMethod] + public void GetRunSettingsShouldReturnWithoutChangeIfUserProvidesBothParallelSwitchAndSettings() + { + string settingXml = @"<RunSettings><RunConfiguration><MaxCpuCount>2</MaxCpuCount></RunConfiguration></RunSettings>"; + var settingsProvider = new TestableRunSettingsProvider(); + settingsProvider.SetActiveRunSettings(settingXml); + + CommandLineOptions.Instance.Parallel = true; + + var runSettings = RunSettingsUtilities.GetRunSettings(settingsProvider, CommandLineOptions.Instance); + + var parallelValue = Environment.ProcessorCount; + StringAssert.Contains(runSettings, "<MaxCpuCount>2</MaxCpuCount>", "RunSettings Parallel value should take precendence over parallel switch."); + } + + + #region Testable Implementations + + private string GetDefaultRunSettings() + { + var defaultResultsDirectory = Path.Combine(Directory.GetCurrentDirectory(), "TestResults"); + return DefaultRunSettingsTemplate.Replace("%ResultsDirectory%", defaultResultsDirectory); + } + + private class TestableRunSettingsProvider : IRunSettingsProvider + { + public RunSettings ActiveRunSettings { get; private set; } + + public void SetActiveRunSettings(RunSettings runSettings) + { + this.ActiveRunSettings = runSettings; + } + + public void SetActiveRunSettings(string settingsXml) + { + var runSettings = new RunSettings(); + runSettings.LoadSettingsXml(settingsXml); + + SetActiveRunSettings(runSettings); + } + } + + #endregion + } +} diff --git a/test/vstest.console.UnitTests/Properties/AssemblyInfo.cs b/test/vstest.console.UnitTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..2d4f836347 --- /dev/null +++ b/test/vstest.console.UnitTests/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +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: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("vstest.console.UnitTests")] +[assembly: AssemblyTrademark("")] + +// 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("6deb7dc1-c8df-465e-8b31-96248d3ffad0")] diff --git a/test/vstest.console.UnitTests/project.json b/test/vstest.console.UnitTests/project.json new file mode 100644 index 0000000000..7ba42723ec --- /dev/null +++ b/test/vstest.console.UnitTests/project.json @@ -0,0 +1,36 @@ +{ + "version": "15.0.0-*", + + "buildOptions": { + "delaySign": true, + "keyFile": "../../scripts/key.snk", + "warningsAsErrors": true + }, + + "dependencies": { + "Microsoft.NETCore.App": { + "type": "platform", + "version": "1.0.0-rc2-3002702" + }, + "dotnet-test-mstest": { + "version": "1.0.1-preview", + "exclude": "compile" + }, + "MSTest.TestFramework": "1.0.0-preview", + "moq.netcore": "4.4.0-beta8", + "System.Diagnostics.TraceSource": "4.0.0-rc2-24015", + "vstest.console": "15.0.0-*", + "Microsoft.TestPlatform.Client": "15.0.0-*" + }, + + "frameworks": { + "netcoreapp1.0": { + "imports": [ + "dnxcore50", + "portable-net45+win8" + ] + } + }, + + "testRunner": "mstest" +} diff --git a/test/vstest.console.UnitTests/vstest.console.UnitTests.xproj b/test/vstest.console.UnitTests/vstest.console.UnitTests.xproj new file mode 100644 index 0000000000..8cf29e36ce --- /dev/null +++ b/test/vstest.console.UnitTests/vstest.console.UnitTests.xproj @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion> + <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath> + </PropertyGroup> + <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" /> + <PropertyGroup Label="Globals"> + <ProjectGuid>6deb7dc1-c8df-465e-8b31-96248d3ffad0</ProjectGuid> + <RootNamespace>Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests</RootNamespace> + <BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath> + <OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\</OutputPath> + <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion> + </PropertyGroup> + <PropertyGroup> + <SchemaVersion>2.0</SchemaVersion> + </PropertyGroup> + <ItemGroup> + <Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" /> + </ItemGroup> + <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" /> +</Project> \ No newline at end of file