diff --git a/Tasks/VsTest/Helpers.ps1 b/Tasks/VsTest/Helpers.ps1 deleted file mode 100644 index a897aca45340..000000000000 --- a/Tasks/VsTest/Helpers.ps1 +++ /dev/null @@ -1,599 +0,0 @@ -function Test-Container { - [CmdletBinding()] - param( - [Parameter(Mandatory = $true)] - [string]$LiteralPath) - - Write-Host "Testing container: '$LiteralPath'" - if ((Test-Path -LiteralPath $LiteralPath -PathType Container)) { - Write-Host 'Exists.' - return $true - } - Write-Host 'Does not exist.' - return $false -} - - - -function Test-Leaf { - [CmdletBinding()] - param( - [Parameter(Mandatory = $true)] - [string]$LiteralPath) - - Write-Host "Testing leaf: '$LiteralPath'" - if ((Test-Path -LiteralPath $LiteralPath -PathType Leaf)) { - Write-Host 'Exists.' - return $true - } - Write-Host 'Does not exist.' - return $false -} - -function Get-VSTestConsole15Path { - [CmdletBinding()] - param( - [Parameter(Mandatory = $true)] - [string]$Path) - - $shellFolder15 = $Path.TrimEnd('\'[0]) + '\' - $installDir15 = ([System.IO.Path]::Combine($shellFolder15, 'Common7', 'IDE')) + '\' - $testWindowDir15 = [System.IO.Path]::Combine($installDir15, 'CommonExtensions', 'Microsoft', 'TestWindow') + '\' - $vstestConsole15 = [System.IO.Path]::Combine($testWindowDir15, 'vstest.console.exe') - return $vstestConsole15 -} - -function Get-VisualStudio_15_0 { - - [CmdletBinding()] - - param() - - - - try { - - # Short-circuit if the setup configuration class ID isn't registered. - - if (!(Test-Container -LiteralPath 'REGISTRY::HKEY_CLASSES_ROOT\CLSID\{177F0C4A-1CD3-4DE7-A32C-71DBBB9FA36D}')) { - - return - - } - - - - # If the type has already been loaded once, then it is not loaded again. - - Write-Host "Adding Visual Studio setup helpers." - - Add-Type -Debug:$false -TypeDefinition @' - -namespace CapabilityHelpers.VisualStudio.Setup - -{ - - using System; - - using System.Collections.Generic; - - using CapabilityHelpers.VisualStudio.Setup.Com; - - - - public sealed class Instance - - { - - public string Description { get; set; } - - - - public string DisplayName { get; set; } - - - - public string Id { get; set; } - - - - public System.Runtime.InteropServices.ComTypes.FILETIME InstallDate { get; set; } - - - - public string Name { get; set; } - - - - public string Path { get; set; } - - - - public Version Version - - { - - get - - { - - try - - { - - return new Version(VersionString); - - } - - catch (Exception) - - { - - return new Version(0, 0); - - } - - } - - } - - - - public string VersionString { get; set; } - - - - public static List GetInstances() - - { - - List list = new List(); - - ISetupConfiguration config = new SetupConfiguration() as ISetupConfiguration; - - IEnumSetupInstances enumInstances = config.EnumInstances(); - - ISetupInstance[] instances = new ISetupInstance[1]; - - int fetched = 0; - - enumInstances.Next(1, instances, out fetched); - - while (fetched > 0) - - { - - ISetupInstance instance = instances[0]; - - list.Add(new Instance() - - { - - Description = instance.GetDescription(), - - DisplayName = instance.GetDisplayName(), - - Id = instance.GetInstanceId(), - - InstallDate = instance.GetInstallDate(), - - Name = instance.GetInstallationName(), - - Path = instance.GetInstallationPath(), - - VersionString = instance.GetInstallationVersion(), - - }); - - - - enumInstances.Next(1, instances, out fetched); - - } - - - - return list; - - } - - } - -} - - - -namespace CapabilityHelpers.VisualStudio.Setup.Com - -{ - - using System; - - using System.Runtime.InteropServices; - - - - [ComImport] - - [Guid("6380BCFF-41D3-4B2E-8B2E-BF8A6810C848")] - - [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - - public interface IEnumSetupInstances - - { - - void Next( - - [In, MarshalAs(UnmanagedType.U4)] int celt, - - [Out, MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.Interface)] ISetupInstance[] rgelt, - - [Out, MarshalAs(UnmanagedType.U4)] out int pceltFetched); - - - - void Skip([In, MarshalAs(UnmanagedType.U4)] int celt); - - - - void Reset(); - - - - [return: MarshalAs(UnmanagedType.Interface)] - - IEnumSetupInstances Clone(); - - } - - - - [ComImport] - - [Guid("42843719-DB4C-46C2-8E7C-64F1816EFD5B")] - - [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - - public interface ISetupConfiguration - - { - - [return: MarshalAs(UnmanagedType.Interface)] - - IEnumSetupInstances EnumInstances(); - - } - - - - [ComImport] - - [Guid("B41463C3-8866-43B5-BC33-2B0676F7F42E")] - - [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - - public interface ISetupInstance - - { - - [return: MarshalAs(UnmanagedType.BStr)] - - string GetInstanceId(); - - - - [return: MarshalAs(UnmanagedType.Struct)] - - System.Runtime.InteropServices.ComTypes.FILETIME GetInstallDate(); - - - - [return: MarshalAs(UnmanagedType.BStr)] - - string GetInstallationName(); - - - - [return: MarshalAs(UnmanagedType.BStr)] - - string GetInstallationPath(); - - - - [return: MarshalAs(UnmanagedType.BStr)] - - string GetInstallationVersion(); - - - - [return: MarshalAs(UnmanagedType.BStr)] - - string GetDisplayName([In, MarshalAs(UnmanagedType.U4)] int lcid = default(int)); - - - - [return: MarshalAs(UnmanagedType.BStr)] - - string GetDescription([In, MarshalAs(UnmanagedType.U4)] int lcid = default(int)); - - } - - - - [ComImport] - - [Guid("42843719-DB4C-46C2-8E7C-64F1816EFD5B")] - - [CoClass(typeof(SetupConfigurationClass))] - - [TypeLibImportClass(typeof(SetupConfigurationClass))] - - public interface SetupConfiguration : ISetupConfiguration - - { - - } - - - - [ComImport] - - [Guid("177F0C4A-1CD3-4DE7-A32C-71DBBB9FA36D")] - - [ClassInterface(ClassInterfaceType.None)] - - public class SetupConfigurationClass - - { - - } - -} - -'@ - - Write-Host "Getting Visual Studio setup instances." - - $instances = @( [CapabilityHelpers.VisualStudio.Setup.Instance]::GetInstances() ) - - Write-Host "Found $($instances.Count) instances." - - Write-Host ($instances | Format-List * | Out-String) - - return $instances | - - Where-Object { $_.Version.Major -eq 15 -and $_.Version.Minor -eq 0 } | - - Sort-Object -Descending -Property Version | - - Select-Object -First 1 - - } catch { - - Write-Host ($_ | Out-String) - - } - -} - -function CmdletHasMember { - [cmdletbinding()] - [OutputType([System.Boolean])] - param( - [string]$memberName - ) - - $publishParameters = (gcm Publish-TestResults).Parameters.Keys.Contains($memberName) - return $publishParameters -} - -function InvokeVsTestCmdletHasMember { - [cmdletbinding()] - [OutputType([System.Boolean])] - param( - [string]$memberName - ) - - $invokeVstestParams = (gcm Invoke-VSTest).Parameters.Keys.Contains($memberName) - return $invokeVstestParams -} - -function SetRegistryKeyForParallel { - [cmdletbinding()] - param( - [string]$vsTestVersion - ) - - $regkey = "HKCU\SOFTWARE\Microsoft\VisualStudio\" + $vsTestVersion + "_Config\FeatureFlags\TestingTools\UnitTesting\Taef" - reg add $regkey /v Value /t REG_DWORD /d 1 /f /reg:32 > $null -} - -function IsVisualStudio2015Update1OrHigherInstalled { - [cmdletbinding()] - [OutputType([System.Boolean])] - param( - [string]$vsTestVersion, - [string]$vsTestLocation - ) - - if ([string]::IsNullOrWhiteSpace($vsTestVersion)) - { - $vsTestVersion = Get-VSVersion $vsTestLocation - } - - $version = [int]($vsTestVersion) - # with dev15 we are back to vstest and away from taef - if($version -ge 15) - { - return $true - } - - if($version -eq 14) - { - # checking for dll introduced in vs2015 update1 - # since path of the dll will change in dev15+ using vstestversion>14 as a blanket yes - $teModesDll = [io.path]::Combine("$env:VS140COMNTools", "..", "IDE", "CommonExtensions", "Microsoft", "TestWindow", "TE.TestModes.dll"); - if(Test-Path -Path $teModesDll) - { - $devenvExe = [io.path]::Combine("$env:VS140COMNTools", "..", "IDE", "devenv.exe"); - $devenvVersion = [System.Diagnostics.FileVersionInfo]::GetVersionInfo($devenvExe); - if($devenvVersion.ProductBuildPart -lt 25420) #update3 build# - { - # ensure the registry is set otherwise you need to launch VSIDE - SetRegistryKeyForParallel $vsTestVersion - } - - return $true - } - } - - return $false -} - -function SetupRunSettingsFileForParallel { - [cmdletbinding()] - [OutputType([System.String])] - param( - [string]$runInParallelFlag, - [string]$runSettingsFilePath, - [string]$defaultCpuCount - ) - - if($runInParallelFlag -eq "True") - { - if([string]::Compare([io.path]::GetExtension($runSettingsFilePath), ".testsettings", $True) -eq 0) - { - Write-Warning "Run in Parallel is not supported with testsettings file." - } - else - { - $runSettingsForParallel = [xml]'' - if([System.String]::IsNullOrWhiteSpace($runSettingsFilePath) -Or ([string]::Compare([io.path]::GetExtension($runSettingsFilePath), ".runsettings", $True) -ne 0) -Or (Test-Path $runSettingsFilePath -pathtype container)) # no file provided so create one and use it for the run - { - Write-Verbose "No runsettings file provided" - $runSettingsForParallel = [xml]' - - - 0 - - - ' - } - else - { - Write-Verbose "Adding maxcpucount element to runsettings file provided" - $runSettingsForParallel = [System.Xml.XmlDocument](Get-Content $runSettingsFilePath) - $runConfigurationElement = $runSettingsForParallel.SelectNodes("//RunSettings/RunConfiguration") - if($runConfigurationElement.Count -eq 0) - { - $runConfigurationElement = $runSettingsForParallel.RunSettings.AppendChild($runSettingsForParallel.CreateElement("RunConfiguration")) - } - - $maxCpuCountElement = $runSettingsForParallel.SelectNodes("//RunSettings/RunConfiguration/MaxCpuCount") - if($maxCpuCountElement.Count -eq 0) - { - $newMaxCpuCountElement = $runConfigurationElement.AppendChild($runSettingsForParallel.CreateElement("MaxCpuCount")) - } - } - - $runSettingsForParallel.RunSettings.RunConfiguration.MaxCpuCount = $defaultCpuCount - $tempFile = [io.path]::GetTempFileName() - $runSettingsForParallel.Save($tempFile) - Write-Verbose "Temporary runsettings file created at $tempFile" - return $tempFile - } - } - - return $runSettingsFilePath -} - -function Get-SubKeysInFloatFormat($keys) -{ - $targetKeys = @() # New array - foreach ($key in $keys) - { - $targetKeys += $key -as [decimal] - } - - return $targetKeys -} - -function Get-VSVersion($vsTestLocation) -{ - if(![String]::IsNullOrWhiteSpace($vsTestLocation)) - { - Write-Verbose "Using vstest location provided to get the version" - $vstestConsoleExeVersion = [System.Diagnostics.FileVersionInfo]::GetVersionInfo($vsTestLocation); - return $vstestConsoleExeVersion.ProductMajorPart; - } - - #Find the latest version - $regPath = "HKLM:\SOFTWARE\Microsoft\VisualStudio" - if (-not (Test-Path $regPath)) - { - return $null - } - - $keys = Get-Item $regPath | %{$_.GetSubKeyNames()} -ErrorAction SilentlyContinue - $version = Get-SubKeysInFloatFormat $keys | Sort-Object -Descending | Select-Object -First 1 - - if ([string]::IsNullOrWhiteSpace($version)) - { - return $null - } - return $version -} - -function Get-ResultsLocation { - [cmdletbinding()] - [OutputType([System.String])] - param( - [string]$runSettingsFilePath - ) - - # If this is a runsettings file then try to get the custom results location from it - - if(!(CheckIfDirectory $runSettingsFilePath) -And (CheckIfRunsettings $runSettingsFilePath)) - { - $runSettingsForTestResults = [System.Xml.XmlDocument](Get-Content $runSettingsFilePath) - $resultsDirElement = $runSettingsForTestResults.SelectNodes("//RunSettings/RunConfiguration/ResultsDirectory") - - if($resultsDirElement -And $resultsDirElement.Count -ne 0) - { - $customLocation = $runSettingsForTestResults.RunSettings.RunConfiguration.ResultsDirectory - if([io.path]::IsPathRooted($customLocation)) - { - return $customLocation - } - else - { - if(![string]::IsNullOrWhiteSpace($customLocation)) - { - # Resutls directory is relative to the location of runsettings - return [io.path]::GetFullPath([io.path]::Combine([io.path]::GetDirectoryName($runSettingsFilePath), $customLocation)) - } - } - } - } - - return $null -} - -function CheckIfRunsettings($runSettingsFilePath) -{ - if(([string]::Compare([io.path]::GetExtension($runSettingsFilePath), ".runsettings", $True) -eq 0) -Or ([string]::Compare([io.path]::GetExtension($runSettingsFilePath), ".tmp", $True) -eq 0)) - { - return $true - } - return $false -} - -function CheckIfDirectory($filePath) -{ - if(Test-Path $filePath -pathtype container) - { - return $true - } - return $false -} \ No newline at end of file diff --git a/Tasks/VsTest/Strings/resources.resjson/en-US/resources.resjson b/Tasks/VsTest/Strings/resources.resjson/en-US/resources.resjson index ff1cd515ce22..3f44fed930f7 100644 --- a/Tasks/VsTest/Strings/resources.resjson/en-US/resources.resjson +++ b/Tasks/VsTest/Strings/resources.resjson/en-US/resources.resjson @@ -55,12 +55,12 @@ "loc.messages.NoMatchingTestAssemblies": "No test assemblies found matching the pattern: %s.", "loc.messages.VstestNotFound": "Vstest of version %d is not found. Try again with a visual studio version that exists on your build agent machine.", "loc.messages.VstestFailed": "Vstest failed with error. Check logs for failures. There might be failed tests.", - "loc.messages.VstestTIANotSupported": "Install Visual Studio version 15.0.25807 or higher to run Test Impact Analysis.", + "loc.messages.VstestTIANotSupported": "Install Visual Studio 2015 update 3 or Visual Studio 2017 RC or above to run Test Impact Analysis.", "loc.messages.NoResultsToPublish": "No results found to publish.", "loc.messages.ErrorWhileReadingRunSettings": "Error occured while reading run settings file. Error : %s.", "loc.messages.ErrorWhileReadingTestSettings": "Error occured while reading test settings file. Error : %s.", "loc.messages.RunInParallelNotSupported": "Run in Parallel is not supported with testsettings file.", - "loc.messages.FailedToSetRunInParallel": "Failed to set run in parallel. Invalid run settings file.", + "loc.messages.FailedToSetRunConfiguration": "The specified settings file is invalid. Ignoring it.", "loc.messages.UpdateOneOrHigherRequired": "Install Visual Studio 2015 Update 1 or higher on your build agent machine to run the tests in parallel.", "loc.messages.ErrorOccuredWhileSettingRegistry": "Error occured while setting registry key, Error: %s.", "loc.messages.ErrorWhileSettingTestImpactCollectorTestSettings": "Error occurred while setting Test Impact Collector in test settings file.", @@ -76,5 +76,9 @@ "loc.messages.UnexpectedVersionString": "Unexpected version string detected for vstest.console.exe: %s.", "loc.messages.UnexpectedVersionNumber": "Unexpected version number detected for vstest.console.exe: %s.", "loc.messages.VstestDiagNotSupported": "vstest.console.exe version does not support the /diag flag. Enable diagnositics via the exe.config files", - "loc.messages.NoIncludePatternFound": "No include pattern found. Specify atleast one include pattern to search test assemblies." + "loc.messages.NoIncludePatternFound": "No include pattern found. Specify atleast one include pattern to search test assemblies.", + "loc.messages.ErrorWhileUpdatingSettings": "Error occurred while updating the settings file. Using the specified settings file.", + "loc.messages.VideoCollectorNotSupportedWithRunSettings": "Video collector is not supported with run settings.", + "loc.messages.runTestInIsolationNotSupported": "Running tests in isolation is not supported for multi-agent scenario.", + "loc.messages.tiaNotSupportedInDta": "Running only impacted tests is not supported for multi-agent scenario." } \ No newline at end of file diff --git a/Tasks/VsTest/VSTest.ps1 b/Tasks/VsTest/VSTest.ps1 deleted file mode 100644 index 9da3693d0668..000000000000 --- a/Tasks/VsTest/VSTest.ps1 +++ /dev/null @@ -1,292 +0,0 @@ -[cmdletbinding()] -param( - [string]$vsTestVersion, - [string]$testAssembly, - [string]$testFiltercriteria, - [string]$runSettingsFile, - [string]$codeCoverageEnabled, - [string]$pathtoCustomTestAdapters, - [string]$overrideTestrunParameters, - [string]$otherConsoleOptions, - [string]$testRunTitle, - [string]$platform, - [string]$configuration, - [string]$publishRunAttachments, - [string]$runInParallel, - [string]$vstestLocationMethod, - [string]$vstestLocation, - [string]$runOnlyImpactedTests, - [string]$runAllTestsAfterXBuilds - ) - -Write-Verbose "Entering script VSTest.ps1" -Write-Verbose "vsTestVersion = $vsTestVersion" -Write-Verbose "testAssembly = $testAssembly" -Write-Verbose "testFiltercriteria = $testFiltercriteria" -Write-Verbose "runSettingsFile = $runSettingsFile" -Write-Verbose "codeCoverageEnabled = $codeCoverageEnabled" -Write-Verbose "pathtoCustomTestAdapters = $pathtoCustomTestAdapters" -Write-Verbose "overrideTestrunParameters = $overrideTestrunParameters" -Write-Verbose "otherConsoleOptions = $otherConsoleOptions" -Write-Verbose "testRunTitle = $testRunTitle" -Write-Verbose "platform = $platform" -Write-Verbose "configuration = $configuration" -Write-Verbose "publishRunAttachments = $publishRunAttachments" -Write-Verbose "vstestLocation = $vstestLocation" - -# Import the Task.Common and Task.Internal dll that has all the cmdlets we need for Build -import-module "Microsoft.TeamFoundation.DistributedTask.Task.Internal" -import-module "Microsoft.TeamFoundation.DistributedTask.Task.Common" -# Import the Task.TestResults dll that has the cmdlet we need for publishing results -import-module "Microsoft.TeamFoundation.DistributedTask.Task.TestResults" -import-module "Microsoft.TeamFoundation.DistributedTask.Task.CodeCoverage" - -. $PSScriptRoot\Helpers.ps1 - - -$testResultsDirectory="" - -try -{ - if (!$testAssembly) - { - throw (Get-LocalizedString -Key "No test assembly specified. Provide a test assembly parameter and try again.") - } - - $sourcesDirectory = Get-TaskVariable -Context $distributedTaskContext -Name "Build.SourcesDirectory" - if(!$sourcesDirectory) - { - # For RM, look for the test assemblies under the release directory. - $sourcesDirectory = Get-TaskVariable -Context $distributedTaskContext -Name "Agent.ReleaseDirectory" - } - - if(!$sourcesDirectory) - { - # If there is still no sources directory, error out immediately. - throw (Get-LocalizedString -Key "No source directory found.") - } - - $testAssemblyFiles = @() - # check for solution pattern - if ($testAssembly.Contains("*") -Or $testAssembly.Contains("?")) - { - Write-Verbose "Pattern found in solution parameter. Calling Find-Files." - Write-Verbose "Calling Find-Files with pattern: $testAssembly" - $testAssemblyFiles = Find-Files -SearchPattern $testAssembly -RootFolder $sourcesDirectory - Write-Verbose "Found files: $testAssemblyFiles" - } - else - { - Write-Verbose "No Pattern found in solution parameter." - $testAssembly = $testAssembly.Replace(';;', "`0") # Barrowed from Legacy File Handler - foreach ($assembly in $testAssembly.Split(";")) - { - $testAssemblyFiles += ,($assembly.Replace("`0",";")) - } - } - - $codeCoverage = Convert-String $codeCoverageEnabled Boolean - - if($testAssemblyFiles.count -gt 0) - { - Write-Verbose -Verbose "Calling Invoke-VSTest for all test assemblies" - - if($vsTestVersion -eq "latest") - { - # null out vsTestVersion before passing to cmdlet so it will default to the latest on the machine. - $vsTestVersion = $null - } - - $vstestLocationInput = $vstestLocation - if ($vstestLocationMethod -eq "location") - { - Write-Verbose "User has specified vstest location" - if (InvokeVsTestCmdletHasMember "VSTestLocation") - { - $vsTestVersion = $null - if([String]::IsNullOrWhiteSpace($vstestLocation)) - { - throw (Get-LocalizedString -Key "Invalid location specified '{0}'. Provide a valid path to vstest.console.exe and try again" -ArgumentList $vstestLocation) - } - else - { - $vstestLocationInput.Trim() - $vstestConsoleExeName = "vstest.console.exe" - if(!$vstestLocationInput.EndsWith($vstestConsoleExeName, [System.StringComparison]::OrdinalIgnoreCase)) - { - $vstestLocationInput = [io.path]::Combine($vstestLocationInput, $vstestConsoleExeName) - if(![io.file]::Exists($vstestLocationInput)) - { - throw (Get-LocalizedString -Key "Invalid location specified '{0}'. Provide a valid path to vstest.console.exe and try again" -ArgumentList $vstestLocation) - } - } - } - } - else - { - Write-Warning (Get-LocalizedString -Key "Update the agent to try out the '{0}' feature." -ArgumentList "specify vstest location") - $vstestLocationInput = $null - } - } - else - { - Write-Verbose "User has chosen vs version" - $vstestLocationInput = $null - } - - if($runInParallel -eq "True") - { - $rightVSVersionAvailable = IsVisualStudio2015Update1OrHigherInstalled $vsTestVersion $vstestLocationInput - if(-Not $rightVSVersionAvailable) - { - Write-Warning (Get-LocalizedString -Key "Install Visual Studio 2015 Update 1 or higher on your build agent machine to run the tests in parallel.") - $runInParallel = "false" - } - } - - $defaultCpuCount = "0" - $runSettingsFileWithParallel = [string](SetupRunSettingsFileForParallel $runInParallel $runSettingsFile $defaultCpuCount) - - #If there is settings file and no override parameters, try to get the custom resutls location - if(![System.String]::IsNullOrWhiteSpace($runSettingsFileWithParallel) -and !$overrideTestrunParameters) - { - $testResultsDirectory = Get-ResultsLocation $runSettingsFileWithParallel - } - - $buildSourcesDirectory = Get-TaskVariable -Context $distributedTaskContext -Name "Build.SourcesDirectory" - if(![string]::IsNullOrEmpty($buildSourcesDirectory)) - { - #For Build - $workingDirectory = Get-TaskVariable -Context $distributedTaskContext -Name "System.DefaultWorkingDirectory" - } - else - { - #For RM - $workingDirectory = Get-TaskVariable -Context $distributedTaskContext -Name "System.ArtifactsDirectory" - } - - if([string]::IsNullOrEmpty($testResultsDirectory)) - { - $testResultsDirectory = $workingDirectory + [System.IO.Path]::DirectorySeparatorChar + "TestResults" - } - - Write-Verbose "Test results directory: $testResultsDirectory" - - if($runOnlyImpactedTests -eq "True") - { - Write-Warning ("Running all tests. To run only impacted tests, move the task to the node implementation.") - } - - - if (![String]::IsNullOrWhiteSpace($vstestLocationInput) -And (InvokeVsTestCmdletHasMember "VSTestLocation")) - { - Invoke-VSTest -TestAssemblies $testAssemblyFiles -VSTestVersion $vsTestVersion -TestFiltercriteria $testFiltercriteria -RunSettingsFile $runSettingsFileWithParallel -PathtoCustomTestAdapters $pathtoCustomTestAdapters -CodeCoverageEnabled $codeCoverage -OverrideTestrunParameters $overrideTestrunParameters -OtherConsoleOptions $otherConsoleOptions -WorkingFolder $workingDirectory -TestResultsFolder $testResultsDirectory -SourcesDirectory $sourcesDirectory -VSTestLocation $vstestLocationInput - } - else - { - if (![String]::IsNullOrWhiteSpace($vsTestVersion) -and $vsTestVersion -ne "15.0" ) - { - Invoke-VSTest -TestAssemblies $testAssemblyFiles -VSTestVersion $vsTestVersion -TestFiltercriteria $testFiltercriteria -RunSettingsFile $runSettingsFileWithParallel -PathtoCustomTestAdapters $pathtoCustomTestAdapters -CodeCoverageEnabled $codeCoverage -OverrideTestrunParameters $overrideTestrunParameters -OtherConsoleOptions $otherConsoleOptions -WorkingFolder $workingDirectory -TestResultsFolder $testResultsDirectory -SourcesDirectory $sourcesDirectory - } - else # go for latest installation - { - # first find willow installation - $vs15 = Get-VisualStudio_15_0 - if ($vs15 -and $vs15.Path) { - $vstestConsole15 = Get-VSTestConsole15Path -Path $vs15.Path - if ((Test-Leaf -LiteralPath $vstestConsole15) -and (InvokeVsTestCmdletHasMember "VSTestLocation")) { - Invoke-VSTest -TestAssemblies $testAssemblyFiles -TestFiltercriteria $testFiltercriteria -RunSettingsFile $runSettingsFileWithParallel -PathtoCustomTestAdapters $pathtoCustomTestAdapters -CodeCoverageEnabled $codeCoverage -OverrideTestrunParameters $overrideTestrunParameters -OtherConsoleOptions $otherConsoleOptions -WorkingFolder $workingDirectory -TestResultsFolder $testResultsDirectory -SourcesDirectory $sourcesDirectory -VSTestLocation $vstestConsole15 - } - else # fallback to latest - { - Invoke-VSTest -TestAssemblies $testAssemblyFiles -VSTestVersion $vsTestVersion -TestFiltercriteria $testFiltercriteria -RunSettingsFile $runSettingsFileWithParallel -PathtoCustomTestAdapters $pathtoCustomTestAdapters -CodeCoverageEnabled $codeCoverage -OverrideTestrunParameters $overrideTestrunParameters -OtherConsoleOptions $otherConsoleOptions -WorkingFolder $workingDirectory -TestResultsFolder $testResultsDirectory -SourcesDirectory $sourcesDirectory - } - } - else - { - Invoke-VSTest -TestAssemblies $testAssemblyFiles -VSTestVersion $vsTestVersion -TestFiltercriteria $testFiltercriteria -RunSettingsFile $runSettingsFileWithParallel -PathtoCustomTestAdapters $pathtoCustomTestAdapters -CodeCoverageEnabled $codeCoverage -OverrideTestrunParameters $overrideTestrunParameters -OtherConsoleOptions $otherConsoleOptions -WorkingFolder $workingDirectory -TestResultsFolder $testResultsDirectory -SourcesDirectory $sourcesDirectory - } - } - } - - } - else - { - Write-Host "##vso[task.logissue type=warning;code=002004;]" - Write-Warning (Get-LocalizedString -Key "No test assemblies found matching the pattern: '{0}'." -ArgumentList $testAssembly) - } -} -catch -{ - # Catching reliability issues and logging them here. - Write-Host "##vso[task.logissue type=error;code=" $_.Exception.Message ";TaskName=VSTest]" - throw -} -finally -{ - try - { - # Try to publish test results, only if the results directory has been set. - - if($testResultsDirectory) - { - $resultFiles = Find-Files -SearchPattern "*.trx" -RootFolder $testResultsDirectory - - $publishResultsOption = Convert-String $publishRunAttachments Boolean - - if($resultFiles) - { - # Remove the below hack once the min agent version is updated to S91 or above - $runTitleMemberExists = CmdletHasMember "RunTitle" - $publishRunLevelAttachmentsExists = CmdletHasMember "PublishRunLevelAttachments" - if($runTitleMemberExists) - { - if($publishRunLevelAttachmentsExists) - { - Publish-TestResults -Context $distributedTaskContext -TestResultsFiles $resultFiles -TestRunner "VSTest" -Platform $platform -Configuration $configuration -RunTitle $testRunTitle -PublishRunLevelAttachments $publishResultsOption - } - else - { - if(!$publishResultsOption) - { - Write-Warning (Get-LocalizedString -Key "Update the agent to try out the '{0}' feature." -ArgumentList "opt in/out of publishing test run attachments") - } - Publish-TestResults -Context $distributedTaskContext -TestResultsFiles $resultFiles -TestRunner "VSTest" -Platform $platform -Configuration $configuration -RunTitle $testRunTitle - } - } - else - { - if($testRunTitle) - { - Write-Warning (Get-LocalizedString -Key "Update the agent to try out the '{0}' feature." -ArgumentList "custom run title") - } - - if($publishRunLevelAttachmentsExists) - { - Publish-TestResults -Context $distributedTaskContext -TestResultsFiles $resultFiles -TestRunner "VSTest" -Platform $platform -Configuration $configuration -PublishRunLevelAttachments $publishResultsOption - } - else - { - if(!$publishResultsOption) - { - Write-Warning (Get-LocalizedString -Key "Update the agent to try out the '{0}' feature." -ArgumentList "opt in/out of publishing test run attachments") - } - Publish-TestResults -Context $distributedTaskContext -TestResultsFiles $resultFiles -TestRunner "VSTest" -Platform $platform -Configuration $configuration - } - } - } - else - { - Write-Host "##vso[task.logissue type=warning;code=002003;]" - Write-Warning (Get-LocalizedString -Key "No results found to publish.") - } - } - } - catch - { - Write-Host "##vso[task.logissue type=error;code=" $_.Exception.Message ";TaskName=VSTest]" - throw - } -} - -Write-Verbose "Leaving script VSTest.ps1" \ No newline at end of file diff --git a/Tasks/VsTest/distributedTest.ts b/Tasks/VsTest/distributedTest.ts index c2de6171423d..5638a7d28c72 100644 --- a/Tasks/VsTest/distributedTest.ts +++ b/Tasks/VsTest/distributedTest.ts @@ -5,6 +5,7 @@ import * as ps from 'child_process'; import * as tl from 'vsts-task-lib/task'; import * as tr from 'vsts-task-lib/toolrunner'; import * as models from './models'; +import * as settingsHelper from './settingsHelper'; import * as utils from './helpers'; import * as ta from './testAgent'; @@ -76,8 +77,17 @@ export class DistributedTest { utils.Helper.addToProcessEnvVars(envVars, 'sourcefilter', '!**\obj\**'); } + //Modify settings file to enable configurations and data collectors. + var settingsFile = this.dtaTestConfig.settingsFile; + try { + settingsFile = await settingsHelper.updateSettingsFileAsRequired(this.dtaTestConfig.settingsFile, this.dtaTestConfig.runInParallel, this.dtaTestConfig.tiaConfig, null, false); + } catch (error) { + tl.warning(tl.loc('ErrorWhileUpdatingSettings')); + tl.debug(error); + } + utils.Helper.addToProcessEnvVars(envVars, 'testcasefilter', this.dtaTestConfig.testcaseFilter); - utils.Helper.addToProcessEnvVars(envVars, 'runsettings', this.dtaTestConfig.runSettingsFile); + utils.Helper.addToProcessEnvVars(envVars, 'runsettings', settingsFile); utils.Helper.addToProcessEnvVars(envVars, 'testdroplocation', this.dtaTestConfig.testDropLocation); utils.Helper.addToProcessEnvVars(envVars, 'testrunparams', this.dtaTestConfig.overrideTestrunParameters); utils.Helper.setEnvironmentVariableToString(envVars, 'codecoverageenabled', this.dtaTestConfig.codeCoverageEnabled); @@ -96,9 +106,20 @@ export class DistributedTest { utils.Helper.setEnvironmentVariableToString(envVars, 'customslicingenabled', 'true'); await runDistributesTestTool.exec({ cwd: path.join(__dirname, 'modules'), env: envVars }); - tl.debug('Run Distributed Test finished'); + await this.cleanUp(settingsFile); + tl.debug('Run Distributed Test finished'); } + private async cleanUp(temporarySettingsFile: string) { + //cleanup the runsettings file + if (temporarySettingsFile && this.dtaTestConfig.settingsFile != temporarySettingsFile) { + try { + tl.rmRF(temporarySettingsFile, true); + } catch (error) { + //Ignore. + } + } + } private dtaTestConfig: models.DtaTestConfigurations; private dtaPid: number; } \ No newline at end of file diff --git a/Tasks/VsTest/make.json b/Tasks/VsTest/make.json index ae485251a228..bb25dfd207a4 100644 --- a/Tasks/VsTest/make.json +++ b/Tasks/VsTest/make.json @@ -2,7 +2,7 @@ "externals": { "archivePackages": [ { - "url": "https://testselector.blob.core.windows.net/testselector/3570841/TestSelector.zip", + "url": "https://testselector.blob.core.windows.net/testselector/3614206/TestSelector.zip", "dest": "./" }, { diff --git a/Tasks/VsTest/models.ts b/Tasks/VsTest/models.ts index ef1cff9b2174..40adad67215b 100644 --- a/Tasks/VsTest/models.ts +++ b/Tasks/VsTest/models.ts @@ -6,7 +6,7 @@ export interface ExecutabaleInfo { export interface TestConfigurations { sourceFilter: string[]; testcaseFilter: string; - runSettingsFile: string; + settingsFile: string; testDropLocation: string; // search folder overrideTestrunParameters: string; codeCoverageEnabled: boolean; @@ -17,6 +17,8 @@ export interface TestConfigurations { vsTestVersion: string; pathtoCustomTestAdapters: string; tiaConfig: TiaConfiguration; + runInParallel: boolean; + runTestsInIsolation: boolean; } export interface DtaTestConfigurations extends TestConfigurations { @@ -38,15 +40,13 @@ export interface DtaEnvironment { } export interface VsTestConfigurations extends TestConfigurations { - publishRunAttachments: string; - runInParallel: boolean; + publishRunAttachments: string; vstestDiagFile: string; ignoreVstestFailure: string; vs15HelperPath: string; } -export interface TiaConfiguration -{ +export interface TiaConfiguration { tiaEnabled: boolean; tiaRebaseLimit: string; tiaFilterPaths: string; diff --git a/Tasks/VsTest/settingsHelper.ts b/Tasks/VsTest/settingsHelper.ts new file mode 100644 index 000000000000..6559e107ff6e --- /dev/null +++ b/Tasks/VsTest/settingsHelper.ts @@ -0,0 +1,290 @@ +import tl = require('vsts-task-lib/task'); +import path = require('path'); +import Q = require('q'); +import models = require('./models') +import utilities = require('./utilities') + +var os = require('os'); +var uuid = require('node-uuid'); +var fs = require('fs'); +var xml2js = require('xml2js'); +var parser = new xml2js.Parser(); +var builder = new xml2js.Builder(); +var headlessBuilder = new xml2js.Builder({headless: true}); + +const runSettingsExt = ".runsettings"; +const testSettingsExt = ".testsettings"; + +const TestSettingsAgentNameTag = "agent-5d76a195-1e43-4b90-a6ce-4ec3de87ed25"; +const TestSettingsNameTag = "testSettings-5d76a195-1e43-4b90-a6ce-4ec3de87ed25"; +const TestSettingsIDTag = "5d76a195-1e43-4b90-a6ce-4ec3de87ed25"; +const TestSettingsXmlnsTag = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010" + +//TestImpact collector +const TestImpactFriendlyName = "Test Impact"; +const TestImpactDataCollectorTemplate = ""; + +//Video collector +const VideoCollectorFriendlyName ="Screen and Voice Recorder"; +const VideoDataCollectorTemplate = ""; + +//Parallel configuration +var runSettingsForParallel = '0'; + +const testSettingsTemplate ="" + +"" + + "" + + "" + + "" + + "" + + "" + + "" + +""; + +const runSettingsTemplate = "" + +"" + + "" + + "" + + "" + + "" + +""; + +export async function updateSettingsFileAsRequired(settingsFile: string, isParallelRun: boolean, tiaConfig: models.TiaConfiguration, vsVersion: any, videoCollector: boolean) : Promise +{ + var defer=Q.defer(); + var result: any; + + if(!isParallelRun && !videoCollector && !tiaConfig.tiaEnabled) { + defer.resolve(settingsFile); + return defer.promise; + } + + //Get extension of settings file and contents + var settingsExt = null; + if (settingsFile && fs.lstatSync(settingsFile).isFile() && settingsFile.split('.').pop().toLowerCase() === "testsettings") { + settingsExt=testSettingsExt; + result = await utilities.getXmlContents(settingsFile); + if(!result || result.TestSettings === undefined) { + tl.warning(tl.loc('FailedToSetRunConfiguration')); + settingsExt = null; + } + } else if (settingsFile && utilities.pathExistsAsFile(settingsFile)) { + settingsExt = runSettingsExt; + result = await utilities.getXmlContents(settingsFile); + if(!result || result.RunSettings === undefined) { + tl.warning(tl.loc('FailedToSetRunConfiguration')); + settingsExt = null; + } + } + + if (isParallelRun) { + if (settingsExt === testSettingsExt) { + tl.warning(tl.loc('RunInParallelNotSupported')); + } else if (settingsExt === runSettingsExt) { + tl.debug("Enabling run in parallel by editing given runsettings.") + result = await setupRunSettingsFileForRunConfig(result, {MaxCpuCount: 0}); + } else { + tl.debug("Enabling run in parallel by creating new runsettings."); + settingsExt = runSettingsExt; + result = await CreateSettings(runSettingsForParallel); + } + } + + if (videoCollector) { + //Enable video collector only in test settings. + var videoCollectorNode = null; + parser.parseString(VideoDataCollectorTemplate, function(err, data) { + if(err) { + defer.reject(err); + } + videoCollectorNode = data; + }); + if (settingsExt === testSettingsExt) { + tl.debug("Enabling video data collector by editing given testsettings.") + result = updateTestSettingsWithDataCollector(result, VideoCollectorFriendlyName, videoCollectorNode); + } else if (settingsExt === runSettingsExt) { + tl.warning(tl.loc('VideoCollectorNotSupportedWithRunSettings')); + } else { + tl.debug("Enabling video data collection by creating new test settings.") + settingsExt = testSettingsExt; + result = await CreateSettings(testSettingsTemplate); + result = updateTestSettingsWithDataCollector(result, VideoCollectorFriendlyName, videoCollectorNode) + } + } + + if (tiaConfig.tiaEnabled) { + var testImpactCollectorNode = null; + parser.parseString(TestImpactDataCollectorTemplate, function(err, data) { + if(err) { + defer.reject(err); + } + testImpactCollectorNode = data; + if(tiaConfig.useNewCollector) { + testImpactCollectorNode.DataCollector.$.codebase = getTraceCollectorUri(vsVersion); + } + testImpactCollectorNode.DataCollector.Configuration[0].ImpactLevel = getTIALevel(tiaConfig); + if (getTIALevel(tiaConfig) === 'file') { + testImpactCollectorNode.DataCollector.Configuration[0].LogFilePath = 'true'; + } + if (tiaConfig.context === "CD") { + testImpactCollectorNode.DataCollector.Configuration[0].RootPath = ""; + } else { + testImpactCollectorNode.DataCollector.Configuration[0].RootPath = tiaConfig.sourcesDir; + } + }); + //var baseLineBuildId = await utilities.readFileContents(tiaConfig.baseLineBuildIdFile, "utf-8"); + + if(settingsExt === testSettingsExt) + { + tl.debug("Enabling Test Impact collector by editing given testsettings.") + result = updateTestSettingsWithDataCollector(result, TestImpactFriendlyName, testImpactCollectorNode); + //result = await setupTestSettingsFileForRunConfig(result, { TestImpact : { '$': {enabled: 'true'} }, BaseLineRunId : baseLineBuildId}); + } else if (settingsExt === runSettingsExt) { + tl.debug("Enabling Test Impact collector by editing given runsettings.") + result = updateRunSettingsWithDataCollector(result, TestImpactFriendlyName, testImpactCollectorNode); + //result = await setupRunSettingsFileForRunConfig(result, { TestImpact : { '$': {enabled: 'true'} }, BaseLineRunId : baseLineBuildId}); + } else { + tl.debug("Enabling test impact data collection by creating new runsettings.") + settingsExt = runSettingsExt; + result = await CreateSettings(runSettingsTemplate); + result = updateRunSettingsWithDataCollector(result, TestImpactFriendlyName, testImpactCollectorNode); + //result = await setupRunSettingsFileForRunConfig(result, { TestImpact : { '$': {enabled: 'true'} }, BaseLineRunId : baseLineBuildId}); + } + } + + if (result) { + utilities.writeXmlFile(result, settingsFile, settingsExt) + .then(function (filename) { + defer.resolve(filename); + }); + } else { + tl.debug("Not editing settings file. Using specified file as it is.") + defer.resolve(settingsFile); + } + return defer.promise; +} + +function updateRunSettingsWithDataCollector(result: any, dataCollectorFriendlyName: string, dataCollectorNodeToAdd) { + if (!result.RunSettings) { + tl.debug("Updating runsettings file from RunSettings node"); + result.RunSettings = { DataCollectionRunSettings: { DataCollectors: dataCollectorNodeToAdd } }; + } else if (!result.RunSettings.DataCollectionRunSettings) { + tl.debug("Updating runsettings file from DataCollectionSettings node"); + result.RunSettings.DataCollectionRunSettings = { DataCollectors: dataCollectorNodeToAdd }; + } else if (!result.RunSettings.DataCollectionRunSettings[0].DataCollectors) { + tl.debug("Updating runsettings file from DataCollectors node"); + result.RunSettings.DataCollectionRunSettings[0] = { DataCollectors: dataCollectorNodeToAdd }; + } else { + var dataCollectorArray = result.RunSettings.DataCollectionRunSettings[0].DataCollectors[0].DataCollector; + if (!dataCollectorArray) { + tl.debug("Updating runsettings file from DataCollector node"); + result.RunSettings.DataCollectionRunSettings[0] = { DataCollectors: dataCollectorNodeToAdd }; + } else { + if (!isDataCollectorPresent(dataCollectorArray, dataCollectorFriendlyName)) { + tl.debug("Updating runsettings file, adding a DataCollector node"); + dataCollectorArray.push(dataCollectorNodeToAdd.DataCollector); + } + } + } + return result; +} + +function isDataCollectorPresent(dataCollectorArray, dataCollectorFriendlyName: string): Boolean { + var found = false; + for (var node of dataCollectorArray) { + if (node.$.friendlyName && node.$.friendlyName.toUpperCase() === dataCollectorFriendlyName.toUpperCase()) { + tl.debug("Data collector already present, will not add the node."); + found = true; + break; + } + } + return found; +} + +function updateTestSettingsWithDataCollector(result: any, dataCollectorFriendlyName: string, dataCollectorNodeToAdd) { + if (!result.TestSettings) { + tl.debug("Updating testsettings file from TestSettings node"); + result.TestSettings = { Execution: { AgentRule: { DataCollectors: dataCollectorNodeToAdd } } }; + result.TestSettings.Execution.AgentRule.$ = { name: TestSettingsAgentNameTag }; + result.TestSettings.$ = { name: TestSettingsNameTag, id: TestSettingsIDTag, xmlns: TestSettingsXmlnsTag }; + } else if (!result.TestSettings.Execution) { + tl.debug("Updating testsettings file from Execution node"); + result.TestSettings.Execution = { AgentRule: { DataCollectors: dataCollectorNodeToAdd } }; + result.TestSettings.Execution.AgentRule.$ = { name: TestSettingsAgentNameTag }; + } else if (!result.TestSettings.Execution[0].AgentRule) { + tl.debug("Updating testsettings file from AgentRule node"); + result.TestSettings.Execution[0] = { AgentRule: { DataCollectors: dataCollectorNodeToAdd } }; + result.TestSettings.Execution[0].AgentRule.$ = { name: TestSettingsAgentNameTag }; + } else if (!result.TestSettings.Execution[0].AgentRule[0].DataCollectors) { + tl.debug("Updating testsettings file from DataCollectors node"); + result.TestSettings.Execution[0].AgentRule[0] = { DataCollectors: dataCollectorNodeToAdd }; + result.TestSettings.Execution[0].AgentRule.$ = { name: TestSettingsAgentNameTag }; + } else { + var dataCollectorArray = result.TestSettings.Execution[0].AgentRule[0].DataCollectors[0].DataCollector; + if (!dataCollectorArray) { + tl.debug("Updating testsettings file from DataCollector node"); + result.TestSettings.Execution[0].AgentRule[0].DataCollectors[0] = dataCollectorNodeToAdd; + } else { + if (!isDataCollectorPresent(dataCollectorArray, dataCollectorFriendlyName)) { + tl.debug("Updating testsettings file, adding a DataCollector node"); + dataCollectorArray.push(dataCollectorNodeToAdd.DataCollector); + } + } + } + return result; +} + +function CreateSettings(runSettingsContents: string) : Q.Promise { + var defer=Q.defer(); + parser.parseString(runSettingsContents, function (err, result) { + if(err) { + defer.reject(err); + } + defer.resolve(result); + }); + return defer.promise; +} + +function setupRunSettingsFileForRunConfig(result: any, innerNode: any) : Q.Promise { + var defer=Q.defer(); + if (!result.RunSettings) { + result.RunSettings = { RunConfiguration: innerNode }; + } + else if (!result.RunSettings.RunConfiguration || !result.RunSettings.RunConfiguration[0]) { + result.RunSettings.RunConfiguration = innerNode ; + } + defer.resolve(result); + return defer.promise; +} + +function setupTestSettingsFileForRunConfig(result: any, innerNode: any) : Q.Promise { + var defer=Q.defer(); + if (!result || result.TestSettings === undefined) { + tl.warning(tl.loc('FailedToSetRunConfiguration')); + defer.resolve(null); + } + if (!result.TestSettings) { + result.RunSettings = { Execution: innerNode }; + } + else if (!result.TestSettings.Execution || !result.TestSettings.Execution[0]) { + result.TestSettings.Execution = innerNode ; + } + defer.resolve(result); + return defer.promise; +} + +function getTraceCollectorUri(vsVersion: any): string { + if(vsVersion === 15) { + return "file://" + path.join(__dirname, "TestSelector/Microsoft.VisualStudio.TraceCollector.dll"); + } + else { + return "file://" + path.join(__dirname, "TestSelector/14.0/Microsoft.VisualStudio.TraceCollector.dll"); + } +} + +function getTIALevel(tiaConfig: models.TiaConfiguration) { + if (tiaConfig.fileLevel && tiaConfig.fileLevel.toUpperCase() === "FALSE") { + return "method"; + } + return "file"; +} \ No newline at end of file diff --git a/Tasks/VsTest/task.json b/Tasks/VsTest/task.json index 7077442b9ea3..98ab7794437a 100644 --- a/Tasks/VsTest/task.json +++ b/Tasks/VsTest/task.json @@ -323,12 +323,12 @@ "NoMatchingTestAssemblies": "No test assemblies found matching the pattern: %s.", "VstestNotFound": "Vstest of version %d is not found. Try again with a visual studio version that exists on your build agent machine.", "VstestFailed": "Vstest failed with error. Check logs for failures. There might be failed tests.", - "VstestTIANotSupported": "Install Visual Studio version 15.0.25807 or higher to run Test Impact Analysis.", + "VstestTIANotSupported": "Install Visual Studio 2015 update 3 or Visual Studio 2017 RC or above to run Test Impact Analysis.", "NoResultsToPublish": "No results found to publish.", "ErrorWhileReadingRunSettings": "Error occured while reading run settings file. Error : %s.", "ErrorWhileReadingTestSettings": "Error occured while reading test settings file. Error : %s.", "RunInParallelNotSupported": "Run in Parallel is not supported with testsettings file.", - "FailedToSetRunInParallel": "Failed to set run in parallel. Invalid run settings file.", + "FailedToSetRunConfiguration": "The specified settings file is invalid. Ignoring it.", "UpdateOneOrHigherRequired": "Install Visual Studio 2015 Update 1 or higher on your build agent machine to run the tests in parallel.", "ErrorOccuredWhileSettingRegistry": "Error occured while setting registry key, Error: %s.", "ErrorWhileSettingTestImpactCollectorTestSettings": "Error occurred while setting Test Impact Collector in test settings file.", @@ -344,6 +344,10 @@ "UnexpectedVersionString": "Unexpected version string detected for vstest.console.exe: %s.", "UnexpectedVersionNumber": "Unexpected version number detected for vstest.console.exe: %s.", "VstestDiagNotSupported": "vstest.console.exe version does not support the /diag flag. Enable diagnositics via the exe.config files", - "NoIncludePatternFound": "No include pattern found. Specify atleast one include pattern to search test assemblies." + "NoIncludePatternFound": "No include pattern found. Specify atleast one include pattern to search test assemblies.", + "ErrorWhileUpdatingSettings": "Error occurred while updating the settings file. Using the specified settings file.", + "VideoCollectorNotSupportedWithRunSettings": "Video collector is not supported with run settings.", + "runTestInIsolationNotSupported": "Running tests in isolation is not supported for multi-agent scenario.", + "tiaNotSupportedInDta": "Running only impacted tests is not supported for multi-agent scenario." } } \ No newline at end of file diff --git a/Tasks/VsTest/task.loc.json b/Tasks/VsTest/task.loc.json index f94eb0c61520..d0696d7785f3 100644 --- a/Tasks/VsTest/task.loc.json +++ b/Tasks/VsTest/task.loc.json @@ -328,7 +328,7 @@ "ErrorWhileReadingRunSettings": "ms-resource:loc.messages.ErrorWhileReadingRunSettings", "ErrorWhileReadingTestSettings": "ms-resource:loc.messages.ErrorWhileReadingTestSettings", "RunInParallelNotSupported": "ms-resource:loc.messages.RunInParallelNotSupported", - "FailedToSetRunInParallel": "ms-resource:loc.messages.FailedToSetRunInParallel", + "FailedToSetRunConfiguration": "ms-resource:loc.messages.FailedToSetRunConfiguration", "UpdateOneOrHigherRequired": "ms-resource:loc.messages.UpdateOneOrHigherRequired", "ErrorOccuredWhileSettingRegistry": "ms-resource:loc.messages.ErrorOccuredWhileSettingRegistry", "ErrorWhileSettingTestImpactCollectorTestSettings": "ms-resource:loc.messages.ErrorWhileSettingTestImpactCollectorTestSettings", @@ -344,6 +344,10 @@ "UnexpectedVersionString": "ms-resource:loc.messages.UnexpectedVersionString", "UnexpectedVersionNumber": "ms-resource:loc.messages.UnexpectedVersionNumber", "VstestDiagNotSupported": "ms-resource:loc.messages.VstestDiagNotSupported", - "NoIncludePatternFound": "ms-resource:loc.messages.NoIncludePatternFound" + "NoIncludePatternFound": "ms-resource:loc.messages.NoIncludePatternFound", + "ErrorWhileUpdatingSettings": "ms-resource:loc.messages.ErrorWhileUpdatingSettings", + "VideoCollectorNotSupportedWithRunSettings": "ms-resource:loc.messages.VideoCollectorNotSupportedWithRunSettings", + "runTestInIsolationNotSupported": "ms-resource:loc.messages.runTestInIsolationNotSupported", + "tiaNotSupportedInDta": "ms-resource:loc.messages.tiaNotSupportedInDta" } } \ No newline at end of file diff --git a/Tasks/VsTest/taskinputparser.ts b/Tasks/VsTest/taskinputparser.ts index 1af479d1b85e..c6f1974ba064 100644 --- a/Tasks/VsTest/taskinputparser.ts +++ b/Tasks/VsTest/taskinputparser.ts @@ -12,7 +12,14 @@ export function getDistributedTestConfigurations(): models.DtaTestConfigurations tl.setResourcePath(path.join(__dirname, 'task.json')); const dtaConfiguration = {} as models.DtaTestConfigurations; initTestConfigurations(dtaConfiguration); - dtaConfiguration.onDemandTestRunId = tl.getInput('tcmTestRun'); + dtaConfiguration.onDemandTestRunId = tl.getInput('tcmTestRun'); + if(dtaConfiguration.tiaConfig.tiaEnabled) { + tl.warning(tl.loc('tiaNotSupportedInDta')); + dtaConfiguration.tiaConfig.tiaEnabled = false; + } + if(dtaConfiguration.runTestsInIsolation) { + tl.warning(tl.loc('runTestInIsolationNotSupported')); + } dtaConfiguration.dtaEnvironment = initDtaEnvironment(); return dtaConfiguration; } @@ -21,11 +28,9 @@ export function getvsTestConfigurations(): models.VsTestConfigurations { tl.setResourcePath(path.join(__dirname, 'task.json')); const vsTestConfiguration = {} as models.VsTestConfigurations; initTestConfigurations(vsTestConfiguration); - vsTestConfiguration.publishRunAttachments = tl.getInput('publishRunAttachments'); - vsTestConfiguration.runInParallel = tl.getBoolInput('runInParallel'); + vsTestConfiguration.publishRunAttachments = tl.getInput('publishRunAttachments'); vsTestConfiguration.vstestDiagFile = path.join(os.tmpdir(), uuid.v1() + '.txt'); vsTestConfiguration.ignoreVstestFailure = tl.getVariable('vstest.ignoretestfailures'); - vsTestConfiguration.tiaConfig = getTiaConfiguration(); // only to facilitate the writing of unit tests vsTestConfiguration.vs15HelperPath = tl.getVariable('vs15Helper'); @@ -70,18 +75,20 @@ function initTestConfigurations(testConfiguration: models.TestConfigurations) testConfiguration.sourceFilter = tl.getDelimitedInput('testAssemblyVer2', '\n', true); testConfiguration.testDropLocation = tl.getInput('searchFolder'); testConfiguration.testcaseFilter = tl.getInput('testFiltercriteria'); - testConfiguration.runSettingsFile = tl.getPathInput('runSettingsFile'); + testConfiguration.settingsFile = tl.getPathInput('runSettingsFile'); testConfiguration.overrideTestrunParameters = tl.getInput('overrideTestrunParameters'); testConfiguration.buildConfig = tl.getInput('configuration'); testConfiguration.buildPlatform = tl.getInput('platform'); testConfiguration.testRunTitle = tl.getInput('testRunTitle'); + testConfiguration.runInParallel = tl.getBoolInput('runTestsInParallel'); + testConfiguration.runTestsInIsolation = tl.getBoolInput('runTestsInIsolation'); + testConfiguration.tiaConfig = getTiaConfiguration(); testConfiguration.vsTestVersion = tl.getInput('vsTestVersion'); if(utils.Helper.isNullEmptyOrUndefined(testConfiguration.vsTestVersion)) { tl._writeLine('vsTestVersion is null or empty'); throw new Error("vsTestVersion is null or empty"); } - initDataCollectorConfigurations(testConfiguration); } diff --git a/Tasks/VsTest/utilities.ts b/Tasks/VsTest/utilities.ts new file mode 100644 index 000000000000..15b338c5e77a --- /dev/null +++ b/Tasks/VsTest/utilities.ts @@ -0,0 +1,79 @@ +import tl = require('vsts-task-lib/task'); +import tr = require('vsts-task-lib/toolrunner'); +import path = require('path'); +import Q = require('q'); +import models = require('./models') + +var os = require('os'); +var regedit = require('regedit'); +var uuid = require('node-uuid'); +var fs = require('fs'); +var xml2js = require('xml2js'); +var perf = require("performance-now"); +var process = require('process'); +var parser = new xml2js.Parser(); +var builder = new xml2js.Builder(); +var headlessBuilder = new xml2js.Builder({headless: true}); + +export function pathExistsAsFile(path: string) { + return tl.exist(path) && tl.stats(path).isFile(); +} + +export function getXmlContents(filePath: string): Q.Promise { + var defer=Q.defer(); + readFileContents(filePath, "utf-8") + .then(function (xmlContents) { + parser.parseString(xmlContents, function (err, result) { + if (err) { + defer.resolve(null); + } + else{ + defer.resolve(result); + } + }); + }) + .fail(function(err) { + defer.reject(err); + }); + return defer.promise; +} + +export function saveToFile(fileContents: string, extension: string): Q.Promise { + var defer = Q.defer(); + var tempFile = path.join(os.tmpdir(), uuid.v1() + extension); + fs.writeFile(tempFile, fileContents, function (err) { + if (err) { + defer.reject(err); + } + tl.debug("Temporary file created at " + tempFile); + defer.resolve(tempFile); + }); + return defer.promise; +} + +export function readFileContents(filePath: string, encoding: string): Q.Promise { + var defer = Q.defer(); + fs.readFile(filePath, encoding, (err, data) => { + if (err) { + defer.reject(new Error('Could not read file (' + filePath + '): ' + err.message)); + } + else { + defer.resolve(data); + } + }); + return defer.promise; +} + +export function writeXmlFile(result: any, settingsFile: string, fileExt: string): Q.Promise { + var defer = Q.defer(); + var runSettingsForTestImpact = builder.buildObject(result); + saveToFile(runSettingsForTestImpact, fileExt) + .then(function (fileName) { + defer.resolve(fileName); + return defer.promise; + }) + .fail(function (err) { + defer.reject(err); + }); + return defer.promise; +} \ No newline at end of file diff --git a/Tasks/VsTest/vstest.ts b/Tasks/VsTest/vstest.ts index c226811f7941..659dd9f97c08 100644 --- a/Tasks/VsTest/vstest.ts +++ b/Tasks/VsTest/vstest.ts @@ -4,6 +4,8 @@ import path = require('path'); import Q = require('q'); import models = require('./models') import taskInputParser = require('./taskInputParser') +import settingsHelper = require('./settingsHelper') +import utilities = require('./utilities') var os = require('os'); var regedit = require('regedit'); @@ -15,12 +17,6 @@ var process = require('process'); const runSettingsExt = ".runsettings"; const testSettingsExt = ".testsettings"; -const TIFriendlyName = "Test Impact"; -const TICollectorURI = "datacollector://microsoft/TestImpact/1.0"; -const TITestSettingsAgentNameTag = "testImpact-5d76a195-1e43-4b90-a6ce-4ec3de87ed25"; -const TITestSettingsNameTag = "testSettings-5d76a195-1e43-4b90-a6ce-4ec3de87ed25"; -const TITestSettingsIDTag = "5d76a195-1e43-4b90-a6ce-4ec3de87ed25"; -const TITestSettingsXmlnsTag = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010" let vstestConfig: models.VsTestConfigurations = undefined; let tiaConfig: models.TiaConfiguration = undefined; @@ -38,7 +34,7 @@ export async function startTest() { testAssemblyFiles = getTestAssemblies(); if (testAssemblyFiles && testAssemblyFiles.length !== 0) { - getTestResultsDirectory(vstestConfig.runSettingsFile, path.join(workingDirectory, 'TestResults')) + getTestResultsDirectory(vstestConfig.settingsFile, path.join(workingDirectory, 'TestResults')) .then(function (resultsDirectory) { invokeVSTest(resultsDirectory) .then(function (code) { @@ -135,9 +131,15 @@ function getVstestArguments(settingsFile: string, tiaEnabled: boolean): string[] } if (settingsFile && pathExistsAsFile(settingsFile)) { argsArray.push("/Settings:" + settingsFile); + utilities.readFileContents(settingsFile, "utf-8").then(function (settings) { + tl.debug("Running VsTest with settings : " + settings); + }); } if (vstestConfig.codeCoverageEnabled) { argsArray.push("/EnableCodeCoverage"); + } + if (vstestConfig.runTestsInIsolation) { + argsArray.push("/InIsolation"); } argsArray.push("/logger:trx"); @@ -189,10 +191,6 @@ function getTestSelectorLocation(): string { return path.join(__dirname, "TestSelector/TestSelector.exe"); } -function getTraceCollectorUri(): string { - return "file://" + path.join(__dirname, "TestSelector/Microsoft.VisualStudio.TraceCollector.dll"); -} - function uploadTestResults(testResultsDirectory: string): Q.Promise { var startTime = perf(); var endTime; @@ -493,6 +491,12 @@ function getVstestTestsList(vsVersion: number): Q.Promise { } let vstest = tl.tool(vsVersionDetails.location); + + if(vsVersion === 14.0) { + tl.debug("Visual studio 2015 selected. Selecting vstest.console.exe in task "); + let vsTestPath = path.join(__dirname, "TestSelector/14.0/vstest.console.exe") // Use private vstest as the changes to discover tests are not there in update3 + vstest = tl.tool(vsTestPath); + } addVstestArgs(argsArray, vstest); tl.cd(workingDirectory); @@ -728,7 +732,7 @@ function invokeVSTest(testResultsDirectory: string): Q.Promise { if (vstestConfig.vsTestVersion && vstestConfig.vsTestVersion.toLowerCase() === "latest") { vstestConfig.vsTestVersion = null; } - overrideTestRunParametersIfRequired(vstestConfig.runSettingsFile) + overrideTestRunParametersIfRequired(vstestConfig.settingsFile) .then(function (overriddenSettingsFile) { let vsVersion = vsVersionDetails.version; try { @@ -740,7 +744,12 @@ function invokeVSTest(testResultsDirectory: string): Q.Promise { if ((sysDebug !== undefined && sysDebug.toLowerCase() === "true") || tiaConfig.tiaEnabled) { vsTestVersionForTIA = getVsTestVersion(); - if (tiaConfig.tiaEnabled && (vsTestVersionForTIA === null || (vsTestVersionForTIA[0] < 15 || (vsTestVersionForTIA[0] === 15 && vsTestVersionForTIA[1] === 0 && vsTestVersionForTIA[2] < 25727)))) { + if (tiaConfig.tiaEnabled && + (vsTestVersionForTIA === null || + (vsTestVersionForTIA[0] < 14 || + (vsTestVersionForTIA[0] === 15 && vsTestVersionForTIA[1] === 0 && vsTestVersionForTIA[2] < 25727) || + // VS 2015 U3 + (vsTestVersionForTIA[0] === 14 && vsTestVersionForTIA[1] === 0 && vsTestVersionForTIA[2] < 25420)))) { tl.warning(tl.loc("VstestTIANotSupported")); tiaConfig.tiaEnabled = false; } @@ -750,28 +759,42 @@ function invokeVSTest(testResultsDirectory: string): Q.Promise { defer.resolve(1); return defer.promise; } - setupSettingsFileForTestImpact(vsVersion, overriddenSettingsFile) - .then(function (runSettingswithTestImpact) { - setRunInParallellIfApplicable(vsVersion); - setupRunSettingsFileForParallel(vstestConfig.runInParallel, runSettingswithTestImpact) - .then(function (parallelRunSettingsFile) { - runVStest(testResultsDirectory, parallelRunSettingsFile, vsVersion) - .then(function (code) { - defer.resolve(code); - }) - .fail(function (code) { - defer.resolve(code); - }); - }) - .fail(function (err) { - tl.error(err); - defer.resolve(1); - }); - }) - .fail(function (err) { - tl.error(err); - defer.resolve(1); - }); + + // We need to use private data collector dll + if(vsTestVersionForTIA[0] === 14) { + tiaConfig.useNewCollector = true; + } + + setRunInParallellIfApplicable(vsVersion); + var newSettingsFile = overriddenSettingsFile; + try { + settingsHelper.updateSettingsFileAsRequired(overriddenSettingsFile, vstestConfig.runInParallel, vstestConfig.tiaConfig, vsVersionDetails.version, false). + then(function(ret) { + newSettingsFile = ret; + if(newSettingsFile != overriddenSettingsFile) { + cleanUp(overriddenSettingsFile); + } + runVStest(testResultsDirectory, newSettingsFile, vsVersion) + .then(function (code) { + defer.resolve(code); + }) + .fail(function (code) { + defer.resolve(code); + }); + + }) + } catch (error) { + tl.warning(tl.loc('ErrorWhileUpdatingSettings')); + tl.debug(error); + //Should continue to run without the selected configurations. + runVStest(testResultsDirectory, newSettingsFile, vsVersion) + .then(function (code) { + defer.resolve(code); + }) + .fail(function (code) { + defer.resolve(code); + }); + } }) .fail(function (err) { tl.error(err); @@ -797,7 +820,7 @@ function publishTestResults(testResultsDirectory: string) { function cleanUp(temporarySettingsFile: string) { //cleanup the runsettings file - if (temporarySettingsFile && vstestConfig.runSettingsFile != temporarySettingsFile) { + if (temporarySettingsFile && vstestConfig.settingsFile != temporarySettingsFile) { try { tl.rmRF(temporarySettingsFile, true); } catch (error) { @@ -828,7 +851,7 @@ function overrideTestRunParametersIfRequired(settingsFile: string): Q.Promise { - var defer = Q.defer(); - tl.debug("Adding test impact data collector element to runsettings file provided."); - readFileContents(settingsFile, "utf-8") - .then(function (xmlContents) { - var parser = new xml2js.Parser(); - parser.parseString(xmlContents, function (err, result) { - if (err) { - tl.warning(tl.loc('ErrorWhileReadingRunSettings', err)); - tl.debug(exitErrorMessage); - defer.resolve(settingsFile); - return defer.promise; - } - if (result.RunSettings === undefined) { - tl.warning(tl.loc('ErrorWhileSettingTestImpactCollectorRunSettings')); - defer.resolve(settingsFile); - return defer.promise; - } - updateRunSettings(result, vsVersion); - writeXmlFile(result, settingsFile, runSettingsExt, exitErrorMessage) - .then(function (filename) { - defer.resolve(filename); - return defer.promise; - }); - }); - }) - .fail(function (err) { - tl.warning(err); - tl.debug(exitErrorMessage); - defer.resolve(settingsFile); - }); - return defer.promise; -} - -function updatTestSettings(result: any, vsVersion: number) { - var dataCollectorNode = null; - if (!result.TestSettings) { - tl.debug("Updating testsettings file from TestSettings node"); - result.TestSettings = { Execution: { AgentRule: { DataCollectors: { DataCollector: { Configuration: roothPathGenerator() } } } } }; - result.TestSettings.Execution.AgentRule.$ = { name: TITestSettingsAgentNameTag }; - result.TestSettings.$ = { name: TITestSettingsNameTag, id: TITestSettingsIDTag, xmlns: TITestSettingsXmlnsTag }; - dataCollectorNode = result.TestSettings.Execution.AgentRule.DataCollectors.DataCollector; - } else if (!result.TestSettings.Execution) { - tl.debug("Updating testsettings file from Execution node"); - result.TestSettings.Execution = { AgentRule: { DataCollectors: { DataCollector: { Configuration: roothPathGenerator() } } } }; - result.TestSettings.Execution.AgentRule.$ = { name: TITestSettingsAgentNameTag }; - dataCollectorNode = result.TestSettings.Execution.AgentRule.DataCollectors.DataCollector; - } else if (!result.TestSettings.Execution[0].AgentRule) { - tl.debug("Updating testsettings file from AgentRule node"); - result.TestSettings.Execution[0] = { AgentRule: { DataCollectors: { DataCollector: { Configuration: roothPathGenerator() } } } }; - result.TestSettings.Execution[0].AgentRule.$ = { name: TITestSettingsAgentNameTag }; - dataCollectorNode = result.TestSettings.Execution[0].AgentRule.DataCollectors.DataCollector; - } else if (!result.TestSettings.Execution[0].AgentRule[0].DataCollectors) { - tl.debug("Updating testsettings file from DataCollectors node"); - result.TestSettings.Execution[0].AgentRule[0] = { DataCollectors: { DataCollector: { Configuration: roothPathGenerator() } } }; - dataCollectorNode = result.TestSettings.Execution[0].AgentRule[0].DataCollectors.DataCollector; - } else { - var dataCollectorArray = result.TestSettings.Execution[0].AgentRule[0].DataCollectors[0].DataCollector; - if (!dataCollectorArray) { - tl.debug("Updating testsettings file from DataCollector node"); - result.TestSettings.Execution[0].AgentRule[0].DataCollectors[0] = { DataCollector: { Configuration: roothPathGenerator() } }; - dataCollectorNode = result.TestSettings.Execution[0].AgentRule[0].DataCollectors[0].DataCollector; - } else { - if (!isTestImapctCollectorPresent(dataCollectorArray)) { - tl.debug("Updating testsettings file, adding a DataCollector node"); - dataCollectorArray.push({ Configuration: roothPathGenerator() }); - dataCollectorNode = dataCollectorArray[dataCollectorArray.length - 1]; - } else { - pushImpactLevelAndRootPathIfNotFound(dataCollectorArray); - } - } - } - if (dataCollectorNode) { - tl.debug("Setting attributes for test impact data collector"); - if (tiaConfig.useNewCollector) { - dataCollectorNode.$ = getTestImpactAttributes(vsVersion); - } else { - dataCollectorNode.$ = getTestImpactAttributesWithoutNewCollector(vsVersion); - } - } -} - -function writeXmlFile(result: any, settingsFile: string, fileExt: string, exitErrorMessage: string): Q.Promise { - var defer = Q.defer(); - var builder = new xml2js.Builder(); - var runSettingsForTestImpact = builder.buildObject(result); - saveToFile(runSettingsForTestImpact, fileExt) - .then(function (fileName) { - cleanUp(settingsFile); - defer.resolve(fileName); - return defer.promise; - }) - .fail(function (err) { - tl.debug(exitErrorMessage); - tl.warning(err); - defer.resolve(settingsFile); - }); - return defer.promise; -} - -function updateTestSettingsFileForTestImpact(vsVersion: number, settingsFile: string, exitErrorMessage: string): Q.Promise { - var defer = Q.defer(); - tl.debug("Adding test impact data collector element to testsettings file provided."); - readFileContents(settingsFile, "utf-8") - .then(function (xmlContents) { - var parser = new xml2js.Parser(); - parser.parseString(xmlContents, function (err, result) { - if (err) { - tl.warning(tl.loc('ErrorWhileReadingTestSettings', err)); - tl.debug(exitErrorMessage); - defer.resolve(settingsFile); - return defer.promise; - } - if (result.TestSettings === undefined) { - tl.warning(tl.loc('ErrorWhileSettingTestImpactCollectorTestSettings')); - defer.resolve(settingsFile); - return defer.promise; - } - updatTestSettings(result, vsVersion); - writeXmlFile(result, settingsFile, testSettingsExt, exitErrorMessage) - .then(function (filename) { - defer.resolve(filename); - return defer.promise; - }); - }); - }) - .fail(function (err) { - tl.warning(err); - tl.debug(exitErrorMessage); - defer.resolve(settingsFile); - }); - return defer.promise; -} - - -function createRunSettingsForTestImpact(vsVersion: number, settingsFile: string, exitErrorMessage: string): Q.Promise { - var defer = Q.defer(); - tl.debug("No settings file provided or the provided settings file does not exist. Creating run settings file for enabling test impact data collector."); - var runSettingsForTIA = '' + - '' + - '' + - '' + getTIALevel() + ''; - - if (getTIALevel() === 'file') { - runSettingsForTIA = runSettingsForTIA + - '' + 'true' + ''; - } - - runSettingsForTIA = runSettingsForTIA + - '' + (tiaConfig.context === "CD" ? "" : tiaConfig.sourcesDir) + '' + - '' + - '' + - ''; - saveToFile(runSettingsForTIA, runSettingsExt) - .then(function (fileName) { - defer.resolve(fileName); - return defer.promise; - }) - .fail(function (err) { - tl.debug(exitErrorMessage); - tl.warning(err); - defer.resolve(settingsFile); - }); - return defer.promise; -} - -function setupSettingsFileForTestImpact(vsVersion: number, settingsFile: string): Q.Promise { - var defer = Q.defer(); - var exitErrorMessage = "Error occured while setting in test impact data collector. Continuing..."; - if (isTiaAllowed()) { - if (settingsFile && settingsFile.split('.').pop().toLowerCase() === "testsettings") { - updateTestSettingsFileForTestImpact(vsVersion, settingsFile, exitErrorMessage) - .then(function (updatedFile) { - defer.resolve(updatedFile); - return defer.promise; - }); - } - else if (!settingsFile || settingsFile.split('.').pop().toLowerCase() != "runsettings" || !pathExistsAsFile(settingsFile)) { - createRunSettingsForTestImpact(vsVersion, settingsFile, exitErrorMessage) - .then(function (updatedFile) { - defer.resolve(updatedFile); - return defer.promise; - }); - } - else { - updateRunSettingsFileForTestImpact(vsVersion, settingsFile, exitErrorMessage) - .then(function (updatedFile) { - defer.resolve(updatedFile); - return defer.promise; - }); - } - } - else { - tl.debug("Settings are not sufficient for setting test impact. Not updating the settings file"); - defer.resolve(settingsFile); - } - return defer.promise; -} - -function setupRunSettingsFileForParallel(runInParallel: boolean, settingsFile: string): Q.Promise { - var defer = Q.defer(); - var exitErrorMessage = "Error occured while setting run in parallel. Continuing..."; - if (runInParallel) { - if (settingsFile && settingsFile.split('.').pop().toLowerCase() === "testsettings") { - tl.warning(tl.loc('RunInParallelNotSupported')); - defer.resolve(settingsFile); - return defer.promise; - } - - if (!settingsFile || settingsFile.split('.').pop().toLowerCase() != "runsettings" || !pathExistsAsFile(settingsFile)) { - tl.debug("No settings file provided or the provided settings file does not exist."); - var runSettingsForParallel = '0'; - saveToFile(runSettingsForParallel, runSettingsExt) - .then(function (fileName) { - defer.resolve(fileName); - return defer.promise; - }) - .fail(function (err) { - tl.debug(exitErrorMessage); - tl.warning(err); - defer.resolve(settingsFile); - }); - } else { - tl.debug("Adding maxcpucount element to runsettings file provided."); - readFileContents(settingsFile, "utf-8") - .then(function (xmlContents) { - var parser = new xml2js.Parser(); - parser.parseString(xmlContents, function (err, result) { - if (err) { - tl.warning(tl.loc('ErrorWhileReadingRunSettings', err)); - tl.debug(exitErrorMessage); - defer.resolve(settingsFile); - return defer.promise; - } - - if (result.RunSettings === undefined) { - tl.warning(tl.loc('FailedToSetRunInParallel')); - defer.resolve(settingsFile); - return defer.promise; - } - - if (!result.RunSettings) { - result.RunSettings = { RunConfiguration: { MaxCpuCount: 0 } }; - } - else if (!result.RunSettings.RunConfiguration || !result.RunSettings.RunConfiguration[0]) { - result.RunSettings.RunConfiguration = { MaxCpuCount: 0 }; - } - else { - var runConfigArray = result.RunSettings.RunConfiguration[0]; - runConfigArray.MaxCpuCount = 0; - } - - var builder = new xml2js.Builder(); - var runSettingsForParallel = builder.buildObject(result); - saveToFile(runSettingsForParallel, runSettingsExt) - .then(function (fileName) { - cleanUp(settingsFile); - defer.resolve(fileName); - return defer.promise; - }) - .fail(function (err) { - tl.debug(exitErrorMessage); - tl.warning(err); - defer.resolve(settingsFile); - }); - }); - }) - .fail(function (err) { - tl.warning(err); - tl.debug(exitErrorMessage); - defer.resolve(settingsFile); - }); - } - } else { - defer.resolve(settingsFile); - } - - return defer.promise; -} - -function saveToFile(fileContents: string, extension: string): Q.Promise { - var defer = Q.defer(); - var tempFile = path.join(os.tmpdir(), uuid.v1() + extension); - fs.writeFile(tempFile, fileContents, function (err) { - if (err) { - defer.reject(err); - } - tl.debug("Temporary runsettings file created at " + tempFile); - defer.resolve(tempFile); - }); - return defer.promise; -} - function setRunInParallellIfApplicable(vsVersion: number) { if (vstestConfig.runInParallel) { if (!isNaN(vsVersion) && vsVersion >= 14) { @@ -1422,12 +1024,12 @@ function locateVSVersion(version: string): Q.Promise { let deferred = Q.defer(); let vsVersion: number = parseFloat(version); - if (isNaN(vsVersion) || vsVersion === 15) { + if (isNaN(vsVersion) || vsVersion === 15.0) { // latest tl.debug('Searching for latest Visual Studio'); let vstestconsole15Path = getVSTestConsole15Path(); if (vstestconsole15Path) { - deferred.resolve({ version: 15, location: vstestconsole15Path }); + deferred.resolve({ version: 15.0, location: vstestconsole15Path }); } else { // fallback tl.debug('Unable to find an instance of Visual Studio 2017'); @@ -1477,21 +1079,6 @@ function setRegistryKeyForParallelExecution(vsVersion: number) { }); } -function readFileContents(filePath: string, encoding: string): Q.Promise { - var defer = Q.defer(); - - fs.readFile(filePath, encoding, (err, data) => { - if (err) { - defer.reject(new Error('Could not read file (' + filePath + '): ' + err.message)); - } - else { - defer.resolve(data); - } - }); - - return defer.promise; -} - function pathExistsAsFile(path: string) { return tl.exist(path) && tl.stats(path).isFile(); } @@ -1522,7 +1109,7 @@ function getTIALevel() { } function responseContainsNoTests(filePath: string): Q.Promise { - return readFileContents(filePath, "utf-8").then(function (resp) { + return utilities.readFileContents(filePath, "utf-8").then(function (resp) { if (resp === "/Tests:") { return true; }