diff --git a/Tasks/VsTest/Strings/resources.resjson/en-US/resources.resjson b/Tasks/VsTest/Strings/resources.resjson/en-US/resources.resjson index 576406cf5182..a6ee9004ca57 100644 --- a/Tasks/VsTest/Strings/resources.resjson/en-US/resources.resjson +++ b/Tasks/VsTest/Strings/resources.resjson/en-US/resources.resjson @@ -59,6 +59,7 @@ "loc.messages.VstestReturnCode": "Vstest exited with return code: %d.", "loc.messages.NoMatchingTestAssemblies": "No test assemblies found matching the pattern: %s.", "loc.messages.VstestNotFound": "Visual Studio %d is not found. Try again with a version that exists on your build agent machine.", + "loc.messages.NoVstestFound": "Test platform is not found. Try again after installing it 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 2015 update 3 or Visual Studio 2017 RC or above to run Test Impact Analysis.", "loc.messages.NoResultsToPublish": "No results found to publish.", @@ -87,10 +88,10 @@ "loc.messages.runTestInIsolationNotSupported": "Running tests in isolation is not supported when using the multi-agent phase setting. This option will be ignored.", "loc.messages.tiaNotSupportedInDta": "Running only impacted tests is not supported when using the multi-agent phase setting. This option will be ignored.", "loc.messages.overrideNotSupported": "Overriding test run parameters is supported only with runsettings file. This option will be ignored.", - "loc.messages.vs2013NotSupportedInDta": "Running tests using Visual Studio 2013 with multi-agent phase settings is not supported.", + "loc.messages.vstestVersionInvalid": "Given test platform version %s is not supported.", "loc.messages.configureDtaAgentFailed": "Configuring the test agent with the server failed even after %d retries with error %s", "loc.messages.otherConsoleOptionsNotSupported": "Other console options is not supported when using the multi-agent phase setting. This option will be ignored.", - "loc.messages.distributedTestWorkflow": "In distributed testing flow....", + "loc.messages.distributedTestWorkflow": "In distributed testing flow", "loc.messages.dtaNumberOfAgents": "Distributed test execution, number of agents in phase : %s", "loc.messages.testSelectorInput": "Test selector : %s", "loc.messages.searchFolderInput": "Search folder : %s", @@ -106,7 +107,7 @@ "loc.messages.testSuiteSelected": "Test suite Id selected: %s", "loc.messages.testAssemblyFilterInput": "Test assembly filter : %s", "loc.messages.vsVersionSelected": "VisualStudio version selected for test execution : %s", - "loc.messages.runTestsLocally": "Run the tests locally using %s....", + "loc.messages.runTestsLocally": "Run the tests locally using %s", "loc.messages.vstestLocationSpecified": "%s, specified location : %s", "loc.messages.uitestsparallel": "Running UI tests in parallel on the same machine can lead to errors. Consider disabling the ‘run in parallel’ option or run UI tests using a separate task. To learn more, see https://aka.ms/paralleltestexecution " } \ No newline at end of file diff --git a/Tasks/VsTest/distributedtest.ts b/Tasks/VsTest/distributedtest.ts index 17b7b07ed955..f7747ca04c48 100644 --- a/Tasks/VsTest/distributedtest.ts +++ b/Tasks/VsTest/distributedtest.ts @@ -52,29 +52,21 @@ export class DistributedTest { utils.Helper.addToProcessEnvVars(envVars, 'DTA.LocalTestDropPath', this.dtaTestConfig.testDropLocation); utils.Helper.addToProcessEnvVars(envVars, 'DTA.EnableConsoleLogs', 'true'); - if (this.dtaTestConfig.vsTestLocationMethod === utils.Constants.vsTestVersionString) { - utils.Helper.addToProcessEnvVars(envVars, 'DTA.TestPlatformVersion', this.dtaTestConfig.vsTestVersion); - } - - const exeInfo = await versionFinder.locateVSTestConsole(this.dtaTestConfig); - if (exeInfo) { - const exelocation = path.dirname(exeInfo); - tl.debug('Adding env var DTA.TestWindow.Path = ' + exelocation); - - // Split the TestWindow path out of full path - if we can't find it, will assume - // that this is nuget/xcopyable package where the dlls are present in test window folder - const testWindowRelativeDir = 'CommonExtensions\\Microsoft\\TestWindow'; - if (exelocation && exelocation.indexOf(testWindowRelativeDir) !== -1) { - const ideLocation = exelocation.split(testWindowRelativeDir)[0]; - tl.debug('Adding env var DTA.VisualStudio.Path = ' + ideLocation); - utils.Helper.addToProcessEnvVars(envVars, 'DTA.VisualStudio.Path', ideLocation); - } else { - utils.Helper.addToProcessEnvVars(envVars, 'DTA.VisualStudio.Path', exelocation); - } - utils.Helper.addToProcessEnvVars(envVars, 'DTA.TestWindow.Path', exelocation); + // If we are setting the path version is not needed + const exelocation = path.dirname(this.dtaTestConfig.vsTestVersionDetais.vstestExeLocation); + tl.debug('Adding env var DTA.TestWindow.Path = ' + exelocation); + + // Split the TestWindow path out of full path - if we can't find it, will assume + // that this is nuget/xcopyable package where the dlls are present in test window folder + const testWindowRelativeDir = 'CommonExtensions\\Microsoft\\TestWindow'; + if (exelocation && exelocation.indexOf(testWindowRelativeDir) !== -1) { + const ideLocation = exelocation.split(testWindowRelativeDir)[0]; + tl.debug('Adding env var DTA.VisualStudio.Path = ' + ideLocation); + utils.Helper.addToProcessEnvVars(envVars, 'DTA.VisualStudio.Path', ideLocation); } else { - tl.error(tl.loc('VstestNotFound', utils.Helper.getVSVersion(parseFloat(this.dtaTestConfig.vsTestVersion)))); + utils.Helper.addToProcessEnvVars(envVars, 'DTA.VisualStudio.Path', exelocation); } + utils.Helper.addToProcessEnvVars(envVars, 'DTA.TestWindow.Path', exelocation); // We are logging everything to a DTAExecutionHost.exe.log file and reading it at the end and adding to the build task debug logs // So we are not redirecting the IO streams from the DTAExecutionHost.exe process diff --git a/Tasks/VsTest/models.ts b/Tasks/VsTest/models.ts index 9d8c776be7e2..9d8f39bc1276 100644 --- a/Tasks/VsTest/models.ts +++ b/Tasks/VsTest/models.ts @@ -1,3 +1,5 @@ +import * as version from './vstestversion'; + export interface TestConfigurations { sourceFilter: string[]; testcaseFilter: string; @@ -12,6 +14,7 @@ export interface TestConfigurations { vsTestLocationMethod: string; vsTestVersion: string; vsTestLocation: string; + vsTestVersionDetais: version.VSTestVersion; pathtoCustomTestAdapters: string; tiaConfig: TiaConfiguration; runInParallel: boolean; @@ -41,9 +44,9 @@ export interface DtaEnvironment { } export interface VsTestConfigurations extends TestConfigurations { - publishRunAttachments: string; + publishRunAttachments: string; vstestDiagFile: string; - ignoreVstestFailure: string; + ignoreVstestFailure: string; } export interface TiaConfiguration { diff --git a/Tasks/VsTest/task.json b/Tasks/VsTest/task.json index 5711868c4058..6c447f03e3a5 100644 --- a/Tasks/VsTest/task.json +++ b/Tasks/VsTest/task.json @@ -17,7 +17,7 @@ "version": { "Major": 2, "Minor": 0, - "Patch": 39 + "Patch": 40 }, "demands": [ "vstest" @@ -185,16 +185,17 @@ "location": "Specific location" } }, - { + { "name": "vsTestVersion", "type": "pickList", "label": "Test platform version", - "defaultValue": "15.0", + "defaultValue": "latest", "required": false, "helpMarkDown": "The version of Visual Studio test to use.", "visibleRule": "vstestLocationMethod = version", "groupName": "executionOptions", "options": { + "latest": "Latest", "15.0": "Visual Studio 2017", "14.0": "Visual Studio 2015" } @@ -347,6 +348,7 @@ "VstestReturnCode": "Vstest exited with return code: %d.", "NoMatchingTestAssemblies": "No test assemblies found matching the pattern: %s.", "VstestNotFound": "Visual Studio %d is not found. Try again with a version that exists on your build agent machine.", + "NoVstestFound": "Test platform is not found. Try again after installing it on your build agent machine.", "VstestFailed": "Vstest failed with error. Check logs for failures. There might be failed tests.", "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.", @@ -375,10 +377,10 @@ "runTestInIsolationNotSupported": "Running tests in isolation is not supported when using the multi-agent phase setting. This option will be ignored.", "tiaNotSupportedInDta": "Running only impacted tests is not supported when using the multi-agent phase setting. This option will be ignored.", "overrideNotSupported": "Overriding test run parameters is supported only with runsettings file. This option will be ignored.", - "vs2013NotSupportedInDta": "Running tests using Visual Studio 2013 with multi-agent phase settings is not supported.", + "vstestVersionInvalid": "Given test platform version %s is not supported.", "configureDtaAgentFailed": "Configuring the test agent with the server failed even after %d retries with error %s", "otherConsoleOptionsNotSupported": "Other console options is not supported when using the multi-agent phase setting. This option will be ignored.", - "distributedTestWorkflow": "In distributed testing flow....", + "distributedTestWorkflow": "In distributed testing flow", "dtaNumberOfAgents": "Distributed test execution, number of agents in phase : %s", "testSelectorInput": "Test selector : %s", "searchFolderInput": "Search folder : %s", @@ -394,7 +396,7 @@ "testSuiteSelected": "Test suite Id selected: %s", "testAssemblyFilterInput": "Test assembly filter : %s", "vsVersionSelected": "VisualStudio version selected for test execution : %s", - "runTestsLocally": "Run the tests locally using %s....", + "runTestsLocally": "Run the tests locally using %s", "vstestLocationSpecified": "%s, specified location : %s", "uitestsparallel": "Running UI tests in parallel on the same machine can lead to errors. Consider disabling the ‘run in parallel’ option or run UI tests using a separate task. To learn more, see https://aka.ms/paralleltestexecution " diff --git a/Tasks/VsTest/task.loc.json b/Tasks/VsTest/task.loc.json index 89b40530350a..a1e8cff91d8e 100644 --- a/Tasks/VsTest/task.loc.json +++ b/Tasks/VsTest/task.loc.json @@ -17,7 +17,7 @@ "version": { "Major": 2, "Minor": 0, - "Patch": 39 + "Patch": 40 }, "demands": [ "vstest" @@ -189,12 +189,13 @@ "name": "vsTestVersion", "type": "pickList", "label": "ms-resource:loc.input.label.vsTestVersion", - "defaultValue": "15.0", + "defaultValue": "latest", "required": false, "helpMarkDown": "ms-resource:loc.input.help.vsTestVersion", "visibleRule": "vstestLocationMethod = version", "groupName": "executionOptions", "options": { + "latest": "Latest", "15.0": "Visual Studio 2017", "14.0": "Visual Studio 2015" } @@ -347,6 +348,7 @@ "VstestReturnCode": "ms-resource:loc.messages.VstestReturnCode", "NoMatchingTestAssemblies": "ms-resource:loc.messages.NoMatchingTestAssemblies", "VstestNotFound": "ms-resource:loc.messages.VstestNotFound", + "NoVstestFound": "ms-resource:loc.messages.NoVstestFound", "VstestFailed": "ms-resource:loc.messages.VstestFailed", "VstestTIANotSupported": "ms-resource:loc.messages.VstestTIANotSupported", "NoResultsToPublish": "ms-resource:loc.messages.NoResultsToPublish", @@ -375,7 +377,7 @@ "runTestInIsolationNotSupported": "ms-resource:loc.messages.runTestInIsolationNotSupported", "tiaNotSupportedInDta": "ms-resource:loc.messages.tiaNotSupportedInDta", "overrideNotSupported": "ms-resource:loc.messages.overrideNotSupported", - "vs2013NotSupportedInDta": "ms-resource:loc.messages.vs2013NotSupportedInDta", + "vstestVersionInvalid": "ms-resource:loc.messages.vstestVersionInvalid", "configureDtaAgentFailed": "ms-resource:loc.messages.configureDtaAgentFailed", "otherConsoleOptionsNotSupported": "ms-resource:loc.messages.otherConsoleOptionsNotSupported", "distributedTestWorkflow": "ms-resource:loc.messages.distributedTestWorkflow", diff --git a/Tasks/VsTest/taskinputparser.ts b/Tasks/VsTest/taskinputparser.ts index 037b6589059e..860b3d4c3a91 100644 --- a/Tasks/VsTest/taskinputparser.ts +++ b/Tasks/VsTest/taskinputparser.ts @@ -5,10 +5,10 @@ import * as tr from 'vsts-task-lib/toolrunner'; import * as models from './models'; import * as utils from './helpers'; import * as os from 'os'; - +import * as versionFinder from './versionfinder'; const uuid = require('node-uuid'); -export function getDistributedTestConfigurations(): models.DtaTestConfigurations { +export function getDistributedTestConfigurations() { tl.setResourcePath(path.join(__dirname, 'task.json')); const dtaConfiguration = {} as models.DtaTestConfigurations; initTestConfigurations(dtaConfiguration); @@ -41,7 +41,7 @@ export function getDistributedTestConfigurations(): models.DtaTestConfigurations return dtaConfiguration; } -export function getvsTestConfigurations(): models.VsTestConfigurations { +export function getvsTestConfigurations() { tl.setResourcePath(path.join(__dirname, 'task.json')); const vsTestConfiguration = {} as models.VsTestConfigurations; initTestConfigurations(vsTestConfiguration); @@ -155,22 +155,27 @@ function initTestConfigurations(testConfiguration: models.TestConfigurations) { tl._writeLine('vsTestVersion is null or empty'); throw new Error('vsTestVersion is null or empty'); } + if ((testConfiguration.vsTestVersion !== '15.0') && (testConfiguration.vsTestVersion !== '14.0') + && (testConfiguration.vsTestVersion.toLowerCase() !== 'latest')) { + throw new Error(tl.loc('vstestVersionInvalid', testConfiguration.vsTestVersion)); + } tl._writeLine(tl.loc('vsVersionSelected', testConfiguration.vsTestVersion)); } else { testConfiguration.vsTestLocation = tl.getInput('vsTestLocation'); tl._writeLine(tl.loc('vstestLocationSpecified', 'vstest.console.exe', testConfiguration.vsTestLocation)); } - if(tl.getBoolInput('uiTests') && testConfiguration.runInParallel) - { + if (tl.getBoolInput('uiTests') && testConfiguration.runInParallel) { tl.warning(tl.loc('uitestsparallel')); } - // only to facilitate the writing of unit tests + // only to facilitate the writing of unit tests testConfiguration.vs15HelperPath = tl.getVariable('vs15Helper'); if (!testConfiguration.vs15HelperPath) { testConfiguration.vs15HelperPath = path.join(__dirname, 'vs15Helper.ps1'); } + + versionFinder.getVsTestRunnerDetails(testConfiguration); } function getTiaConfiguration() : models.TiaConfiguration { diff --git a/Tasks/VsTest/versionfinder.ts b/Tasks/VsTest/versionfinder.ts index 05f12cdab91b..05a3b54befba 100644 --- a/Tasks/VsTest/versionfinder.ts +++ b/Tasks/VsTest/versionfinder.ts @@ -1,156 +1,117 @@ -import tl = require('vsts-task-lib/task'); -import path = require('path'); -import Q = require('q'); -import models = require('./models') -import utils = require('./helpers'); - -var regedit = require('regedit'); -var xml2js = require('xml2js'); - -export async function locateVSTestConsole(testConfig): Promise { - let deferred = Q.defer(); - let vstestExeFolder = await locateTestWindow(testConfig); - - var vstestExePath = vstestExeFolder; - if(vstestExeFolder){ - vstestExePath = path.join(vstestExeFolder, "vstest.console.exe"); - } - - return Promise.resolve(vstestExePath); -} - -export class VSTestVersion { - - constructor(public vstestExeLocation: string, public majorVersion: number, public minorversion: number, public patchNumber: number) { - } - - isTestImpactSupported(): boolean { - return (this.majorVersion >= 15); - } - - vstestDiagSupported(): boolean { - return (this.majorVersion >= 15); - } - - isPrivateDataCollectorNeededForTIA(): boolean { - return false; - } - - isRunInParallelSupported(): boolean { - return (this.majorVersion >= 15); - } -} - - -export class Dev14VSTestVersion extends VSTestVersion { - constructor(runnerLocation: string, minorVersion: number, patchNumber: number) { - super(runnerLocation, 14, minorVersion, patchNumber); - } - - isTestImpactSupported(): boolean { - return (this.patchNumber >= 25420); - } - - isRunInParallelSupported(): boolean { - return (this.patchNumber >= 25420); - } - - isPrivateDataCollectorNeededForTIA(): boolean { - return true; +import * as tl from 'vsts-task-lib/task'; +import * as path from 'path'; +import * as Q from 'q'; +import * as models from './models'; +import * as version from './vstestversion'; +import * as utils from './helpers'; + +const regedit = require('regedit'); +const xml2js = require('xml2js'); + +export function getVsTestRunnerDetails(testConfig : models.TestConfigurations) { + const vstestexeLocation = locateVSTestConsole(testConfig); + const vstestLocationEscaped = vstestexeLocation.replace(/\\/g, '\\\\'); + const wmicTool = tl.tool('wmic'); + const wmicArgs = ['datafile', 'where', 'name=\''.concat(vstestLocationEscaped, '\''), 'get', 'Version', '/Value']; + wmicTool.arg(wmicArgs); + const output = wmicTool.execSync(); + tl.debug('VSTest Version information: ' + output.stdout); + + const verSplitArray = output.stdout.split('='); + if (verSplitArray.length !== 2) { + tl.error(tl.loc('ErrorReadingVstestVersion')); + throw new Error(tl.loc('ErrorReadingVstestVersion')); + } + + const versionArray = verSplitArray[1].split('.'); + if (versionArray.length !== 4) { + tl.warning(tl.loc('UnexpectedVersionString', output.stdout)); + throw new Error(tl.loc('UnexpectedVersionString', output.stdout)); + } + + const majorVersion = parseInt(versionArray[0]); + const minorVersion = parseInt(versionArray[1]); + const patchNumber = parseInt(versionArray[2]); + + if (isNaN(majorVersion) || isNaN(minorVersion) || isNaN(patchNumber)) { + tl.warning(tl.loc('UnexpectedVersionNumber', verSplitArray[1])); + throw new Error(tl.loc('UnexpectedVersionNumber', verSplitArray[1])); + } + + switch (majorVersion) { + case 14: + testConfig.vsTestVersionDetais = new version.Dev14VSTestVersion(vstestexeLocation, minorVersion, patchNumber); + break; + case 15: + testConfig.vsTestVersionDetais = new version.Dev15VSTestVersion(vstestexeLocation, minorVersion, patchNumber); + break; + default: + testConfig.vsTestVersionDetais = new version.VSTestVersion(vstestexeLocation, majorVersion, minorVersion, patchNumber); + break; } } -export class Dev15VSTestVersion extends VSTestVersion { - constructor(runnerLocation: string, minorVersion: number, patchNumber: number) { - super(runnerLocation, 15, minorVersion, patchNumber); - } - - isTestImpactSupported(): boolean { - return (this.patchNumber >= 25727); - } - - vstestDiagSupported(): boolean { - return (this.patchNumber > 25428); +function locateVSTestConsole(testConfig : models.TestConfigurations) : string{ + const vstestExeFolder = locateTestWindow(testConfig); + let vstestExePath : string = vstestExeFolder; + if (vstestExeFolder) { + vstestExePath = path.join(vstestExeFolder, 'vstest.console.exe'); } + return vstestExePath; } -function locateTestWindow(testConfig: models.TestConfigurations): Promise { - let deferred = Q.defer(); - let vsVersion: number = parseFloat(testConfig.vsTestVersion); - if(testConfig.vsTestLocationMethod === utils.Constants.vsTestLocationString) { +function locateTestWindow(testConfig: models.TestConfigurations): string { + if (testConfig.vsTestLocationMethod === utils.Constants.vsTestLocationString) { if (utils.Helper.pathExistsAsFile(testConfig.vsTestLocation)) { - return Promise.resolve(path.join(testConfig.vsTestLocation,"..")); - } - - if (utils.Helper.pathExistsAsDirectory(testConfig.vsTestLocation) && - utils.Helper.pathExistsAsFile(path.join(testConfig.vsTestLocation, 'vstest.console.exe'))) { - return Promise.resolve(testConfig.vsTestLocation); - } + return path.join(testConfig.vsTestLocation, '..'); + } + if (utils.Helper.pathExistsAsDirectory(testConfig.vsTestLocation) && + utils.Helper.pathExistsAsFile(path.join(testConfig.vsTestLocation, 'vstest.console.exe'))) { + return testConfig.vsTestLocation; + } throw (new Error(tl.loc('PathDoesNotExist', testConfig.vsTestLocation))); - } + } - if (isNaN(vsVersion)) { + if (testConfig.vsTestVersion.toLowerCase() === 'latest') { // latest tl.debug('Searching for latest Visual Studio'); - let vstestconsole15Path = getVSTestConsole15Path(testConfig.vs15HelperPath); + const vstestconsole15Path = getVSTestConsole15Path(testConfig.vs15HelperPath); if (vstestconsole15Path) { - return Promise.resolve(vstestconsole15Path); - } + return vstestconsole15Path; + } // fallback - tl.debug('Unable to find an instance of Visual Studio 2017'); - return getLatestVSTestConsolePathFromRegistry(); + tl.debug('Unable to find an instance of Visual Studio 2017..'); + tl.debug('Searching for Visual Studio 2015..'); + return getVSTestLocation(14); } - - if (vsVersion === 15.0) { - let vstestconsole15Path = getVSTestConsole15Path(testConfig.vs15HelperPath); - if (vstestconsole15Path) { - return Promise.resolve(vstestconsole15Path); - } + const vsVersion: number = parseFloat(testConfig.vsTestVersion); + + if (vsVersion === 15.0) { + const vstestconsole15Path = getVSTestConsole15Path(testConfig.vs15HelperPath); + if (vstestconsole15Path) { + return vstestconsole15Path; + } throw (new Error(tl.loc('VstestNotFound', utils.Helper.getVSVersion(vsVersion)))); } - tl.debug('Searching for Visual Studio ' + vsVersion.toString()); - return Promise.resolve(getVSTestLocation(vsVersion)); -} - -function getLatestVSTestConsolePathFromRegistry(): Promise { - let deferred = Q.defer(); - let regPath = 'HKLM\\SOFTWARE\\Microsoft\\VisualStudio'; - regedit.list(regPath).on('data', (entry) => { - let subkeys = entry.data.keys; - let versions = getFloatsFromStringArray(subkeys); - if (versions && versions.length > 0) { - versions.sort((a, b) => a - b); - let selectedVersion = versions[versions.length - 1]; - tl.debug('Registry entry found. Selected version is ' + selectedVersion.toString()); - return Promise.resolve(getVSTestLocation(selectedVersion)); - } - - tl.debug('No Registry entry found under VisualStudio node'); - return Promise.resolve(null); - }).on('error', () => { - tl.debug('Registry entry not found under VisualStudio node'); - return Promise.resolve(null); - }); - - return Promise.resolve(null); + return getVSTestLocation(vsVersion); } function getVSTestConsole15Path(vs15HelperPath: string): string { - let powershellTool = tl.tool('powershell'); - let powershellArgs = ['-NonInteractive', '-ExecutionPolicy', 'Unrestricted', '-file', vs15HelperPath] + const powershellTool = tl.tool('powershell'); + const powershellArgs = ['-NonInteractive', '-ExecutionPolicy', 'Unrestricted', '-file', vs15HelperPath] powershellTool.arg(powershellArgs); - let xml = powershellTool.execSync().stdout; - let deferred = Q.defer(); + const xml = powershellTool.execSync().stdout; + const deferred = Q.defer(); let vstestconsolePath: string = null; xml2js.parseString(xml, (err, result) => { if (result) { try { - let vs15InstallDir = result['Objs']['S'][0]; + const vs15InstallDir = result['Objs']['S'][0]; vstestconsolePath = path.join(vs15InstallDir, 'Common7', 'IDE', 'CommonExtensions', 'Microsoft', 'TestWindow'); } catch (e) { tl.debug('Unable to read Visual Studio 2017 installation path'); @@ -164,25 +125,23 @@ function getVSTestConsole15Path(vs15HelperPath: string): string { } function getVSTestLocation(vsVersion: number): string { - let vsCommon: string = tl.getVariable('VS' + vsVersion + '0COMNTools'); + const vsCommon: string = tl.getVariable('VS' + vsVersion + '0COMNTools'); if (!vsCommon) { throw (new Error(tl.loc('VstestNotFound', utils.Helper.getVSVersion(vsVersion)))); - } - + } return path.join(vsCommon, '..\\IDE\\CommonExtensions\\Microsoft\\TestWindow'); } function getFloatsFromStringArray(inputArray: string[]): number[] { - var outputArray: number[] = []; - var count; + const outputArray: number[] = []; + let count; if (inputArray) { for (count = 0; count < inputArray.length; count++) { - var floatValue = parseFloat(inputArray[count]); + const floatValue = parseFloat(inputArray[count]); if (!isNaN(floatValue)) { outputArray.push(floatValue); } } } - return outputArray; } \ No newline at end of file diff --git a/Tasks/VsTest/vstest.ts b/Tasks/VsTest/vstest.ts index ae9fdd3f242a..527df551f377 100644 --- a/Tasks/VsTest/vstest.ts +++ b/Tasks/VsTest/vstest.ts @@ -5,7 +5,7 @@ import Q = require('q'); import models = require('./models'); import taskInputParser = require('./taskinputparser'); import settingsHelper = require('./settingshelper'); -import versionFinder = require('./versionfinder'); +import vstestVersion = require('./vstestversion'); import * as utils from './helpers'; import * as outStream from './outputstream'; @@ -22,13 +22,12 @@ const testSettingsExt = '.testsettings'; let vstestConfig: models.VsTestConfigurations = undefined; let tiaConfig: models.TiaConfiguration = undefined; -let vstestRunnerDetails: versionFinder.VSTestVersion = undefined; const systemDefaultWorkingDirectory = tl.getVariable('System.DefaultWorkingDirectory'); const workingDirectory = systemDefaultWorkingDirectory; let testAssemblyFiles = undefined; let resultsDirectory = null; -export async function startTest() { +export function startTest() { try { tl._writeLine(tl.loc('runTestsLocally', 'vstest.console.exe')); tl._writeLine('========================================================'); @@ -36,8 +35,6 @@ export async function startTest() { tl._writeLine('========================================================'); tiaConfig = vstestConfig.tiaConfig; - const vstestlocation = await versionFinder.locateVSTestConsole(vstestConfig); - vstestRunnerDetails = getVsTestRunnerDetails(vstestlocation); //Try to find the results directory for clean up. This may change later if runsettings has results directory and location go runsettings file changes. resultsDirectory = getTestResultsDirectory(vstestConfig.settingsFile, path.join(workingDirectory, 'TestResults')); @@ -91,46 +88,6 @@ function getTestAssemblies(): string[] { return tl.findMatch(vstestConfig.testDropLocation, vstestConfig.sourceFilter); } -function getVsTestRunnerDetails(vstestexeLocation: string): versionFinder.VSTestVersion { - const vstestLocationEscaped = vstestexeLocation.replace(/\\/g, '\\\\'); - const wmicTool = tl.tool('wmic'); - const wmicArgs = ['datafile', 'where', 'name=\''.concat(vstestLocationEscaped, '\''), 'get', 'Version', '/Value']; - wmicTool.arg(wmicArgs); - const output = wmicTool.execSync(); - tl.debug('VSTest Version information: ' + output.stdout); - - const verSplitArray = output.stdout.split('='); - if (verSplitArray.length !== 2) { - tl.error(tl.loc('ErrorReadingVstestVersion')); - throw new Error(tl.loc('ErrorReadingVstestVersion')); - } - - const versionArray = verSplitArray[1].split('.'); - if (versionArray.length !== 4) { - tl.warning(tl.loc('UnexpectedVersionString', output.stdout)); - throw new Error(tl.loc('UnexpectedVersionString', output.stdout)); - } - - const majorVersion = parseInt(versionArray[0]); - const minorVersion = parseInt(versionArray[1]); - const patchNumber = parseInt(versionArray[2]); - - if (isNaN(majorVersion) || isNaN(minorVersion) || isNaN(patchNumber)) { - tl.warning(tl.loc('UnexpectedVersionNumber', verSplitArray[1])); - throw new Error(tl.loc('UnexpectedVersionNumber', verSplitArray[1])); - } - - switch (majorVersion) { - case 14: - return new versionFinder.Dev14VSTestVersion(vstestexeLocation, minorVersion, patchNumber); - - case 15: - return new versionFinder.Dev15VSTestVersion(vstestexeLocation, minorVersion, patchNumber); - } - - return new versionFinder.VSTestVersion(vstestexeLocation, majorVersion, minorVersion, patchNumber); -} - function getVstestArguments(settingsFile: string, tiaEnabled: boolean): string[] { const argsArray: string[] = []; testAssemblyFiles.forEach(function (testAssembly) { @@ -184,7 +141,7 @@ function getVstestArguments(settingsFile: string, tiaEnabled: boolean): string[] } if (isDebugEnabled()) { - if (vstestRunnerDetails != null && vstestRunnerDetails.vstestDiagSupported()) { + if (vstestConfig.vsTestVersionDetais != null && vstestConfig.vsTestVersionDetais.vstestDiagSupported()) { argsArray.push('/diag:' + vstestConfig.vstestDiagFile); } else { tl.warning(tl.loc('VstestDiagNotSupported')); @@ -454,7 +411,7 @@ function publishCodeChanges(): Q.Promise { function executeVstest(testResultsDirectory: string, parallelRunSettingsFile: string, vsVersion: number, argsArray: string[]): Q.Promise { const defer = Q.defer(); - const vstest = tl.tool(vstestRunnerDetails.vstestExeLocation); + const vstest = tl.tool(vstestConfig.vsTestVersionDetais.vstestExeLocation); addVstestArgs(argsArray, vstest); // Adding the other console options here @@ -541,7 +498,7 @@ function getVstestTestsList(vsVersion: number): Q.Promise { argsArray.push('/UseVsixExtensions:true'); } - let vstest = tl.tool(vstestRunnerDetails.vstestExeLocation); + let vstest = tl.tool(vstestConfig.vsTestVersionDetais.vstestExeLocation); if (vsVersion === 14.0) { tl.debug('Visual studio 2015 selected. Selecting vstest.console.exe in task '); @@ -573,7 +530,7 @@ function cleanFiles(responseFile: string, listFile: string): void { } function deleteVstestDiagFile(): void { - if (pathExistsAsFile(vstestConfig.vstestDiagFile)) { + if (vstestConfig && vstestConfig.vstestDiagFile && pathExistsAsFile(vstestConfig.vstestDiagFile)) { tl.debug('Deleting vstest diag file ' + vstestConfig.vstestDiagFile); tl.rmRF(vstestConfig.vstestDiagFile, true); } @@ -780,9 +737,6 @@ function runVStest(testResultsDirectory: string, settingsFile: string, vsVersion function invokeVSTest(testResultsDirectory: string): Q.Promise { const defer = Q.defer(); - if (vstestConfig.vsTestVersion && vstestConfig.vsTestVersion.toLowerCase() === 'latest') { - vstestConfig.vsTestVersion = null; - } try { const disableTIA = tl.getVariable('DisableTestImpactAnalysis'); @@ -790,7 +744,7 @@ function invokeVSTest(testResultsDirectory: string): Q.Promise { tiaConfig.tiaEnabled = false; } - if (tiaConfig.tiaEnabled && (vstestRunnerDetails === null || !vstestRunnerDetails.isTestImpactSupported())) { + if (tiaConfig.tiaEnabled && (vstestConfig.vsTestVersionDetais === null || !vstestConfig.vsTestVersionDetais.isTestImpactSupported())) { tl.warning(tl.loc('VstestTIANotSupported')); tiaConfig.tiaEnabled = false; } @@ -801,14 +755,14 @@ function invokeVSTest(testResultsDirectory: string): Q.Promise { } // We need to use private data collector dll - if (vstestRunnerDetails !== null) { - tiaConfig.useNewCollector = vstestRunnerDetails.isPrivateDataCollectorNeededForTIA(); + if (vstestConfig.vsTestVersionDetais !== null) { + tiaConfig.useNewCollector = vstestConfig.vsTestVersionDetais.isPrivateDataCollectorNeededForTIA(); } setRunInParallellIfApplicable(); let newSettingsFile = vstestConfig.settingsFile; - const vsVersion = vstestRunnerDetails.majorVersion; + const vsVersion = vstestConfig.vsTestVersionDetais.majorVersion; if (newSettingsFile) { if (!pathExistsAsFile(newSettingsFile)) { @@ -924,7 +878,7 @@ function getTestResultsDirectory(settingsFile: string, defaultResultsDirectory: function setRunInParallellIfApplicable() { if (vstestConfig.runInParallel) { - if (vstestRunnerDetails != null && vstestRunnerDetails.isRunInParallelSupported()) { + if (vstestConfig.vsTestVersionDetais != null && vstestConfig.vsTestVersionDetais.isRunInParallelSupported()) { return; } diff --git a/Tasks/VsTest/vstestversion.ts b/Tasks/VsTest/vstestversion.ts new file mode 100644 index 000000000000..40f7b161675d --- /dev/null +++ b/Tasks/VsTest/vstestversion.ts @@ -0,0 +1,54 @@ +export class VSTestVersion { + + constructor(public vstestExeLocation: string, public majorVersion: number, public minorversion: number, public patchNumber: number) { + } + + public isTestImpactSupported(): boolean { + return (this.majorVersion >= 15); + } + + public vstestDiagSupported(): boolean { + return (this.majorVersion >= 15); + } + + public isPrivateDataCollectorNeededForTIA(): boolean { + return false; + } + + public isRunInParallelSupported(): boolean { + return (this.majorVersion >= 15); + } +} + + +export class Dev14VSTestVersion extends VSTestVersion { + constructor(runnerLocation: string, minorVersion: number, patchNumber: number) { + super(runnerLocation, 14, minorVersion, patchNumber); + } + + public isTestImpactSupported(): boolean { + return (this.patchNumber >= 25420); + } + + public isRunInParallelSupported(): boolean { + return (this.patchNumber >= 25420); + } + + public isPrivateDataCollectorNeededForTIA(): boolean { + return true; + } +} + +export class Dev15VSTestVersion extends VSTestVersion { + constructor(runnerLocation: string, minorVersion: number, patchNumber: number) { + super(runnerLocation, 15, minorVersion, patchNumber); + } + + public isTestImpactSupported(): boolean { + return (this.patchNumber >= 25727); + } + + public vstestDiagSupported(): boolean { + return (this.patchNumber > 25428); + } +} \ No newline at end of file diff --git a/Tests-Legacy/L0/VsTest/_suite.ts b/Tests-Legacy/L0/VsTest/_suite.ts index b47119021459..535d226ebfe4 100644 --- a/Tests-Legacy/L0/VsTest/_suite.ts +++ b/Tests-Legacy/L0/VsTest/_suite.ts @@ -194,6 +194,32 @@ describe('VsTest Suite', function () { tr.setInput('testSelector', 'testAssemblies'); tr.setInput('testAssemblyVer2', '/path/to/test.dll'); tr.setInput('vsTestVersion', 'latest'); + tr.setInput('vstestLocationMethod', 'version'); + + tr.run() + .then(() => { + console.log(tr.stdout); + assert(tr.resultWasSet, 'task should have set a result' + tr.stderr + tr.stdout); + assert(tr.stderr.length === 0, 'should not have written to stderr. error: ' + tr.stderr + tr.stdout); + assert(tr.succeeded, 'task should have succeeded'); + assert(tr.ran(vstestCmd), 'should have run vstest' + tr.stdout + tr.stderr); + done(); + }) + .fail((err) => { + done(err); + }); + }) + + it('VSTest task with only VS2015 installed on build agent and latest option is selected in definition', (done) => { + + const vstestCmd = ['\\vs\\IDE\\CommonExtensions\\Microsoft\\TestWindow\\vstest.console.exe', '/path/to/test.dll', '/logger:trx'].join(' '); + setResponseFile('vs2015.json'); + + const tr = new trm.TaskRunner('VSTest'); + tr.setInput('testSelector', 'testAssemblies'); + tr.setInput('testAssemblyVer2', '/path/to/test.dll'); + tr.setInput('vsTestVersion', 'latest'); + tr.setInput('vstestLocationMethod', 'version'); tr.run() .then(() => { @@ -417,7 +443,7 @@ describe('VsTest Suite', function () { setResponseFile('vstestGood.json'); let tr = new trm.TaskRunner('VSTest'); - tr.setInput('testSelector', 'testAssemblies'); + tr.setInput('testSelector', 'testAssemblies'); tr.setInput('testAssemblyVer2', '/source/dir/someFile1'); tr.setInput('vstestLocationMethod', 'version'); tr.setInput('vsTestVersion', '14.0'); @@ -438,33 +464,6 @@ describe('VsTest Suite', function () { }); }) - it('Vstest task with run in parallel and vs 2013', (done) => { - - const vstestCmd = [sysVstestLocation, '/source/dir/someFile1', '/logger:trx'].join(' '); - setResponseFile('vstestGood.json'); - - const tr = new trm.TaskRunner('VSTest'); - tr.setInput('testSelector', 'testAssemblies'); - tr.setInput('testAssemblyVer2', '/source/dir/someFile1'); - tr.setInput('vstestLocationMethod', 'version'); - tr.setInput('vsTestVersion', '12.0'); - tr.setInput('runInParallel', 'true'); - - tr.run() - .then(() => { - assert(tr.resultWasSet, 'task should have set a result'); - assert(tr.stderr.length === 0, 'should not have written to stderr. error: ' + tr.stderr); - assert(tr.succeeded, 'task should have succeeded'); - assert(tr.ran(vstestCmd), 'should have run vstest'); - assert(tr.stdout.search(/##vso\[results.publish type=VSTest;mergeResults=false;resultFiles=a.trx;\]/) >= 0, 'should publish test results.'); - assert(tr.stdout.search(/Install Visual Studio 2015 Update 3 or higher on your build agent machine to run the tests in parallel./) >= 0, 'should have given a warning for update3 or higher requirement'); - done(); - }) - .fail((err) => { - done(err); - }); - }) - it('Vstest task with run in parallel and vs 2015 below update1', (done) => { const vstestCmd = [sysVstestLocation, '/source/dir/someFile1', '/logger:trx'].join(' '); diff --git a/Tests-Legacy/L0/VsTest/vs2015.json b/Tests-Legacy/L0/VsTest/vs2015.json new file mode 100644 index 000000000000..0c33d20788e0 --- /dev/null +++ b/Tests-Legacy/L0/VsTest/vs2015.json @@ -0,0 +1,64 @@ +{ + "getVariable": { + "System.DefaultWorkingDirectory": "/source/dir", + "build.sourcesdirectory": "/source/dir", + "VS150COMNTools": "/vs/path", + "VS140COMNTools": "/vs/path", + "vs15Helper": "/path/to/vs15Helper.ps1", + "VSTest_14.0": "/vs/IDE/CommonExtensions/Microsoft/TestWindow" + }, + "match": { + "**\\packages\\**\\*TestAdapter.dll": [] + }, + "findMatch": { + "\\source\\dir\\TestResults\\*.trx": [ + "a.trx" + ], + "**\\packages\\**\\*TestAdapter.dll": [], + "/path/to/test.dll": [ + "/path/to/test.dll" + ] + }, + "exec": { + "\\vs\\IDE\\CommonExtensions\\Microsoft\\TestWindow\\vstest.console.exe /path/to/test.dll /logger:trx": { + "code": 0, + "stdout": "vstest 2015..." + }, + "powershell -NonInteractive -ExecutionPolicy Unrestricted -file /path/to/vs15Helper.ps1": { + "code": 0, + "stdout": "" + }, + "wmic datafile where name='\\\\vs\\\\IDE\\\\CommonExtensions\\\\Microsoft\\\\TestWindow\\\\vstest.console.exe' get Version /Value": { + "code": 0, + "stdout" : "version=14.0.0.0" + } + }, + "rmRF": { + "\\source\\dir\\TestResults": { + "success": true, + "message": "success" + } + }, + "exist": { + "settings.runsettings": true, + "path/to/customadapters": true, + "\\vs\\IDE\\CommonExtensions\\Microsoft\\TestWindow\\TE.TestModes.dll": false, + "some\\path\\to\\vstest.console.exe": true, + "some\\illegal\\path\\to\\vstest.console.exe": false, + "\\path\\to\\vstest\\directory": true + }, + "stats": { + "settings.runsettings": { + "isFile": true + }, + "some\\path\\to\\vstest.console.exe": { + "isFile": true + }, + "path/to/customadapters": { + "isDirectory": true + }, + "\\path\\to\\vstest\\directory": { + "isDirectory": true + } + } +} \ No newline at end of file