Skip to content

Commit

Permalink
Merge pull request #3541 from Microsoft/users/kavipriya/settings
Browse files Browse the repository at this point in the history
Users/kavipriya/settings
  • Loading branch information
kaadhina authored Feb 7, 2017
2 parents bdcc00f + b24dbe2 commit 7787cf9
Show file tree
Hide file tree
Showing 9 changed files with 474 additions and 499 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
"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.",
Expand All @@ -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."
}
25 changes: 23 additions & 2 deletions Tasks/VsTest/distributedTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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);
Expand All @@ -96,9 +106,20 @@ export class DistributedTest {
utils.Helper.setEnvironmentVariableToString(envVars, 'customslicingenabled', 'true');

await runDistributesTestTool.exec(<tr.IExecOptions>{ 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;
}
7 changes: 4 additions & 3 deletions Tasks/VsTest/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -17,6 +17,8 @@ export interface TestConfigurations {
vsTestVersion: string;
pathtoCustomTestAdapters: string;
tiaConfig: TiaConfiguration;
runInParallel: boolean;
runTestsInIsolation: boolean;
}

export interface DtaTestConfigurations extends TestConfigurations {
Expand All @@ -38,8 +40,7 @@ export interface DtaEnvironment {
}

export interface VsTestConfigurations extends TestConfigurations {
publishRunAttachments: string;
runInParallel: boolean;
publishRunAttachments: string;
vstestDiagFile: string;
ignoreVstestFailure: string;
vs15HelperPath: string;
Expand Down
290 changes: 290 additions & 0 deletions Tasks/VsTest/settingsHelper.ts
Original file line number Diff line number Diff line change
@@ -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 = "<DataCollector uri=\"datacollector://microsoft/TestImpact/1.0\" assemblyQualifiedName=\"Microsoft.VisualStudio.TraceCollector.TestImpactDataCollector, Microsoft.VisualStudio.TraceCollector, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\" friendlyName=\"Test Impact\"><Configuration><RootPath></RootPath></Configuration></DataCollector>";

//Video collector
const VideoCollectorFriendlyName ="Screen and Voice Recorder";
const VideoDataCollectorTemplate = "<DataCollector uri=\"datacollector://microsoft/VideoRecorder/1.0\" assemblyQualifiedName=\"Microsoft.VisualStudio.TestTools.DataCollection.VideoRecorder.VideoRecorderDataCollector, Microsoft.VisualStudio.TestTools.DataCollection.VideoRecorder, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\" friendlyName=\"Screen and Voice Recorder\"></DataCollector>";

//Parallel configuration
var runSettingsForParallel = '<?xml version="1.0" encoding="utf-8"?><RunSettings><RunConfiguration><MaxCpuCount>0</MaxCpuCount></RunConfiguration></RunSettings>';

const testSettingsTemplate ="<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<TestSettings name=\"testSettings-5d76a195-1e43-4b90-a6ce-4ec3de87ed25\" id=\"5d76a195-1e43-4b90-a6ce-4ec3de87ed25\" xmlns=\"http://microsoft.com/schemas/VisualStudio/TeamTest/2010\">" +
"<Execution>" +
"<AgentRule name=\"agent-5d76a195-1e43-4b90-a6ce-4ec3de87ed25\">" +
"<DataCollectors>" +
"</DataCollectors>" +
"</AgentRule>" +
"</Execution>" +
"</TestSettings>";

const runSettingsTemplate = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
"<RunSettings>" +
"<DataCollectionRunSettings>" +
"<DataCollectors>" +
"</DataCollectors>" +
"</DataCollectionRunSettings>" +
"</RunSettings>";

export async function updateSettingsFileAsRequired(settingsFile: string, isParallelRun: boolean, tiaConfig: models.TiaConfiguration, vsVersion: any, videoCollector: boolean) : Promise<string>
{
var defer=Q.defer<string>();
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<any> {
var defer=Q.defer<any>();
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<any> {
var defer=Q.defer<any>();
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<any> {
var defer=Q.defer<any>();
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";
}
Loading

0 comments on commit 7787cf9

Please sign in to comment.