diff --git a/NuGet.config b/NuGet.config index 5cb7dc3752..87909589d8 100644 --- a/NuGet.config +++ b/NuGet.config @@ -1,4 +1,4 @@ - + diff --git a/azure-pipelines.yml b/azure-pipelines.yml index afcd052d01..3787067058 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -64,12 +64,12 @@ jobs: testResultsFiles: '**\*.trx' condition: succeededOrFailed() -- job: Linux - pool: - vmImage: 'ubuntu-18.04' - variables: - buildConfiguration: 'Release' - steps: - - script: ./build.sh -c $(buildConfiguration) - displayName: './build.sh -c $(buildConfiguration)' +# - job: Linux +# pool: +# vmImage: 'ubuntu-18.04' +# variables: +# buildConfiguration: 'Release' +# steps: +# - script: ./build.sh -c $(buildConfiguration) +# displayName: './build.sh -c $(buildConfiguration)' diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index d80d058862..0bd729be70 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -3,25 +3,25 @@ - + https://github.com/dotnet/arcade - 7cc59275eade471e29b88a797275818b0d513d0f + 2e804f8d57972faf64a19a7295728dc7bfcb5fce - + https://github.com/dotnet/arcade - 7cc59275eade471e29b88a797275818b0d513d0f + 2e804f8d57972faf64a19a7295728dc7bfcb5fce - + https://github.com/dotnet/arcade - 7cc59275eade471e29b88a797275818b0d513d0f + 2e804f8d57972faf64a19a7295728dc7bfcb5fce - + https://github.com/dotnet/arcade - 7cc59275eade471e29b88a797275818b0d513d0f + 2e804f8d57972faf64a19a7295728dc7bfcb5fce - + https://github.com/dotnet/arcade - 7cc59275eade471e29b88a797275818b0d513d0f + 2e804f8d57972faf64a19a7295728dc7bfcb5fce https://github.com/dotnet/arcade-services diff --git a/eng/Versions.props b/eng/Versions.props index beceeb5eed..c154fd1efd 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -60,8 +60,8 @@ 2.4.1 2.0.3 2.4.1 - 2.2.0-beta.20365.6 - 1.0.0-beta.20365.6 + 2.2.0-beta.20411.9 + 1.0.0-beta.20411.9 1.22.0 1.1.2 2.0.0 @@ -73,7 +73,7 @@ 1.7.0 1.1.0-beta.19556.4 1.0.0-beta2-19554-01 - 1.0.0-beta.20365.6 + 1.0.0-beta.20411.9 1.0.0-beta.20055.1 diff --git a/eng/common/SetupNugetSources.ps1 b/eng/common/SetupNugetSources.ps1 index a8b5280d9d..6d88a1904b 100644 --- a/eng/common/SetupNugetSources.ps1 +++ b/eng/common/SetupNugetSources.ps1 @@ -11,6 +11,8 @@ # See example YAML call for this script below. Note the use of the variable `$(dn-bot-dnceng-artifact-feeds-rw)` # from the AzureDevOps-Artifact-Feeds-Pats variable group. # +# Any disabledPackageSources entries which start with "darc-int" will be re-enabled as part of this script executing +# # - task: PowerShell@2 # displayName: Setup Private Feeds Credentials # condition: eq(variables['Agent.OS'], 'Windows_NT') @@ -94,6 +96,14 @@ function InsertMaestroPrivateFeedCredentials($Sources, $Creds, $Username, $Passw } } +function EnablePrivatePackageSources($DisabledPackageSources) { + $maestroPrivateSources = $DisabledPackageSources.SelectNodes("add[contains(@key,'darc-int')]") + ForEach ($DisabledPackageSource in $maestroPrivateSources) { + Write-Host "`tEnsuring private source '$($DisabledPackageSource.key)' is enabled" + $DisabledPackageSource.SetAttribute("value", "false") + } +} + if (!(Test-Path $ConfigFile -PathType Leaf)) { Write-PipelineTelemetryError -Category 'Build' -Message "Eng/common/SetupNugetSources.ps1 returned a non-zero exit code. Couldn't find the NuGet config file: $ConfigFile" ExitWithExitCode 1 @@ -123,6 +133,13 @@ if ($creds -eq $null) { $doc.DocumentElement.AppendChild($creds) | Out-Null } +# Check for disabledPackageSources; we'll enable any darc-int ones we find there +$disabledSources = $doc.DocumentElement.SelectSingleNode("disabledPackageSources") +if ($disabledSources -ne $null) { + Write-Host "Checking for any darc-int disabled package sources in the disabledPackageSources node" + EnablePrivatePackageSources -DisabledPackageSources $disabledSources +} + $userName = "dn-bot" # Insert credential nodes for Maestro's private feeds diff --git a/eng/common/SetupNugetSources.sh b/eng/common/SetupNugetSources.sh index 4ebb1e5a44..00e8f45b91 100644 --- a/eng/common/SetupNugetSources.sh +++ b/eng/common/SetupNugetSources.sh @@ -13,6 +13,8 @@ # See example YAML call for this script below. Note the use of the variable `$(dn-bot-dnceng-artifact-feeds-rw)` # from the AzureDevOps-Artifact-Feeds-Pats variable group. # +# Any disabledPackageSources entries which start with "darc-int" will be re-enabled as part of this script executing. +# # - task: Bash@3 # displayName: Setup Private Feeds Credentials # inputs: @@ -63,7 +65,7 @@ if [ "$?" != "0" ]; then ConfigNodeHeader="" PackageSourcesTemplate="${TB}${NL}${TB}" - sed -i.bak "s|$ConfigNodeHeader|$ConfigNodeHeader${NL}$PackageSourcesTemplate|" NuGet.config + sed -i.bak "s|$ConfigNodeHeader|$ConfigNodeHeader${NL}$PackageSourcesTemplate|" $ConfigFile fi # Ensure there is a ... section. @@ -74,7 +76,7 @@ if [ "$?" != "0" ]; then PackageSourcesNodeFooter="" PackageSourceCredentialsTemplate="${TB}${NL}${TB}" - sed -i.bak "s|$PackageSourcesNodeFooter|$PackageSourcesNodeFooter${NL}$PackageSourceCredentialsTemplate|" NuGet.config + sed -i.bak "s|$PackageSourcesNodeFooter|$PackageSourcesNodeFooter${NL}$PackageSourceCredentialsTemplate|" $ConfigFile fi PackageSources=() @@ -146,4 +148,21 @@ for FeedName in ${PackageSources[@]} ; do sed -i.bak "s|$PackageSourceCredentialsNodeFooter|$NewCredential${NL}$PackageSourceCredentialsNodeFooter|" $ConfigFile fi -done \ No newline at end of file +done + +# Re-enable any entries in disabledPackageSources where the feed name contains darc-int +grep -i "" $ConfigFile +if [ "$?" == "0" ]; then + DisabledDarcIntSources=() + echo "Re-enabling any disabled \"darc-int\" package sources in $ConfigFile" + DisabledDarcIntSources+=$(grep -oh '"darc-int-[^"]*" value="true"' $ConfigFile | tr -d '"') + for DisabledSourceName in ${DisabledDarcIntSources[@]} ; do + if [[ $DisabledSourceName == darc-int* ]] + then + OldDisableValue="add key=\"$DisabledSourceName\" value=\"true\"" + NewDisableValue="add key=\"$DisabledSourceName\" value=\"false\"" + sed -i.bak "s|$OldDisableValue|$NewDisableValue|" $ConfigFile + echo "Neutralized disablePackageSources entry for '$DisabledSourceName'" + fi + done +fi \ No newline at end of file diff --git a/eng/common/tools.ps1 b/eng/common/tools.ps1 index bc228dfdf9..f50507a06c 100644 --- a/eng/common/tools.ps1 +++ b/eng/common/tools.ps1 @@ -329,7 +329,7 @@ function InitializeXCopyMSBuild([string]$packageVersion, [bool]$install) { Create-Directory $packageDir Write-Host "Downloading $packageName $packageVersion" $ProgressPreference = 'SilentlyContinue' # Don't display the console progress UI - it's a huge perf hit - Invoke-WebRequest "https://dotnet.myget.org/F/roslyn-tools/api/v2/package/$packageName/$packageVersion/" -OutFile $packagePath + Invoke-WebRequest "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/flat2/$packageName/$packageVersion/$packageName.$packageVersion.nupkg" -OutFile $packagePath Unzip $packagePath $packageDir } diff --git a/global.json b/global.json index f6d1f2af4a..c43398e67e 100644 --- a/global.json +++ b/global.json @@ -1,15 +1,15 @@ { "sdk": { - "version": "3.1.101", + "version": "5.0.100-rc.1.20380.12", "rollForward": "minor", "allowPrerelease": false, "architecture": "x64" }, "tools": { - "dotnet": "3.1.101" + "dotnet": "5.0.100-rc.1.20380.12" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "1.0.0-beta.20365.6", - "Microsoft.DotNet.Helix.Sdk": "2.0.0-beta.20365.6" + "Microsoft.DotNet.Arcade.Sdk": "1.0.0-beta.20411.9", + "Microsoft.DotNet.Helix.Sdk": "2.0.0-beta.20411.9" } } diff --git a/scripts/build.ps1 b/scripts/build.ps1 index 476c3c585f..f5beb59f84 100644 --- a/scripts/build.ps1 +++ b/scripts/build.ps1 @@ -82,7 +82,7 @@ $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 $env:NUGET_EXE_Version = "3.4.3" -$env:DOTNET_CLI_VERSION = "3.1.101" +$env:DOTNET_CLI_VERSION = "5.0.100-rc.1.20380.12" # $env:DOTNET_RUNTIME_VERSION = "LATEST" $env:VSWHERE_VERSION = "2.0.2" $env:MSBUILD_VERSION = "15.0" diff --git a/scripts/build.sh b/scripts/build.sh index fe0797beef..e4454a5dc8 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -111,7 +111,7 @@ VERSION=$(test -z $VERSION && grep TPVersionPrefix $TP_ROOT_DIR/scripts/build/Te export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 # Dotnet build doesnt support --packages yet. See https://github.com/dotnet/cli/issues/2712 export NUGET_PACKAGES=$TP_PACKAGES_DIR -DOTNET_CLI_VERSION="3.1.101" +DOTNET_CLI_VERSION="5.0.100-rc.1.20380.12" #DOTNET_RUNTIME_VERSION="LATEST" # diff --git a/scripts/build/TestPlatform.Dependencies.props b/scripts/build/TestPlatform.Dependencies.props index 5a60ea9724..6c775999d0 100644 --- a/scripts/build/TestPlatform.Dependencies.props +++ b/scripts/build/TestPlatform.Dependencies.props @@ -31,7 +31,7 @@ 5.0.0 9.0.1 4.7.63 - 16.7.0-preview-3740203 + 16.8.0-preview-3968212 16.6.3-beta.20221.2 16.0.461 diff --git a/src/DataCollectors/TraceDataCollector/VanguardCollector/DynamicCoverageDataCollectorImpl.cs b/src/DataCollectors/TraceDataCollector/VanguardCollector/DynamicCoverageDataCollectorImpl.cs index 40a8a9a38f..6d0e0b56ee 100644 --- a/src/DataCollectors/TraceDataCollector/VanguardCollector/DynamicCoverageDataCollectorImpl.cs +++ b/src/DataCollectors/TraceDataCollector/VanguardCollector/DynamicCoverageDataCollectorImpl.cs @@ -112,8 +112,14 @@ public virtual void Initialize( try { - var processor = new CodeCoverageRunSettingsProcessor(defaultConfigurationElement); - configurationElement = (XmlElement)processor.Process(configurationElement); + // WARNING: Do NOT remove this function call !!! + // + // Due to a dependency we took on Microsoft.TestPlatform.Utilities.dll, an + // exception may be thrown if we cannot resolve CodeCoverageRunSettingsProcessor. + // If such an exception is thrown we cannot catch it in this try-catch block + // because all method dependencies must be resolved before the method call, thus + // we introduced an additional layer of indirection. + configurationElement = this.AddDefaultExclusions(configurationElement, defaultConfigurationElement); } catch (Exception ex) { @@ -326,5 +332,17 @@ private void CreateDirectory(DataCollectionContext context, string path) throw; } } + + /// + /// Adding default exclusions to the configuration element. + /// + /// The configuration element. + /// The default configuration element. + /// The original configuration element with additional default exclusions. + private XmlElement AddDefaultExclusions(XmlElement configurationElement, XmlElement defaultConfigurationElement) + { + var processor = new CodeCoverageRunSettingsProcessor(defaultConfigurationElement); + return (XmlElement)processor.Process(configurationElement); + } } } \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Build/Resources/Resources.resx b/src/Microsoft.TestPlatform.Build/Resources/Resources.resx index d9211421ee..e3277bc036 100644 --- a/src/Microsoft.TestPlatform.Build/Resources/Resources.resx +++ b/src/Microsoft.TestPlatform.Build/Resources/Resources.resx @@ -127,7 +127,7 @@ Skipping running test for project {0}. To run tests with dotnet test add "<IsTestProject>true</IsTestProject>" property to project file. - Test run for {0}({1}) + Test run for {0} ({1}) Warning: Update the Microsoft.NET.Test.Sdk package reference to version 15.8.0 or later to collect code coverage. diff --git a/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.cs.xlf b/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.cs.xlf index 72b0383ff7..5436e4ca79 100644 --- a/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.cs.xlf +++ b/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.cs.xlf @@ -31,9 +31,9 @@ - Test run for {0}({1}) - Testovací běh pro: {0}({1}) - + Test run for {0} ({1}) + Testovací běh pro: {0}({1}) + Warning: Update the Microsoft.NET.Test.Sdk package reference to version 15.8.0 or later to collect code coverage. diff --git a/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.de.xlf b/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.de.xlf index 2ad342093e..61d4e2dd35 100644 --- a/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.de.xlf +++ b/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.de.xlf @@ -31,9 +31,9 @@ - Test run for {0}({1}) - Testlauf für "{0}" ({1}) - + Test run for {0} ({1}) + Testlauf für "{0}" ({1}) + Warning: Update the Microsoft.NET.Test.Sdk package reference to version 15.8.0 or later to collect code coverage. diff --git a/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.es.xlf b/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.es.xlf index eecdc46459..83186e7077 100644 --- a/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.es.xlf +++ b/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.es.xlf @@ -31,9 +31,9 @@ - Test run for {0}({1}) - Serie de pruebas para {0}({1}) - + Test run for {0} ({1}) + Serie de pruebas para {0}({1}) + Warning: Update the Microsoft.NET.Test.Sdk package reference to version 15.8.0 or later to collect code coverage. diff --git a/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.fr.xlf b/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.fr.xlf index 539b7cac43..19f6a092a2 100644 --- a/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.fr.xlf +++ b/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.fr.xlf @@ -31,9 +31,9 @@ - Test run for {0}({1}) - Série de tests pour {0}({1}) - + Test run for {0} ({1}) + Série de tests pour {0}({1}) + Warning: Update the Microsoft.NET.Test.Sdk package reference to version 15.8.0 or later to collect code coverage. diff --git a/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.it.xlf b/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.it.xlf index 506ee61244..3a91eab678 100644 --- a/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.it.xlf +++ b/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.it.xlf @@ -31,9 +31,9 @@ - Test run for {0}({1}) - Esecuzione dei test per {0}({1}) - + Test run for {0} ({1}) + Esecuzione dei test per {0}({1}) + Warning: Update the Microsoft.NET.Test.Sdk package reference to version 15.8.0 or later to collect code coverage. diff --git a/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.ja.xlf b/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.ja.xlf index 6f04c3930d..3a9e5d0e6c 100644 --- a/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.ja.xlf +++ b/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.ja.xlf @@ -31,9 +31,9 @@ - Test run for {0}({1}) - {0}({1}) のテスト実行 - + Test run for {0} ({1}) + {0}({1}) のテスト実行 + Warning: Update the Microsoft.NET.Test.Sdk package reference to version 15.8.0 or later to collect code coverage. diff --git a/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.ko.xlf b/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.ko.xlf index c9b807264b..64998a2d4b 100644 --- a/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.ko.xlf +++ b/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.ko.xlf @@ -31,9 +31,9 @@ - Test run for {0}({1}) - {0}({1})에 대한 테스트 실행 - + Test run for {0} ({1}) + {0}({1})에 대한 테스트 실행 + Warning: Update the Microsoft.NET.Test.Sdk package reference to version 15.8.0 or later to collect code coverage. diff --git a/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.pl.xlf b/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.pl.xlf index 9746ca63b7..2f1a0547d0 100644 --- a/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.pl.xlf +++ b/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.pl.xlf @@ -31,9 +31,9 @@ - Test run for {0}({1}) - Przebieg testu dla: {0}({1}) - + Test run for {0} ({1}) + Przebieg testu dla: {0}({1}) + Warning: Update the Microsoft.NET.Test.Sdk package reference to version 15.8.0 or later to collect code coverage. diff --git a/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.pt-BR.xlf b/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.pt-BR.xlf index b1a8228022..7a01f64e8d 100644 --- a/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.pt-BR.xlf +++ b/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.pt-BR.xlf @@ -31,9 +31,9 @@ - Test run for {0}({1}) - Execução de teste para {0}({1}) - + Test run for {0} ({1}) + Execução de teste para {0}({1}) + Warning: Update the Microsoft.NET.Test.Sdk package reference to version 15.8.0 or later to collect code coverage. diff --git a/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.ru.xlf b/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.ru.xlf index 8bd2c517b3..ec259d8377 100644 --- a/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.ru.xlf +++ b/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.ru.xlf @@ -31,9 +31,9 @@ - Test run for {0}({1}) - Тестовый запуск {0}({1}) - + Test run for {0} ({1}) + Тестовый запуск {0}({1}) + Warning: Update the Microsoft.NET.Test.Sdk package reference to version 15.8.0 or later to collect code coverage. diff --git a/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.tr.xlf b/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.tr.xlf index 8771b54b23..61f26907e5 100644 --- a/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.tr.xlf +++ b/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.tr.xlf @@ -31,9 +31,9 @@ - Test run for {0}({1}) - {0}({1}) için test çalıştırması - + Test run for {0} ({1}) + {0} ({1}) için test çalıştırması + Warning: Update the Microsoft.NET.Test.Sdk package reference to version 15.8.0 or later to collect code coverage. diff --git a/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.xlf b/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.xlf index 48d18887e0..4740531abf 100644 --- a/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.xlf +++ b/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.xlf @@ -13,7 +13,7 @@ - Test run for {0}({1}) + Test run for {0} ({1}) Test run for {0}({1}) diff --git a/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.zh-Hans.xlf b/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.zh-Hans.xlf index c040a60e31..ab3adf93fd 100644 --- a/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.zh-Hans.xlf +++ b/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.zh-Hans.xlf @@ -31,9 +31,9 @@ - Test run for {0}({1}) - {0} 的测试运行({1}) - + Test run for {0} ({1}) + {0} 的测试运行({1}) + Warning: Update the Microsoft.NET.Test.Sdk package reference to version 15.8.0 or later to collect code coverage. diff --git a/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.zh-Hant.xlf b/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.zh-Hant.xlf index 71038770c7..7d3b92e6d4 100644 --- a/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.zh-Hant.xlf +++ b/src/Microsoft.TestPlatform.Build/Resources/xlf/Resources.zh-Hant.xlf @@ -31,9 +31,9 @@ - Test run for {0}({1}) - {0}({1}) 的測試回合 - + Test run for {0} ({1}) + {0}({1}) 的測試回合 + Warning: Update the Microsoft.NET.Test.Sdk package reference to version 15.8.0 or later to collect code coverage. diff --git a/src/Microsoft.TestPlatform.Common/DataCollection/DataCollectionAttachmentManager.cs b/src/Microsoft.TestPlatform.Common/DataCollection/DataCollectionAttachmentManager.cs index f1606df6b6..6938babef3 100644 --- a/src/Microsoft.TestPlatform.Common/DataCollection/DataCollectionAttachmentManager.cs +++ b/src/Microsoft.TestPlatform.Common/DataCollection/DataCollectionAttachmentManager.cs @@ -140,7 +140,10 @@ public List GetAttachments(DataCollectionContext dataCollectionCo { try { - Task.WhenAll(this.attachmentTasks[dataCollectionContext].ToArray()).Wait(); + if (this.attachmentTasks.TryGetValue(dataCollectionContext, out var tasks)) + { + Task.WhenAll(tasks.ToArray()).Wait(); + } } catch (Exception ex) { diff --git a/src/Microsoft.TestPlatform.Common/ExtensionFramework/TestPluginCache.cs b/src/Microsoft.TestPlatform.Common/ExtensionFramework/TestPluginCache.cs index b2905830f5..a7dc4e04fe 100644 --- a/src/Microsoft.TestPlatform.Common/ExtensionFramework/TestPluginCache.cs +++ b/src/Microsoft.TestPlatform.Common/ExtensionFramework/TestPluginCache.cs @@ -3,7 +3,7 @@ namespace Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework { -#if NET451 +#if NETFRAMEWORK using System.Threading; #endif using System; @@ -196,7 +196,7 @@ public Dictionary DiscoverTestExtensions(Type extensionType) } throw; } -#if NET451 +#if NETFRAMEWORK else if (ex is SystemException) { if (EqtTrace.IsErrorEnabled) diff --git a/src/Microsoft.TestPlatform.Common/Logging/InternalTestLoggerEvents.cs b/src/Microsoft.TestPlatform.Common/Logging/InternalTestLoggerEvents.cs index 725fc1c5d4..26bbcef7d1 100644 --- a/src/Microsoft.TestPlatform.Common/Logging/InternalTestLoggerEvents.cs +++ b/src/Microsoft.TestPlatform.Common/Logging/InternalTestLoggerEvents.cs @@ -11,7 +11,7 @@ namespace Microsoft.VisualStudio.TestPlatform.Common.Logging using System.Collections.ObjectModel; using System.Diagnostics; -#if NET451 +#if NETFRAMEWORK using System.Configuration; #endif @@ -398,7 +398,7 @@ private int GetMaxBytesQueueCanHold() private static bool IsBoundsEnabledOnLoggerEventQueue() { bool enableBounds; -#if NET451 +#if NETFRAMEWORK string enableBoundsOnEventQueueIsDefined = ConfigurationManager.AppSettings[TestPlatformDefaults.EnableBoundsOnLoggerEventQueue]; #else string enableBoundsOnEventQueueIsDefined = null; @@ -444,7 +444,7 @@ private static int FindTestResultSize(TestResultEventArgs args) private int GetSetting(string appSettingKey, int defaultValue) { int value; -#if NET451 +#if NETFRAMEWORK string appSettingValue = ConfigurationManager.AppSettings[appSettingKey]; #else string appSettingValue = null; diff --git a/src/Microsoft.TestPlatform.Common/Microsoft.TestPlatform.Common.csproj b/src/Microsoft.TestPlatform.Common/Microsoft.TestPlatform.Common.csproj index b6287a724f..7a7af2d6ce 100644 --- a/src/Microsoft.TestPlatform.Common/Microsoft.TestPlatform.Common.csproj +++ b/src/Microsoft.TestPlatform.Common/Microsoft.TestPlatform.Common.csproj @@ -46,8 +46,5 @@ Microsoft.VisualStudio.TestPlatform.Common - - - diff --git a/src/Microsoft.TestPlatform.Common/Utilities/FakesUtilities.cs b/src/Microsoft.TestPlatform.Common/Utilities/FakesUtilities.cs index a269a16d18..502de37d13 100644 --- a/src/Microsoft.TestPlatform.Common/Utilities/FakesUtilities.cs +++ b/src/Microsoft.TestPlatform.Common/Utilities/FakesUtilities.cs @@ -216,7 +216,7 @@ private static void EnsureSettingsNode(XmlDocument settings, TestRunSettings set private static Func, string> TryGetNetFrameworkFakesDataCollectorConfigurator() { -#if NET451 +#if NETFRAMEWORK try { Assembly assembly = Assembly.Load(FakesConfiguratorAssembly); diff --git a/src/Microsoft.TestPlatform.Common/Utilities/RunSettingsProviderExtensions.cs b/src/Microsoft.TestPlatform.Common/Utilities/RunSettingsProviderExtensions.cs index 7af12b0f87..99f6909bdd 100644 --- a/src/Microsoft.TestPlatform.Common/Utilities/RunSettingsProviderExtensions.cs +++ b/src/Microsoft.TestPlatform.Common/Utilities/RunSettingsProviderExtensions.cs @@ -226,7 +226,7 @@ private static XmlDocument GetRunSettingXmlDocument(this IRunSettingsProvider ru { var settingsXml = runSettingsProvider.ActiveRunSettings.SettingsXml; -#if NET451 +#if NETFRAMEWORK using (var reader = XmlReader.Create(new StringReader(settingsXml), new XmlReaderSettings() { XmlResolver = null, CloseInput = true, DtdProcessing = DtdProcessing.Prohibit })) { #else @@ -240,7 +240,7 @@ private static XmlDocument GetRunSettingXmlDocument(this IRunSettingsProvider ru } else { -#if NET451 +#if NETFRAMEWORK doc = (XmlDocument) XmlRunSettingsUtilities.CreateDefaultRunSettings(); #else using ( diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/SocketClient.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/SocketClient.cs index 9d7dca6b98..7059a538bc 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/SocketClient.cs +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/SocketClient.cs @@ -115,7 +115,7 @@ private void Stop(Exception error) this.stopped = true; // Close the client and dispose the underlying stream -#if NET451 +#if NETFRAMEWORK // tcpClient.Close() calls tcpClient.Dispose(). this.tcpClient?.Close(); #else diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/SocketCommunicationManager.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/SocketCommunicationManager.cs index a0d2540eae..6365219ba9 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/SocketCommunicationManager.cs +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/SocketCommunicationManager.cs @@ -222,7 +222,7 @@ public bool WaitForServerConnection(int connectionTimeout) /// public void StopClient() { -#if NET451 +#if NETFRAMEWORK // tcpClient.Close() calls tcpClient.Dispose(). this.tcpClient?.Close(); #else diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/SocketServer.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/SocketServer.cs index 84adaee226..51de6af960 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/SocketServer.cs +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/SocketServer.cs @@ -117,7 +117,7 @@ private void Stop(Exception error) this.tcpListener.Stop(); // Close the client and dispose the underlying stream -#if NET451 +#if NETFRAMEWORK // tcpClient.Close() calls tcpClient.Dispose(). this.tcpClient?.Close(); #else diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Tracing/EqtTrace.cs b/src/Microsoft.TestPlatform.CoreUtilities/Tracing/EqtTrace.cs index bb43be33ce..6a798a33bc 100644 --- a/src/Microsoft.TestPlatform.CoreUtilities/Tracing/EqtTrace.cs +++ b/src/Microsoft.TestPlatform.CoreUtilities/Tracing/EqtTrace.cs @@ -25,7 +25,7 @@ public static class EqtTrace { private static IPlatformEqtTrace traceImpl = new PlatformEqtTrace(); -#if NET451 +#if NETFRAMEWORK public static void SetupRemoteEqtTraceListeners(AppDomain childDomain) { traceImpl.SetupRemoteEqtTraceListeners(childDomain); diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/BlameCollector.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/BlameCollector.cs index 837860ba8a..834b410510 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/BlameCollector.cs +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/BlameCollector.cs @@ -49,6 +49,8 @@ public class BlameCollector : DataCollector, ITestExecutionEnvironmentSpecifier private bool dumpWasCollectedByHangDumper; private string targetFramework; private List> environmentVariables = new List>(); + private bool uploadDumpFiles; + private string tempDirectory; /// /// Initializes a new instance of the class. @@ -134,6 +136,18 @@ public override void Initialize( if (this.collectProcessDumpOnTrigger) { this.ValidateAndAddTriggerBasedProcessDumpParameters(collectDumpNode); + + // enabling dumps on MacOS needs to be done explicitly https://github.com/dotnet/runtime/pull/40105 + this.environmentVariables.Add(new KeyValuePair("COMPlus_DbgEnableElfDumpOnMacOS", "1")); + this.environmentVariables.Add(new KeyValuePair("COMPlus_DbgEnableMiniDump", "1")); + + // https://github.com/dotnet/coreclr/blob/master/Documentation/botr/xplat-minidump-generation.md + // 2 MiniDumpWithPrivateReadWriteMemory + // 4 MiniDumpWithFullMemory + this.environmentVariables.Add(new KeyValuePair("COMPlus_DbgMiniDumpType", this.processFullDumpEnabled ? "4" : "2")); + var dumpDirectory = this.GetDumpDirectory(); + var dumpPath = Path.Combine(dumpDirectory, $"%e_%p_%t_crashdump.dmp"); + this.environmentVariables.Add(new KeyValuePair("COMPlus_DbgMiniDumpName", dumpPath)); } var collectHangBasedDumpNode = this.configurationElement[Constants.CollectDumpOnTestSessionHang]; @@ -171,7 +185,23 @@ public override void Initialize( private void CollectDumpAndAbortTesthost() { this.inactivityTimerAlreadyFired = true; - var message = string.Format(CultureInfo.CurrentUICulture, Resources.Resources.InactivityTimeout, (int)this.inactivityTimespan.TotalMinutes); + + string value; + string unit; + + if (this.inactivityTimespan.TotalSeconds <= 90) + { + value = ((int)this.inactivityTimespan.TotalSeconds).ToString(); + unit = Resources.Resources.Seconds; + } + else + { + value = Math.Round(this.inactivityTimespan.TotalMinutes, 2).ToString(); + unit = Resources.Resources.Minutes; + } + + var message = string.Format(CultureInfo.CurrentUICulture, Resources.Resources.InactivityTimeout, value, unit); + EqtTrace.Warning(message); this.logger.LogWarning(this.context.SessionDataCollectionContext, message); @@ -187,7 +217,9 @@ private void CollectDumpAndAbortTesthost() try { - this.processDumpUtility.StartHangBasedProcessDump(this.testHostProcessId, this.attachmentGuid, this.GetTempDirectory(), this.processFullDumpEnabled, this.targetFramework); + Action logWarning = m => this.logger.LogWarning(this.context.SessionDataCollectionContext, m); + var dumpDirectory = this.GetDumpDirectory(); + this.processDumpUtility.StartHangBasedProcessDump(this.testHostProcessId, dumpDirectory, this.processFullDumpEnabled, this.targetFramework, logWarning); } catch (Exception ex) { @@ -201,24 +233,42 @@ private void CollectDumpAndAbortTesthost() this.processDumpUtility.DetachFromTargetProcess(this.testHostProcessId); } - try + if (this.uploadDumpFiles) { - var dumpFile = this.processDumpUtility.GetDumpFile(); - if (!string.IsNullOrEmpty(dumpFile)) + try { - this.dumpWasCollectedByHangDumper = true; - var fileTransferInformation = new FileTransferInformation(this.context.SessionDataCollectionContext, dumpFile, true, this.fileHelper); - this.dataCollectionSink.SendFileAsync(fileTransferInformation); + var dumpFiles = this.processDumpUtility.GetDumpFiles(); + foreach (var dumpFile in dumpFiles) + { + try + { + if (!string.IsNullOrEmpty(dumpFile)) + { + this.dumpWasCollectedByHangDumper = true; + var fileTransferInformation = new FileTransferInformation(this.context.SessionDataCollectionContext, dumpFile, true, this.fileHelper); + this.dataCollectionSink.SendFileAsync(fileTransferInformation); + } + } + catch (Exception ex) + { + // Eat up any exception here and log it but proceed with killing the test host process. + EqtTrace.Error(ex); + } + + if (!dumpFiles.Any()) + { + EqtTrace.Error("BlameCollector.CollectDumpAndAbortTesthost: blame:CollectDumpOnHang was enabled but dump file was not generated."); + } + } } - else + catch (Exception ex) { - EqtTrace.Error("BlameCollector.CollectDumpAndAbortTesthost: blame:CollectDumpOnHang was enabled but dump file was not generated."); + ConsoleOutput.Instance.Error(true, $"Blame: Collecting hang dump failed with error {ex}."); } } - catch (Exception ex) + else { - // Eat up any exception here and log it but proceed with killing the test host process. - EqtTrace.Error(ex); + EqtTrace.Info("BlameCollector.CollectDumpAndAbortTesthost: Custom path to dump directory was provided via VSTEST_DUMP_PATH. Skipping attachment upload, the caller is responsible for collecting and uploading the dumps themselves."); } try @@ -394,8 +444,12 @@ private void SessionEndedHandler(object sender, SessionEndEventArgs args) var filepath = Path.Combine(this.GetTempDirectory(), Constants.AttachmentFileName + "_" + this.attachmentGuid); filepath = this.blameReaderWriter.WriteTestSequence(this.testSequence, this.testObjectDictionary, filepath); - var fileTranferInformation = new FileTransferInformation(this.context.SessionDataCollectionContext, filepath, true); - this.dataCollectionSink.SendFileAsync(fileTranferInformation); + var fti = new FileTransferInformation(this.context.SessionDataCollectionContext, filepath, true); + this.dataCollectionSink.SendFileAsync(fti); + } + else + { + this.logger.LogWarning(this.context.SessionDataCollectionContext, Resources.Resources.NotGeneratingSequenceFile); } if (this.collectProcessDumpOnTrigger) @@ -406,24 +460,37 @@ private void SessionEndedHandler(object sender, SessionEndEventArgs args) // we won't dump the killed process again and that would just show a warning on the command line if ((this.testStartCount > this.testEndCount || this.collectDumpAlways) && !this.dumpWasCollectedByHangDumper) { - try + if (this.uploadDumpFiles) { - var dumpFile = this.processDumpUtility.GetDumpFile(); - if (!string.IsNullOrEmpty(dumpFile)) + try { - var fileTranferInformation = new FileTransferInformation(this.context.SessionDataCollectionContext, dumpFile, true); - this.dataCollectionSink.SendFileAsync(fileTranferInformation); + var dumpFiles = this.processDumpUtility.GetDumpFiles(); + foreach (var dumpFile in dumpFiles) + { + if (!string.IsNullOrEmpty(dumpFile)) + { + try + { + var fileTranferInformation = new FileTransferInformation(this.context.SessionDataCollectionContext, dumpFile, true); + this.dataCollectionSink.SendFileAsync(fileTranferInformation); + } + catch (FileNotFoundException ex) + { + EqtTrace.Warning(ex.ToString()); + this.logger.LogWarning(args.Context, ex.ToString()); + } + } + } } - else + catch (FileNotFoundException ex) { - EqtTrace.Warning("BlameCollector.SessionEndedHandler: blame:CollectDump was enabled but dump file was not generated."); - this.logger.LogWarning(args.Context, Resources.Resources.ProcDumpNotGenerated); + EqtTrace.Warning(ex.ToString()); + this.logger.LogWarning(args.Context, ex.ToString()); } } - catch (FileNotFoundException ex) + else { - EqtTrace.Warning(ex.ToString()); - this.logger.LogWarning(args.Context, ex.ToString()); + EqtTrace.Info("BlameCollector.CollectDumpAndAbortTesthost: Custom path to dump directory was provided via VSTEST_DUMP_PATH. Skipping attachment upload, the caller is responsible for collecting and uploading the dumps themselves."); } } } @@ -457,7 +524,8 @@ private void TestHostLaunchedHandler(object sender, TestHostLaunchedEventArgs ar try { - this.processDumpUtility.StartTriggerBasedProcessDump(args.TestHostProcessId, this.attachmentGuid, this.GetTempDirectory(), this.processFullDumpEnabled, ".NETFramework,Version=v4.0"); + var dumpDirectory = this.GetDumpDirectory(); + this.processDumpUtility.StartTriggerBasedProcessDump(args.TestHostProcessId, dumpDirectory, this.processFullDumpEnabled, this.targetFramework); } catch (TestPlatformException e) { @@ -512,13 +580,36 @@ private void DeregisterEvents() private string GetTempDirectory() { - var tmp = Path.GetTempPath(); - if (!Directory.Exists(tmp)) + if (string.IsNullOrWhiteSpace(this.tempDirectory)) + { + this.tempDirectory = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + Directory.CreateDirectory(this.tempDirectory); + return this.tempDirectory; + } + + return this.tempDirectory; + } + + private string GetDumpDirectory() + { + // Using a custom dump path for scenarios where we want to upload the + // dump files ourselves, such as when running in Helix. + // This will save into the directory specified via VSTEST_DUMP_PATH, and + // skip uploading dumps via attachments. + var dumpDirectoryOverride = Environment.GetEnvironmentVariable("VSTEST_DUMP_PATH"); + var dumpDirectoryOverrideHasValue = !string.IsNullOrWhiteSpace(dumpDirectoryOverride); + this.uploadDumpFiles = !dumpDirectoryOverrideHasValue; + + var dumpDirectory = dumpDirectoryOverrideHasValue ? dumpDirectoryOverride : this.GetTempDirectory(); + Directory.CreateDirectory(dumpDirectory); + var dumpPath = Path.Combine(Path.GetFullPath(dumpDirectory)); + + if (!this.uploadDumpFiles) { - Directory.CreateDirectory(tmp); + this.logger.LogWarning(this.context.SessionDataCollectionContext, $"VSTEST_DUMP_PATH is specified. Dump files will be saved in: {dumpPath}, and won't be added to attachments."); } - return tmp; + return dumpPath; } } } diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/BlameLogger.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/BlameLogger.cs index a24cd409c0..255e4451ae 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/BlameLogger.cs +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/BlameLogger.cs @@ -152,8 +152,8 @@ private IEnumerable GetFaultyTestCaseNames(TestRunCompleteEventArgs e) var testCaseList = this.blameReaderWriter.ReadTestSequence(filepath); if (testCaseList.Count > 0) { - var testcase = testCaseList.Last(); - faultyTestCaseNames.Add(testcase.FullyQualifiedName); + var testcases = testCaseList.Where(t => !t.IsCompleted).Select(t => t.FullyQualifiedName).ToList(); + faultyTestCaseNames.AddRange(testcases); } } } diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/CrashDumperFactory.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/CrashDumperFactory.cs index cd0e82fa06..06d5eda6a4 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/CrashDumperFactory.cs +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/CrashDumperFactory.cs @@ -6,19 +6,48 @@ namespace Microsoft.TestPlatform.Extensions.BlameDataCollector using System; using System.Runtime.InteropServices; using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using NuGet.Frameworks; internal class CrashDumperFactory : ICrashDumperFactory { public ICrashDumper Create(string targetFramework) { + if (targetFramework is null) + { + throw new ArgumentNullException(nameof(targetFramework)); + } + EqtTrace.Info($"CrashDumperFactory: Creating dumper for {RuntimeInformation.OSDescription} with target framework {targetFramework}."); + + var tfm = NuGetFramework.Parse(targetFramework); + + if (tfm == null || tfm.IsUnsupported) + { + EqtTrace.Error($"CrashDumperFactory: Could not parse target framework {targetFramework}, to a supported framework version."); + throw new NotSupportedException($"Could not parse target framework {targetFramework}, to a supported framework version."); + } + + var isNet50OrNewer = tfm.Framework == ".NETCoreApp" && tfm.Version >= Version.Parse("5.0.0.0"); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - EqtTrace.Info($"CrashDumperFactory: This is Windows, returning ProcDumpCrashDumper that uses ProcDump utility."); - return new ProcDumpCrashDumper(); + if (!isNet50OrNewer) + { + EqtTrace.Info($"CrashDumperFactory: This is Windows on {targetFramework} which is not net5.0 or newer, returning ProcDumpCrashDumper that uses ProcDump utility."); + return new ProcDumpCrashDumper(); + } + + EqtTrace.Info($"CrashDumperFactory: This is Windows on {targetFramework}, returning the .NETClient dumper which uses env variables to collect crashdumps of testhost and any child process."); + return new NetClientCrashDumper(); + } + + if (isNet50OrNewer) + { + EqtTrace.Info($"CrashDumperFactory: This is {RuntimeInformation.OSDescription} on {targetFramework} .NETClient dumper which uses env variables to collect crashdumps of testhost and any child process."); + return new NetClientCrashDumper(); } - throw new PlatformNotSupportedException($"Unsupported operating system: {RuntimeInformation.OSDescription}"); + throw new PlatformNotSupportedException($"Unsupported operating system: {RuntimeInformation.OSDescription}, and framework: {targetFramework}."); } } } diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/HangDumperFactory.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/HangDumperFactory.cs index 1363aab5dd..ea6e355d4e 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/HangDumperFactory.cs +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/HangDumperFactory.cs @@ -6,21 +6,39 @@ namespace Microsoft.TestPlatform.Extensions.BlameDataCollector using System; using System.Runtime.InteropServices; using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using NuGet.Frameworks; internal class HangDumperFactory : IHangDumperFactory { + public Action LogWarning { get; set; } + public IHangDumper Create(string targetFramework) { + if (targetFramework is null) + { + throw new ArgumentNullException(nameof(targetFramework)); + } + EqtTrace.Info($"HangDumperFactory: Creating dumper for {RuntimeInformation.OSDescription} with target framework {targetFramework}."); + + var tfm = NuGetFramework.Parse(targetFramework); + + if (tfm == null || tfm.IsUnsupported) + { + EqtTrace.Error($"HangDumperFactory: Could not parse target framework {targetFramework}, to a supported framework version."); + throw new NotSupportedException($"Could not parse target framework {targetFramework}, to a supported framework version."); + } + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { EqtTrace.Info($"HangDumperFactory: This is Windows, returning the default WindowsHangDumper that P/Invokes MiniDumpWriteDump."); - return new WindowsHangDumper(); + return new WindowsHangDumper(this.LogWarning); } if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { - if (!string.IsNullOrWhiteSpace(targetFramework) && targetFramework.Contains("v2.1")) + var isLessThan31 = tfm.Framework == ".NETCoreApp" && tfm.Version < Version.Parse("3.1.0.0"); + if (isLessThan31) { EqtTrace.Info($"HangDumperFactory: This is Linux on netcoreapp2.1, returning SigtrapDumper."); @@ -28,12 +46,13 @@ public IHangDumper Create(string targetFramework) } EqtTrace.Info($"HangDumperFactory: This is Linux netcoreapp3.1 or newer, returning the standard NETClient library dumper."); - return new NetClientDumper(); + return new NetClientHangDumper(); } if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { - if (!string.IsNullOrWhiteSpace(targetFramework) && !targetFramework.Contains("v5.0")) + var isLessThan50 = tfm.Framework == ".NETCoreApp" && tfm.Version < Version.Parse("5.0.0.0"); + if (isLessThan50) { EqtTrace.Info($"HangDumperFactory: This is OSX on {targetFramework}, This combination of OS and framework is not supported."); @@ -41,8 +60,7 @@ public IHangDumper Create(string targetFramework) } EqtTrace.Info($"HangDumperFactory: This is OSX on net5.0 or newer, returning the standard NETClient library dumper."); - - return new NetClientDumper(); + return new NetClientHangDumper(); } throw new PlatformNotSupportedException($"Unsupported operating system: {RuntimeInformation.OSDescription}"); diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ICrashDumper.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ICrashDumper.cs index 5f0e522d59..8f05092fc8 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ICrashDumper.cs +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ICrashDumper.cs @@ -5,7 +5,7 @@ namespace Microsoft.TestPlatform.Extensions.BlameDataCollector { public interface ICrashDumper { - void AttachToTargetProcess(int processId, string outputFile, DumpTypeOption dumpType); + void AttachToTargetProcess(int processId, string outputDirectory, DumpTypeOption dumpType); void WaitForDumpToFinish(); diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/IHangDumper.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/IHangDumper.cs index 53a8c4a695..c13e099b3b 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/IHangDumper.cs +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/IHangDumper.cs @@ -5,6 +5,6 @@ namespace Microsoft.TestPlatform.Extensions.BlameDataCollector { public interface IHangDumper { - void Dump(int processId, string outputFile, DumpTypeOption dumpType); + void Dump(int processId, string outputDirectory, DumpTypeOption dumpType); } } diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/IHangDumperFactory.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/IHangDumperFactory.cs index 02978a2131..3abc057078 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/IHangDumperFactory.cs +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/IHangDumperFactory.cs @@ -3,8 +3,12 @@ namespace Microsoft.TestPlatform.Extensions.BlameDataCollector { + using System; + public interface IHangDumperFactory { + Action LogWarning { get; set; } + IHangDumper Create(string targetFramework); } } diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Interfaces/IProcessDumpUtility.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Interfaces/IProcessDumpUtility.cs index aff139119f..74acf34e53 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Interfaces/IProcessDumpUtility.cs +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Interfaces/IProcessDumpUtility.cs @@ -3,6 +3,9 @@ namespace Microsoft.TestPlatform.Extensions.BlameDataCollector { + using System; + using System.Collections.Generic; + public interface IProcessDumpUtility { /// @@ -11,7 +14,7 @@ public interface IProcessDumpUtility /// /// Path of dump file /// - string GetDumpFile(); + IEnumerable GetDumpFiles(); /// /// Launch proc dump process @@ -19,9 +22,6 @@ public interface IProcessDumpUtility /// /// Process ID of test host /// - /// - /// Guid as postfix for dump file, testhost.exe_<guid>.dmp - /// /// /// Path to TestResults directory /// @@ -31,7 +31,7 @@ public interface IProcessDumpUtility /// /// The target framework of the process /// - void StartTriggerBasedProcessDump(int processId, string dumpFileGuid, string testResultsDirectory, bool isFullDump, string targetFramework); + void StartTriggerBasedProcessDump(int processId, string testResultsDirectory, bool isFullDump, string targetFramework); /// /// Launch proc dump process to capture dump in case of a testhost hang and wait for it to exit @@ -39,9 +39,6 @@ public interface IProcessDumpUtility /// /// Process ID of test host /// - /// - /// Guid as postfix for dump file, testhost.exe_<guid>.dmp - /// /// /// Path to TestResults directory /// @@ -51,7 +48,10 @@ public interface IProcessDumpUtility /// /// The target framework of the process /// - void StartHangBasedProcessDump(int processId, string dumpFileGuid, string testResultsDirectory, bool isFullDump, string targetFramework); + /// + /// Callback to datacollector logger to log warning + /// + void StartHangBasedProcessDump(int processId, string testResultsDirectory, bool isFullDump, string targetFramework, Action logWarning = null); /// /// Detaches the proc dump process from the target process diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Microsoft.TestPlatform.Extensions.BlameDataCollector.csproj b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Microsoft.TestPlatform.Extensions.BlameDataCollector.csproj index a29f60e15c..11a26e7f56 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Microsoft.TestPlatform.Extensions.BlameDataCollector.csproj +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Microsoft.TestPlatform.Extensions.BlameDataCollector.csproj @@ -23,7 +23,7 @@ - + @@ -32,9 +32,8 @@ - 0.2.0-preview.20378.10 + 0.2.0-preview.20419.2 - diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/NetClientCrashDumper.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/NetClientCrashDumper.cs new file mode 100644 index 0000000000..6dbdc3242c --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/NetClientCrashDumper.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.TestPlatform.Extensions.BlameDataCollector +{ + internal class NetClientCrashDumper : ICrashDumper + { + public void AttachToTargetProcess(int processId, string outputDirectory, DumpTypeOption dumpType) + { + // we don't need to do anything directly here, we setup the env variables + // in the dumper configuration, including the path + } + + public void DetachFromTargetProcess(int processId) + { + // here we might consider renaming the files to have timestamp + } + + public void WaitForDumpToFinish() + { + } + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/NetClientDumper.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/NetClientDumper.cs deleted file mode 100644 index 4514f09d0f..0000000000 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/NetClientDumper.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace Microsoft.TestPlatform.Extensions.BlameDataCollector -{ - using Microsoft.Diagnostics.NETCore.Client; - using Microsoft.VisualStudio.TestPlatform.ObjectModel; - - internal class NetClientDumper : IHangDumper - { - public void Dump(int processId, string outputFile, DumpTypeOption type) - { - var client = new DiagnosticsClient(processId); - - // Connecting the dump generation logging to verbose output to avoid changing the interfaces again - // before we test this on some big repo. - client.WriteDump(type == DumpTypeOption.Full ? DumpType.Full : DumpType.Normal, outputFile, logDumpGeneration: EqtTrace.IsVerboseEnabled); - } - } -} diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/NetClientHangDumper.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/NetClientHangDumper.cs new file mode 100644 index 0000000000..2a7d0e16d6 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/NetClientHangDumper.cs @@ -0,0 +1,88 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.TestPlatform.Extensions.BlameDataCollector +{ + using System; + using System.Diagnostics; + using System.IO; + using System.Linq; + using Microsoft.Diagnostics.NETCore.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.Utilities; + + internal class NetClientHangDumper : IHangDumper + { + public void Dump(int processId, string outputDirectory, DumpTypeOption type) + { + var process = Process.GetProcessById(processId); + var processTree = process.GetProcessTree(); + + if (EqtTrace.IsVerboseEnabled) + { + if (processTree.Count > 1) + { + EqtTrace.Verbose("NetClientHangDumper.Dump: Dumping this process tree (from bottom):"); + ConsoleOutput.Instance.Information(false, "Blame: Dumping this process tree (from bottom):"); + + foreach (var p in processTree.OrderBy(t => t.Level)) + { + EqtTrace.Verbose($"NetClientHangDumper.Dump: {(p.Level != 0 ? " + " : " > ")}{new string('-', p.Level)} {p.Process.Id} - {p.Process.ProcessName}"); + ConsoleOutput.Instance.Information(false, $"Blame: {(p.Level != 0 ? " + " : " > ")}{new string('-', p.Level)} {p.Process.Id} - {p.Process.ProcessName}"); + } + } + else + { + EqtTrace.Verbose($"NetClientHangDumper.Dump: Dumping {process.Id} - {process.ProcessName}."); + ConsoleOutput.Instance.Information(false, $"Blame: Dumping {process.Id} - {process.ProcessName}"); + } + } + + var bottomUpTree = processTree.OrderByDescending(t => t.Level).Select(t => t.Process); + + foreach (var p in bottomUpTree) + { + try + { + p.Suspend(); + } + catch (Exception ex) + { + EqtTrace.Error($"NetClientHangDumper.Dump: Error suspending process {p.Id} - {p.ProcessName}: {ex}."); + } + } + + foreach (var p in bottomUpTree) + { + try + { + var outputFile = Path.Combine(outputDirectory, $"{p.ProcessName}_{p.Id}_{DateTime.Now:yyyyMMddTHHmmss}_hangdump.dmp"); + EqtTrace.Verbose($"NetClientHangDumper.CollectDump: Selected dump type {type}. Dumping {process.Id} - {process.ProcessName} in {outputFile}. "); + + var client = new DiagnosticsClient(p.Id); + + // Connecting the dump generation logging to verbose output to avoid changing the interfaces again -> EqtTrace.IsVerboseEnabled + // before we test this on some big repo. + client.WriteDump(type == DumpTypeOption.Full ? DumpType.Full : DumpType.Normal, outputFile, logDumpGeneration: false); + } + catch (Exception ex) + { + EqtTrace.Error($"NetClientHangDumper.Dump: Error dumping process {p.Id} - {p.ProcessName}: {ex}."); + } + } + + foreach (var p in bottomUpTree) + { + try + { + EqtTrace.Verbose($"NetClientHangDumper.Dump: Killing process {p.Id} - {p.ProcessName}."); + p.Kill(); + } + catch (Exception ex) + { + EqtTrace.Error($"NetClientHangDumper.Dump: Error killing process {p.Id} - {p.ProcessName}: {ex}."); + } + } + } + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcDumpCrashDumper.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcDumpCrashDumper.cs index 9baf0ad0f8..008e194a6a 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcDumpCrashDumper.cs +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcDumpCrashDumper.cs @@ -69,8 +69,10 @@ public void WaitForDumpToFinish() } /// - public void AttachToTargetProcess(int processId, string outputFile, DumpTypeOption dumpType) + public void AttachToTargetProcess(int processId, string outputDirectory, DumpTypeOption dumpType) { + var process = Process.GetProcessById(processId); + var outputFile = Path.Combine(outputDirectory, $"{process.ProcessName}_{process.Id}_{DateTime.Now:yyyyMMddTHHmmss}_crashdump.dmp"); EqtTrace.Info($"ProcDumpCrashDumper.AttachToTargetProcess: Attaching to process '{processId}' to dump into '{outputFile}'."); // Procdump will append .dmp at the end of the dump file. We generate this internally so it is rather a safety check. diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcessCodeMethods.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcessCodeMethods.cs new file mode 100644 index 0000000000..6ffbd90086 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcessCodeMethods.cs @@ -0,0 +1,267 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.TestPlatform.Extensions.BlameDataCollector +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Linq; + using System.Runtime.InteropServices; + using System.Text; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions; + using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces; + using Microsoft.VisualStudio.TestPlatform.Utilities; + using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers; + using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces; + + /// + /// Helper functions for process info. + /// + internal static class ProcessCodeMethods + { + private const int InvalidProcessId = -1; + + public static void Suspend(this Process process) + { + if (process.HasExited) + { + EqtTrace.Verbose($"ProcessCodeMethods.Suspend: Process {process.Id} - {process.ProcessName} already exited, skipping."); + return; + } + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + SuspendWindows(process); + } + else + { + // TODO: do not suspend on Mac and Linux, this prevents the process from being dumped when we use the net client dumper, checking if we can post a different signal + // SuspendLinuxMacOs(process); + } + } + + public static List GetProcessTree(this Process process) + { + var childProcesses = Process.GetProcesses() + .Where(p => IsChildCandidate(p, process)) + .ToList(); + + var acc = new List(); + foreach (var c in childProcesses) + { + try + { + var parentId = GetParentPid(c); + + // c.ParentId = parentId; + acc.Add(new ProcessTreeNode { ParentId = parentId, Process = c }); + } + catch + { + // many things can go wrong with this + // just ignore errors + } + } + + var level = 1; + var limit = 10; + ResolveChildren(process, acc, level, limit); + + return new List { new ProcessTreeNode { Process = process, Level = 0 } }.Concat(acc.Where(a => a.Level > 0)).ToList(); + } + + internal static void SuspendWindows(Process process) + { + EqtTrace.Verbose($"ProcessCodeMethods.Suspend: Suspending process {process.Id} - {process.ProcessName}."); + NtSuspendProcess(process.Handle); + } + + internal static void SuspendLinuxMacOs(Process process) + { + try + { + var output = new StringBuilder(); + var err = new StringBuilder(); + Process ps = new Process(); + ps.StartInfo.FileName = "kill"; + ps.StartInfo.Arguments = $"-STOP {process.Id}"; + ps.StartInfo.UseShellExecute = false; + ps.StartInfo.RedirectStandardOutput = true; + ps.OutputDataReceived += (_, e) => output.Append(e.Data); + ps.ErrorDataReceived += (_, e) => err.Append(e.Data); + ps.Start(); + ps.BeginOutputReadLine(); + ps.WaitForExit(5_000); + + if (!string.IsNullOrWhiteSpace(err.ToString())) + { + EqtTrace.Verbose($"ProcessCodeMethods.SuspendLinuxMacOs: Error suspending process {process.Id} - {process.ProcessName}, {err}."); + } + } + catch (Exception ex) + { + EqtTrace.Verbose($"ProcessCodeMethods.GetParentPidMacOs: Error getting parent of process {process.Id} - {process.ProcessName}, {ex}."); + } + } + + /// + /// Returns the parent id of a process or -1 if it fails. + /// + /// The process to find parent of. + /// The pid of the parent process. + internal static int GetParentPid(Process process) + { + return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? GetParentPidWindows(process) + : RuntimeInformation.IsOSPlatform(OSPlatform.Linux) + ? GetParentPidLinux(process) + : RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? + GetParentPidMacOs(process) + : throw new PlatformNotSupportedException(); + } + + internal static int GetParentPidWindows(Process process) + { + try + { + var handle = process.Handle; + PROCESS_BASIC_INFORMATION pbi; + int size; + var res = NtQueryInformationProcess(handle, 0, out pbi, Marshal.SizeOf(), out size); + + var p = res != 0 ? InvalidProcessId : pbi.InheritedFromUniqueProcessId.ToInt32(); + + return p; + } + catch (Exception ex) + { + EqtTrace.Verbose($"ProcessCodeMethods.GetParentPidLinux: Error getting parent of process {process.Id} - {process.ProcessName}, {ex}."); + return InvalidProcessId; + } + } + + /// Read the /proc file system for information about the parent. + /// The process to get the parent process from. + /// The process id. + internal static int GetParentPidLinux(Process process) + { + var pid = process.Id; + + // read /proc//stat + // 4th column will contain the ppid, 92 in the example below + // ex: 93 (bash) S 92 93 2 4294967295 ... + var path = $"/proc/{pid}/stat"; + try + { + var stat = File.ReadAllText(path); + var parts = stat.Split(' '); + + if (parts.Length < 5) + { + return InvalidProcessId; + } + + return int.Parse(parts[3]); + } + catch (Exception ex) + { + EqtTrace.Verbose($"ProcessCodeMethods.GetParentPidLinux: Error getting parent of process {process.Id} - {process.ProcessName}, {ex}."); + return InvalidProcessId; + } + } + + internal static int GetParentPidMacOs(Process process) + { + try + { + var output = new StringBuilder(); + var err = new StringBuilder(); + Process ps = new Process(); + ps.StartInfo.FileName = "ps"; + ps.StartInfo.Arguments = $"-o ppid= {process.Id}"; + ps.StartInfo.UseShellExecute = false; + ps.StartInfo.RedirectStandardOutput = true; + ps.OutputDataReceived += (_, e) => output.Append(e.Data); + ps.ErrorDataReceived += (_, e) => err.Append(e.Data); + ps.Start(); + ps.BeginOutputReadLine(); + ps.WaitForExit(5_000); + + var o = output.ToString(); + var parent = int.TryParse(o.Trim(), out var ppid) ? ppid : InvalidProcessId; + + if (!string.IsNullOrWhiteSpace(err.ToString())) + { + EqtTrace.Verbose($"ProcessCodeMethods.GetParentPidMacOs: Error getting parent of process {process.Id} - {process.ProcessName}, {err}."); + } + + return parent; + } + catch (Exception ex) + { + EqtTrace.Verbose($"ProcessCodeMethods.GetParentPidMacOs: Error getting parent of process {process.Id} - {process.ProcessName}, {ex}."); + return InvalidProcessId; + } + } + + private static void ResolveChildren(Process parent, List acc, int level, int limit) + { + if (limit < 0) + { + // hit recursion limit, just returning + return; + } + + // only take children that are newer than the parent, because process ids (PIDs) get recycled + var children = acc.Where(p => p.ParentId == parent.Id && p.Process.StartTime > parent.StartTime).ToList(); + + foreach (var child in children) + { + child.Level = level; + ResolveChildren(child.Process, acc, level + 1, limit); + } + } + + private static bool IsChildCandidate(Process child, Process parent) + { + // this is extremely slow under debugger, but fast without it + try + { + return child.StartTime > parent.StartTime && child.Id != parent.Id; + } + catch + { + /* access denied or process has exits most likely */ + return false; + } + } + + [StructLayout(LayoutKind.Sequential)] + private struct PROCESS_BASIC_INFORMATION + { + public IntPtr ExitStatus; + public IntPtr PebBaseAddress; + public IntPtr AffinityMask; + public IntPtr BasePriority; + public IntPtr UniqueProcessId; + public IntPtr InheritedFromUniqueProcessId; + } + + [DllImport("ntdll.dll", SetLastError = true)] +#pragma warning disable SA1201 // Elements must appear in the correct order + private static extern int NtQueryInformationProcess( + IntPtr processHandle, + int processInformationClass, + out PROCESS_BASIC_INFORMATION processInformation, + int processInformationLength, + out int returnLength); + + [DllImport("ntdll.dll", SetLastError = true)] + private static extern IntPtr NtSuspendProcess(IntPtr processHandle); + +#pragma warning restore SA1201 // Elements must appear in the correct order + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcessDumpUtility.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcessDumpUtility.cs index 251e6029f1..6065f1c4e3 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcessDumpUtility.cs +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcessDumpUtility.cs @@ -4,7 +4,10 @@ namespace Microsoft.TestPlatform.Extensions.BlameDataCollector { using System; + using System.Collections.Generic; + using System.Diagnostics; using System.IO; + using System.Linq; using System.Runtime.InteropServices; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions; @@ -20,8 +23,8 @@ internal class ProcessDumpUtility : IProcessDumpUtility private readonly IHangDumperFactory hangDumperFactory; private readonly ICrashDumperFactory crashDumperFactory; private ICrashDumper crashDumper; - private string hangDumpPath; - private string crashDumpPath; + private string hangDumpDirectory; + private string crashDumpDirectory; private bool wasHangDumped; public ProcessDumpUtility() @@ -48,41 +51,56 @@ public ProcessDumpUtility(IProcessHelper processHelper, IFileHelper fileHelper, }; /// - public string GetDumpFile() + public IEnumerable GetDumpFiles() { - string dumpPath; if (!this.wasHangDumped) { this.crashDumper.WaitForDumpToFinish(); - dumpPath = this.crashDumpPath; } - else + + IEnumerable crashDumps = this.fileHelper.DirectoryExists(this.crashDumpDirectory) + ? this.fileHelper.EnumerateFiles(this.crashDumpDirectory, SearchOption.AllDirectories, new[] { ".dmp" }) + : new List(); + + IEnumerable hangDumps = this.fileHelper.DirectoryExists(this.hangDumpDirectory) + ? this.fileHelper.EnumerateFiles(this.hangDumpDirectory, SearchOption.TopDirectoryOnly, new[] { ".dmp" }) + : new List(); + + var foundDumps = new List(); + foreach (var dumpPath in crashDumps.Concat(hangDumps)) { - dumpPath = this.hangDumpPath; + EqtTrace.Info($"ProcessDumpUtility.GetDumpFiles: Looking for dump file '{dumpPath}'."); + var found = this.fileHelper.Exists(dumpPath); + if (found) + { + EqtTrace.Info($"ProcessDumpUtility.GetDumpFile: Found dump file '{dumpPath}'."); + foundDumps.Add(dumpPath); + } + else + { + EqtTrace.Warning($"ProcessDumpUtility.GetDumpFile: Dump file '{dumpPath}' was not found."); + } } - EqtTrace.Info($"ProcessDumpUtility.GetDumpFile: Looking for dump file '{dumpPath}'."); - var found = this.fileHelper.Exists(dumpPath); - if (found) + if (!foundDumps.Any()) { - EqtTrace.Info($"ProcessDumpUtility.GetDumpFile: Found dump file '{dumpPath}'."); - return dumpPath; + EqtTrace.Error($"ProcessDumpUtility.GetDumpFile: Could not find any dump file in {this.hangDumpDirectory}."); + throw new FileNotFoundException(Resources.Resources.DumpFileNotGeneratedErrorMessage); } - EqtTrace.Error($"ProcessDumpUtility.GetDumpFile: Dump file '{dumpPath}' was not found."); - throw new FileNotFoundException(Resources.Resources.DumpFileNotGeneratedErrorMessage); + return foundDumps; } /// - public void StartHangBasedProcessDump(int processId, string dumpFileGuid, string tempDirectory, bool isFullDump, string targetFramework) + public void StartHangBasedProcessDump(int processId, string tempDirectory, bool isFullDump, string targetFramework, Action logWarning = null) { - this.HangDump(processId, dumpFileGuid, tempDirectory, isFullDump ? DumpTypeOption.Full : DumpTypeOption.Mini, targetFramework); + this.HangDump(processId, tempDirectory, isFullDump ? DumpTypeOption.Full : DumpTypeOption.Mini, targetFramework, logWarning); } /// - public void StartTriggerBasedProcessDump(int processId, string dumpFileGuid, string testResultsDirectory, bool isFullDump, string targetFramework) + public void StartTriggerBasedProcessDump(int processId, string testResultsDirectory, bool isFullDump, string targetFramework) { - this.CrashDump(processId, dumpFileGuid, testResultsDirectory, isFullDump ? DumpTypeOption.Full : DumpTypeOption.Mini, targetFramework); + this.CrashDump(processId, testResultsDirectory, isFullDump ? DumpTypeOption.Full : DumpTypeOption.Mini, targetFramework); } /// @@ -91,47 +109,34 @@ public void DetachFromTargetProcess(int targetProcessId) this.crashDumper?.DetachFromTargetProcess(targetProcessId); } - private void CrashDump(int processId, string dumpFileGuid, string tempDirectory, DumpTypeOption dumpType, string targetFramework) + private void CrashDump(int processId, string tempDirectory, DumpTypeOption dumpType, string targetFramework) { - var dumpPath = this.GetDumpPath(processId, dumpFileGuid, tempDirectory, isHangDump: false, out var processName); - - EqtTrace.Info($"ProcessDumpUtility.CrashDump: Creating {dumpType.ToString().ToLowerInvariant()} dump of process {processName} ({processId}) into temporary path '{dumpPath}'."); - this.crashDumpPath = dumpPath; - - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - throw new NotSupportedException($"Operating system {RuntimeInformation.OSDescription} is not supported for crash dumps."); - } + var processName = this.processHelper.GetProcessName(processId); + EqtTrace.Info($"ProcessDumpUtility.CrashDump: Creating {dumpType.ToString().ToLowerInvariant()} dump of process {processName} ({processId}) into temporary path '{tempDirectory}'."); + this.crashDumpDirectory = tempDirectory; this.crashDumper = this.crashDumperFactory.Create(targetFramework); ConsoleOutput.Instance.Information(false, $"Blame: Attaching crash dump utility to process {processName} ({processId})."); - this.crashDumper.AttachToTargetProcess(processId, dumpPath, dumpType); + this.crashDumper.AttachToTargetProcess(processId, tempDirectory, dumpType); } - private void HangDump(int processId, string dumpFileGuid, string tempDirectory, DumpTypeOption dumpType, string targetFramework) + private void HangDump(int processId, string tempDirectory, DumpTypeOption dumpType, string targetFramework, Action logWarning = null) { this.wasHangDumped = true; - // the below format is extremely ugly maybe we can use: - - // https://github.com/microsoft/testfx/issues/678 - // which will order the files correctly gives more info when transported out of - // the context of the run, and keeps the file name unique-enough for our purposes" - // $"{processName}_{processId}_{dumpFileGuid}_hangdump.dmp" - // var dumpFileName = $"crash_{processName}_{DateTime.Now:yyyyMMddTHHmmss}_{processId}.dmp"; - // var dumpFileName = $"{prefix}_{processName}_{DateTime.Now:yyyyMMddTHHmmss}_{processId}.dmp"; - var dumpPath = this.GetDumpPath(processId, dumpFileGuid, tempDirectory, isHangDump: true, out var processName); + var processName = this.processHelper.GetProcessName(processId); + EqtTrace.Info($"ProcessDumpUtility.HangDump: Creating {dumpType.ToString().ToLowerInvariant()} dump of process {processName} ({processId}) into temporary path '{tempDirectory}'."); - EqtTrace.Info($"ProcessDumpUtility.HangDump: Creating {dumpType.ToString().ToLowerInvariant()} dump of process {processName} ({processId}) into temporary path '{dumpPath}'."); - this.hangDumpPath = dumpPath; + this.hangDumpDirectory = tempDirectory; + // oh how ugly this is, but the whole infra above this starts with initializing the logger in Initialize + // the logger needs to pass around 2 parameters, so I am just passing it around as callback instead + this.hangDumperFactory.LogWarning = logWarning; var dumper = this.hangDumperFactory.Create(targetFramework); try { - ConsoleOutput.Instance.Information(false, $"Blame: Creating hang dump of process {processName} ({processId})."); - dumper.Dump(processId, dumpPath, dumpType); - EqtTrace.Info($"ProcessDumpUtility.HangDump: Process {processName} ({processId}) was dumped into temporary path '{dumpPath}'."); + dumper.Dump(processId, tempDirectory, dumpType); } catch (Exception ex) { @@ -139,15 +144,5 @@ private void HangDump(int processId, string dumpFileGuid, string tempDirectory, throw; } } - - private string GetDumpPath(int processId, string dumpFileGuid, string tempDirectory, bool isHangDump, out string processName) - { - processName = this.processHelper.GetProcessName(processId); - var suffix = isHangDump ? "hang" : "crash"; - var dumpFileName = $"{processName}_{processId}_{dumpFileGuid}_{suffix}dump.dmp"; - - var path = Path.GetFullPath(tempDirectory); - return Path.Combine(path, dumpFileName); - } } } diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcessTreeNode.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcessTreeNode.cs new file mode 100644 index 0000000000..e093c0fa63 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcessTreeNode.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.TestPlatform.Extensions.BlameDataCollector +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Linq; + using System.Runtime.InteropServices; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions; + using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces; + using Microsoft.VisualStudio.TestPlatform.Utilities; + using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers; + using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces; + + internal class ProcessTreeNode + { + public Process Process { get; set; } + + public int Level { get; set; } + + public int ParentId { get; set; } + + public Process ParentProcess { get; set; } + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/Resources.Designer.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/Resources.Designer.cs index 96a3ee8a04..53cf4ade1e 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/Resources.Designer.cs +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/Resources.Designer.cs @@ -97,7 +97,25 @@ internal static string DumpFileNotGeneratedErrorMessage { } /// - /// Looks up a localized string similar to The specified inactivity time of {0} minute/s has elapsed. Collecting a dump and killing the test host process.. + /// Looks up a localized string similar to Dumping {0} - {1}. + /// + internal static string Dumping { + get { + return ResourceManager.GetString("Dumping", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Dumping this process tree (from bottom). + /// + internal static string DumpingTree { + get { + return ResourceManager.GetString("DumpingTree", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The specified inactivity time of {0} {1} has elapsed. Collecting hang dumps from testhost and its child processes. /// internal static string InactivityTimeout { get { @@ -105,6 +123,24 @@ internal static string InactivityTimeout { } } + /// + /// Looks up a localized string similar to minutes. + /// + internal static string Minutes { + get { + return ResourceManager.GetString("Minutes", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to All tests finished running, Sequence file will not be generated. + /// + internal static string NotGeneratingSequenceFile { + get { + return ResourceManager.GetString("NotGeneratingSequenceFile", resourceCulture); + } + } + /// /// Looks up a localized string similar to Could not start process dump: {0}. /// @@ -132,6 +168,15 @@ internal static string ProcDumpNotGenerated { } } + /// + /// Looks up a localized string similar to seconds. + /// + internal static string Seconds { + get { + return ResourceManager.GetString("Seconds", resourceCulture); + } + } + /// /// Looks up a localized string similar to Unexpected value '{0}' provided as timeout. Please provide a value in this format: 1.5h / 90m / 5400s / 5400000ms / 5400000. /// diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/Resources.resx b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/Resources.resx index 6631dcada8..a500d52c84 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/Resources.resx +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/Resources.resx @@ -129,8 +129,23 @@ Collect dump was enabled but no dump file was generated. + + Dumping {0} - {1} + as in creating a process dump, {0} process id, {1} process name + + + Dumping this process tree (from bottom) + - The specified inactivity time of {0} minute/s has elapsed. Collecting a dump and killing the test host process. + The specified inactivity time of {0} {1} has elapsed. Collecting hang dumps from testhost and its child processes + {0} value, {1} one of the unit strings below, Minutes/Seconds + + + minutes + + + All tests finished running, Sequence file will not be generated + "Sequence" is the name of the file. No . at the end, because this is a blame message and the . will be added automatically. Could not start process dump: {0} @@ -141,6 +156,9 @@ CollectDump was enabled but dump file was not generated. + + seconds + Unexpected value '{0}' provided as timeout. Please provide a value in this format: 1.5h / 90m / 5400s / 5400000ms / 5400000 diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.cs.xlf b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.cs.xlf index 121aec7232..7f9f38e301 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.cs.xlf +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.cs.xlf @@ -56,15 +56,40 @@ - The specified inactivity time of {0} minute/s has elapsed. Collecting a dump and killing the test host process. - Uplynul zadaný čas nečinnosti {0} min. Shromažďuje se výpis paměti a ukončuje se proces hostitele testu. - + The specified inactivity time of {0} {1} has elapsed. Collecting hang dumps from testhost and its child processes + Uplynul zadaný čas nečinnosti {0} min. Shromažďuje se výpis paměti a ukončuje se proces hostitele testu. + {0} value, {1} one of the unit strings below, Minutes/Seconds Unexpected value '{0}' provided as timeout. Please provide a value in this format: 1.5h / 90m / 5400s / 5400000ms / 5400000 Jako časový limit se přidala neočekávaná hodnota {0}. Zadejte prosím hodnotu v tomto formátu: 1.5h / 90m / 5400s / 5400000ms / 5400000 + + All tests finished running, Sequence file will not be generated + All tests finished running, Sequence file will not be generated. + "Sequence" is the name of the file. + + + Dumping {0} - {1} + Dumping {0} - {1} + as in creating a process dump, {0} process id, {1} process name + + + Dumping this process tree (from bottom) + Dumping this process tree (from bottom) + + + + minutes + minutes + + + + seconds + seconds + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.de.xlf b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.de.xlf index 8f55cedd73..9df340f2a0 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.de.xlf +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.de.xlf @@ -56,15 +56,40 @@ - The specified inactivity time of {0} minute/s has elapsed. Collecting a dump and killing the test host process. - Die angegebene Inaktivitätszeit von {0} Minuten ist abgelaufen. Ein Speicherabbild wird erfasst, und der Testhostprozess wird beendet. - + The specified inactivity time of {0} {1} has elapsed. Collecting hang dumps from testhost and its child processes + Die angegebene Inaktivitätszeit von {0} Minuten ist abgelaufen. Ein Speicherabbild wird erfasst, und der Testhostprozess wird beendet. + {0} value, {1} one of the unit strings below, Minutes/Seconds Unexpected value '{0}' provided as timeout. Please provide a value in this format: 1.5h / 90m / 5400s / 5400000ms / 5400000 Unerwarteter Wert "{0}" als Timeout angegeben. Geben Sie einen Wert in folgendem Format an: 1.5h / 90m / 5400s / 5400000ms / 5400000 + + All tests finished running, Sequence file will not be generated + All tests finished running, Sequence file will not be generated. + "Sequence" is the name of the file. + + + Dumping {0} - {1} + Dumping {0} - {1} + as in creating a process dump, {0} process id, {1} process name + + + Dumping this process tree (from bottom) + Dumping this process tree (from bottom) + + + + minutes + minutes + + + + seconds + seconds + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.es.xlf b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.es.xlf index 16f4d7e317..7f7b152970 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.es.xlf +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.es.xlf @@ -56,15 +56,40 @@ - The specified inactivity time of {0} minute/s has elapsed. Collecting a dump and killing the test host process. - Ha transcurrido el tiempo de inactividad especificado en minutos ({0}). Se va a recopilar un volcado y a terminar el proceso del host de prueba. - + The specified inactivity time of {0} {1} has elapsed. Collecting hang dumps from testhost and its child processes + Ha transcurrido el tiempo de inactividad especificado en minutos ({0}). Se va a recopilar un volcado y a terminar el proceso del host de prueba. + {0} value, {1} one of the unit strings below, Minutes/Seconds Unexpected value '{0}' provided as timeout. Please provide a value in this format: 1.5h / 90m / 5400s / 5400000ms / 5400000 Valor inesperado "{0}" proporcionado como tiempo de espera. Indique un valor en este formato: 1.5h / 90m / 5400s / 5400000ms / 5400000 + + All tests finished running, Sequence file will not be generated + All tests finished running, Sequence file will not be generated. + "Sequence" is the name of the file. + + + Dumping {0} - {1} + Dumping {0} - {1} + as in creating a process dump, {0} process id, {1} process name + + + Dumping this process tree (from bottom) + Dumping this process tree (from bottom) + + + + minutes + minutes + + + + seconds + seconds + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.fr.xlf b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.fr.xlf index 1c1824d101..4b02e78d1c 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.fr.xlf +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.fr.xlf @@ -56,15 +56,40 @@ - The specified inactivity time of {0} minute/s has elapsed. Collecting a dump and killing the test host process. - Le délai d'inactivité spécifié, {0} minutes/s, s'est écoulé. Collecte d'une image mémoire et arrêt du processus hôte de test. - + The specified inactivity time of {0} {1} has elapsed. Collecting hang dumps from testhost and its child processes + Le délai d'inactivité spécifié, {0} minutes/s, s'est écoulé. Collecte d'une image mémoire et arrêt du processus hôte de test. + {0} value, {1} one of the unit strings below, Minutes/Seconds Unexpected value '{0}' provided as timeout. Please provide a value in this format: 1.5h / 90m / 5400s / 5400000ms / 5400000 Valeur inattendue '{0}' fournie en tant que délai d'expiration. Indiquez une valeur au format suivant : 1.5h / 90m / 5400s / 5400000ms / 5400000 + + All tests finished running, Sequence file will not be generated + All tests finished running, Sequence file will not be generated. + "Sequence" is the name of the file. + + + Dumping {0} - {1} + Dumping {0} - {1} + as in creating a process dump, {0} process id, {1} process name + + + Dumping this process tree (from bottom) + Dumping this process tree (from bottom) + + + + minutes + minutes + + + + seconds + seconds + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.it.xlf b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.it.xlf index bd8d4cb261..f7c63ec22f 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.it.xlf +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.it.xlf @@ -56,15 +56,40 @@ - The specified inactivity time of {0} minute/s has elapsed. Collecting a dump and killing the test host process. - Il tempo di inattività specificato {0} di minuto/i è scaduto. Verrà raccolto un dump e il processo host di test verrà terminato. - + The specified inactivity time of {0} {1} has elapsed. Collecting hang dumps from testhost and its child processes + Il tempo di inattività specificato {0} di minuto/i è scaduto. Verrà raccolto un dump e il processo host di test verrà terminato. + {0} value, {1} one of the unit strings below, Minutes/Seconds Unexpected value '{0}' provided as timeout. Please provide a value in this format: 1.5h / 90m / 5400s / 5400000ms / 5400000 Il valore '{0}', specificato come timeout, è imprevisto. Specificare un valore in questo formato: 1.5h / 90m / 5400s / 5400000ms / 5400000 + + All tests finished running, Sequence file will not be generated + All tests finished running, Sequence file will not be generated. + "Sequence" is the name of the file. + + + Dumping {0} - {1} + Dumping {0} - {1} + as in creating a process dump, {0} process id, {1} process name + + + Dumping this process tree (from bottom) + Dumping this process tree (from bottom) + + + + minutes + minutes + + + + seconds + seconds + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.ja.xlf b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.ja.xlf index 85c40beaa7..f72bb9bcb9 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.ja.xlf +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.ja.xlf @@ -56,15 +56,40 @@ - The specified inactivity time of {0} minute/s has elapsed. Collecting a dump and killing the test host process. - 指定された無通信時間 ({0} 分) が経過しました。ダンプを収集し、テスト ホスト プロセスを中止しています。 - + The specified inactivity time of {0} {1} has elapsed. Collecting hang dumps from testhost and its child processes + 指定された無通信時間 ({0} 分) が経過しました。ダンプを収集し、テスト ホスト プロセスを中止しています。 + {0} value, {1} one of the unit strings below, Minutes/Seconds Unexpected value '{0}' provided as timeout. Please provide a value in this format: 1.5h / 90m / 5400s / 5400000ms / 5400000 予期しない値 '{0}' がタイムアウトとして指定されました。次の形式で値を指定してください: 1.5h、90m、5400s、5400000ms、5400000 + + All tests finished running, Sequence file will not be generated + All tests finished running, Sequence file will not be generated. + "Sequence" is the name of the file. + + + Dumping {0} - {1} + Dumping {0} - {1} + as in creating a process dump, {0} process id, {1} process name + + + Dumping this process tree (from bottom) + Dumping this process tree (from bottom) + + + + minutes + minutes + + + + seconds + seconds + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.ko.xlf b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.ko.xlf index 8d7d9ee5e1..921bebc97b 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.ko.xlf +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.ko.xlf @@ -56,15 +56,40 @@ - The specified inactivity time of {0} minute/s has elapsed. Collecting a dump and killing the test host process. - 지정된 비활성 시간 {0}분을 경과했습니다. 덤프를 수집하고 테스트 호스트 프로세스를 종료합니다. - + The specified inactivity time of {0} {1} has elapsed. Collecting hang dumps from testhost and its child processes + 지정된 비활성 시간 {0}분을 경과했습니다. 덤프를 수집하고 테스트 호스트 프로세스를 종료합니다. + {0} value, {1} one of the unit strings below, Minutes/Seconds Unexpected value '{0}' provided as timeout. Please provide a value in this format: 1.5h / 90m / 5400s / 5400000ms / 5400000 예기치 않은 값 '{0}'이(가) 시간 제한으로 제공되었습니다. 1.5h/90m/5400s/5400000ms/5400000 형식으로 값을 제공하세요. + + All tests finished running, Sequence file will not be generated + All tests finished running, Sequence file will not be generated. + "Sequence" is the name of the file. + + + Dumping {0} - {1} + Dumping {0} - {1} + as in creating a process dump, {0} process id, {1} process name + + + Dumping this process tree (from bottom) + Dumping this process tree (from bottom) + + + + minutes + minutes + + + + seconds + seconds + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.pl.xlf b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.pl.xlf index 896259e275..5f6203d752 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.pl.xlf +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.pl.xlf @@ -56,15 +56,40 @@ - The specified inactivity time of {0} minute/s has elapsed. Collecting a dump and killing the test host process. - Wybrany czas nieaktywności wynoszący {0} min upłynął. Zbieranie zrzutu i zabijanie procesu hosta testu. - + The specified inactivity time of {0} {1} has elapsed. Collecting hang dumps from testhost and its child processes + Wybrany czas nieaktywności wynoszący {0} min upłynął. Zbieranie zrzutu i zabijanie procesu hosta testu. + {0} value, {1} one of the unit strings below, Minutes/Seconds Unexpected value '{0}' provided as timeout. Please provide a value in this format: 1.5h / 90m / 5400s / 5400000ms / 5400000 Podano nieoczekiwaną wartość „{0}” jako limit czasu. Podaj wartość w następującym formacie: 1.5h / 90m / 5400s / 5400000ms / 5400000 + + All tests finished running, Sequence file will not be generated + All tests finished running, Sequence file will not be generated. + "Sequence" is the name of the file. + + + Dumping {0} - {1} + Dumping {0} - {1} + as in creating a process dump, {0} process id, {1} process name + + + Dumping this process tree (from bottom) + Dumping this process tree (from bottom) + + + + minutes + minutes + + + + seconds + seconds + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.pt-BR.xlf b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.pt-BR.xlf index 27eb3d8fd4..4f074f78ff 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.pt-BR.xlf +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.pt-BR.xlf @@ -56,15 +56,40 @@ - The specified inactivity time of {0} minute/s has elapsed. Collecting a dump and killing the test host process. - O tempo de inatividade especificado de {0} minutos terminou. Coletando um despejo e encerrando o processo de host do teste. - + The specified inactivity time of {0} {1} has elapsed. Collecting hang dumps from testhost and its child processes + O tempo de inatividade especificado de {0} minutos terminou. Coletando um despejo e encerrando o processo de host do teste. + {0} value, {1} one of the unit strings below, Minutes/Seconds Unexpected value '{0}' provided as timeout. Please provide a value in this format: 1.5h / 90m / 5400s / 5400000ms / 5400000 Valor inesperado '{0}' fornecido como tempo limite. Forneça um valor neste formato: 1.5h/90m/5400s/5400000ms/5400000 + + All tests finished running, Sequence file will not be generated + All tests finished running, Sequence file will not be generated. + "Sequence" is the name of the file. + + + Dumping {0} - {1} + Dumping {0} - {1} + as in creating a process dump, {0} process id, {1} process name + + + Dumping this process tree (from bottom) + Dumping this process tree (from bottom) + + + + minutes + minutes + + + + seconds + seconds + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.ru.xlf b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.ru.xlf index 261885dd09..31dc3a8f86 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.ru.xlf +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.ru.xlf @@ -56,15 +56,40 @@ - The specified inactivity time of {0} minute/s has elapsed. Collecting a dump and killing the test host process. - Указанное время бездействия ({0} мин) прошло. Сбор дампа и завершение хост-процесса тестирования. - + The specified inactivity time of {0} {1} has elapsed. Collecting hang dumps from testhost and its child processes + Указанное время бездействия ({0} мин) прошло. Сбор дампа и завершение хост-процесса тестирования. + {0} value, {1} one of the unit strings below, Minutes/Seconds Unexpected value '{0}' provided as timeout. Please provide a value in this format: 1.5h / 90m / 5400s / 5400000ms / 5400000 В качестве времени ожидания указано непредвиденное значение "{0}". Укажите значение в следующем формате: 1.5h / 90m / 5400s / 5400000ms / 5400000 + + All tests finished running, Sequence file will not be generated + All tests finished running, Sequence file will not be generated. + "Sequence" is the name of the file. + + + Dumping {0} - {1} + Dumping {0} - {1} + as in creating a process dump, {0} process id, {1} process name + + + Dumping this process tree (from bottom) + Dumping this process tree (from bottom) + + + + minutes + minutes + + + + seconds + seconds + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.tr.xlf b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.tr.xlf index b8ae1b7b1f..fda57e82e4 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.tr.xlf +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.tr.xlf @@ -56,15 +56,40 @@ - The specified inactivity time of {0} minute/s has elapsed. Collecting a dump and killing the test host process. - Belirtilen {0} dakikalık etkin olmama süresi geçti. Döküm toplanıyor ve test ana bilgisayarı işlemi sonlandırılıyor. - + The specified inactivity time of {0} {1} has elapsed. Collecting hang dumps from testhost and its child processes + Belirtilen {0} dakikalık etkin olmama süresi geçti. Döküm toplanıyor ve test ana bilgisayarı işlemi sonlandırılıyor. + {0} value, {1} one of the unit strings below, Minutes/Seconds Unexpected value '{0}' provided as timeout. Please provide a value in this format: 1.5h / 90m / 5400s / 5400000ms / 5400000 Zaman aşımı olarak beklenmeyen '{0}' değeri sağlandı. Lütfen şu biçimde bir değer girin: 1.5h / 90m / 5400s / 5400000ms / 5400000 + + All tests finished running, Sequence file will not be generated + All tests finished running, Sequence file will not be generated. + "Sequence" is the name of the file. + + + Dumping {0} - {1} + Dumping {0} - {1} + as in creating a process dump, {0} process id, {1} process name + + + Dumping this process tree (from bottom) + Dumping this process tree (from bottom) + + + + minutes + minutes + + + + seconds + seconds + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.xlf b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.xlf index e6980ecaf4..adde775921 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.xlf +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.xlf @@ -38,15 +38,40 @@ - The specified inactivity time of {0} minute/s has elapsed. Collecting a dump and killing the test host process. + The specified inactivity time of {0} {1} has elapsed. Collecting hang dumps from testhost and its child processes The specified inactivity time {0} has elapsed. Collecting a dump and killing the test host process. - + {0} value, {1} one of the unit strings below, Minutes/Seconds Unexpected value '{0}' provided as timeout. Please provide a value in this format: 1.5h / 90m / 5400s / 5400000ms / 5400000 Unexpected value {0} provided for ExpectedExecutionTimeOfLongestRunningTestInMinutes. Please provide a positive integer as input. + + All tests finished running, Sequence file will not be generated + All tests finished running, Sequence file will not be generated. + "Sequence" is the name of the file. + + + Dumping {0} - {1} + Dumping {0} - {1} + as in creating a process dump, {0} process id, {1} process name + + + Dumping this process tree (from bottom) + Dumping this process tree (from bottom) + + + + minutes + minutes + + + + seconds + seconds + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.zh-Hans.xlf b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.zh-Hans.xlf index 7de8cbc49e..5bdd34c08c 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.zh-Hans.xlf +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.zh-Hans.xlf @@ -56,15 +56,40 @@ - The specified inactivity time of {0} minute/s has elapsed. Collecting a dump and killing the test host process. - 指定的非活动时间({0} 分钟)已经过。收集转储并终止测试主机进程。 - + The specified inactivity time of {0} {1} has elapsed. Collecting hang dumps from testhost and its child processes + 指定的非活动时间({0} 分钟)已经过。收集转储并终止测试主机进程。 + {0} value, {1} one of the unit strings below, Minutes/Seconds Unexpected value '{0}' provided as timeout. Please provide a value in this format: 1.5h / 90m / 5400s / 5400000ms / 5400000 提供了意外的值“{0}”作为超时。请按以下格式提供值: 1.5h / 90m / 5400s / 5400000ms / 5400000 + + All tests finished running, Sequence file will not be generated + All tests finished running, Sequence file will not be generated. + "Sequence" is the name of the file. + + + Dumping {0} - {1} + Dumping {0} - {1} + as in creating a process dump, {0} process id, {1} process name + + + Dumping this process tree (from bottom) + Dumping this process tree (from bottom) + + + + minutes + minutes + + + + seconds + seconds + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.zh-Hant.xlf b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.zh-Hant.xlf index 8eea946b4e..c5cf368aa6 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.zh-Hant.xlf +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Resources/xlf/Resources.zh-Hant.xlf @@ -56,15 +56,40 @@ - The specified inactivity time of {0} minute/s has elapsed. Collecting a dump and killing the test host process. - 已經過指定的閒置時間 {0} 分鐘/秒。正在收集傾印以及終止測試主機處理序。 - + The specified inactivity time of {0} {1} has elapsed. Collecting hang dumps from testhost and its child processes + 已經過指定的閒置時間 {0} 分鐘/秒。正在收集傾印以及終止測試主機處理序。 + {0} value, {1} one of the unit strings below, Minutes/Seconds Unexpected value '{0}' provided as timeout. Please provide a value in this format: 1.5h / 90m / 5400s / 5400000ms / 5400000 提供了非預期的值 '{0}' 作為逾時。請提供以下格式的值: 1.5h / 90m / 5400s / 5400000ms / 5400000 + + All tests finished running, Sequence file will not be generated + All tests finished running, Sequence file will not be generated. + "Sequence" is the name of the file. + + + Dumping {0} - {1} + Dumping {0} - {1} + as in creating a process dump, {0} process id, {1} process name + + + Dumping this process tree (from bottom) + Dumping this process tree (from bottom) + + + + minutes + minutes + + + + seconds + seconds + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/SigtrapDumper.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/SigtrapDumper.cs index 64f6afbb48..2c2bbfc17c 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/SigtrapDumper.cs +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/SigtrapDumper.cs @@ -7,7 +7,7 @@ namespace Microsoft.TestPlatform.Extensions.BlameDataCollector internal class SigtrapDumper : IHangDumper { - public void Dump(int processId, string outputFile, DumpTypeOption type) + public void Dump(int processId, string outputDirectory, DumpTypeOption type) { Process.Start("kill", $"-s SIGTRAP {processId}"); } diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/WindowsHangDumper.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/WindowsHangDumper.cs index 682a92be88..c1912b5fb1 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/WindowsHangDumper.cs +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/WindowsHangDumper.cs @@ -5,20 +5,105 @@ namespace Microsoft.TestPlatform.Extensions.BlameDataCollector { using System; using System.Diagnostics; + using System.Globalization; using System.IO; + using System.Linq; using System.Runtime.InteropServices; + using System.Threading; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection; + using Microsoft.VisualStudio.TestPlatform.Utilities; using Microsoft.Win32.SafeHandles; internal class WindowsHangDumper : IHangDumper { - public void Dump(int processId, string outputFile, DumpTypeOption type) + private Action logWarning; + + public WindowsHangDumper(Action logWarning) + { + this.logWarning = logWarning ?? (_ => { }); + } + + public void Dump(int processId, string outputDirectory, DumpTypeOption type) { var process = Process.GetProcessById(processId); - CollectDump(process, outputFile, type); + var processTree = process.GetProcessTree(); + + if (processTree.Count > 1) + { + var tree = processTree.OrderBy(t => t.Level); + EqtTrace.Verbose("WindowsHangDumper.Dump: Dumping this process tree (from bottom):"); + foreach (var p in tree) + { + EqtTrace.Verbose($"WindowsHangDumper.Dump: {new string(' ', p.Level)}{(p.Level != 0 ? " +" : " >-")} {p.Process.Id} - {p.Process.ProcessName}"); + } + + // logging warning separately to avoid interleving the messages in the log which make this tree unreadable + this.logWarning(Resources.Resources.DumpingTree); + foreach (var p in tree) + { + this.logWarning($"{new string(' ', p.Level)}{(p.Level != 0 ? "+-" : ">")} {p.Process.Id} - {p.Process.ProcessName}"); + } + } + else + { + EqtTrace.Verbose($"NetClientHangDumper.Dump: Dumping {process.Id} - {process.ProcessName}."); + var message = string.Format(CultureInfo.CurrentUICulture, Resources.Resources.Dumping, process.Id, process.ProcessName); + this.logWarning(message); + } + + var bottomUpTree = processTree.OrderByDescending(t => t.Level).Select(t => t.Process); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + foreach (var p in bottomUpTree) + { + try + { + p.Suspend(); + } + catch (Exception ex) + { + EqtTrace.Error($"WindowsHangDumper.Dump: Error suspending process {p.Id} - {p.ProcessName}: {ex}."); + } + } + } + + Thread.Sleep(1300); + foreach (var p in bottomUpTree) + { + try + { + Thread.Sleep(500); + var outputFile = Path.Combine(outputDirectory, $"{p.ProcessName}_{p.Id}_{DateTime.Now:yyyyMMddTHHmmss}_hangdump.dmp"); + CollectDump(p, outputFile, type); + } + catch (Exception ex) + { + EqtTrace.Error($"WindowsHangDumper.Dump: Error dumping process {p.Id} - {p.ProcessName}: {ex}."); + } + + try + { + EqtTrace.Verbose($"WindowsHangDumper.Dump: Killing process {p.Id} - {p.ProcessName}."); + p.Kill(); + } + catch (Exception ex) + { + EqtTrace.Error($"WindowsHangDumper.Dump: Error killing process {p.Id} - {p.ProcessName}: {ex}."); + } + } } internal static void CollectDump(Process process, string outputFile, DumpTypeOption type) { + if (process.HasExited) + { + EqtTrace.Verbose($"WindowsHangDumper.CollectDump: {process.Id} - {process.ProcessName} already exited, skipping."); + return; + } + + EqtTrace.Verbose($"WindowsHangDumper.CollectDump: Selected dump type {type}. Dumping {process.Id} - {process.ProcessName} in {outputFile}. "); + // Open the file for writing using (var stream = new FileStream(outputFile, FileMode.Create, FileAccess.ReadWrite, FileShare.None)) { @@ -68,6 +153,8 @@ internal static void CollectDump(Process process, string outputFile, DumpTypeOpt } } } + + EqtTrace.Verbose($"WindowsHangDumper.CollectDump: Finished dumping {process.Id} - {process.ProcessName} in {outputFile}. "); } private static class NativeMethods diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestResult.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestResult.cs index 661d7fc9fb..ea6e327b2c 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestResult.cs +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestResult.cs @@ -231,6 +231,7 @@ internal class TestResult : ITestResult, IXmlTestStore /// Directory containing the test result files, relative to the root test results directory /// private string relativeTestResultsDirectory; + private readonly TrxFileHelper trxFileHelper; /// /// Paths to test result files, relative to the test results folder, sorted in increasing order @@ -270,7 +271,8 @@ public TestResult( string computerName, TestOutcome outcome, TestType testType, - TestListCategoryId testCategoryId) + TestListCategoryId testCategoryId, + TrxFileHelper trxFileHelper) { Debug.Assert(computerName != null, "computername is null"); Debug.Assert(!Guid.Empty.Equals(executionId), "ExecutionId is empty"); @@ -285,6 +287,7 @@ public TestResult( this.outcome = outcome; this.categoryId = testCategoryId; this.relativeTestResultsDirectory = TestRunDirectories.GetRelativeTestResultsDirectory(executionId); + this.trxFileHelper = trxFileHelper; } #endregion @@ -532,7 +535,7 @@ internal void AddResultFiles(IEnumerable resultFileList) Debug.Assert(!string.IsNullOrEmpty(resultFile), "'resultFile' is null or empty"); Debug.Assert(resultFile.Trim() == resultFile, "'resultFile' has whitespace at the ends"); - this.resultFiles[FileHelper.MakePathRelative(resultFile, testResultsDirectory)] = null; + this.resultFiles[trxFileHelper.MakePathRelative(resultFile, testResultsDirectory)] = null; } } diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestResultAggregation.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestResultAggregation.cs index 1561fa287e..8c06d8ec58 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestResultAggregation.cs +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestResultAggregation.cs @@ -5,6 +5,7 @@ namespace Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel { using System; using System.Collections.Generic; + using Microsoft.TestPlatform.Extensions.TrxLogger.Utility; using Microsoft.TestPlatform.Extensions.TrxLogger.XML; /// @@ -23,7 +24,8 @@ public TestResultAggregation( string computerName, TestOutcome outcome, TestType testType, - TestListCategoryId testCategoryId) : base(runId, testId, executionId, parentExecutionId, resultName, computerName, outcome, testType, testCategoryId) { } + TestListCategoryId testCategoryId, + TrxFileHelper trxFileHelper) : base(runId, testId, executionId, parentExecutionId, resultName, computerName, outcome, testType, testCategoryId, trxFileHelper) { } /// /// Gets the inner results. diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestRunConfiguration.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestRunConfiguration.cs index e4b99888c1..4d4d6ab430 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestRunConfiguration.cs +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestRunConfiguration.cs @@ -20,6 +20,7 @@ internal class TestRunConfiguration : IXmlTestStore, IXmlTestStoreCustom #region Fields private TestRunConfigurationId id; + private readonly TrxFileHelper trxFileHelper; [StoreXmlSimpleField(DefaultValue = "")] private string name; @@ -34,14 +35,17 @@ internal class TestRunConfiguration : IXmlTestStore, IXmlTestStoreCustom /// /// The name of Run Configuration. /// - public TestRunConfiguration(string name) + /// + /// InternalFileHelper instance to use in file operations. + /// + internal TestRunConfiguration(string name, TrxFileHelper trxFileHelper) { EqtAssert.ParameterNotNull(name, "name"); this.name = name; - this.runDeploymentRoot = string.Empty; this.id = new TestRunConfigurationId(); + this.trxFileHelper = trxFileHelper; } #region IXmlTestStoreCustom Members @@ -141,7 +145,7 @@ public void Save(XmlElement element, XmlTestStoreParameters parameters) helper.SaveSimpleField( element, "Deployment/@runDeploymentRoot", - FileHelper.MakePathRelative(this.runDeploymentRoot, Path.GetDirectoryName(this.runDeploymentRoot)), + trxFileHelper.MakePathRelative(this.runDeploymentRoot, Path.GetDirectoryName(this.runDeploymentRoot)), string.Empty); } else diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/UnitTestResult.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/UnitTestResult.cs index 66c815765e..d37352497c 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/UnitTestResult.cs +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/UnitTestResult.cs @@ -4,6 +4,7 @@ namespace Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel { using System; + using Microsoft.TestPlatform.Extensions.TrxLogger.Utility; /// /// Class for unit test result. @@ -19,6 +20,8 @@ public UnitTestResult( string computerName, TestOutcome outcome, TestType testType, - TestListCategoryId testCategoryId) : base(runId, testId, executionId, parentExecutionId, resultName, computerName, outcome, testType, testCategoryId) { } + TestListCategoryId testCategoryId, + TrxFileHelper trxFileHelper + ) : base(runId, testId, executionId, parentExecutionId, resultName, computerName, outcome, testType, testCategoryId, trxFileHelper) { } } } diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/UriDataAttachment.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/UriDataAttachment.cs index e61184b7c7..ee5d05bb29 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/UriDataAttachment.cs +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/UriDataAttachment.cs @@ -17,6 +17,7 @@ namespace Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel /// internal class UriDataAttachment : IDataAttachment, IXmlTestStore { + private readonly TrxFileHelper trxFileHelper; #region Private fields /// @@ -36,11 +37,14 @@ internal class UriDataAttachment : IDataAttachment, IXmlTestStore /// /// Short description for the attachment /// The URI pointing to the resource + /// InternalFileHelper class instance to use in file operations. /// 'name' is null or empty /// 'uri' is null - public UriDataAttachment(string description, Uri uri) + public UriDataAttachment(string description, Uri uri, TrxFileHelper trxFileHelper) { - this.Initialize(description, uri); + this.trxFileHelper = trxFileHelper; + + Initialize(description, uri); } #region IDataAttachment Members @@ -121,10 +125,10 @@ internal UriDataAttachment Clone(string baseDirectory, bool useAbsoluteUri) } else { - uriToUse = new Uri(FileHelper.MakePathRelative(this.uri.OriginalString, baseDirectory), UriKind.Relative); + uriToUse = new Uri(trxFileHelper.MakePathRelative(this.uri.OriginalString, baseDirectory), UriKind.Relative); } - return new UriDataAttachment(this.description, uriToUse); + return new UriDataAttachment(this.description, uriToUse, trxFileHelper); } // The URI in this instance is already how we want it, and since this class is immutable, no need to clone diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/TrxResource.Designer.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/TrxResource.Designer.cs index 2bb4a683ae..21303cc06e 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/TrxResource.Designer.cs +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/TrxResource.Designer.cs @@ -87,6 +87,15 @@ internal static string Common_CannotGetNextIterationName { } } + /// + /// Looks up a localized string similar to Cannot get find an available filename for {0} using timestamp format '{2}' at {1}.. + /// + internal static string Common_CannotGetNextTimestampFileName { + get { + return ResourceManager.GetString("Common_CannotGetNextTimestampFileName", resourceCulture); + } + } + /// /// Looks up a localized string similar to deployment item '{0}'. /// diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/TrxResource.resx b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/TrxResource.resx index b50850a64f..b7bd4433b2 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/TrxResource.resx +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/TrxResource.resx @@ -214,4 +214,7 @@ Error Details: {1}:{2} The parameters LogFileName and LogFilePrefix cannot be used together. + + Cannot get find an available filename for {0} using timestamp format '{2}' at {1}. + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.cs.xlf b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.cs.xlf index c11126e9c5..47705beeaa 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.cs.xlf +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.cs.xlf @@ -303,6 +303,11 @@ Fehlerdetails: {1}: {2} Parametry LogFileName a LogFilePrefix nejde použít společně. + + Cannot get find an available filename for {0} using timestamp format '{2}' at {1}. + Cannot get find an available filename for {0} using timestamp format '{2}' at {1}. + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.de.xlf b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.de.xlf index d85ea85ca8..a3ff187953 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.de.xlf +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.de.xlf @@ -303,6 +303,11 @@ Fehlerdetails: {1}: {2} Die Parameter "LogFileName" und "LogFilePrefix" können nicht zusammen verwendet werden. + + Cannot get find an available filename for {0} using timestamp format '{2}' at {1}. + Cannot get find an available filename for {0} using timestamp format '{2}' at {1}. + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.es.xlf b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.es.xlf index 8866a8786f..bd533f167c 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.es.xlf +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.es.xlf @@ -303,6 +303,11 @@ Fehlerdetails: {1}: {2} Los parámetros LogFileName y LogFilePrefix no se pueden usar juntos. + + Cannot get find an available filename for {0} using timestamp format '{2}' at {1}. + Cannot get find an available filename for {0} using timestamp format '{2}' at {1}. + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.fr.xlf b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.fr.xlf index 7dbdabd118..76c95a72d4 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.fr.xlf +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.fr.xlf @@ -303,6 +303,11 @@ Fehlerdetails: {1}: {2} Les paramètres LogFileName et LogFilePrefix ne peuvent pas être utilisés ensemble. + + Cannot get find an available filename for {0} using timestamp format '{2}' at {1}. + Cannot get find an available filename for {0} using timestamp format '{2}' at {1}. + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.it.xlf b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.it.xlf index bd04a954b3..754df4bd28 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.it.xlf +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.it.xlf @@ -303,6 +303,11 @@ Fehlerdetails: {1}: {2} Non è possibile usare insieme i parametri LogFileName e LogFilePrefix. + + Cannot get find an available filename for {0} using timestamp format '{2}' at {1}. + Cannot get find an available filename for {0} using timestamp format '{2}' at {1}. + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.ja.xlf b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.ja.xlf index f909a48a99..28c923da3a 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.ja.xlf +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.ja.xlf @@ -303,6 +303,11 @@ Fehlerdetails: {1}: {2} パラメーター LogFileName と LogFilePrefix を同時に使用することはできません。 + + Cannot get find an available filename for {0} using timestamp format '{2}' at {1}. + Cannot get find an available filename for {0} using timestamp format '{2}' at {1}. + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.ko.xlf b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.ko.xlf index 01656272f0..ef3f078165 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.ko.xlf +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.ko.xlf @@ -303,6 +303,11 @@ Fehlerdetails: {1}: {2} LogFileName 및 LogFilePrefix 매개 변수는 함께 사용할 수 없습니다. + + Cannot get find an available filename for {0} using timestamp format '{2}' at {1}. + Cannot get find an available filename for {0} using timestamp format '{2}' at {1}. + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.pl.xlf b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.pl.xlf index c511ef3305..bdbedc31ad 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.pl.xlf +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.pl.xlf @@ -303,6 +303,11 @@ Fehlerdetails: {1}: {2} Parametry LogFileName i LogFilePrefix nie mogą być używane razem. + + Cannot get find an available filename for {0} using timestamp format '{2}' at {1}. + Cannot get find an available filename for {0} using timestamp format '{2}' at {1}. + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.pt-BR.xlf b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.pt-BR.xlf index 2820671aa9..dd2c12574f 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.pt-BR.xlf +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.pt-BR.xlf @@ -303,6 +303,11 @@ Fehlerdetails: {1}: {2} Os parâmetros LogFileName e LogFilePrefix não podem ser usados juntos. + + Cannot get find an available filename for {0} using timestamp format '{2}' at {1}. + Cannot get find an available filename for {0} using timestamp format '{2}' at {1}. + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.ru.xlf b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.ru.xlf index 819299bc6c..eafbe35699 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.ru.xlf +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.ru.xlf @@ -303,6 +303,11 @@ Fehlerdetails: {1}: {2} Параметры LogFileName и LogFilePrefix недопустимо использовать вместе. + + Cannot get find an available filename for {0} using timestamp format '{2}' at {1}. + Cannot get find an available filename for {0} using timestamp format '{2}' at {1}. + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.tr.xlf b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.tr.xlf index e545bbc9e5..21c1b93cd0 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.tr.xlf +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.tr.xlf @@ -303,6 +303,11 @@ Fehlerdetails: {1}: {2} LogFileName ve LogFilePrefix parametreleri birlikte kullanılamaz. + + Cannot get find an available filename for {0} using timestamp format '{2}' at {1}. + "{1}" altında "{2}" biçimlendirmesi ve "{0}" ismi kullanılarak müsait bir dosya adı bulunamadı. + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.xlf b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.xlf index 31f1cbdf24..7268b9dbc7 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.xlf +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.xlf @@ -133,6 +133,11 @@ Error Details: {1}:{2} The parameters LogFileName and LogFilePrefix cannot be given together. + + Cannot get find an available filename for {0} using timestamp format '{2}' at {1}. + Cannot get find an available filename for {0} using timestamp format '{2}' at {1}. + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.zh-Hans.xlf b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.zh-Hans.xlf index 1dcb92dbf6..4d87c2e95f 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.zh-Hans.xlf +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.zh-Hans.xlf @@ -303,6 +303,11 @@ Fehlerdetails: {1}: {2} 参数 LogFileName 和 LogFilePrefix 不能一起使用。 + + Cannot get find an available filename for {0} using timestamp format '{2}' at {1}. + Cannot get find an available filename for {0} using timestamp format '{2}' at {1}. + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.zh-Hant.xlf b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.zh-Hant.xlf index db7b564bb6..079adba278 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.zh-Hant.xlf +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Resources/xlf/TrxResource.zh-Hant.xlf @@ -303,6 +303,11 @@ Fehlerdetails: {1}: {2} 不能同時使用參數 LogFileName 和 LogFilePrefix。 + + Cannot get find an available filename for {0} using timestamp format '{2}' at {1}. + Cannot get find an available filename for {0} using timestamp format '{2}' at {1}. + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/TrxLogger.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/TrxLogger.cs index 6819883599..0c669ecfc8 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/TrxLogger.cs +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/TrxLogger.cs @@ -3,6 +3,15 @@ namespace Microsoft.VisualStudio.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 Microsoft.VisualStudio.TestPlatform.Utilities; + using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces; + using NuGet.Frameworks; + using ObjectModel.Logging; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -11,17 +20,8 @@ namespace Microsoft.VisualStudio.TestPlatform.Extensions.TrxLogger using System.Globalization; using System.IO; using System.Text; + using System.Threading; using System.Xml; - 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 Microsoft.VisualStudio.TestPlatform.Utilities; - using NuGet.Frameworks; - using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers; - using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces; - using ObjectModel.Logging; using TrxLoggerConstants = Microsoft.TestPlatform.Extensions.TrxLogger.Utility.Constants; using TrxLoggerObjectModel = Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel; using TrxLoggerResources = Microsoft.VisualStudio.TestPlatform.Extensions.TrxLogger.Resources.TrxResource; @@ -33,30 +33,30 @@ namespace Microsoft.VisualStudio.TestPlatform.Extensions.TrxLogger [ExtensionUri(TrxLoggerConstants.ExtensionUri)] public class TrxLogger : ITestLoggerWithParameters { - #region Fields - #region Constructor /// /// Initializes a new instance of the class. /// - public TrxLogger(): - this (new Utilities.Helpers.FileHelper()) - { - } + public TrxLogger() : this(new Utilities.Helpers.FileHelper()) { } /// /// Initializes a new instance of the class. /// Constructor with Dependency injection. Used for unit testing. /// /// The file helper interface. - protected TrxLogger(IFileHelper fileHelper) + protected TrxLogger(IFileHelper fileHelper) : this(new Utilities.Helpers.FileHelper(), new TrxFileHelper()) { } + + internal TrxLogger(IFileHelper fileHelper, TrxFileHelper trxFileHelper) { - this.converter = new Converter(fileHelper); + this.converter = new Converter(fileHelper, trxFileHelper); + this.trxFileHelper = trxFileHelper; } #endregion + #region Fields + /// /// Cache the TRX file path /// @@ -74,6 +74,8 @@ protected TrxLogger(IFileHelper fileHelper) private ConcurrentDictionary innerResults; private ConcurrentDictionary innerTestEntries; + private readonly TrxFileHelper trxFileHelper; + /// /// Specifies the run level "out" messages /// @@ -389,8 +391,8 @@ internal void TestRunCompleteHandler(object sender, TestRunCompleteEventArgs e) helper.SaveObject(runSummary, rootElement, "ResultSummary", parameters); - //Save results to Trx file - this.DeriveTrxFilePath(); + + this.ReserveTrxFilePath(); this.PopulateTrxFile(this.trxFilePath, rootElement); } @@ -407,21 +409,7 @@ internal virtual void PopulateTrxFile(string trxFileName, XmlElement rootElement { try { - var trxFileDirPath = Path.GetDirectoryName(trxFilePath); - if (Directory.Exists(trxFileDirPath) == false) - { - Directory.CreateDirectory(trxFileDirPath); - } - - if (File.Exists(trxFilePath)) - { - var overwriteWarningMsg = string.Format(CultureInfo.CurrentCulture, - TrxLoggerResources.TrxLoggerResultsFileOverwriteWarning, trxFileName); - ConsoleOutput.Instance.Warning(false, overwriteWarningMsg); - EqtTrace.Warning(overwriteWarningMsg); - } - - using (var fs = File.Open(trxFileName, FileMode.Create)) + using (var fs = File.Open(trxFileName, FileMode.Truncate)) { using (XmlWriter writer = XmlWriter.Create(fs, new XmlWriterSettings { NewLineHandling = NewLineHandling.Entitize, Indent = true })) { @@ -446,7 +434,7 @@ private void InitializeInternal() this.results = new ConcurrentDictionary(); this.innerResults = new ConcurrentDictionary(); this.testElements = new ConcurrentDictionary(); - this.entries = new ConcurrentDictionary(); + this.entries = new ConcurrentDictionary(); this.innerTestEntries = new ConcurrentDictionary(); this.runLevelErrorsAndWarnings = new List(); this.testRun = null; @@ -480,47 +468,79 @@ private void HandleSkippedTest(ObjectModel.TestResult rsTestResult) this.AddRunLevelInformationalMessage(message); } - private void DeriveTrxFilePath() + private void ReserveTrxFilePath() { - var isLogFilePrefixParameterExists = this.parametersDictionary.TryGetValue(TrxLoggerConstants.LogFilePrefixKey, out string logFilePrefixValue); - var isLogFileNameParameterExists = this.parametersDictionary.TryGetValue(TrxLoggerConstants.LogFileNameKey, out string logFileNameValue); - - if (isLogFilePrefixParameterExists) + for (short retries = 0; retries != short.MaxValue; retries++) { - if (!string.IsNullOrWhiteSpace(logFilePrefixValue)) + var filePath = AcquireTrxFileNamePath(out var shouldOverwrite); + + if (shouldOverwrite && File.Exists(filePath)) { - var framework = this.parametersDictionary[DefaultLoggerParameterNames.TargetFramework]; - if (framework != null) + var overwriteWarningMsg = string.Format(CultureInfo.CurrentCulture, TrxLoggerResources.TrxLoggerResultsFileOverwriteWarning, filePath); + ConsoleOutput.Instance.Warning(false, overwriteWarningMsg); + EqtTrace.Warning(overwriteWarningMsg); + } + else + { + try { - framework = NuGetFramework.Parse(framework).GetShortFolderName(); - logFilePrefixValue = logFilePrefixValue + "_" + framework; + using (var fs = File.Open(filePath, FileMode.CreateNew)) { } + } + catch (IOException) + { + // File already exists, try again! + continue; } - - logFilePrefixValue = logFilePrefixValue + DateTime.Now.ToString("_yyyyMMddHHmmss", DateTimeFormatInfo.InvariantInfo) + this.trxFileExtension; - this.trxFilePath = Path.Combine(this.testResultsDirPath, logFilePrefixValue); - return; } + + trxFilePath = filePath; + return; } + } - else if (isLogFileNameParameterExists) + private string AcquireTrxFileNamePath(out bool shouldOverwrite) + { + shouldOverwrite = false; + var isLogFileNameParameterExists = parametersDictionary.TryGetValue(TrxLoggerConstants.LogFileNameKey, out string logFileNameValue) && !string.IsNullOrWhiteSpace(logFileNameValue); + var isLogFilePrefixParameterExists = parametersDictionary.TryGetValue(TrxLoggerConstants.LogFilePrefixKey, out string logFilePrefixValue) && !string.IsNullOrWhiteSpace(logFilePrefixValue); + + string filePath = null; + if (isLogFilePrefixParameterExists) { - if (!string.IsNullOrWhiteSpace(logFileNameValue)) + if (parametersDictionary.TryGetValue(DefaultLoggerParameterNames.TargetFramework, out var framework) && framework != null) { - this.trxFilePath = Path.Combine(this.testResultsDirPath, logFileNameValue); - return; + framework = NuGetFramework.Parse(framework).GetShortFolderName(); + logFilePrefixValue = logFilePrefixValue + "_" + framework; } + + filePath = trxFileHelper.GetNextTimestampFileName(this.testResultsDirPath, logFilePrefixValue + this.trxFileExtension, "_yyyyMMddHHmmss"); + } + + else if (isLogFileNameParameterExists) + { + filePath = Path.Combine(this.testResultsDirPath, logFileNameValue); + shouldOverwrite = true; + } + + filePath = filePath ?? this.SetDefaultTrxFilePath(); + + var trxFileDirPath = Path.GetDirectoryName(filePath); + if (Directory.Exists(trxFileDirPath) == false) + { + Directory.CreateDirectory(trxFileDirPath); } - this.SetDefaultTrxFilePath(); + return filePath; } /// - /// Sets auto generated Trx file name under test results directory. + /// Returns an auto generated Trx file name under test results directory. /// - private void SetDefaultTrxFilePath() + private string SetDefaultTrxFilePath() { var defaultTrxFileName = this.testRun.RunConfiguration.RunDeploymentRootDirectory + ".trx"; - this.trxFilePath = Microsoft.TestPlatform.Extensions.TrxLogger.Utility.FileHelper.GetNextIterationFileName(this.testResultsDirPath, defaultTrxFileName, false); + + return trxFileHelper.GetNextIterationFileName(this.testResultsDirPath, defaultTrxFileName, false); } /// @@ -542,8 +562,8 @@ private void CreateTestRun() this.testRun.Started = this.testRunStartTime; // Save default test settings - string runDeploymentRoot = Microsoft.TestPlatform.Extensions.TrxLogger.Utility.FileHelper.ReplaceInvalidFileNameChars(this.testRun.Name); - TestRunConfiguration testrunConfig = new TestRunConfiguration("default"); + string runDeploymentRoot = trxFileHelper.ReplaceInvalidFileNameChars(this.testRun.Name); + TestRunConfiguration testrunConfig = new TestRunConfiguration("default", trxFileHelper); testrunConfig.RunDeploymentRootDirectory = runDeploymentRoot; this.testRun.RunConfiguration = testrunConfig; } diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/Converter.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/Converter.cs index 1b4eacbb5a..d9f479ff68 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/Converter.cs +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/Converter.cs @@ -23,13 +23,15 @@ namespace Microsoft.TestPlatform.Extensions.TrxLogger.Utility /// internal class Converter { + private readonly TrxFileHelper trxFileHelper; private IFileHelper fileHelper; /// /// Initializes a new instance of the class. /// - public Converter(IFileHelper fileHelper) + public Converter(IFileHelper fileHelper, TrxFileHelper trxFileHelper) { + this.trxFileHelper = trxFileHelper; this.fileHelper = fileHelper; } @@ -68,6 +70,8 @@ public ITestElement ToTestElement( return testElement; } + + /// /// Converts the rockSteady result to unit test result /// @@ -473,7 +477,7 @@ private CollectorDataEntry ToCollectorEntry(ObjectModel.AttachmentSet attachment 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); + string targetFileName = trxFileHelper.GetNextIterationFileName(targetDirectory, Path.GetFileName(sourceFile), false); try { @@ -483,7 +487,7 @@ private CollectorDataEntry ToCollectorEntry(ObjectModel.AttachmentSet attachment // (Trx viewer automatically adds In\ to the collected file. string fileName = Path.Combine(Environment.MachineName, Path.GetFileName(targetFileName)); Uri sourceFileUri = new Uri(fileName, UriKind.Relative); - TrxObjectModel.UriDataAttachment dataAttachment = new TrxObjectModel.UriDataAttachment(uriDataAttachment.Description, sourceFileUri); + TrxObjectModel.UriDataAttachment dataAttachment = new TrxObjectModel.UriDataAttachment(uriDataAttachment.Description, sourceFileUri, trxFileHelper); uriDataAttachments.Add(dataAttachment); } @@ -535,7 +539,7 @@ private IList ToResultFiles(ObjectModel.AttachmentSet attachmentSet, Gui 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); + string targetFileName = trxFileHelper.GetNextIterationFileName(testResultDirectory, Path.GetFileName(sourceFile), false); try { @@ -719,8 +723,8 @@ private TrxObjectModel.TestResult CreateTestResult( TestListCategoryId testCategoryId) { return testType.Equals(Constants.OrderedTestType) ? - new TestResultAggregation(runId, testId, executionId, parentExecutionId, resultName, Environment.MachineName, outcome, testType, testCategoryId) : - new UnitTestResult(runId, testId, executionId, parentExecutionId, resultName, Environment.MachineName, outcome, testType, testCategoryId); + new TestResultAggregation(runId, testId, executionId, parentExecutionId, resultName, Environment.MachineName, outcome, testType, testCategoryId, trxFileHelper) : + new UnitTestResult(runId, testId, executionId, parentExecutionId, resultName, Environment.MachineName, outcome, testType, testCategoryId, trxFileHelper); } } diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/FileHelper.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/TrxFileHelper.cs similarity index 85% rename from src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/FileHelper.cs rename to src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/TrxFileHelper.cs index d103506a7d..6461def174 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/FileHelper.cs +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/TrxFileHelper.cs @@ -11,25 +11,26 @@ namespace Microsoft.TestPlatform.Extensions.TrxLogger.Utility using System.IO; using System.Text; using System.Text.RegularExpressions; - using TrxLoggerResources = Microsoft.VisualStudio.TestPlatform.Extensions.TrxLogger.Resources.TrxResource; /// /// Helper function to deal with file name. /// - internal static class FileHelper + internal class TrxFileHelper + { 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\$)(\..*)?)$"); + private readonly Func TimeProvider; #region Constructors [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification = "Reviewed. Suppression is OK here.")] // Have to init InvalidFileNameChars dynamically. - static FileHelper() + static TrxFileHelper() { // Create a hash table of invalid chars. On Windows, this should match the contents of System.IO.Path.GetInvalidFileNameChars. // See https://github.com/dotnet/coreclr/blob/8e99cd8031b2f568ea69116e7cf96d55e32cb7f5/src/mscorlib/shared/System/IO/Path.Windows.cs#L12-L19 @@ -63,6 +64,13 @@ static FileHelper() AdditionalInvalidFileNameChars.Add(' ', null); } + public TrxFileHelper() : this(() => DateTime.Now) { } + + public TrxFileHelper(Func timeProvider) + { + TimeProvider = timeProvider ?? (() => DateTime.Now); + } + #endregion /// @@ -70,7 +78,7 @@ static FileHelper() /// /// the name of the file /// Replaced string. - public static string ReplaceInvalidFileNameChars(string fileName) + public string ReplaceInvalidFileNameChars(string fileName) { EqtAssert.StringNotNullOrEmpty(fileName, "fileName"); @@ -126,14 +134,53 @@ public static string ReplaceInvalidFileNameChars(string fileName) /// /// The . /// - public static string GetNextIterationFileName(string parentDirectoryName, string originalFileName, bool checkMatchingDirectory) + public 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) + /// + /// Constructs and returns first available timestamped file name. + /// This does not checks for the file permissions. + /// + /// Directory to try timestamped file names in. + /// Filename (with extension) of the desired file. Timestamp will be added just before extension. + /// Timestamp format to be passed into DateTime.ToString method. + /// First available filename with the format of `FileName{Timestamp}.ext`. + /// + /// GetNextTimestampFileName("c:\data", "log.txt", "_yyyyMMddHHmmss") will return "c:\data\log_20200801185521.txt", if available. + /// + public string GetNextTimestampFileName(string directoryName, string fileName, string timestampFormat) + { + EqtAssert.StringNotNullOrEmpty(directoryName, "parentDirectoryName"); + EqtAssert.StringNotNullOrEmpty(fileName, "fileName"); + EqtAssert.StringNotNullOrEmpty(timestampFormat, "timestampFormat"); + + ushort iteration = 0; + var iterationStamp = TimeProvider(); + var fileNamePrefix = Path.GetFileNameWithoutExtension(fileName); + var extension = Path.GetExtension(fileName); + do + { + var tryMe = fileNamePrefix + iterationStamp.ToString(timestampFormat, DateTimeFormatInfo.InvariantInfo) + extension; + + string tryMePath = Path.Combine(directoryName, tryMe); + if (!File.Exists(tryMePath)) + { + return tryMePath; + } + + iterationStamp = iterationStamp.AddSeconds(1); + ++iteration; + } + while (iteration != ushort.MaxValue); + + throw new Exception(string.Format(CultureInfo.CurrentCulture, TrxLoggerResources.Common_CannotGetNextTimestampFileName, fileName, directoryName, timestampFormat)); + } + + public string MakePathRelative(string path, string basePath) { EqtAssert.StringNotNullOrEmpty(path, "path"); diff --git a/src/Microsoft.TestPlatform.ObjectModel/Adapter/TestPlatformFormatException.cs b/src/Microsoft.TestPlatform.ObjectModel/Adapter/TestPlatformFormatException.cs index f92930757e..e704e11842 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/Adapter/TestPlatformFormatException.cs +++ b/src/Microsoft.TestPlatform.ObjectModel/Adapter/TestPlatformFormatException.cs @@ -10,7 +10,7 @@ namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter /// Exception thrown on parsing error in user provided filter expression. /// This can happen when filter has invalid format or has unsupported properties. /// -#if NET451 +#if NETFRAMEWORK [Serializable] #endif public class TestPlatformFormatException : Exception @@ -55,7 +55,7 @@ public TestPlatformFormatException(string message, Exception innerException) { } -#if NET451 +#if NETFRAMEWORK /// /// Serialization constructor. /// @@ -79,7 +79,7 @@ public string FilterValue private set; } -#if NET451 +#if NETFRAMEWORK /// /// Serialization helper. /// diff --git a/src/Microsoft.TestPlatform.ObjectModel/Adapter/TestsCanceledException.cs b/src/Microsoft.TestPlatform.ObjectModel/Adapter/TestsCanceledException.cs index 090df725fe..5b09bce3ec 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/Adapter/TestsCanceledException.cs +++ b/src/Microsoft.TestPlatform.ObjectModel/Adapter/TestsCanceledException.cs @@ -5,7 +5,7 @@ namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter { using System; -#if NET451 +#if NETFRAMEWORK using System.Runtime.Serialization; #endif @@ -13,7 +13,7 @@ namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter /// Exception thrown by the framework when an executor attempts to send /// test result to the framework when the test is canceled. /// -#if NET451 +#if NETFRAMEWORK [Serializable] #endif public class TestCanceledException : Exception @@ -46,7 +46,7 @@ public TestCanceledException(string message, Exception innerException) { } -#if NET451 +#if NETFRAMEWORK /// /// Serialization constructor. /// diff --git a/src/Microsoft.TestPlatform.ObjectModel/Constants.cs b/src/Microsoft.TestPlatform.ObjectModel/Constants.cs index d3aee31966..4ddd017778 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/Constants.cs +++ b/src/Microsoft.TestPlatform.ObjectModel/Constants.cs @@ -194,7 +194,7 @@ public static class Constants /// The default execution thread apartment state. /// [CLSCompliant(false)] -#if NET451 +#if NETFRAMEWORK // Keeping default STA thread for desktop tests for UI/Functional test scenarios public static readonly PlatformApartmentState DefaultExecutionThreadApartmentState = PlatformApartmentState.STA; #else diff --git a/src/Microsoft.TestPlatform.ObjectModel/DataCollector/DataCollectionEnvironmentContext.cs b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/DataCollectionEnvironmentContext.cs index 386a75647f..612b8b09cb 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/DataCollector/DataCollectionEnvironmentContext.cs +++ b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/DataCollectionEnvironmentContext.cs @@ -3,14 +3,14 @@ namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection { -#if NET451 +#if NETFRAMEWORK using System; #endif /// /// Encapsulates the context of the environment a data collector is being hosted in. /// -#if NET451 +#if NETFRAMEWORK [Serializable] #endif public sealed class DataCollectionEnvironmentContext diff --git a/src/Microsoft.TestPlatform.ObjectModel/DataCollector/Events/DataCollectionEventArgs.cs b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/Events/DataCollectionEventArgs.cs index 0276ec003c..37596ceffc 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/DataCollector/Events/DataCollectionEventArgs.cs +++ b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/Events/DataCollectionEventArgs.cs @@ -9,7 +9,7 @@ namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection /// /// Base class for all execution event arguments /// -#if NET451 +#if NETFRAMEWORK [Serializable] #endif public abstract class DataCollectionEventArgs : EventArgs diff --git a/src/Microsoft.TestPlatform.ObjectModel/ExceptionConverter.cs b/src/Microsoft.TestPlatform.ObjectModel/ExceptionConverter.cs index cf59e85610..638ad2ad6b 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/ExceptionConverter.cs +++ b/src/Microsoft.TestPlatform.ObjectModel/ExceptionConverter.cs @@ -9,7 +9,7 @@ namespace Microsoft.VisualStudio.TestPlatform.ObjectModel /// /// Base exception for all Rocksteady service exceptions /// -#if NET451 +#if NETFRAMEWORK [Serializable] #endif [SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors")] @@ -96,7 +96,7 @@ private static Exception ConvertException(String exceptionType, String message, } #endif -#if NET451 +#if NETFRAMEWORK [Serializable] #endif [SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors")] diff --git a/src/Microsoft.TestPlatform.ObjectModel/Framework.cs b/src/Microsoft.TestPlatform.ObjectModel/Framework.cs index a84d074e58..005df6147e 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/Framework.cs +++ b/src/Microsoft.TestPlatform.ObjectModel/Framework.cs @@ -11,7 +11,7 @@ namespace Microsoft.VisualStudio.TestPlatform.ObjectModel /// public class Framework { -#if NET451 +#if NETFRAMEWORK private static readonly Framework Default = Framework.FromString(".NETFramework,Version=v4.0"); #else private static readonly Framework Default = Framework.FromString(".NETCoreApp,Version=v1.0"); diff --git a/src/Microsoft.TestPlatform.ObjectModel/RegistryFreeActivationContext.cs b/src/Microsoft.TestPlatform.ObjectModel/RegistryFreeActivationContext.cs index a402c7ccaa..4c9600f246 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/RegistryFreeActivationContext.cs +++ b/src/Microsoft.TestPlatform.ObjectModel/RegistryFreeActivationContext.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -#if NET451 +#if NETFRAMEWORK namespace Microsoft.VisualStudio.TestPlatform.ObjectModel { diff --git a/src/Microsoft.TestPlatform.ObjectModel/RunSettings/SettingsException.cs b/src/Microsoft.TestPlatform.ObjectModel/RunSettings/SettingsException.cs index 539d5de954..4bc746f0c2 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/RunSettings/SettingsException.cs +++ b/src/Microsoft.TestPlatform.ObjectModel/RunSettings/SettingsException.cs @@ -4,7 +4,7 @@ namespace Microsoft.VisualStudio.TestPlatform.ObjectModel { using System; -#if NET451 +#if NETFRAMEWORK using System.Runtime.Serialization; #endif @@ -13,7 +13,7 @@ namespace Microsoft.VisualStudio.TestPlatform.ObjectModel /// Exception thrown by Run Settings when an error with a settings provider /// is encountered. /// -#if NET451 +#if NETFRAMEWORK [Serializable] #endif public class SettingsException : Exception @@ -46,7 +46,7 @@ public SettingsException(string message, Exception innerException) { } -#if NET451 +#if NETFRAMEWORK /// /// Serialization constructor. /// diff --git a/src/Microsoft.TestPlatform.ObjectModel/Trait.cs b/src/Microsoft.TestPlatform.ObjectModel/Trait.cs index 3c2ca54138..2cca9be161 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/Trait.cs +++ b/src/Microsoft.TestPlatform.ObjectModel/Trait.cs @@ -3,7 +3,7 @@ namespace Microsoft.VisualStudio.TestPlatform.ObjectModel { -#if NET451 +#if NETFRAMEWORK using System; #endif using System.Collections.Generic; @@ -13,7 +13,7 @@ namespace Microsoft.VisualStudio.TestPlatform.ObjectModel /// Class that holds Trait. /// A traits is Name, Value pair. /// -#if NET451 +#if NETFRAMEWORK [Serializable] #endif [DataContract] diff --git a/src/Microsoft.TestPlatform.ObjectModel/TraitCollection.cs b/src/Microsoft.TestPlatform.ObjectModel/TraitCollection.cs index f172b3d72f..3c594f7813 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/TraitCollection.cs +++ b/src/Microsoft.TestPlatform.ObjectModel/TraitCollection.cs @@ -11,7 +11,7 @@ namespace Microsoft.VisualStudio.TestPlatform.ObjectModel /// /// Class that holds collection of traits /// -#if NET451 +#if NETFRAMEWORK [Serializable] #endif public class TraitCollection : IEnumerable @@ -33,7 +33,7 @@ public class TraitCollection : IEnumerable #pragma warning restore 618 typeof(TestObject)); -#if NET451 +#if NETFRAMEWORK [NonSerialized] #endif private readonly TestObject testObject; diff --git a/src/Microsoft.TestPlatform.ObjectModel/Utilities/AssemblyHelper.cs b/src/Microsoft.TestPlatform.ObjectModel/Utilities/AssemblyHelper.cs index 36906b8cd9..3c1616c217 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/Utilities/AssemblyHelper.cs +++ b/src/Microsoft.TestPlatform.ObjectModel/Utilities/AssemblyHelper.cs @@ -3,7 +3,7 @@ namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities { -#if NET451 +#if NETFRAMEWORK using System; using System.Collections.Generic; using System.Diagnostics; diff --git a/src/Microsoft.TestPlatform.ObjectModel/Utilities/AssemblyLoadWorker.cs b/src/Microsoft.TestPlatform.ObjectModel/Utilities/AssemblyLoadWorker.cs index 11be2f2f69..275b880db3 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/Utilities/AssemblyLoadWorker.cs +++ b/src/Microsoft.TestPlatform.ObjectModel/Utilities/AssemblyLoadWorker.cs @@ -3,7 +3,7 @@ namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities { -#if NET451 +#if NETFRAMEWORK using System; using System.Diagnostics; using System.IO; diff --git a/src/Microsoft.TestPlatform.ObjectModel/Utilities/SuspendCodeCoverage.cs b/src/Microsoft.TestPlatform.ObjectModel/Utilities/SuspendCodeCoverage.cs index 9b54a56d80..bc1ea0354f 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/Utilities/SuspendCodeCoverage.cs +++ b/src/Microsoft.TestPlatform.ObjectModel/Utilities/SuspendCodeCoverage.cs @@ -3,7 +3,7 @@ namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities { -#if NET451 +#if NETFRAMEWORK using System; /// diff --git a/src/Microsoft.TestPlatform.PlatformAbstractions/common/System/ProcessHelper.cs b/src/Microsoft.TestPlatform.PlatformAbstractions/common/System/ProcessHelper.cs index f032383af0..f7c3adb26d 100644 --- a/src/Microsoft.TestPlatform.PlatformAbstractions/common/System/ProcessHelper.cs +++ b/src/Microsoft.TestPlatform.PlatformAbstractions/common/System/ProcessHelper.cs @@ -208,7 +208,7 @@ public string GetNativeDllDirectory() public void WaitForProcessExit(object process) { var proc = process as Process; - if (proc != null || !proc.HasExited) + if (proc != null && !proc.HasExited) { proc.WaitForExit(); } diff --git a/src/Microsoft.TestPlatform.Utilities/XmlUtilities.cs b/src/Microsoft.TestPlatform.Utilities/XmlUtilities.cs index 074a738186..ed6f6220d7 100644 --- a/src/Microsoft.TestPlatform.Utilities/XmlUtilities.cs +++ b/src/Microsoft.TestPlatform.Utilities/XmlUtilities.cs @@ -5,7 +5,7 @@ namespace Microsoft.VisualStudio.TestPlatform.Utilities { using System; using System.Diagnostics.CodeAnalysis; -#if NET451 +#if NETFRAMEWORK using System.Security; #endif using System.Xml; @@ -62,7 +62,7 @@ internal static void AppendOrModifyChild( // TODO: There isn't an equivalent API to SecurityElement.Escape in Core yet. // So trusting that the XML is always valid for now. -#if NET451 +#if NETFRAMEWORK var secureInnerXml = SecurityElement.Escape(innerXml); #else // fixing manually as we currently target to netcore 1.1 and we don't have default implementation for Escape functionality diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/ConsoleParameters.cs b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/ConsoleParameters.cs index 6595f24375..d40d888127 100644 --- a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/ConsoleParameters.cs +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/ConsoleParameters.cs @@ -4,7 +4,7 @@ namespace Microsoft.TestPlatform.VsTestConsole.TranslationLayer { using System; -#if NET451 +#if NETFRAMEWORK using System.Collections.Generic; #endif using System.Diagnostics; @@ -40,7 +40,7 @@ public ConsoleParameters(IFileHelper fileHelper) this.fileHelper = fileHelper; } -#if NET451 +#if NETFRAMEWORK /// /// TODO: Remove the #if when project is targeted to netstandard2.0 diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/VsTestConsoleProcessManager.cs b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/VsTestConsoleProcessManager.cs index e57b34641a..abeb12e72b 100644 --- a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/VsTestConsoleProcessManager.cs +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/VsTestConsoleProcessManager.cs @@ -113,7 +113,7 @@ public void StartProcess(ConsoleParameters consoleParameters) EqtTrace.Verbose("VsTestCommandLineWrapper: Process Start Info {0} {1}", info.FileName, info.Arguments); -#if NET451 +#if NETFRAMEWORK if (consoleParameters.EnvironmentVariables != null) { info.EnvironmentVariables.Clear(); diff --git a/src/testhost.x86/AppDomainEngineInvoker.cs b/src/testhost.x86/AppDomainEngineInvoker.cs index 101c0a878b..9c2213958d 100644 --- a/src/testhost.x86/AppDomainEngineInvoker.cs +++ b/src/testhost.x86/AppDomainEngineInvoker.cs @@ -3,7 +3,7 @@ namespace Microsoft.VisualStudio.TestPlatform.TestHost { -#if NET451 +#if NETFRAMEWORK using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.CoreUtilities.Tracing; diff --git a/src/testhost.x86/DefaultEngineInvoker.cs b/src/testhost.x86/DefaultEngineInvoker.cs index 8b1d2cc568..7495aa848c 100644 --- a/src/testhost.x86/DefaultEngineInvoker.cs +++ b/src/testhost.x86/DefaultEngineInvoker.cs @@ -25,7 +25,7 @@ namespace Microsoft.VisualStudio.TestPlatform.TestHost using CoreUtilitiesConstants = Microsoft.VisualStudio.TestPlatform.CoreUtilities.Constants; internal class DefaultEngineInvoker : -#if NET451 +#if NETFRAMEWORK MarshalByRefObject, #endif IEngineInvoker @@ -77,7 +77,7 @@ public void Invoke(IDictionary argsDictionary) { EqtTrace.Info("DefaultEngineInvoker.Invoke: Testhost process started with args :{0}", string.Join(",", argsDictionary)); -#if NET451 +#if NETFRAMEWORK var appConfigText = System.IO.File.ReadAllText(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile); EqtTrace.Info("DefaultEngineInvoker: Using Application Configuration: '{0}'", appConfigText); diff --git a/src/testhost.x86/Program.cs b/src/testhost.x86/Program.cs index dd97bf6d65..1f9f5a4f70 100644 --- a/src/testhost.x86/Program.cs +++ b/src/testhost.x86/Program.cs @@ -62,7 +62,7 @@ public static void Run(string[] args) private static IEngineInvoker GetEngineInvoker(IDictionary argsDictionary) { IEngineInvoker invoker = null; -#if NET451 +#if NETFRAMEWORK // If Args contains test source argument, invoker Engine in new appdomain string testSourcePath; if (argsDictionary.TryGetValue(TestSourceArgumentString, out testSourcePath) && !string.IsNullOrWhiteSpace(testSourcePath)) diff --git a/src/testhost.x86/testhost.x86.csproj b/src/testhost.x86/testhost.x86.csproj index 244737eb76..535a6e6afb 100644 --- a/src/testhost.x86/testhost.x86.csproj +++ b/src/testhost.x86/testhost.x86.csproj @@ -30,7 +30,8 @@ true - + + diff --git a/src/testhost/testhost.csproj b/src/testhost/testhost.csproj index 494fcb5ab6..c239485d24 100644 --- a/src/testhost/testhost.csproj +++ b/src/testhost/testhost.csproj @@ -32,7 +32,8 @@ true - + + diff --git a/src/vstest.console/Internal/ConsoleLogger.cs b/src/vstest.console/Internal/ConsoleLogger.cs index 846ad79c7e..a9d2371d0c 100644 --- a/src/vstest.console/Internal/ConsoleLogger.cs +++ b/src/vstest.console/Internal/ConsoleLogger.cs @@ -105,7 +105,7 @@ internal enum Verbosity /// /// Level of verbosity /// -#if NET451 +#if NETFRAMEWORK private Verbosity verbosityLevel = Verbosity.Normal; #else // Keep default verbosity for x-plat command line as minimal diff --git a/src/vstest.console/Processors/EnableBlameArgumentProcessor.cs b/src/vstest.console/Processors/EnableBlameArgumentProcessor.cs index 18fcf80ce3..b8ab87e27f 100644 --- a/src/vstest.console/Processors/EnableBlameArgumentProcessor.cs +++ b/src/vstest.console/Processors/EnableBlameArgumentProcessor.cs @@ -311,9 +311,11 @@ private string GetResultsDirectory(string settings) private bool IsDumpCollectionSupported() { var dumpCollectionSupported = - this.environment.OperatingSystem == PlatformOperatingSystem.Windows + this.environment.OperatingSystem == PlatformOperatingSystem.Unix || + this.environment.OperatingSystem == PlatformOperatingSystem.OSX || + (this.environment.OperatingSystem == PlatformOperatingSystem.Windows && this.environment.Architecture != PlatformArchitecture.ARM64 - && this.environment.Architecture != PlatformArchitecture.ARM; + && this.environment.Architecture != PlatformArchitecture.ARM); if (!dumpCollectionSupported) { @@ -329,8 +331,7 @@ private bool IsDumpCollectionSupported() /// Dump collection supported flag. private bool IsHangDumpCollectionSupported() { - var dumpCollectionSupported = - this.environment.OperatingSystem != PlatformOperatingSystem.OSX; + var dumpCollectionSupported = true; if (!dumpCollectionSupported) { diff --git a/src/vstest.console/Processors/EnableLoggerArgumentProcessor.cs b/src/vstest.console/Processors/EnableLoggerArgumentProcessor.cs index c6cd204c11..d05365dc88 100644 --- a/src/vstest.console/Processors/EnableLoggerArgumentProcessor.cs +++ b/src/vstest.console/Processors/EnableLoggerArgumentProcessor.cs @@ -99,7 +99,7 @@ internal class EnableLoggerArgumentProcessorCapabilities : BaseArgumentProcessor /// /// Gets the help content resource name. /// -#if NET451 +#if NETFRAMEWORK public override string HelpContentResourceName => CommandLineResources.EnableLoggersArgumentHelp; #else public override string HelpContentResourceName => CommandLineResources.EnableLoggerArgumentsInNetCore; diff --git a/src/vstest.console/Processors/EnvironmentArgumentProcessor.cs b/src/vstest.console/Processors/EnvironmentArgumentProcessor.cs new file mode 100644 index 0000000000..2ea77d84a7 --- /dev/null +++ b/src/vstest.console/Processors/EnvironmentArgumentProcessor.cs @@ -0,0 +1,145 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Processors +{ + using System; + using System.Diagnostics.Contracts; + using Microsoft.VisualStudio.TestPlatform.Common; + using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; + using Microsoft.VisualStudio.TestPlatform.Common.Utilities; + using Microsoft.VisualStudio.TestPlatform.Utilities; + using CommandLineResources = Microsoft.VisualStudio.TestPlatform.CommandLine.Resources.Resources; + + /// + /// Argument Executor for the "-e|--Environment|/e|/Environment" command line argument. + /// + internal class EnvironmentArgumentProcessor : IArgumentProcessor + { + #region Constants + /// + /// The short name of the command line argument that the EnvironmentArgumentProcessor handles. + /// + public const string ShortCommandName = "/e"; + + /// + /// The name of the command line argument that the EnvironmentArgumentProcessor handles. + /// + public const string CommandName = "/Environment"; + #endregion + + private Lazy metadata; + + private Lazy executor; + + public Lazy Executor + { + get + { + if (this.executor == null) + { + this.executor = new Lazy( + () => new ArgumentExecutor(CommandLineOptions.Instance, RunSettingsManager.Instance, ConsoleOutput.Instance) + ); + } + + return this.executor; + } + set + { + this.executor = value; + } + } + + public Lazy Metadata + { + get + { + if (this.metadata == null) + { + this.metadata = new Lazy(() => new ArgumentProcessorCapabilities()); + } + + return this.metadata; + } + } + + internal class ArgumentProcessorCapabilities : BaseArgumentProcessorCapabilities + { + public override string CommandName => EnvironmentArgumentProcessor.CommandName; + public override string ShortCommandName => EnvironmentArgumentProcessor.ShortCommandName; + public override bool AllowMultiple => true; + public override bool IsAction => false; + public override string HelpContentResourceName => CommandLineResources.EnvironmentArgumentHelp; + public override ArgumentProcessorPriority Priority => ArgumentProcessorPriority.Normal; + public override HelpContentPriority HelpPriority => HelpContentPriority.EnvironmentArgumentProcessorHelpPriority; + } + + internal class ArgumentExecutor : IArgumentExecutor + { + #region Fields + /// + /// Used when warning about overriden environment variables. + /// + private IOutput output; + + /// + /// Used when setting Environemnt variables. + /// + private IRunSettingsProvider runSettingsProvider; + + /// + /// Used when checking and forcing InIsolation mode. + /// + private CommandLineOptions commandLineOptions; + #endregion + + public ArgumentExecutor(CommandLineOptions commandLineOptions, IRunSettingsProvider runSettingsProvider, IOutput output) + { + this.commandLineOptions = commandLineOptions; + this.output = output; + this.runSettingsProvider = runSettingsProvider; + } + + /// + /// Set the environment variables in RunSettings.xml + /// + /// + /// Environment variable to set. + /// + public void Initialize(string argument) + { + Contract.Assert(!string.IsNullOrWhiteSpace(argument)); + Contract.Assert(this.output != null); + Contract.Assert(this.commandLineOptions != null); + Contract.Assert(!string.IsNullOrWhiteSpace(this.runSettingsProvider.ActiveRunSettings.SettingsXml)); + Contract.EndContractBlock(); + + var key = argument; + var value = string.Empty; + + if(key.Contains("=")) + { + value = key.Substring(key.IndexOf("=") + 1); + key = key.Substring(0, key.IndexOf("=")); + } + + var node = this.runSettingsProvider.QueryRunSettingsNode($"RunConfiguration.EnvironmentVariables.{key}"); + if(node != null) + { + output.Warning(true, CommandLineResources.EnvironmentVariableXIsOverriden, key); + } + + this.runSettingsProvider.UpdateRunSettingsNode($"RunConfiguration.EnvironmentVariables.{key}", value); + + if(!this.commandLineOptions.InIsolation) { + this.commandLineOptions.InIsolation = true; + this.runSettingsProvider.UpdateRunSettingsNode(InIsolationArgumentExecutor.RunSettingsPath, "true"); + } + } + + // Nothing to do here, the work was done in initialization. + public ArgumentProcessorResult Execute() => ArgumentProcessorResult.Success; + } + } +} diff --git a/src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs b/src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs index ed7b76ca31..b86413406e 100644 --- a/src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs +++ b/src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs @@ -243,7 +243,8 @@ public IEnumerable GetArgumentProcessorsToAlwaysExecute() new ListLoggersArgumentProcessor(), new ListSettingsProvidersArgumentProcessor(), new ListFullyQualifiedTestsArgumentProcessor(), - new ListTestsTargetPathArgumentProcessor() + new ListTestsTargetPathArgumentProcessor(), + new EnvironmentArgumentProcessor() }; /// diff --git a/src/vstest.console/Processors/Utilities/HelpContentPriority.cs b/src/vstest.console/Processors/Utilities/HelpContentPriority.cs index ee9d1435c3..ecd57d18de 100644 --- a/src/vstest.console/Processors/Utilities/HelpContentPriority.cs +++ b/src/vstest.console/Processors/Utilities/HelpContentPriority.cs @@ -67,6 +67,11 @@ internal enum HelpContentPriority /// PlatformArgumentProcessorHelpPriority, + /// + /// EnvironmentArgumentProcessor Help + /// + EnvironmentArgumentProcessorHelpPriority, + /// /// RunSettingsArgumentProcessor Help /// diff --git a/src/vstest.console/Program.cs b/src/vstest.console/Program.cs index 4363137a53..abe4ca5eed 100644 --- a/src/vstest.console/Program.cs +++ b/src/vstest.console/Program.cs @@ -19,6 +19,9 @@ public static class Program /// 0 if everything was successful and 1 otherwise. public static int Main(string[] args) { + Environment.SetEnvironmentVariable("COMPlus_DbgEnableElfDumpOnMacOS", "1"); + Environment.SetEnvironmentVariable("COMPlus_DbgEnableMiniDump", "1"); + var debugEnabled = Environment.GetEnvironmentVariable("VSTEST_RUNNER_DEBUG"); if (!string.IsNullOrEmpty(debugEnabled) && debugEnabled.Equals("1", StringComparison.Ordinal)) { diff --git a/src/vstest.console/Resources/Resources.Designer.cs b/src/vstest.console/Resources/Resources.Designer.cs index d4db636f10..840adf11b8 100644 --- a/src/vstest.console/Resources/Resources.Designer.cs +++ b/src/vstest.console/Resources/Resources.Designer.cs @@ -39,7 +39,7 @@ internal Resources() { internal 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.Resources", typeof(Resources).Assembly); + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.VisualStudio.TestPlatform.CommandLine.Resources.Resources", typeof(Resources).Assembly); resourceMan = temp; } return resourceMan; @@ -508,6 +508,31 @@ internal static string EnableLoggersArgumentHelp { } } + /// + /// Looks up a localized string similar to -e|--Environment|/e|/Environment:<NAME>=<VALUE> + /// Sets the value of an environment variable. Creates the variable if it does not exist, overrides if it does. This will imply /InIsolation switch and force the tests to be run in an isolated process. + /// + /// This argument can be specified multiple times to provide multiple variables. + /// + /// Example: -e:VARIABLE1=VALUE1 + /// -e:ANOTHER_VARIABLE="VALUE WITH SPACES" + /// -e:ANOTHER_VARIABLE="VALUE;seperated with;semicolons". + /// + internal static string EnvironmentArgumentHelp { + get { + return ResourceManager.GetString("EnvironmentArgumentHelp", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Environment variable '{0}' was already defined, but it's overridden by -Environment argument.. + /// + internal static string EnvironmentVariableXIsOverriden { + get { + return ResourceManager.GetString("EnvironmentVariableXIsOverriden", resourceCulture); + } + } + /// /// Looks up a localized string similar to Error Message:. /// diff --git a/src/vstest.console/Resources/Resources.resx b/src/vstest.console/Resources/Resources.resx index 1e4808fbf4..2c0bb837a1 100644 --- a/src/vstest.console/Resources/Resources.resx +++ b/src/vstest.console/Resources/Resources.resx @@ -750,4 +750,17 @@ Collecting hang dumps by option CollectDump with TestTimeout for Blame is not supported for this platform. + + -e|--Environment|/e|/Environment:<NAME>=<VALUE> + Sets the value of an environment variable. Creates the variable if it does not exist, overrides if it does. This will imply /InIsolation switch and force the tests to be run in an isolated process. + + This argument can be specified multiple times to provide multiple variables. + + Example: -e:VARIABLE1=VALUE1 + -e:ANOTHER_VARIABLE="VALUE WITH SPACES" + -e:ANOTHER_VARIABLE="VALUE;seperated with;semicolons" + + + Environment variable '{0}' was already defined, but it's overridden by -Environment argument. + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.cs.xlf b/src/vstest.console/Resources/xlf/Resources.cs.xlf index a9c537603d..2543c3e99e 100644 --- a/src/vstest.console/Resources/xlf/Resources.cs.xlf +++ b/src/vstest.console/Resources/xlf/Resources.cs.xlf @@ -1683,6 +1683,28 @@ - {0} {1} + + -e|--Environment|/e|/Environment:<NAME>=<VALUE> + Sets the value of an environment variable. Creates the variable if it does not exist, overrides if it does. This will imply /InIsolation switch and force the tests to be run in an isolated process. + + This argument can be specified multiple times to provide multiple variables. + + Example: -e:VARIABLE1=VALUE1 + -e:ANOTHER_VARIABLE="VALUE WITH SPACES" + -e:ANOTHER_VARIABLE="VALUE;seperated with;semicolons" + -e|--Environment|/e|/Environment:<NAME>=<VALUE> + Sets the value of an environment variable. Creates the variable if one with the requested name does not exist. + + Example: -e:VARIABLE1=VALUE1 + -e:ANOTHER_VARIABLE="VALUE WITH SPACES" + -e:ANOTHER_VARIABLE="VALUE;seperated with;semi-columns" + + + + Environment variable '{0}' was already defined, but it's overridden by -Environment argument. + Environment variable '{0}' was already defined, but it's overridden by -Environment argument. + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.de.xlf b/src/vstest.console/Resources/xlf/Resources.de.xlf index 84e784589e..32f93332c0 100644 --- a/src/vstest.console/Resources/xlf/Resources.de.xlf +++ b/src/vstest.console/Resources/xlf/Resources.de.xlf @@ -1683,6 +1683,28 @@ - {0} {1} + + -e|--Environment|/e|/Environment:<NAME>=<VALUE> + Sets the value of an environment variable. Creates the variable if it does not exist, overrides if it does. This will imply /InIsolation switch and force the tests to be run in an isolated process. + + This argument can be specified multiple times to provide multiple variables. + + Example: -e:VARIABLE1=VALUE1 + -e:ANOTHER_VARIABLE="VALUE WITH SPACES" + -e:ANOTHER_VARIABLE="VALUE;seperated with;semicolons" + -e|--Environment|/e|/Environment:<NAME>=<VALUE> + Sets the value of an environment variable. Creates the variable if one with the requested name does not exist. + + Example: -e:VARIABLE1=VALUE1 + -e:ANOTHER_VARIABLE="VALUE WITH SPACES" + -e:ANOTHER_VARIABLE="VALUE;seperated with;semi-columns" + + + + Environment variable '{0}' was already defined, but it's overridden by -Environment argument. + Environment variable '{0}' was already defined, but it's overridden by -Environment argument. + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.es.xlf b/src/vstest.console/Resources/xlf/Resources.es.xlf index 8673f05e98..98aaacac5e 100644 --- a/src/vstest.console/Resources/xlf/Resources.es.xlf +++ b/src/vstest.console/Resources/xlf/Resources.es.xlf @@ -1686,6 +1686,28 @@ - {0} {1} + + -e|--Environment|/e|/Environment:<NAME>=<VALUE> + Sets the value of an environment variable. Creates the variable if it does not exist, overrides if it does. This will imply /InIsolation switch and force the tests to be run in an isolated process. + + This argument can be specified multiple times to provide multiple variables. + + Example: -e:VARIABLE1=VALUE1 + -e:ANOTHER_VARIABLE="VALUE WITH SPACES" + -e:ANOTHER_VARIABLE="VALUE;seperated with;semicolons" + -e|--Environment|/e|/Environment:<NAME>=<VALUE> + Sets the value of an environment variable. Creates the variable if one with the requested name does not exist. + + Example: -e:VARIABLE1=VALUE1 + -e:ANOTHER_VARIABLE="VALUE WITH SPACES" + -e:ANOTHER_VARIABLE="VALUE;seperated with;semi-columns" + + + + Environment variable '{0}' was already defined, but it's overridden by -Environment argument. + Environment variable '{0}' was already defined, but it's overridden by -Environment argument. + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.fr.xlf b/src/vstest.console/Resources/xlf/Resources.fr.xlf index 08dd4979d9..52b7e9667a 100644 --- a/src/vstest.console/Resources/xlf/Resources.fr.xlf +++ b/src/vstest.console/Resources/xlf/Resources.fr.xlf @@ -1683,6 +1683,28 @@ - {0} {1} + + -e|--Environment|/e|/Environment:<NAME>=<VALUE> + Sets the value of an environment variable. Creates the variable if it does not exist, overrides if it does. This will imply /InIsolation switch and force the tests to be run in an isolated process. + + This argument can be specified multiple times to provide multiple variables. + + Example: -e:VARIABLE1=VALUE1 + -e:ANOTHER_VARIABLE="VALUE WITH SPACES" + -e:ANOTHER_VARIABLE="VALUE;seperated with;semicolons" + -e|--Environment|/e|/Environment:<NAME>=<VALUE> + Sets the value of an environment variable. Creates the variable if one with the requested name does not exist. + + Example: -e:VARIABLE1=VALUE1 + -e:ANOTHER_VARIABLE="VALUE WITH SPACES" + -e:ANOTHER_VARIABLE="VALUE;seperated with;semi-columns" + + + + Environment variable '{0}' was already defined, but it's overridden by -Environment argument. + Environment variable '{0}' was already defined, but it's overridden by -Environment argument. + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.it.xlf b/src/vstest.console/Resources/xlf/Resources.it.xlf index 359f587dbd..44dca81a9b 100644 --- a/src/vstest.console/Resources/xlf/Resources.it.xlf +++ b/src/vstest.console/Resources/xlf/Resources.it.xlf @@ -1683,6 +1683,28 @@ - {0} {1} + + -e|--Environment|/e|/Environment:<NAME>=<VALUE> + Sets the value of an environment variable. Creates the variable if it does not exist, overrides if it does. This will imply /InIsolation switch and force the tests to be run in an isolated process. + + This argument can be specified multiple times to provide multiple variables. + + Example: -e:VARIABLE1=VALUE1 + -e:ANOTHER_VARIABLE="VALUE WITH SPACES" + -e:ANOTHER_VARIABLE="VALUE;seperated with;semicolons" + -e|--Environment|/e|/Environment:<NAME>=<VALUE> + Sets the value of an environment variable. Creates the variable if one with the requested name does not exist. + + Example: -e:VARIABLE1=VALUE1 + -e:ANOTHER_VARIABLE="VALUE WITH SPACES" + -e:ANOTHER_VARIABLE="VALUE;seperated with;semi-columns" + + + + Environment variable '{0}' was already defined, but it's overridden by -Environment argument. + Environment variable '{0}' was already defined, but it's overridden by -Environment argument. + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.ja.xlf b/src/vstest.console/Resources/xlf/Resources.ja.xlf index 68dec3d54e..8039796cc0 100644 --- a/src/vstest.console/Resources/xlf/Resources.ja.xlf +++ b/src/vstest.console/Resources/xlf/Resources.ja.xlf @@ -1683,6 +1683,28 @@ - {0} {1} + + -e|--Environment|/e|/Environment:<NAME>=<VALUE> + Sets the value of an environment variable. Creates the variable if it does not exist, overrides if it does. This will imply /InIsolation switch and force the tests to be run in an isolated process. + + This argument can be specified multiple times to provide multiple variables. + + Example: -e:VARIABLE1=VALUE1 + -e:ANOTHER_VARIABLE="VALUE WITH SPACES" + -e:ANOTHER_VARIABLE="VALUE;seperated with;semicolons" + -e|--Environment|/e|/Environment:<NAME>=<VALUE> + Sets the value of an environment variable. Creates the variable if one with the requested name does not exist. + + Example: -e:VARIABLE1=VALUE1 + -e:ANOTHER_VARIABLE="VALUE WITH SPACES" + -e:ANOTHER_VARIABLE="VALUE;seperated with;semi-columns" + + + + Environment variable '{0}' was already defined, but it's overridden by -Environment argument. + Environment variable '{0}' was already defined, but it's overridden by -Environment argument. + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.ko.xlf b/src/vstest.console/Resources/xlf/Resources.ko.xlf index d645a2eec7..a37ff745fa 100644 --- a/src/vstest.console/Resources/xlf/Resources.ko.xlf +++ b/src/vstest.console/Resources/xlf/Resources.ko.xlf @@ -1683,6 +1683,28 @@ - {0} {1} + + -e|--Environment|/e|/Environment:<NAME>=<VALUE> + Sets the value of an environment variable. Creates the variable if it does not exist, overrides if it does. This will imply /InIsolation switch and force the tests to be run in an isolated process. + + This argument can be specified multiple times to provide multiple variables. + + Example: -e:VARIABLE1=VALUE1 + -e:ANOTHER_VARIABLE="VALUE WITH SPACES" + -e:ANOTHER_VARIABLE="VALUE;seperated with;semicolons" + -e|--Environment|/e|/Environment:<NAME>=<VALUE> + Sets the value of an environment variable. Creates the variable if one with the requested name does not exist. + + Example: -e:VARIABLE1=VALUE1 + -e:ANOTHER_VARIABLE="VALUE WITH SPACES" + -e:ANOTHER_VARIABLE="VALUE;seperated with;semi-columns" + + + + Environment variable '{0}' was already defined, but it's overridden by -Environment argument. + Environment variable '{0}' was already defined, but it's overridden by -Environment argument. + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.pl.xlf b/src/vstest.console/Resources/xlf/Resources.pl.xlf index 600e065613..1270b0464b 100644 --- a/src/vstest.console/Resources/xlf/Resources.pl.xlf +++ b/src/vstest.console/Resources/xlf/Resources.pl.xlf @@ -1683,6 +1683,28 @@ - {0} {1} + + -e|--Environment|/e|/Environment:<NAME>=<VALUE> + Sets the value of an environment variable. Creates the variable if it does not exist, overrides if it does. This will imply /InIsolation switch and force the tests to be run in an isolated process. + + This argument can be specified multiple times to provide multiple variables. + + Example: -e:VARIABLE1=VALUE1 + -e:ANOTHER_VARIABLE="VALUE WITH SPACES" + -e:ANOTHER_VARIABLE="VALUE;seperated with;semicolons" + -e|--Environment|/e|/Environment:<NAME>=<VALUE> + Sets the value of an environment variable. Creates the variable if one with the requested name does not exist. + + Example: -e:VARIABLE1=VALUE1 + -e:ANOTHER_VARIABLE="VALUE WITH SPACES" + -e:ANOTHER_VARIABLE="VALUE;seperated with;semi-columns" + + + + Environment variable '{0}' was already defined, but it's overridden by -Environment argument. + Environment variable '{0}' was already defined, but it's overridden by -Environment argument. + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.pt-BR.xlf b/src/vstest.console/Resources/xlf/Resources.pt-BR.xlf index 839d1a867b..efcce9c25c 100644 --- a/src/vstest.console/Resources/xlf/Resources.pt-BR.xlf +++ b/src/vstest.console/Resources/xlf/Resources.pt-BR.xlf @@ -1683,6 +1683,28 @@ Altere o prefixo de nível de diagnóstico do agente de console, como mostrado a - {0} {1} + + -e|--Environment|/e|/Environment:<NAME>=<VALUE> + Sets the value of an environment variable. Creates the variable if it does not exist, overrides if it does. This will imply /InIsolation switch and force the tests to be run in an isolated process. + + This argument can be specified multiple times to provide multiple variables. + + Example: -e:VARIABLE1=VALUE1 + -e:ANOTHER_VARIABLE="VALUE WITH SPACES" + -e:ANOTHER_VARIABLE="VALUE;seperated with;semicolons" + -e|--Environment|/e|/Environment:<NAME>=<VALUE> + Sets the value of an environment variable. Creates the variable if one with the requested name does not exist. + + Example: -e:VARIABLE1=VALUE1 + -e:ANOTHER_VARIABLE="VALUE WITH SPACES" + -e:ANOTHER_VARIABLE="VALUE;seperated with;semi-columns" + + + + Environment variable '{0}' was already defined, but it's overridden by -Environment argument. + Environment variable '{0}' was already defined, but it's overridden by -Environment argument. + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.ru.xlf b/src/vstest.console/Resources/xlf/Resources.ru.xlf index 6389f284c0..ff3b5d67fe 100644 --- a/src/vstest.console/Resources/xlf/Resources.ru.xlf +++ b/src/vstest.console/Resources/xlf/Resources.ru.xlf @@ -1683,6 +1683,28 @@ - {0} {1} + + -e|--Environment|/e|/Environment:<NAME>=<VALUE> + Sets the value of an environment variable. Creates the variable if it does not exist, overrides if it does. This will imply /InIsolation switch and force the tests to be run in an isolated process. + + This argument can be specified multiple times to provide multiple variables. + + Example: -e:VARIABLE1=VALUE1 + -e:ANOTHER_VARIABLE="VALUE WITH SPACES" + -e:ANOTHER_VARIABLE="VALUE;seperated with;semicolons" + -e|--Environment|/e|/Environment:<NAME>=<VALUE> + Sets the value of an environment variable. Creates the variable if one with the requested name does not exist. + + Example: -e:VARIABLE1=VALUE1 + -e:ANOTHER_VARIABLE="VALUE WITH SPACES" + -e:ANOTHER_VARIABLE="VALUE;seperated with;semi-columns" + + + + Environment variable '{0}' was already defined, but it's overridden by -Environment argument. + Environment variable '{0}' was already defined, but it's overridden by -Environment argument. + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.tr.xlf b/src/vstest.console/Resources/xlf/Resources.tr.xlf index 46d2f82170..697efcc02d 100644 --- a/src/vstest.console/Resources/xlf/Resources.tr.xlf +++ b/src/vstest.console/Resources/xlf/Resources.tr.xlf @@ -1683,6 +1683,28 @@ Günlükler için izleme düzeyini aşağıda gösterildiği gibi değiştirin - {0} {1} + + -e|--Environment|/e|/Environment:<NAME>=<VALUE> + Sets the value of an environment variable. Creates the variable if it does not exist, overrides if it does. This will imply /InIsolation switch and force the tests to be run in an isolated process. + + This argument can be specified multiple times to provide multiple variables. + + Example: -e:VARIABLE1=VALUE1 + -e:ANOTHER_VARIABLE="VALUE WITH SPACES" + -e:ANOTHER_VARIABLE="VALUE;seperated with;semicolons" + -e|--Environment|/e|/Environment:<NAME>=<VALUE> + Sets the value of an environment variable. Creates the variable if one with the requested name does not exist. + + Example: -e:VARIABLE1=VALUE1 + -e:ANOTHER_VARIABLE="VALUE WITH SPACES" + -e:ANOTHER_VARIABLE="VALUE;seperated with;semi-columns" + + + + Environment variable '{0}' was already defined, but it's overridden by -Environment argument. + Environment variable '{0}' was already defined, but it's overridden by -Environment argument. + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.xlf b/src/vstest.console/Resources/xlf/Resources.xlf index f2ccd897b2..7c9e5c3dfb 100644 --- a/src/vstest.console/Resources/xlf/Resources.xlf +++ b/src/vstest.console/Resources/xlf/Resources.xlf @@ -874,6 +874,28 @@ Format : TestRunParameters.Parameter(name="<name>", value="<value>") - {0} {1} + + -e|--Environment|/e|/Environment:<NAME>=<VALUE> + Sets the value of an environment variable. Creates the variable if it does not exist, overrides if it does. This will imply /InIsolation switch and force the tests to be run in an isolated process. + + This argument can be specified multiple times to provide multiple variables. + + Example: -e:VARIABLE1=VALUE1 + -e:ANOTHER_VARIABLE="VALUE WITH SPACES" + -e:ANOTHER_VARIABLE="VALUE;seperated with;semicolons" + -e|--Environment|/e|/Environment:<NAME>=<VALUE> + Sets the value of an environment variable. Creates the variable if one with the requested name does not exist. + + Example: -e:VARIABLE1=VALUE1 + -e:ANOTHER_VARIABLE="VALUE WITH SPACES" + -e:ANOTHER_VARIABLE="VALUE;seperated with;semi-columns" + + + + Environment variable '{0}' was already defined, but it's overridden by -Environment argument. + Environment variable '{0}' was already defined, but it's overridden by -Environment argument. + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.zh-Hans.xlf b/src/vstest.console/Resources/xlf/Resources.zh-Hans.xlf index d37030370b..8a13ccacb3 100644 --- a/src/vstest.console/Resources/xlf/Resources.zh-Hans.xlf +++ b/src/vstest.console/Resources/xlf/Resources.zh-Hans.xlf @@ -1683,6 +1683,28 @@ - {0} {1} + + -e|--Environment|/e|/Environment:<NAME>=<VALUE> + Sets the value of an environment variable. Creates the variable if it does not exist, overrides if it does. This will imply /InIsolation switch and force the tests to be run in an isolated process. + + This argument can be specified multiple times to provide multiple variables. + + Example: -e:VARIABLE1=VALUE1 + -e:ANOTHER_VARIABLE="VALUE WITH SPACES" + -e:ANOTHER_VARIABLE="VALUE;seperated with;semicolons" + -e|--Environment|/e|/Environment:<NAME>=<VALUE> + Sets the value of an environment variable. Creates the variable if one with the requested name does not exist. + + Example: -e:VARIABLE1=VALUE1 + -e:ANOTHER_VARIABLE="VALUE WITH SPACES" + -e:ANOTHER_VARIABLE="VALUE;seperated with;semi-columns" + + + + Environment variable '{0}' was already defined, but it's overridden by -Environment argument. + Environment variable '{0}' was already defined, but it's overridden by -Environment argument. + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.zh-Hant.xlf b/src/vstest.console/Resources/xlf/Resources.zh-Hant.xlf index 0bcc68aa0f..fd40f1d23e 100644 --- a/src/vstest.console/Resources/xlf/Resources.zh-Hant.xlf +++ b/src/vstest.console/Resources/xlf/Resources.zh-Hant.xlf @@ -1684,6 +1684,28 @@ - {0} {1} + + -e|--Environment|/e|/Environment:<NAME>=<VALUE> + Sets the value of an environment variable. Creates the variable if it does not exist, overrides if it does. This will imply /InIsolation switch and force the tests to be run in an isolated process. + + This argument can be specified multiple times to provide multiple variables. + + Example: -e:VARIABLE1=VALUE1 + -e:ANOTHER_VARIABLE="VALUE WITH SPACES" + -e:ANOTHER_VARIABLE="VALUE;seperated with;semicolons" + -e|--Environment|/e|/Environment:<NAME>=<VALUE> + Sets the value of an environment variable. Creates the variable if one with the requested name does not exist. + + Example: -e:VARIABLE1=VALUE1 + -e:ANOTHER_VARIABLE="VALUE WITH SPACES" + -e:ANOTHER_VARIABLE="VALUE;seperated with;semi-columns" + + + + Environment variable '{0}' was already defined, but it's overridden by -Environment argument. + Environment variable '{0}' was already defined, but it's overridden by -Environment argument. + + \ No newline at end of file diff --git a/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs b/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs index 5cfe3f809d..3d1c2b74e6 100644 --- a/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs +++ b/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs @@ -451,7 +451,7 @@ private bool UpdateRunSettingsIfRequired(string runsettingsXml, List sou || chosenFramework.Name.IndexOf("netcoreapp", StringComparison.OrdinalIgnoreCase) >= 0 || chosenFramework.Name.IndexOf("net5", StringComparison.OrdinalIgnoreCase) >= 0) { - defaultArchitecture = Environment.Is64BitProcess ? Architecture.X64 : Architecture.X86; + defaultArchitecture = Environment.Is64BitOperatingSystem ? Architecture.X64 : Architecture.X86; } settingsUpdated |= this.UpdatePlatform(document, navigator, sources, sourcePlatforms, defaultArchitecture, out Architecture chosenPlatform); diff --git a/src/vstest.console/vstest.console.csproj b/src/vstest.console/vstest.console.csproj index 0de0c734c8..ea970a6019 100644 --- a/src/vstest.console/vstest.console.csproj +++ b/src/vstest.console/vstest.console.csproj @@ -13,6 +13,7 @@ AnyCPU true app.manifest + Microsoft.VisualStudio.TestPlatform.CommandLine win7-x64 diff --git a/test/Microsoft.TestPlatform.AcceptanceTests/BlameDataCollectorTests.cs b/test/Microsoft.TestPlatform.AcceptanceTests/BlameDataCollectorTests.cs index ca0448993c..f3939f2e6b 100644 --- a/test/Microsoft.TestPlatform.AcceptanceTests/BlameDataCollectorTests.cs +++ b/test/Microsoft.TestPlatform.AcceptanceTests/BlameDataCollectorTests.cs @@ -53,7 +53,7 @@ public void BlameDataCollectorShouldOutputDumpFile(RunnerInfo runnerInfo) AcceptanceTestBase.SetTestEnvironment(this.testEnvironment, runnerInfo); var assemblyPaths = this.GetAssetFullPath("BlameUnitTestProject.dll"); - var arguments = PrepareArguments(assemblyPaths, this.GetTestAdapterPath(), string.Empty, this.FrameworkArgValue, runnerInfo.InIsolationValue); + var arguments = PrepareArguments(assemblyPaths, this.GetTestAdapterPath(), string.Empty, string.Empty, runnerInfo.InIsolationValue); arguments = string.Concat(arguments, $" /Blame:CollectDump"); arguments = string.Concat(arguments, $" /ResultsDirectory:{resultsDir}"); this.InvokeVsTest(arguments); diff --git a/test/Microsoft.TestPlatform.AcceptanceTests/RunsettingsTests.cs b/test/Microsoft.TestPlatform.AcceptanceTests/RunsettingsTests.cs index 2af781dc81..d8028702f8 100644 --- a/test/Microsoft.TestPlatform.AcceptanceTests/RunsettingsTests.cs +++ b/test/Microsoft.TestPlatform.AcceptanceTests/RunsettingsTests.cs @@ -496,7 +496,7 @@ public void RunSettingsAreLoadedFromProject(RunnerInfo runnerInfo) var projectName = "ProjectFileRunSettingsTestProject.csproj"; var projectPath = this.GetProjectFullPath(projectName); - this.InvokeDotnetTest(projectPath); + this.InvokeDotnetTest($@"{projectPath} --logger:""Console;Verbosity=normal"""); this.ValidateSummaryStatus(0, 1, 0); // make sure that we can revert the project settings back by providing a config from command line @@ -504,7 +504,7 @@ public void RunSettingsAreLoadedFromProject(RunnerInfo runnerInfo) // are honored by dotnet test, instead of just using the default, which would produce the same // result var settingsPath = this.GetProjectAssetFullPath(projectName, "inconclusive.runsettings"); - this.InvokeDotnetTest($"{projectPath} --settings {settingsPath}"); + this.InvokeDotnetTest($@"{projectPath} --settings {settingsPath} --logger:""Console;Verbosity=normal"""); this.ValidateSummaryStatus(0, 0, 1); } diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.PlatformTests/SocketClientTests.cs b/test/Microsoft.TestPlatform.CommunicationUtilities.PlatformTests/SocketClientTests.cs index 8b450ef6c7..c329837bc1 100644 --- a/test/Microsoft.TestPlatform.CommunicationUtilities.PlatformTests/SocketClientTests.cs +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.PlatformTests/SocketClientTests.cs @@ -35,7 +35,7 @@ public SocketClientTests() public void Dispose() { this.socketClient.Stop(); -#if NET451 +#if NETFRAMEWORK // tcpClient.Close() calls tcpClient.Dispose(). this.tcpClient?.Close(); #else @@ -100,7 +100,7 @@ public void SocketClientShouldRaiseClientDisconnectedEventIfConnectionIsBroken() // Close the communication from server side this.tcpClient.GetStream().Dispose(); -#if NET451 +#if NETFRAMEWORK // tcpClient.Close() calls tcpClient.Dispose(). this.tcpClient?.Close(); #else diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.PlatformTests/SocketCommunicationManagerTests.cs b/test/Microsoft.TestPlatform.CommunicationUtilities.PlatformTests/SocketCommunicationManagerTests.cs index eaa60b437a..8bff436bed 100644 --- a/test/Microsoft.TestPlatform.CommunicationUtilities.PlatformTests/SocketCommunicationManagerTests.cs +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.PlatformTests/SocketCommunicationManagerTests.cs @@ -42,7 +42,7 @@ public SocketCommunicationManagerTests() public void Dispose() { this.tcpListener.Stop(); -#if NET451 +#if NETFRAMEWORK // tcpClient.Close() calls tcpClient.Dispose(). this.tcpClient?.Close(); #else diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.PlatformTests/SocketServerTests.cs b/test/Microsoft.TestPlatform.CommunicationUtilities.PlatformTests/SocketServerTests.cs index 69cc154485..b6dbab41ea 100644 --- a/test/Microsoft.TestPlatform.CommunicationUtilities.PlatformTests/SocketServerTests.cs +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.PlatformTests/SocketServerTests.cs @@ -32,7 +32,7 @@ public SocketServerTests() public void Dispose() { this.socketServer.Stop(); -#if NET451 +#if NETFRAMEWORK // tcpClient.Close() calls tcpClient.Dispose(). this.tcpClient?.Close(); #else @@ -131,7 +131,7 @@ public void SocketServerShouldRaiseClientDisconnectedEventIfConnectionIsBroken() }; // Close the client channel. Message loop should stop. -#if NET451 +#if NETFRAMEWORK // tcpClient.Close() calls tcpClient.Dispose(). this.tcpClient?.Close(); #else diff --git a/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/Tracing/EqtTraceTests.cs b/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/Tracing/EqtTraceTests.cs index 5e5bd2ec26..8d0c51fea5 100644 --- a/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/Tracing/EqtTraceTests.cs +++ b/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/Tracing/EqtTraceTests.cs @@ -3,7 +3,7 @@ namespace TestPlatform.CoreUtilities.UnitTests { -#if NET451 +#if NETFRAMEWORK using System.Diagnostics; #endif using System.IO; @@ -46,7 +46,7 @@ public void CheckInitializeLogFileTest() [TestMethod] public void CheckIfTraceStateIsVerboseEnabled() { -#if NET451 +#if NETFRAMEWORK EqtTrace.TraceLevel = TraceLevel.Verbose; #else EqtTrace.TraceLevel = PlatformTraceLevel.Verbose; @@ -57,7 +57,7 @@ public void CheckIfTraceStateIsVerboseEnabled() [TestMethod] public void CheckIfTraceStateIsErrorEnabled() { -#if NET451 +#if NETFRAMEWORK EqtTrace.TraceLevel = TraceLevel.Error; #else EqtTrace.TraceLevel = PlatformTraceLevel.Error; @@ -68,7 +68,7 @@ public void CheckIfTraceStateIsErrorEnabled() [TestMethod] public void CheckIfTraceStateIsInfoEnabled() { -#if NET451 +#if NETFRAMEWORK EqtTrace.TraceLevel = TraceLevel.Info; #else EqtTrace.TraceLevel = PlatformTraceLevel.Info; @@ -79,7 +79,7 @@ public void CheckIfTraceStateIsInfoEnabled() [TestMethod] public void CheckIfTraceStateIsWarningEnabled() { -#if NET451 +#if NETFRAMEWORK EqtTrace.TraceLevel = TraceLevel.Warning; #else EqtTrace.TraceLevel = PlatformTraceLevel.Warning; @@ -90,7 +90,7 @@ public void CheckIfTraceStateIsWarningEnabled() [TestMethod] public void TraceShouldWriteError() { -#if NET451 +#if NETFRAMEWORK EqtTrace.TraceLevel = TraceLevel.Error; #else EqtTrace.TraceLevel = PlatformTraceLevel.Error; @@ -102,7 +102,7 @@ public void TraceShouldWriteError() [TestMethod] public void TraceShouldWriteWarning() { -#if NET451 +#if NETFRAMEWORK EqtTrace.TraceLevel = TraceLevel.Warning; #else EqtTrace.TraceLevel = PlatformTraceLevel.Warning; @@ -114,7 +114,7 @@ public void TraceShouldWriteWarning() [TestMethod] public void TraceShouldWriteVerbose() { -#if NET451 +#if NETFRAMEWORK EqtTrace.TraceLevel = TraceLevel.Verbose; #else EqtTrace.TraceLevel = PlatformTraceLevel.Verbose; @@ -126,7 +126,7 @@ public void TraceShouldWriteVerbose() [TestMethod] public void TraceShouldWriteInfo() { -#if NET451 +#if NETFRAMEWORK EqtTrace.TraceLevel = TraceLevel.Info; #else EqtTrace.TraceLevel = PlatformTraceLevel.Info; @@ -138,7 +138,7 @@ public void TraceShouldWriteInfo() [TestMethod] public void TraceShouldNotWriteVerboseIfTraceLevelIsInfo() { -#if NET451 +#if NETFRAMEWORK EqtTrace.TraceLevel = TraceLevel.Info; #else EqtTrace.TraceLevel = PlatformTraceLevel.Info; @@ -155,7 +155,7 @@ public void TraceShouldNotWriteVerboseIfTraceLevelIsInfo() public void TraceShouldNotWriteIfDoNotInitializationIsSetToTrue() { EqtTrace.DoNotInitailize = true; -#if NET451 +#if NETFRAMEWORK EqtTrace.TraceLevel = TraceLevel.Info; #else EqtTrace.TraceLevel = PlatformTraceLevel.Info; diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyOperationManagerTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyOperationManagerTests.cs index 71b651429b..9313a0ddb8 100644 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyOperationManagerTests.cs +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyOperationManagerTests.cs @@ -99,7 +99,7 @@ public void SetupChannelShouldCreateTimestampedLogFileForHost() It.Is( t => t.LogFile.Contains("log.host." + DateTime.Now.ToString("yy-MM-dd")) && t.LogFile.Contains("_" + Thread.CurrentThread.ManagedThreadId + ".txt")))); -#if NET451 +#if NETFRAMEWORK EqtTrace.TraceLevel = TraceLevel.Off; #else EqtTrace.TraceLevel = PlatformTraceLevel.Off; @@ -124,7 +124,7 @@ public void SetupChannelShouldAddRunnerProcessIdForTestHost() [TestMethod] public void SetupChannelShouldAddCorrectTraceLevelForTestHost() { -#if NET451 +#if NETFRAMEWORK EqtTrace.TraceLevel = TraceLevel.Info; #else EqtTrace.TraceLevel = PlatformTraceLevel.Info; diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/DataCollection/ProxyDataCollectionManagerTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/DataCollection/ProxyDataCollectionManagerTests.cs index e4846fd8f5..c89ffb9fa8 100644 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/DataCollection/ProxyDataCollectionManagerTests.cs +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/DataCollection/ProxyDataCollectionManagerTests.cs @@ -106,7 +106,7 @@ public void InitializeShouldSetTimeoutBasedOnDebugEnvironmentVaribleName() public void InitializeShouldPassDiagArgumentsIfDiagIsEnabled() { // Saving the EqtTrace state -#if NET451 +#if NETFRAMEWORK var traceLevel = EqtTrace.TraceLevel; EqtTrace.TraceLevel = TraceLevel.Off; #else @@ -134,7 +134,7 @@ public void InitializeShouldPassDiagArgumentsIfDiagIsEnabled() { // Restoring to initial state for EqtTrace EqtTrace.InitializeTrace(traceFileName, PlatformTraceLevel.Verbose); -#if NET451 +#if NETFRAMEWORK EqtTrace.TraceLevel = traceLevel; #else EqtTrace.TraceLevel = (PlatformTraceLevel)traceLevel; diff --git a/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/BlameCollectorTests.cs b/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/BlameCollectorTests.cs index 367010a292..fffd563384 100644 --- a/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/BlameCollectorTests.cs +++ b/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/BlameCollectorTests.cs @@ -171,8 +171,8 @@ public void InitializeWithDumpForHangShouldCaptureADumpOnTimeout() this.mockFileHelper.Setup(x => x.Exists(It.Is(y => y == "abc_hang.dmp"))).Returns(true); this.mockFileHelper.Setup(x => x.GetFullPath(It.Is(y => y == "abc_hang.dmp"))).Returns("abc_hang.dmp"); - this.mockProcessDumpUtility.Setup(x => x.StartHangBasedProcessDump(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())); - this.mockProcessDumpUtility.Setup(x => x.GetDumpFile()).Returns(dumpFile); + this.mockProcessDumpUtility.Setup(x => x.StartHangBasedProcessDump(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>())); + this.mockProcessDumpUtility.Setup(x => x.GetDumpFiles()).Returns(new[] { dumpFile }); this.mockDataCollectionSink.Setup(x => x.SendFileAsync(It.IsAny())).Callback(() => hangBasedDumpcollected.Set()); this.blameDataCollector.Initialize( @@ -183,8 +183,8 @@ public void InitializeWithDumpForHangShouldCaptureADumpOnTimeout() this.context); hangBasedDumpcollected.Wait(1000); - this.mockProcessDumpUtility.Verify(x => x.StartHangBasedProcessDump(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); - this.mockProcessDumpUtility.Verify(x => x.GetDumpFile(), Times.Once); + this.mockProcessDumpUtility.Verify(x => x.StartHangBasedProcessDump(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>()), Times.Once); + this.mockProcessDumpUtility.Verify(x => x.GetDumpFiles(), Times.Once); this.mockDataCollectionSink.Verify(x => x.SendFileAsync(It.Is(y => y.Path == dumpFile)), Times.Once); } @@ -205,8 +205,8 @@ public void InitializeWithDumpForHangShouldCaptureKillTestHostOnTimeoutEvenIfGet this.mockFileHelper.Setup(x => x.Exists(It.Is(y => y == "abc_hang.dmp"))).Returns(true); this.mockFileHelper.Setup(x => x.GetFullPath(It.Is(y => y == "abc_hang.dmp"))).Returns("abc_hang.dmp"); - this.mockProcessDumpUtility.Setup(x => x.StartHangBasedProcessDump(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())); - this.mockProcessDumpUtility.Setup(x => x.GetDumpFile()).Callback(() => hangBasedDumpcollected.Set()).Throws(new Exception("Some exception")); + this.mockProcessDumpUtility.Setup(x => x.StartHangBasedProcessDump(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>())); + this.mockProcessDumpUtility.Setup(x => x.GetDumpFiles()).Callback(() => hangBasedDumpcollected.Set()).Throws(new Exception("Some exception")); this.blameDataCollector.Initialize( this.GetDumpConfigurationElement(false, false, true, 0), @@ -216,8 +216,8 @@ public void InitializeWithDumpForHangShouldCaptureKillTestHostOnTimeoutEvenIfGet this.context); hangBasedDumpcollected.Wait(1000); - this.mockProcessDumpUtility.Verify(x => x.StartHangBasedProcessDump(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); - this.mockProcessDumpUtility.Verify(x => x.GetDumpFile(), Times.Once); + this.mockProcessDumpUtility.Verify(x => x.StartHangBasedProcessDump(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>()), Times.Once); + this.mockProcessDumpUtility.Verify(x => x.GetDumpFiles(), Times.Once); } /// @@ -238,8 +238,8 @@ public void InitializeWithDumpForHangShouldCaptureKillTestHostOnTimeoutEvenIfAtt this.mockFileHelper.Setup(x => x.Exists(It.Is(y => y == "abc_hang.dmp"))).Returns(true); this.mockFileHelper.Setup(x => x.GetFullPath(It.Is(y => y == "abc_hang.dmp"))).Returns("abc_hang.dmp"); - this.mockProcessDumpUtility.Setup(x => x.StartHangBasedProcessDump(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())); - this.mockProcessDumpUtility.Setup(x => x.GetDumpFile()).Returns(dumpFile); + this.mockProcessDumpUtility.Setup(x => x.StartHangBasedProcessDump(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>())); + this.mockProcessDumpUtility.Setup(x => x.GetDumpFiles()).Returns(new[] { dumpFile }); this.mockDataCollectionSink.Setup(x => x.SendFileAsync(It.IsAny())).Callback(() => hangBasedDumpcollected.Set()).Throws(new Exception("Some other exception")); this.blameDataCollector.Initialize( @@ -250,8 +250,8 @@ public void InitializeWithDumpForHangShouldCaptureKillTestHostOnTimeoutEvenIfAtt this.context); hangBasedDumpcollected.Wait(1000); - this.mockProcessDumpUtility.Verify(x => x.StartHangBasedProcessDump(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); - this.mockProcessDumpUtility.Verify(x => x.GetDumpFile(), Times.Once); + this.mockProcessDumpUtility.Verify(x => x.StartHangBasedProcessDump(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>()), Times.Once); + this.mockProcessDumpUtility.Verify(x => x.GetDumpFiles(), Times.Once); this.mockDataCollectionSink.Verify(x => x.SendFileAsync(It.Is(y => y.Path == dumpFile)), Times.Once); } @@ -366,7 +366,7 @@ public void TriggerSessionEndedHandlerShouldGetDumpFileIfProcDumpEnabled() this.context); // Setup - this.mockProcessDumpUtility.Setup(x => x.GetDumpFile()).Returns(this.filepath); + this.mockProcessDumpUtility.Setup(x => x.GetDumpFiles()).Returns(new[] { this.filepath }); this.mockBlameReaderWriter.Setup(x => x.WriteTestSequence(It.IsAny>(), It.IsAny>(), It.IsAny())) .Returns(this.filepath); @@ -376,7 +376,7 @@ public void TriggerSessionEndedHandlerShouldGetDumpFileIfProcDumpEnabled() this.mockDataColectionEvents.Raise(x => x.SessionEnd += null, new SessionEndEventArgs(this.dataCollectionContext)); // Verify GetDumpFiles Call - this.mockProcessDumpUtility.Verify(x => x.GetDumpFile(), Times.Once); + this.mockProcessDumpUtility.Verify(x => x.GetDumpFiles(), Times.Once); } /// @@ -418,7 +418,7 @@ public void TriggerSessionEndedHandlerShouldNotGetDumpFileIfNoCrash() this.context); // Setup - this.mockProcessDumpUtility.Setup(x => x.GetDumpFile()).Returns(this.filepath); + this.mockProcessDumpUtility.Setup(x => x.GetDumpFiles()).Returns(new[] { this.filepath }); this.mockBlameReaderWriter.Setup(x => x.WriteTestSequence(It.IsAny>(), It.IsAny>(), It.IsAny())) .Returns(this.filepath); @@ -427,7 +427,7 @@ public void TriggerSessionEndedHandlerShouldNotGetDumpFileIfNoCrash() this.mockDataColectionEvents.Raise(x => x.SessionEnd += null, new SessionEndEventArgs(this.dataCollectionContext)); // Verify GetDumpFiles Call - this.mockProcessDumpUtility.Verify(x => x.GetDumpFile(), Times.Never); + this.mockProcessDumpUtility.Verify(x => x.GetDumpFiles(), Times.Never); } /// @@ -445,7 +445,7 @@ public void TriggerSessionEndedHandlerShouldGetDumpFileIfCollectDumpOnExitIsEnab this.context); // Setup - this.mockProcessDumpUtility.Setup(x => x.GetDumpFile()).Returns(this.filepath); + this.mockProcessDumpUtility.Setup(x => x.GetDumpFiles()).Returns(new[] { this.filepath }); this.mockBlameReaderWriter.Setup(x => x.WriteTestSequence(It.IsAny>(), It.IsAny>(), It.IsAny())) .Returns(this.filepath); @@ -454,7 +454,7 @@ public void TriggerSessionEndedHandlerShouldGetDumpFileIfCollectDumpOnExitIsEnab this.mockDataColectionEvents.Raise(x => x.SessionEnd += null, new SessionEndEventArgs(this.dataCollectionContext)); // Verify GetDumpFiles Call - this.mockProcessDumpUtility.Verify(x => x.GetDumpFile(), Times.Once); + this.mockProcessDumpUtility.Verify(x => x.GetDumpFiles(), Times.Once); } /// @@ -474,7 +474,7 @@ public void TriggerSessionEndedHandlerShouldLogWarningIfGetDumpFileThrowsFileNot // Setup and raise events this.mockBlameReaderWriter.Setup(x => x.WriteTestSequence(It.IsAny>(), It.IsAny>(), It.IsAny())) .Returns(this.filepath); - this.mockProcessDumpUtility.Setup(x => x.GetDumpFile()).Throws(new FileNotFoundException()); + this.mockProcessDumpUtility.Setup(x => x.GetDumpFiles()).Throws(new FileNotFoundException()); this.mockDataColectionEvents.Raise(x => x.TestHostLaunched += null, new TestHostLaunchedEventArgs(this.dataCollectionContext, 1234)); this.mockDataColectionEvents.Raise(x => x.TestCaseStart += null, new TestCaseStartEventArgs(new TestCase())); this.mockDataColectionEvents.Raise(x => x.SessionEnd += null, new SessionEndEventArgs(this.dataCollectionContext)); @@ -501,7 +501,7 @@ public void TriggerTestHostLaunchedHandlerShouldStartProcDumpUtilityIfProcDumpEn this.mockDataColectionEvents.Raise(x => x.TestHostLaunched += null, new TestHostLaunchedEventArgs(this.dataCollectionContext, 1234)); // Verify StartProcessDumpCall - this.mockProcessDumpUtility.Verify(x => x.StartTriggerBasedProcessDump(1234, It.IsAny(), It.IsAny(), false, It.IsAny())); + this.mockProcessDumpUtility.Verify(x => x.StartTriggerBasedProcessDump(1234, It.IsAny(), false, It.IsAny())); } /// @@ -522,7 +522,7 @@ public void TriggerTestHostLaunchedHandlerShouldStartProcDumpUtilityForFullDumpI this.mockDataColectionEvents.Raise(x => x.TestHostLaunched += null, new TestHostLaunchedEventArgs(this.dataCollectionContext, 1234)); // Verify StartProcessDumpCall - this.mockProcessDumpUtility.Verify(x => x.StartTriggerBasedProcessDump(1234, It.IsAny(), It.IsAny(), true, It.IsAny())); + this.mockProcessDumpUtility.Verify(x => x.StartTriggerBasedProcessDump(1234, It.IsAny(), true, It.IsAny())); } /// @@ -551,7 +551,7 @@ public void TriggerTestHostLaunchedHandlerShouldStartProcDumpUtilityForFullDumpI this.mockDataColectionEvents.Raise(x => x.TestHostLaunched += null, new TestHostLaunchedEventArgs(this.dataCollectionContext, 1234)); // Verify StartProcessDumpCall - this.mockProcessDumpUtility.Verify(x => x.StartTriggerBasedProcessDump(1234, It.IsAny(), It.IsAny(), true, It.IsAny())); + this.mockProcessDumpUtility.Verify(x => x.StartTriggerBasedProcessDump(1234, It.IsAny(), true, It.IsAny())); } /// @@ -648,7 +648,7 @@ public void TriggerTestHostLaunchedHandlerShouldCatchTestPlatFormExceptionsAndRe // Make StartProcessDump throw exception var tpex = new TestPlatformException("env var exception"); - this.mockProcessDumpUtility.Setup(x => x.StartTriggerBasedProcessDump(1234, It.IsAny(), It.IsAny(), false, It.IsAny())) + this.mockProcessDumpUtility.Setup(x => x.StartTriggerBasedProcessDump(1234, It.IsAny(), false, It.IsAny())) .Throws(tpex); // Raise TestHostLaunched @@ -674,7 +674,7 @@ public void TriggerTestHostLaunchedHandlerShouldCatchAllUnexpectedExceptionsAndR // Make StartProcessDump throw exception var ex = new Exception("start process failed"); - this.mockProcessDumpUtility.Setup(x => x.StartTriggerBasedProcessDump(1234, It.IsAny(), It.IsAny(), false, It.IsAny())) + this.mockProcessDumpUtility.Setup(x => x.StartTriggerBasedProcessDump(1234, It.IsAny(), false, It.IsAny())) .Throws(ex); // Raise TestHostLaunched diff --git a/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests.csproj b/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests.csproj index 84c0db3ec3..b472cd5412 100644 --- a/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests.csproj +++ b/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests.csproj @@ -19,7 +19,7 @@ - + diff --git a/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/ProcessDumpUtilityTests.cs b/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/ProcessDumpUtilityTests.cs index 3ef927e5ad..1c6bcee8d6 100644 --- a/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/ProcessDumpUtilityTests.cs +++ b/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/ProcessDumpUtilityTests.cs @@ -40,7 +40,6 @@ public ProcessDumpUtilityTests() [TestMethod] public void GetDumpFileWillThrowExceptionIfNoDumpfile() { - var guid = "guid"; var process = "process"; var processId = 12345; var testResultsDirectory = "D:\\TestResults"; @@ -62,9 +61,9 @@ public void GetDumpFileWillThrowExceptionIfNoDumpfile() this.mockHangDumperFactory.Object, this.mockCrashDumperFactory.Object); - processDumpUtility.StartTriggerBasedProcessDump(processId, guid, testResultsDirectory, false, ".NETCoreApp,Version=v5.0"); + processDumpUtility.StartTriggerBasedProcessDump(processId, testResultsDirectory, false, ".NETCoreApp,Version=v5.0"); - var ex = Assert.ThrowsException(() => processDumpUtility.GetDumpFile()); + var ex = Assert.ThrowsException(() => processDumpUtility.GetDumpFiles()); Assert.AreEqual(ex.Message, Resources.Resources.DumpFileNotGeneratedErrorMessage); } } diff --git a/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/TrxLoggerTests.cs b/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/TrxLoggerTests.cs index d9324066b1..c55c000053 100644 --- a/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/TrxLoggerTests.cs +++ b/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/TrxLoggerTests.cs @@ -4,19 +4,21 @@ namespace Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests { + using Microsoft.TestPlatform.Extensions.TrxLogger.Utility; + using Microsoft.VisualStudio.TestPlatform.Extensions.TrxLogger; + using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers; + using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Moq; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; using System.IO; using System.Linq; + using System.Runtime.CompilerServices; using System.Xml; using System.Xml.Linq; - using Microsoft.TestPlatform.Extensions.TrxLogger.Utility; - using Microsoft.VisualStudio.TestPlatform.Extensions.TrxLogger; - using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces; - using Microsoft.VisualStudio.TestTools.UnitTesting; - using Moq; using VisualStudio.TestPlatform.ObjectModel; using VisualStudio.TestPlatform.ObjectModel.Client; using VisualStudio.TestPlatform.ObjectModel.Logging; @@ -33,6 +35,9 @@ public class TrxLoggerTests private Dictionary parameters; private static string DefaultTestRunDirectory = Path.GetTempPath(); private static string DefaultLogFileNameParameterValue = "logfilevalue.trx"; + private const string DefaultLogFilePrefixParameterValue = "log_prefix"; + + private const int MultipleLoggerInstanceCount = 2; [TestInitialize] public void Initialize() @@ -634,6 +639,95 @@ public void DefaultTrxFileShouldCreateIfLogFileNameParameterNotPassed() Assert.IsFalse(string.IsNullOrWhiteSpace(this.testableTrxLogger.trxFile)); } + [TestMethod] + public void DefaultTrxFileNameVerification() + { + this.parameters.Remove(TrxLoggerConstants.LogFileNameKey); + this.parameters[TrxLoggerConstants.LogFilePrefixKey] = DefaultLogFilePrefixParameterValue; + + var time = DateTime.Now; + var trxFileHelper = new TrxFileHelper(() => time); + + testableTrxLogger = new TestableTrxLogger(new FileHelper(), trxFileHelper); + testableTrxLogger.Initialize(this.events.Object, this.parameters); + + MakeTestRunComplete(); + + var fileName = Path.GetFileName(testableTrxLogger.trxFile); + var expectedName = $"{DefaultLogFilePrefixParameterValue}{time:_yyyyMMddHHmmss}.trx"; + + Assert.AreEqual(expectedName, fileName, "Trx file name pattern has changed. It should be in the form of prefix_yyyyMMddHHmmss.trx, Azure Devops VSTest task depends on this naming."); + } + + [TestMethod] + public void DefaultTrxFileShouldIterateIfLogFileNameParameterNotPassed() + { + this.parameters.Remove(TrxLoggerConstants.LogFileNameKey); + + var files = TestMultipleTrxLoggers(); + + Assert.AreEqual(MultipleLoggerInstanceCount, files.Length, "All logger instances should get different file names!"); + } + + [TestMethod] + public void TrxFileNameShouldNotIterate() + { + var files = TestMultipleTrxLoggers(); + + Assert.AreEqual(1, files.Length, "All logger instances should get the same file name!"); + } + + [TestMethod] + public void TrxPrefixFileNameShouldIterate() + { + this.parameters.Remove(TrxLoggerConstants.LogFileNameKey); + this.parameters[TrxLoggerConstants.LogFilePrefixKey] = DefaultLogFilePrefixParameterValue; + + var files = TestMultipleTrxLoggers(); + + Assert.AreEqual(MultipleLoggerInstanceCount, files.Length, "All logger instances should get different file names!"); + } + + private string[] TestMultipleTrxLoggers() + { + var files = new string[2]; + + try + { + var time = new DateTime(2020, 1, 1, 0, 0, 0); + + var trxFileHelper = new TrxFileHelper(() => time); + var trxLogger1 = new TestableTrxLogger(new FileHelper(), trxFileHelper); + var trxLogger2 = new TestableTrxLogger(new FileHelper(), trxFileHelper); + + trxLogger1.Initialize(this.events.Object, this.parameters); + trxLogger2.Initialize(this.events.Object, this.parameters); + + MakeTestRunComplete(trxLogger1); + files[0] = trxLogger1.trxFile; + + MakeTestRunComplete(trxLogger2); + files[1] = trxLogger2.trxFile; + } + finally + { + files = files + .Where(i => !string.IsNullOrWhiteSpace(i)) + .Distinct() + .ToArray(); + + foreach (var file in files) + { + if (!string.IsNullOrEmpty(file) && File.Exists(file)) + { + File.Delete(file); + } + } + } + + return files; + } + [TestMethod] public void CustomTrxFileNameShouldConstructFromLogFileParameter() { @@ -642,6 +736,8 @@ public void CustomTrxFileNameShouldConstructFromLogFileParameter() Assert.AreEqual(Path.Combine(TrxLoggerTests.DefaultTestRunDirectory, TrxLoggerTests.DefaultLogFileNameParameterValue), this.testableTrxLogger.trxFile, "Wrong Trx file name"); } + + /// /// Unit test for reading TestCategories from the TestCase which is part of test result. /// @@ -653,7 +749,7 @@ public void GetCustomPropertyValueFromTestCaseShouldReadCategoyrAttributesFromTe testCase1.SetPropertyValue(testProperty, new[] { "ClassLevel", "AsmLevel" }); - var converter = new Converter(new Mock().Object); + var converter = new Converter(new Mock().Object, new TrxFileHelper()); List listCategoriesActual = converter.GetCustomPropertyValueFromTestCase(testCase1, "MSTestDiscoverer.TestCategory"); List listCategoriesExpected = new List(); @@ -809,9 +905,9 @@ private string GetElementValueFromTrx(string trxFileName, string fieldName) using (FileStream file = File.OpenRead(trxFileName)) using (XmlReader reader = XmlReader.Create(file)) { - while(reader.Read()) + while (reader.Read()) { - if(reader.Name.Equals(fieldName) && reader.NodeType == XmlNodeType.Element) + if (reader.Name.Equals(fieldName) && reader.NodeType == XmlNodeType.Element) { return reader.ReadElementContentAsString(); } @@ -849,17 +945,22 @@ private static Mock CreatePassTestResultEventArgsMock(strin return new Mock(passResult); } - private void MakeTestRunComplete() + private void MakeTestRunComplete() => this.MakeTestRunComplete(this.testableTrxLogger); + + private void MakeTestRunComplete(TestableTrxLogger testableTrxLogger) { var pass = TrxLoggerTests.CreatePassTestResultEventArgsMock(); - this.testableTrxLogger.TestResultHandler(new object(), pass.Object); + testableTrxLogger.TestResultHandler(new object(), pass.Object); var testRunCompleteEventArgs = TrxLoggerTests.CreateTestRunCompleteEventArgs(); - this.testableTrxLogger.TestRunCompleteHandler(new object(), testRunCompleteEventArgs); + testableTrxLogger.TestRunCompleteHandler(new object(), testRunCompleteEventArgs); } } internal class TestableTrxLogger : TrxLogger { + public TestableTrxLogger() : base() { } + public TestableTrxLogger(IFileHelper fileHelper, TrxFileHelper trxFileHelper) : base(fileHelper, trxFileHelper) { } + public string trxFile; internal override void PopulateTrxFile(string trxFileName, XmlElement rootElement) { diff --git a/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/Utility/ConverterTests.cs b/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/Utility/ConverterTests.cs index 1734b9c15d..0c5dd9789a 100644 --- a/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/Utility/ConverterTests.cs +++ b/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/Utility/ConverterTests.cs @@ -25,11 +25,13 @@ public class ConverterTests { private Converter converter; private Mock fileHelper; + private readonly TrxFileHelper trxFileHelper; public ConverterTests() { this.fileHelper = new Mock(); - this.converter = new Converter(this.fileHelper.Object); + this.trxFileHelper = new TrxFileHelper(); + this.converter = new Converter(this.fileHelper.Object, trxFileHelper); } [TestMethod] @@ -67,7 +69,7 @@ public void ToCollectionEntriesShouldRenameAttachmentUriIfTheAttachmentNameIsSam { ConverterTests.SetupForToCollectionEntries(out var tempDir, out var attachmentSets, out var testRun, out var testResultsDirectory); - this.converter = new Converter(new VisualStudio.TestPlatform.Utilities.Helpers.FileHelper()); + this.converter = new Converter(new FileHelper(), trxFileHelper); List collectorDataEntries = this.converter.ToCollectionEntries(attachmentSets, testRun, testResultsDirectory); Assert.AreEqual($@"{Environment.MachineName}\123.coverage", ((ObjectModel.UriDataAttachment) collectorDataEntries[0].Attachments[0]).Uri.OriginalString); @@ -156,7 +158,7 @@ public void ToResultFilesShouldAddAttachmentsWithRelativeURI() }; var testRun = new TestRun(Guid.NewGuid()); - testRun.RunConfiguration = new TestRunConfiguration("Testrun 1"); + testRun.RunConfiguration = new TestRunConfiguration("Testrun 1", trxFileHelper); attachmentSets[0].Attachments.Add(uriDataAttachment1); var resultFiles = this.converter.ToResultFiles(attachmentSets, testRun, @"c:\temp", null); @@ -214,7 +216,7 @@ private static void SetupForToCollectionEntries(out string tempDir, out List - + diff --git a/test/Microsoft.TestPlatform.TestUtilities/PerfInstrumentation/PerfAnalyzer.cs b/test/Microsoft.TestPlatform.TestUtilities/PerfInstrumentation/PerfAnalyzer.cs index d4ad706bbd..8d8922693e 100644 --- a/test/Microsoft.TestPlatform.TestUtilities/PerfInstrumentation/PerfAnalyzer.cs +++ b/test/Microsoft.TestPlatform.TestUtilities/PerfInstrumentation/PerfAnalyzer.cs @@ -5,7 +5,7 @@ namespace Microsoft.TestPlatform.TestUtilities.PerfInstrumentation { using System.Collections.Generic; -#if NET451 +#if NETFRAMEWORK using Microsoft.Diagnostics.Tracing; using Microsoft.Diagnostics.Tracing.Parsers; using Microsoft.Diagnostics.Tracing.Session; @@ -23,7 +23,7 @@ public class PerfAnalyzer /// private const string ETWSessionProviderName = "TestPlatform"; -#if NET451 +#if NETFRAMEWORK private string perfDataFileName; private TraceEventSession traceEventSession; private Dictionary> testPlatformTaskMap; @@ -34,7 +34,7 @@ public class PerfAnalyzer /// public PerfAnalyzer() { -#if NET451 +#if NETFRAMEWORK this.perfDataFileName = "TestPlatformEventsData.etl"; this.testPlatformTaskMap = new Dictionary>(); this.traceEventSession = new TraceEventSession("TestPlatformSession", this.perfDataFileName); @@ -46,7 +46,7 @@ public PerfAnalyzer() /// public void EnableProvider() { -#if NET451 +#if NETFRAMEWORK this.traceEventSession.StopOnDispose = true; this.traceEventSession.EnableProvider(ETWSessionProviderName); #endif @@ -57,7 +57,7 @@ public void EnableProvider() /// public void DisableProvider() { -#if NET451 +#if NETFRAMEWORK this.traceEventSession.Dispose(); #endif } @@ -67,7 +67,7 @@ public void DisableProvider() /// public void AnalyzeEventsData() { -#if NET451 +#if NETFRAMEWORK using (var source = new ETWTraceEventSource(this.perfDataFileName)) { // Open the file @@ -121,7 +121,7 @@ public void AnalyzeEventsData() public double GetElapsedTimeByTaskName(string taskName) { var timeTaken = 0.0; -#if NET451 +#if NETFRAMEWORK var key = GetEventKey(taskName); if (key != null) @@ -145,7 +145,7 @@ public double GetElapsedTimeByTaskName(string taskName) public IDictionary GetEventDataByTaskName(string taskName) { IDictionary properties = new Dictionary(); -#if NET451 +#if NETFRAMEWORK var key = GetEventKey(taskName); if(key != null) @@ -159,7 +159,7 @@ public IDictionary GetEventDataByTaskName(string taskName) public double GetAdapterExecutionTime(string executorUri) { var timeTaken = 0.0; -#if NET451 +#if NETFRAMEWORK var key = GetEventKey(Constants.AdapterExecutionTask); if(key != null) @@ -174,7 +174,7 @@ public double GetAdapterExecutionTime(string executorUri) public long GetAdapterExecutedTests(string executorUri) { long totalTestsExecuted = 0; -#if NET451 +#if NETFRAMEWORK var key = GetEventKey(Constants.AdapterExecutionTask); if (key != null) @@ -186,7 +186,7 @@ public long GetAdapterExecutedTests(string executorUri) return totalTestsExecuted; } -#if NET451 +#if NETFRAMEWORK private string GetEventKey(string taskName) { diff --git a/test/Microsoft.TestPlatform.TestUtilities/PerfInstrumentation/PerformanceTestBase.cs b/test/Microsoft.TestPlatform.TestUtilities/PerfInstrumentation/PerformanceTestBase.cs index 57f3b0adcd..476d7c7684 100644 --- a/test/Microsoft.TestPlatform.TestUtilities/PerfInstrumentation/PerformanceTestBase.cs +++ b/test/Microsoft.TestPlatform.TestUtilities/PerfInstrumentation/PerformanceTestBase.cs @@ -36,14 +36,14 @@ public PerformanceTestBase() public void RunExecutionPerformanceTests(string testAsset, string testAdapterPath, string runSettings) { // Start session and listen -#if NET451 +#if NETFRAMEWORK this.perfAnalyzer.EnableProvider(); #endif // Run Test this.InvokeVsTestForExecution(testAsset, testAdapterPath, ".NETFramework,Version=v4.5.1", runSettings); // Stop Listening -#if NET451 +#if NETFRAMEWORK this.perfAnalyzer.DisableProvider(); #endif } @@ -63,14 +63,14 @@ public void RunExecutionPerformanceTests(string testAsset, string testAdapterPat public void RunDiscoveryPerformanceTests(string testAsset, string testAdapterPath, string runSettings) { // Start session and listen -#if NET451 +#if NETFRAMEWORK this.perfAnalyzer.EnableProvider(); #endif // Run Test this.InvokeVsTestForDiscovery(testAsset, testAdapterPath, runSettings, ".NETFramework,Version=v4.5.1"); // Stop Listening -#if NET451 +#if NETFRAMEWORK this.perfAnalyzer.DisableProvider(); #endif } diff --git a/test/TestAssets/SimpleDataCollector/Class1.cs b/test/TestAssets/SimpleDataCollector/Class1.cs index f052d92c23..50998b8881 100644 --- a/test/TestAssets/SimpleDataCollector/Class1.cs +++ b/test/TestAssets/SimpleDataCollector/Class1.cs @@ -39,7 +39,7 @@ public void TestSessionStart(TestSessionStartArgs testSessionStartArgs) { Console.WriteLine(testSessionStartArgs.Configuration); File.WriteAllText(this.fileName, "TestSessionStart : " + testSessionStartArgs.Configuration + "\r\n"); -#if NET451 +#if NETFRAMEWORK var appDomainFilePath = Path.Combine(Path.GetTempPath(), "appdomain_datacollector.txt"); File.WriteAllText(appDomainFilePath, "AppDomain FriendlyName: "+ AppDomain.CurrentDomain.FriendlyName); #endif diff --git a/test/TestAssets/SimpleTestProject/UnitTest1.cs b/test/TestAssets/SimpleTestProject/UnitTest1.cs index ccb01aef4c..fba55f698b 100644 --- a/test/TestAssets/SimpleTestProject/UnitTest1.cs +++ b/test/TestAssets/SimpleTestProject/UnitTest1.cs @@ -31,7 +31,7 @@ public void PassingTest() [TestMethod] public void FailingTest() { -#if NET451 +#if NETFRAMEWORK // current App domain should be write to file to test DisableAppDomain acceptance test. var appDomainFilePath = Path.Combine(Path.GetTempPath(), "appdomain_test.txt"); File.WriteAllText(appDomainFilePath, "AppDomain FriendlyName: " + AppDomain.CurrentDomain.FriendlyName); diff --git a/test/TestAssets/SimpleTestProject3/UnitTest1.cs b/test/TestAssets/SimpleTestProject3/UnitTest1.cs index 1e3ad8f003..3ce4c563cc 100644 --- a/test/TestAssets/SimpleTestProject3/UnitTest1.cs +++ b/test/TestAssets/SimpleTestProject3/UnitTest1.cs @@ -7,7 +7,7 @@ namespace SampleUnitTestProject3 using System.IO; using System.Reflection; using System.Threading; -#if NET451 +#if NETFRAMEWORK using System.Windows.Forms; #endif @@ -36,7 +36,7 @@ public void ExitWithStackoverFlow() Span s = stackalloc byte[int.MaxValue]; } -#if NET451 +#if NETFRAMEWORK [TestMethod] public void UITestMethod() { diff --git a/test/testhost.UnitTests/AppDomainEngineInvokerTests.cs b/test/testhost.UnitTests/AppDomainEngineInvokerTests.cs index e2e4738226..4d75b7ec9c 100644 --- a/test/testhost.UnitTests/AppDomainEngineInvokerTests.cs +++ b/test/testhost.UnitTests/AppDomainEngineInvokerTests.cs @@ -3,7 +3,7 @@ namespace testhost.UnitTests { -#if NET451 +#if NETFRAMEWORK using Microsoft.VisualStudio.TestPlatform.TestHost; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; diff --git a/test/testhost.UnitTests/DefaultEngineInvokerTests.cs b/test/testhost.UnitTests/DefaultEngineInvokerTests.cs index 0c129ec34a..34fb8c9a31 100644 --- a/test/testhost.UnitTests/DefaultEngineInvokerTests.cs +++ b/test/testhost.UnitTests/DefaultEngineInvokerTests.cs @@ -99,7 +99,7 @@ public void InvokeShouldSetParentProcessExistCallback() public void InvokeShouldInitializeTraceWithCorrectTraceLevel() { // Setting EqtTrace.TraceLevel to a value other than info. -#if NET451 +#if NETFRAMEWORK EqtTrace.TraceLevel = TraceLevel.Verbose; #else EqtTrace.TraceLevel = PlatformTraceLevel.Verbose; @@ -115,7 +115,7 @@ public void InvokeShouldInitializeTraceWithCorrectTraceLevel() public void InvokeShouldInitializeTraceWithVerboseTraceLevelIfInvalidTraceLevelPassed() { // Setting EqtTrace.TraceLevel to a value other than info. -#if NET451 +#if NETFRAMEWORK EqtTrace.TraceLevel = TraceLevel.Warning; #else EqtTrace.TraceLevel = PlatformTraceLevel.Warning; diff --git a/test/vstest.console.UnitTests/Internal/ConsoleLoggerTests.cs b/test/vstest.console.UnitTests/Internal/ConsoleLoggerTests.cs index eeaa16aeba..45cc4f5553 100644 --- a/test/vstest.console.UnitTests/Internal/ConsoleLoggerTests.cs +++ b/test/vstest.console.UnitTests/Internal/ConsoleLoggerTests.cs @@ -115,7 +115,7 @@ public void InitializeWithParametersShouldDefaultToNormalVerbosityLevelForInvali this.consoleLogger.Initialize(new Mock().Object, parameters); -#if NET451 +#if NETFRAMEWORK Assert.AreEqual(ConsoleLogger.Verbosity.Normal, this.consoleLogger.VerbosityLevel); #else Assert.AreEqual(ConsoleLogger.Verbosity.Minimal, this.consoleLogger.VerbosityLevel); diff --git a/test/vstest.console.UnitTests/Processors/EnableBlameArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/EnableBlameArgumentProcessorTests.cs index 807c188eb2..a691cbe5dc 100644 --- a/test/vstest.console.UnitTests/Processors/EnableBlameArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/EnableBlameArgumentProcessorTests.cs @@ -105,7 +105,6 @@ public void InitializeShouldWarnIfPlatformNotSupportedForCollectDumpOption() var unsupportedPlatforms = new List>() { - Tuple.Create(PlatformOperatingSystem.Unix, PlatformArchitecture.X64), Tuple.Create(PlatformOperatingSystem.Windows, PlatformArchitecture.ARM), Tuple.Create(PlatformOperatingSystem.Windows, PlatformArchitecture.ARM64) }; diff --git a/test/vstest.console.UnitTests/Processors/EnableDiagArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/EnableDiagArgumentProcessorTests.cs index 19f43fc542..a74fa767b9 100644 --- a/test/vstest.console.UnitTests/Processors/EnableDiagArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/EnableDiagArgumentProcessorTests.cs @@ -35,7 +35,7 @@ public EnableDiagArgumentProcessorTests() this.diagProcessor = new TestableEnableDiagArgumentProcessor(this.mockFileHelper.Object); // Saving the EqtTrace state -#if NET451 +#if NETFRAMEWORK traceLevel = EqtTrace.TraceLevel; EqtTrace.TraceLevel = TraceLevel.Off; #else @@ -51,7 +51,7 @@ public void Cleanup() { // Restoring to initial state for EqtTrace EqtTrace.InitializeTrace(traceFileName, PlatformTraceLevel.Verbose); -#if NET451 +#if NETFRAMEWORK EqtTrace.TraceLevel = traceLevel; #else EqtTrace.TraceLevel = (PlatformTraceLevel)traceLevel; @@ -137,7 +137,7 @@ public void EnableDiagArgumentProcessorExecutorShouldNotThrowIfValidArgument(str public void EnableDiagArgumentProcessorExecutorShouldInitializeTraceWithCorrectTraceLevel(string argument) { // Setting any trace level other than info. -#if NET451 +#if NETFRAMEWORK EqtTrace.TraceLevel = TraceLevel.Verbose; #else EqtTrace.TraceLevel = PlatformTraceLevel.Verbose; @@ -174,7 +174,7 @@ public void EnableDiagArgumentProcessorExecutorShouldDisableVerboseLoggingIfEqtT this.diagProcessor.Executor.Value.Initialize(this.dummyFilePath); Assert.IsTrue(!EqtTrace.IsVerboseEnabled); -#if NET451 +#if NETFRAMEWORK EqtTrace.TraceLevel = TraceLevel.Off; #else EqtTrace.TraceLevel = PlatformTraceLevel.Off; diff --git a/test/vstest.console.UnitTests/Processors/EnableLoggersArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/EnableLoggersArgumentProcessorTests.cs index 2e38a8579f..c7c7d367b2 100644 --- a/test/vstest.console.UnitTests/Processors/EnableLoggersArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/EnableLoggersArgumentProcessorTests.cs @@ -40,7 +40,7 @@ public void CapabilitiesShouldAppropriateProperties() { EnableLoggerArgumentProcessorCapabilities capabilities = new EnableLoggerArgumentProcessorCapabilities(); Assert.AreEqual("/Logger", capabilities.CommandName); -#if NET451 +#if NETFRAMEWORK Assert.AreEqual("--logger|/logger:" + Environment.NewLine + " Specify a logger for test results. For example, to log results into a " + Environment.NewLine + " Visual Studio Test Results File (TRX) use /logger:trx[;LogFileName=]" + Environment.NewLine + " Creates file in TestResults directory with given LogFileName." + Environment.NewLine + "" + Environment.NewLine + " Change the verbosity level in log messages for console logger as shown below" + Environment.NewLine + " Example: /logger:console;verbosity=" + Environment.NewLine + " Allowed values for verbosity: quiet, minimal, normal and detailed." + Environment.NewLine + "" + Environment.NewLine + " Change the diagnostic level prefix for console logger as shown below" + Environment.NewLine + " Example: /logger:console;prefix=" + Environment.NewLine + " More info on Console Logger here : https://aka.ms/console-logger", capabilities.HelpContentResourceName); #else Assert.AreEqual("--logger|/logger:" + Environment.NewLine + " Specify a logger for test results. For example, to log results into a " + Environment.NewLine + " Visual Studio Test Results File (TRX) use /logger:trx[;LogFileName=]" + Environment.NewLine + " Creates file in TestResults directory with given LogFileName." + Environment.NewLine + "" + Environment.NewLine + " Change the verbosity level in log messages for console logger as shown below" + Environment.NewLine + " Example: /logger:console;verbosity=" + Environment.NewLine + " Allowed values for verbosity: quiet, minimal, normal and detailed." + Environment.NewLine + "" + Environment.NewLine + " Change the diagnostic level prefix for console logger as shown below" + Environment.NewLine + " Example: /logger:console;prefix=" + Environment.NewLine + " More info on Console Logger here : https://aka.ms/console-logger", capabilities.HelpContentResourceName); diff --git a/test/vstest.console.UnitTests/Processors/EnvironmentArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/EnvironmentArgumentProcessorTests.cs new file mode 100644 index 0000000000..5af480c51b --- /dev/null +++ b/test/vstest.console.UnitTests/Processors/EnvironmentArgumentProcessorTests.cs @@ -0,0 +1,183 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors +{ + using System; + using System.Linq; + using System.Xml.Linq; + using Microsoft.VisualStudio.TestPlatform.CommandLine; + using Microsoft.VisualStudio.TestPlatform.CommandLine.Processors; + using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; + using Microsoft.VisualStudio.TestPlatform.Common.Utilities; + using Microsoft.VisualStudio.TestPlatform.Utilities; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Moq; + using vstest.console.UnitTests.Processors; + using CommandLineResources = Microsoft.VisualStudio.TestPlatform.CommandLine.Resources.Resources; + + [TestClass] + public class EnvironmentArgumentProcessorTests + { + private const string DefaultRunSettings = ""; + + private TestableRunSettingsProvider settingsProvider; + private Mock mockOutput; + private CommandLineOptions commandLineOptions; + + [TestInitialize] + public void Initialize() + { + this.commandLineOptions = CommandLineOptions.Instance; + this.settingsProvider = new TestableRunSettingsProvider(); + this.settingsProvider.UpdateRunSettings(DefaultRunSettings); + this.mockOutput = new Mock(); + } + + [TestCleanup] + public void Cleanup() + { + this.commandLineOptions.Reset(); + } + + [TestMethod] + public void CapabilitiesShouldReturnAppropriateValues() + { + // Arrange & Act + var capabilities = new EnvironmentArgumentProcessor.ArgumentProcessorCapabilities(); + + // Assert + Assert.AreEqual("/Environment", capabilities.CommandName); + Assert.AreEqual("/e", capabilities.ShortCommandName); + Assert.IsTrue(capabilities.AllowMultiple); + Assert.IsFalse(capabilities.IsAction); + Assert.AreEqual(ArgumentProcessorPriority.Normal, capabilities.Priority); + Assert.AreEqual(HelpContentPriority.EnvironmentArgumentProcessorHelpPriority, capabilities.HelpPriority); + } + + [TestMethod] + public void AppendsEnvironmentVariableToRunSettings() + { + // Arrange + var executor = GetExecutor(); + + // Act + executor.Initialize("VARIABLE=VALUE"); + + // Assert + var (environmentVariables, inIsolation) = ParseSettingsXML(this.settingsProvider); + var variables = environmentVariables?.Elements()?.ToArray(); + + Assert.IsNotNull(environmentVariables, "Environment variable cannot found in RunSettings.xml."); + Assert.IsNotNull(inIsolation, "Isolation must be forced, an InIsolation entry was missing!"); + Assert.AreEqual(1, variables?.Length ?? 0, "Environment variable count mismatched!"); + + Assert.AreEqual("true", inIsolation.Value, "Isolation must be forced, InIsolation is not set to true."); + Assert.AreEqual("VARIABLE", variables[0].Name.LocalName); + Assert.AreEqual("VALUE", variables[0].Value); + + } + + [TestMethod] + public void AppendsMultipleEnvironmentVariablesToRunSettings() + { + // Arrange + var (executor1, executor2, executor3) = (GetExecutor(), GetExecutor(), GetExecutor()); + + // Act + executor1.Initialize("VARIABLE_ONE=VALUE"); + executor2.Initialize("VARIABLE_TWO=VALUE WITH SPACE"); + executor3.Initialize("VARIABLE_THREE=VALUE WITH SPACE;AND SEMICOLON"); + + // Assert + var (environmentVariables, inIsolation) = ParseSettingsXML(this.settingsProvider); + var variables = environmentVariables?.Elements()?.ToArray(); + + Assert.IsNotNull(environmentVariables, "Environment variable cannot found in RunSettings.xml."); + Assert.IsNotNull(inIsolation, "Isolation must be forced, an InIsolation entry was missing!"); + + Assert.AreEqual("true", inIsolation.Value, "Isolation must be forced, InIsolation is not set to true."); + Assert.AreEqual(3, variables?.Length ?? 0, "Environment variable count mismatched!"); + + Assert.AreEqual("VARIABLE_ONE", variables[0].Name.LocalName); + Assert.AreEqual("VALUE", variables[0].Value); + + Assert.AreEqual("VARIABLE_TWO", variables[1].Name.LocalName); + Assert.AreEqual("VALUE WITH SPACE", variables[1].Value); + + Assert.AreEqual("VARIABLE_THREE", variables[2].Name.LocalName); + Assert.AreEqual("VALUE WITH SPACE;AND SEMICOLON", variables[2].Value); + } + + [TestMethod] + public void InIsolationValueShouldBeOverriden() + { + // Arrange + this.commandLineOptions.InIsolation = false; + this.settingsProvider.UpdateRunSettingsNode(InIsolationArgumentExecutor.RunSettingsPath, "false"); + var executor = GetExecutor(); + + // Act + executor.Initialize("VARIABLE=VALUE"); + + // Assert + var (environmentVariables, inIsolation) = ParseSettingsXML(this.settingsProvider); + var variables = environmentVariables?.Elements()?.ToArray(); + + Assert.IsNotNull(environmentVariables, "Environment variable cannot found in RunSettings.xml."); + Assert.IsNotNull(inIsolation, "Isolation must be forced, an InIsolation entry was missing!"); + + Assert.AreEqual("true", inIsolation.Value, "Isolation must be forced, InIsolation is overriden to true."); + Assert.AreEqual(1, variables?.Length ?? 0, "Environment variable count mismatched!"); + + Assert.AreEqual("VARIABLE", variables[0].Name.LocalName); + Assert.AreEqual("VALUE", variables[0].Value); + } + + [TestMethod] + public void ShoudWarnWhenAValueIsOverriden() + { + // Arrange + this.settingsProvider.UpdateRunSettingsNode("RunConfiguration.EnvironmentVariables.VARIABLE", "Initial value"); + var warningMessage = String.Format(CommandLineResources.CommandLineWarning, + String.Format(CommandLineResources.EnvironmentVariableXIsOverriden, "VARIABLE") + ); + this.mockOutput.Setup(mock => + mock.WriteLine( + It.Is(message => message == warningMessage), + It.Is(level => level == OutputLevel.Warning) + ) + ).Verifiable(); + var executor = GetExecutor(); + + // Act + executor.Initialize("VARIABLE=New value"); + + // Assert + this.mockOutput.VerifyAll(); + } + + private (XElement variables, XElement inIsolation) ParseSettingsXML(IRunSettingsProvider provider) + { + var document = XDocument.Parse(provider.ActiveRunSettings.SettingsXml); + + var runConfiguration = document + ?.Root + ?.Element("RunConfiguration"); + + var variables = runConfiguration?.Element("EnvironmentVariables"); + var inIsolation = runConfiguration?.Element("InIsolation"); + + return (variables, inIsolation); + } + + private IArgumentExecutor GetExecutor() + { + return new EnvironmentArgumentProcessor.ArgumentExecutor( + this.commandLineOptions, + this.settingsProvider, + mockOutput.Object + ); + } + } +} diff --git a/test/vstest.console.UnitTests/Processors/ResultsDirectoryArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/ResultsDirectoryArgumentProcessorTests.cs index b86e0ceeb1..d7e1d562d6 100644 --- a/test/vstest.console.UnitTests/Processors/ResultsDirectoryArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/ResultsDirectoryArgumentProcessorTests.cs @@ -94,7 +94,7 @@ public void InitializeShouldThrowIfGivenPathisIllegal() folder, "The filename, directory name, or volume label syntax is incorrect : \'c:\\som>\\illegal\\path\\\'"); -#if NET451 +#if NETFRAMEWORK message = string.Format( @"The path '{0}' specified in the 'ResultsDirectory' is invalid. Error: {1}", folder, @@ -113,7 +113,7 @@ public void InitializeShouldThrowIfPathIsNotSupported() @"The path '{0}' specified in the 'ResultsDirectory' is invalid. Error: {1}", folder, "The directory name is invalid : \'c:\\path\\to\\in:valid\'"); -#if NET451 +#if NETFRAMEWORK message = string.Format( @"The path '{0}' specified in the 'ResultsDirectory' is invalid. Error: {1}", folder, diff --git a/test/vstest.console.UnitTests/vstest.console.UnitTests.csproj b/test/vstest.console.UnitTests/vstest.console.UnitTests.csproj index 2821f05c9e..b020663932 100644 --- a/test/vstest.console.UnitTests/vstest.console.UnitTests.csproj +++ b/test/vstest.console.UnitTests/vstest.console.UnitTests.csproj @@ -19,12 +19,15 @@ true + + + - +