Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a tool to migrate testsettings to runsettings #1600

Merged
merged 23 commits into from
May 23, 2018
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions TestPlatform.sln
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.VisualStudio.Trac
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TestPlatform.TraceDataCollector.UnitTests", "test\DataCollectors\TraceDataCollector.UnitTests\Microsoft.TestPlatform.TraceDataCollector.UnitTests.csproj", "{A7E2261B-B2E6-4CBF-983F-E3A3FF8E52E3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SettingsMigrator", "src\SettingsMigrator\SettingsMigrator.csproj", "{69F5FF81-5615-4F06-B83C-FCF979BB84CA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SettingsMigrator.UnitTests", "test\SettingsMigrator.UnitTests\SettingsMigrator.UnitTests.csproj", "{E7D4921C-F12D-4E1C-85AC-8B7F91C59B0E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -824,6 +828,30 @@ Global
{A7E2261B-B2E6-4CBF-983F-E3A3FF8E52E3}.Release|x64.Build.0 = Release|Any CPU
{A7E2261B-B2E6-4CBF-983F-E3A3FF8E52E3}.Release|x86.ActiveCfg = Release|Any CPU
{A7E2261B-B2E6-4CBF-983F-E3A3FF8E52E3}.Release|x86.Build.0 = Release|Any CPU
{69F5FF81-5615-4F06-B83C-FCF979BB84CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{69F5FF81-5615-4F06-B83C-FCF979BB84CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{69F5FF81-5615-4F06-B83C-FCF979BB84CA}.Debug|x64.ActiveCfg = Debug|Any CPU
{69F5FF81-5615-4F06-B83C-FCF979BB84CA}.Debug|x64.Build.0 = Debug|Any CPU
{69F5FF81-5615-4F06-B83C-FCF979BB84CA}.Debug|x86.ActiveCfg = Debug|Any CPU
{69F5FF81-5615-4F06-B83C-FCF979BB84CA}.Debug|x86.Build.0 = Debug|Any CPU
{69F5FF81-5615-4F06-B83C-FCF979BB84CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{69F5FF81-5615-4F06-B83C-FCF979BB84CA}.Release|Any CPU.Build.0 = Release|Any CPU
{69F5FF81-5615-4F06-B83C-FCF979BB84CA}.Release|x64.ActiveCfg = Release|Any CPU
{69F5FF81-5615-4F06-B83C-FCF979BB84CA}.Release|x64.Build.0 = Release|Any CPU
{69F5FF81-5615-4F06-B83C-FCF979BB84CA}.Release|x86.ActiveCfg = Release|Any CPU
{69F5FF81-5615-4F06-B83C-FCF979BB84CA}.Release|x86.Build.0 = Release|Any CPU
{E7D4921C-F12D-4E1C-85AC-8B7F91C59B0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E7D4921C-F12D-4E1C-85AC-8B7F91C59B0E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E7D4921C-F12D-4E1C-85AC-8B7F91C59B0E}.Debug|x64.ActiveCfg = Debug|Any CPU
{E7D4921C-F12D-4E1C-85AC-8B7F91C59B0E}.Debug|x64.Build.0 = Debug|Any CPU
{E7D4921C-F12D-4E1C-85AC-8B7F91C59B0E}.Debug|x86.ActiveCfg = Debug|Any CPU
{E7D4921C-F12D-4E1C-85AC-8B7F91C59B0E}.Debug|x86.Build.0 = Debug|Any CPU
{E7D4921C-F12D-4E1C-85AC-8B7F91C59B0E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E7D4921C-F12D-4E1C-85AC-8B7F91C59B0E}.Release|Any CPU.Build.0 = Release|Any CPU
{E7D4921C-F12D-4E1C-85AC-8B7F91C59B0E}.Release|x64.ActiveCfg = Release|Any CPU
{E7D4921C-F12D-4E1C-85AC-8B7F91C59B0E}.Release|x64.Build.0 = Release|Any CPU
{E7D4921C-F12D-4E1C-85AC-8B7F91C59B0E}.Release|x86.ActiveCfg = Release|Any CPU
{E7D4921C-F12D-4E1C-85AC-8B7F91C59B0E}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -895,6 +923,8 @@ Global
{24C7683D-2607-4901-B8EB-83A57E49E93D} = {376C19DE-31E2-4FF6-88FC-0D0D6233C999}
{32BD96BD-16FB-43F0-B952-E7EEDB6DD813} = {B705537C-B82C-4A30-AFA5-6244D9A7DAEB}
{A7E2261B-B2E6-4CBF-983F-E3A3FF8E52E3} = {D9A30E32-D466-4EC5-B4F2-62E17562279B}
{69F5FF81-5615-4F06-B83C-FCF979BB84CA} = {ED0C35EB-7F31-4841-A24F-8EB708FFA959}
{E7D4921C-F12D-4E1C-85AC-8B7F91C59B0E} = {B27FAFDF-2DBA-4AB0-BA85-FD5F21D359D6}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0541B30C-FF51-4E28-B172-83F5F3934BCD}
Expand Down
4 changes: 4 additions & 0 deletions scripts/build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ function Publish-Package
$testhostCorePackageDir = $(Join-Path $env:TP_OUT_DIR "$TPB_Configuration\Microsoft.TestPlatform.TestHost\$TPB_TargetFrameworkCore")
$testhostNS1_4PackageDir = $(Join-Path $env:TP_OUT_DIR "$TPB_Configuration\Microsoft.TestPlatform.TestHost\$TPB_TargetFrameworkNS1_4")
$vstestConsoleProject = Join-Path $env:TP_ROOT_DIR "src\vstest.console\vstest.console.csproj"
$settingsMigratorProject = Join-Path $env:TP_ROOT_DIR "src\SettingsMigrator\SettingsMigrator.csproj"
$dataCollectorProject = Join-Path $env:TP_ROOT_DIR "src\datacollector\datacollector.csproj"

Write-Log "Package: Publish src\package\package\package.csproj"
Expand All @@ -245,6 +246,9 @@ function Publish-Package
Write-Log "Package: Publish src\vstest.console\vstest.console.csproj"
Publish-PackageInternal $vstestConsoleProject $TPB_TargetFramework $fullCLRPackageDir
Publish-PackageInternal $vstestConsoleProject $TPB_TargetFrameworkCore20 $coreCLR20PackageDir

Write-Log "Package: Publish src\SettingsMigrator\SettingsMigrator.csproj"
Publish-PackageInternal $settingsMigratorProject $TPB_TargetFramework $fullCLRPackageDir

Write-Log "Package: Publish src\datacollector\datacollector.csproj"
Publish-PackageInternal $dataCollectorProject $TPB_TargetFramework $fullCLRPackageDir
Expand Down
6 changes: 6 additions & 0 deletions src/SettingsMigrator/App.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is .NETFramework,Version=v4.6.1?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WE can remove app.config.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

</startup>
</configuration>
276 changes: 276 additions & 0 deletions src/SettingsMigrator/Migrator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.IO;
using System.Xml;

namespace Microsoft.VisualStudio.TestPlatform.CommandLine
Copy link
Contributor

@smadala smadala May 17, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we rename Microsoft.VisualStudio.TestPlatform.SettingsMigrator?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

{
public class Migrator
{
/// <summary>
/// Given a runsettings with an embedded testsettings, converts it to runsettings.
/// </summary>
/// <param name="oldRunsettingsPath"></param>
/// <param name="newRunsettingsPath"></param>
public void MigrateRunsettings(string oldRunsettingsPath, string newRunsettingsPath)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: RunSettings*

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

{
string testsettingsPath = null;
using (XmlTextReader reader = new XmlTextReader(oldRunsettingsPath))
{
reader.Namespaces = false;

var document = new XmlDocument();
document.Load(reader);
var root = document.DocumentElement;

var testsettingsNode = root.SelectSingleNode(@"/RunSettings/MSTest/SettingsFile");

if (testsettingsNode != null)
{
testsettingsPath = testsettingsNode.InnerText;
}
if (!string.IsNullOrWhiteSpace(testsettingsPath))
{
// Expand path relative to runsettings location.
if (!Path.IsPathRooted(testsettingsPath))
{
testsettingsPath = Path.Combine(oldRunsettingsPath, testsettingsPath);
}

MigrateTestsettings(testsettingsPath, newRunsettingsPath, File.ReadAllText(oldRunsettingsPath));
}
else
{
Console.WriteLine("Runsettings does not contain an embedded testsettings, not migrating.");
}
}
}

/// <summary>
/// Given a testsettings, converts it to runsettings
/// </summary>
/// <param name="testsettingsPath"></param>
/// <param name="newRunsettingsPath"></param>
/// <param name="oldRunSettingsContent"></param>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be private.

public void MigrateTestsettings(string testsettingsPath, string newRunsettingsPath, string oldRunSettingsContent = null)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's refactor this to create file separately, and have a common api for translating testsettings xml to required runsettings xml
MigrateLegacyNodesToRunSettings

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

{
using (XmlTextReader reader = new XmlTextReader(testsettingsPath))
{
reader.Namespaces = false;

var document = new XmlDocument();
document.Load(reader);
var root = document.DocumentElement;

// Select the interesting nodes from the xml.
var deploymentNode = root.SelectSingleNode(@"/TestSettings/Deployment");
var scriptnode = root.SelectSingleNode(@"/TestSettings/Scripts");
var websettingsNode = root.SelectSingleNode(@"/TestSettings/Execution/TestTypeSpecific/WebTestRunConfiguration");
var oldDatacollectorNodes = root.SelectNodes(@"/TestSettings/AgentRule/DataCollectors/DataCollector");
var timeoutNode = root.SelectSingleNode(@"/TestSettings/Execution/Timeouts");
var assemblyresolutionNode = root.SelectSingleNode(@"/TestSettings/Execution/TestTypeSpecific/UnitTestRunConfig");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assemblyresolutionNode [](start = 20, length = 22)

unitTestConfigNode

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

var hostsNode = root.SelectSingleNode(@"/TestSettings/Execution/Hosts");
var executionNode = root.SelectSingleNode(@"/TestSettings/Execution");

string testTimeout = null;
if (timeoutNode != null && timeoutNode.Attributes[TestTimeoutAttributeName] != null)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add warning for deployment and script timeout

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

{
testTimeout = timeoutNode.Attributes[TestTimeoutAttributeName].Value;
}

string runTimeout = null;
if (timeoutNode != null && timeoutNode.Attributes[RunTimeoutAttributeName] != null)
{
runTimeout = timeoutNode.Attributes[RunTimeoutAttributeName].Value;
}

string parallelTestCount = null;
if (executionNode != null && executionNode.Attributes[ParallelTestCountAttributeName] != null)
{
parallelTestCount = executionNode.Attributes[ParallelTestCountAttributeName].Value;
}

if(string.IsNullOrEmpty(oldRunSettingsContent))
{
oldRunSettingsContent = sampleRunsettingsContent;
}
var newXmlDoc = new XmlDocument();
newXmlDoc.LoadXml(oldRunSettingsContent);

// Remove the embedded testsettings node if it exists.
RemoveEmbeddedTestsettings(newXmlDoc);

// WebTestRunConfiguration node.
if (websettingsNode != null)
{
newXmlDoc.DocumentElement.AppendChild(newXmlDoc.ImportNode(websettingsNode, deep: true));
}

// LegacySettings node.
if (deploymentNode != null || scriptnode != null || assemblyresolutionNode != null ||
!string.IsNullOrEmpty(parallelTestCount) || !string.IsNullOrEmpty(testTimeout) || hostsNode != null)
{
AddLegacyNodes(deploymentNode, scriptnode, testTimeout, assemblyresolutionNode, hostsNode, parallelTestCount, newXmlDoc);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AddLegacyNodes [](start = 20, length = 14)

Create a class for all these nodes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

}

// TestSessionTimeout node.
if (!string.IsNullOrEmpty(runTimeout))
{
AddRunTimeoutNode(runTimeout, newXmlDoc);
}

// DataCollectors node.
if (oldDatacollectorNodes != null && oldDatacollectorNodes.Count > 0)
{
AddDataCollectorNodes(oldDatacollectorNodes, newXmlDoc);
}

newXmlDoc.Save(newRunsettingsPath);
}
}

/// <summary>
/// Removes the embedded testsettings node if present.
/// </summary>
/// <param name="newXmlDoc"></param>
private void RemoveEmbeddedTestsettings(XmlDocument newXmlDoc)
{
var testsettingsNode = newXmlDoc.DocumentElement.SelectSingleNode(@"/RunSettings/MSTest/SettingsFile");
if (testsettingsNode != null)
{
testsettingsNode.ParentNode.RemoveChild(testsettingsNode);
}
}


/// <summary>
/// Adds the legacy nodes to runsettings xml.
/// </summary>
/// <param name="deploymentNode"></param>
/// <param name="scriptnode"></param>
/// <param name="testTimeout"></param>
/// <param name="assemblyresolutionNode"></param>
/// <param name="hostsNode"></param>
/// <param name="parallelTestCount"></param>
/// <param name="newXmlDoc"></param>
private void AddLegacyNodes(XmlNode deploymentNode, XmlNode scriptnode, string testTimeout, XmlNode assemblyresolutionNode, XmlNode hostsNode, string parallelTestCount, XmlDocument newXmlDoc)
{
//Remove if the legacy node already exists.
var legacyNode = newXmlDoc.DocumentElement.SelectSingleNode(@"/RunSettings/LegacySettings");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's put a warning for exist

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

if (legacyNode != null)
{
legacyNode.ParentNode.RemoveChild(legacyNode);
}

legacyNode = newXmlDoc.CreateNode(XmlNodeType.Element, LegacySettingsNodeName, null);

if (deploymentNode != null)
{
legacyNode.AppendChild(newXmlDoc.ImportNode(deploymentNode, deep: true));
}
if (scriptnode != null)
{
legacyNode.AppendChild(newXmlDoc.ImportNode(scriptnode, deep: true));
}

// Execution node.
if (assemblyresolutionNode != null || !string.IsNullOrEmpty(parallelTestCount) || !string.IsNullOrEmpty(testTimeout) || hostsNode != null)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assemblyresolutionNode [](start = 16, length = 22)

Nit: Resolution*

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

{
var newExecutionNode = newXmlDoc.CreateNode(XmlNodeType.Element, ExecutionNodeName, null);

if (string.IsNullOrEmpty(parallelTestCount))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

{
var paralellAttribute = newXmlDoc.CreateAttribute(ParallelTestCountAttributeName);
paralellAttribute.Value = parallelTestCount;
newExecutionNode.Attributes.Append(paralellAttribute);
}
if (!string.IsNullOrEmpty(testTimeout))
{
var newTimeoutsNode = newXmlDoc.CreateNode(XmlNodeType.Element, TimeoutsNodeName, null);
var testtimeoutattribute = newXmlDoc.CreateAttribute(TestTimeoutAttributeName);
Copy link
Contributor

@singhsarab singhsarab May 16, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

testtimeoutattribute [](start = 24, length = 20)

Nit: camelCase

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

testtimeoutattribute.Value = testTimeout;
newTimeoutsNode.Attributes.Append(testtimeoutattribute);
newExecutionNode.AppendChild(newXmlDoc.ImportNode(newTimeoutsNode, deep: true));
}
if (hostsNode != null)
{
newExecutionNode.AppendChild(newXmlDoc.ImportNode(hostsNode, deep: true));
}
if (assemblyresolutionNode != null)
{
var testTypeSpecificNode = newXmlDoc.CreateNode(XmlNodeType.Element, TestTypeSpecificNodeName, null);
testTypeSpecificNode.AppendChild(newXmlDoc.ImportNode(assemblyresolutionNode, deep: true));
newExecutionNode.AppendChild(newXmlDoc.ImportNode(testTypeSpecificNode, deep: true));
}
legacyNode.AppendChild(newXmlDoc.ImportNode(newExecutionNode, deep: true));
}
newXmlDoc.DocumentElement.AppendChild(legacyNode);
}


/// <summary>
/// Adds the datacollector nodes to the runsettings xml.
/// </summary>
/// <param name="oldDatacollectorNodes"></param>
/// <param name="newXmlDoc"></param>
private void AddDataCollectorNodes(XmlNodeList oldDatacollectorNodes, XmlDocument newXmlDoc)
{
var dataCollectionRunSettingsNode = newXmlDoc.DocumentElement.SelectSingleNode(@"/RunSettings/DataCollectionRunSettings");
if (dataCollectionRunSettingsNode == null)
{
dataCollectionRunSettingsNode = newXmlDoc.CreateNode(XmlNodeType.Element, DataCollectionRunSettingsNodeName, null);
}

var dataCollectorsNode = newXmlDoc.DocumentElement.SelectSingleNode(@"/RunSettings/DataCollectionRunSettings/DataCollectors");
if (dataCollectorsNode == null)
{
dataCollectorsNode = newXmlDoc.CreateNode(XmlNodeType.Element, DataCollectorsNodeName, null);
dataCollectionRunSettingsNode.AppendChild(newXmlDoc.ImportNode(dataCollectorsNode, deep: true));
dataCollectorsNode = newXmlDoc.DocumentElement.SelectSingleNode(@"/RunSettings/DataCollectionRunSettings/DataCollectors");
}

foreach (XmlNode datacollector in oldDatacollectorNodes)
{
dataCollectorsNode.AppendChild(newXmlDoc.ImportNode(datacollector, deep: true));
}
newXmlDoc.DocumentElement.AppendChild(dataCollectionRunSettingsNode);
}

/// <summary>
/// Adds run session timeout node.
/// </summary>
/// <param name="runTimeout"></param>
/// <param name="newXmlDoc"></param>
private void AddRunTimeoutNode(string runTimeout, XmlDocument newXmlDoc)
{
var runConfigurationNode = newXmlDoc.DocumentElement.SelectSingleNode(@"/RunSettings/RunConfiguration");
if (runConfigurationNode == null)
{
runConfigurationNode = newXmlDoc.CreateNode(XmlNodeType.Element, RunConfigurationNodeName, null);
}

var testSessionTimeoutNode = newXmlDoc.CreateNode(XmlNodeType.Element, TestSessionTimeoutNodeName, null);
testSessionTimeoutNode.InnerText = runTimeout;
runConfigurationNode.AppendChild(newXmlDoc.ImportNode(testSessionTimeoutNode, deep: true));

newXmlDoc.DocumentElement.AppendChild(runConfigurationNode);
}

const string TestTimeoutAttributeName = "testTimeout";
const string ParallelTestCountAttributeName = "testTimeout";
const string RunTimeoutAttributeName = "runTimeout";
const string LegacySettingsNodeName = "LegacySettings";
const string ExecutionNodeName = "Execution";
const string TimeoutsNodeName = "Timeouts";
const string TestTypeSpecificNodeName = "TestTypeSpecific";
const string RunConfigurationNodeName = "RunConfiguration";
const string TestSessionTimeoutNodeName = "TestSessionTimeout";
const string DataCollectionRunSettingsNodeName = "DataCollectionRunSettings";
const string DataCollectorsNodeName = "DataCollectors";
const string sampleRunsettingsContent = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
"<RunSettings></RunSettings>";
}
}

Loading