Skip to content

Commit

Permalink
Merge pull request #14 from OskiKervinen-MF/topic/recursive-runsettings
Browse files Browse the repository at this point in the history
Try to automatically find runsettings if none are specified
  • Loading branch information
xkbeyer authored Jun 20, 2018
2 parents fa87b5e + 97d6d3a commit 4a029b9
Show file tree
Hide file tree
Showing 11 changed files with 259 additions and 5 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ The Adapter is tested against the Solution in [CatchUnitTestRef](https://github.
### Settings

You can configure the adapter by adding a `CatchAdapter` element to your .runsettings file.
If you do not manually set a runsettings file from the Test menu in Visual Studio, this
adapter will look for `*.runsettings` files from the solution directory and all its ancestors.
Settings closer to the solution take precedence, lists are merged.

```xml
<?xml version="1.0" encoding="utf-8"?>
Expand Down
12 changes: 12 additions & 0 deletions TestAdapter/Settings/CatchAdapterSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,5 +84,17 @@ public override XmlElement ToXml()

return doc.DocumentElement;
}

/// <summary>
/// Combine the settings in an other instance with the settings in this one.
/// The settings in other overwrite settings in this.
/// </summary>
/// <param name="other"></param>
public void MergeFrom( CatchAdapterSettings other )
{
// Combine the xclusion lists.
this.TestExeInclude.AddRange( other.TestExeInclude );
this.TestExeExclude.AddRange( other.TestExeExclude );
}
}
}
3 changes: 3 additions & 0 deletions TestAdapter/Settings/CatchSettingsProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ public class CatchSettingsProvider : ISettingsProvider
/// </summary>
public CatchAdapterSettings Settings { get; set; }

[ImportingConstructor]
public CatchSettingsProvider() { }

/// <summary>
/// Load the settings from xml.
/// </summary>
Expand Down
193 changes: 193 additions & 0 deletions TestAdapter/Settings/CatchSettingsService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.ComponentModel.Composition;
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
using System.Xml.Serialization;
using Microsoft.VisualStudio.TestWindow.Extensibility;
using System.Xml.XPath;
using System.IO;
using EnvDTE;

namespace TestAdapter.Settings
{
/// <summary>
/// Provides an IRunSettingsService to provide Catch settings
/// even when a runsettings file is not manually specified.
/// </summary>
[Export(typeof(IRunSettingsService))]
[SettingsName(CatchAdapterSettings.XmlRoot)]
public class CatchSettingsService : IRunSettingsService
{

/// <summary>
/// IRunSettingsService name.
/// </summary>
public string Name => CatchAdapterSettings.XmlRoot;

private DTE dte;

/// <summary>
/// Must have explicit constructor with the ImportingConstructorAttribute
/// for Visual Studio to successfully initialize the RunSettingsService.
/// </summary>
[ImportingConstructor]
public CatchSettingsService(Microsoft.VisualStudio.Shell.SVsServiceProvider serviceProvider)
{
this.dte = (DTE)serviceProvider.GetService(typeof(DTE));
}

/// <summary>
/// Visual studio calls this method on the IRunSettingsService to collect
/// run settings for tests adapters.
///
/// The settings are serialized as XML, because they need to be passed to the
/// test host service across a process boundary.
/// </summary>
/// <param name="inputRunSettingDocument">Pre-existing run settings. Defaults or from a manually specified runsettings file.</param>
/// <param name="configurationInfo">Contextual information on the test run.</param>
/// <param name="log">Logger.</param>
/// <returns>The entire settings document, as it should be after our modifications.</returns>
public IXPathNavigable AddRunSettings(
IXPathNavigable inputRunSettingDocument,
IRunSettingsConfigurationInfo configurationInfo,
ILogger log )
{
// This shall contain the merged settings.
CatchAdapterSettings settings = new CatchAdapterSettings();

// Try to find an existing catch configuration node.
var settingsFromContext = MaybeReadSettingsFromXml(inputRunSettingDocument);
XPathNavigator navigator = inputRunSettingDocument.CreateNavigator();
if (settingsFromContext == null)
{
// Note that explicit runsettings for catch were not provided.
log.Log(MessageLevel.Informational,
$"No '{CatchAdapterSettings.XmlRoot}' node in explicit runsettings (or no explicit runsettins at all). " +
"Searching for runsettings in solution directory and above.");

// Read settings from files.
foreach (var file in FindSettingsInFoldersAbove( Path.GetDirectoryName( dte.Solution.FullName ), log))
{
try
{
// Try to find settings from the file.
var settingsFromFile = MaybeReadSettingsFromFile(file);
if (settingsFromFile != null)
{
log.Log(MessageLevel.Informational, $"Reading test run settings from {file}.");
settings.MergeFrom(settingsFromFile);
}
}
catch (IOException ex)
{
log.Log(MessageLevel.Warning,
$"Failed to read test run settings from file '{file}'. Exception: {ex.ToString()}");
}
}
}
else
{
// Merge the settings from the context.
settings.MergeFrom(settingsFromContext);

// Erase the original.
if (navigator.MoveToFollowing(CatchAdapterSettings.XmlRoot, ""))
navigator.DeleteSelf();
}

// Write the resolved settings to the xml.
XPathNavigator settingsAsXml = settings.ToXml().CreateNavigator();
navigator.MoveToRoot();
navigator.MoveToFirstChild();
navigator.AppendChild(settingsAsXml);

// Clean up the navigator.
navigator.MoveToRoot();
return navigator;
}

/// <summary>
/// Read catch settings from a runsettings file. Return null if there are none.
/// </summary>
/// <param name="filename"></param>
/// <returns></returns>
private CatchAdapterSettings MaybeReadSettingsFromFile(string filename)
{
XPathDocument doc = new XPathDocument(filename);
return MaybeReadSettingsFromXml(doc);
}

/// <summary>
/// Read catch settings from XML. Return null if there are none.
/// </summary>
/// <param name="settingSource">An XML navigable that may contain catch adapter settings.</param>
/// <returns></returns>
private CatchAdapterSettings MaybeReadSettingsFromXml(IXPathNavigable settingSource)
{
XPathNavigator navigator = settingSource.CreateNavigator();

if (navigator.MoveToFollowing(CatchAdapterSettings.XmlRoot, ""))
{

// Catch adapter settings found. Try to read them.
XmlReader reader = XmlReader.Create(new MemoryStream(Encoding.UTF8.GetBytes(navigator.OuterXml)));
XmlSerializer serializer = new XmlSerializer(typeof(CatchAdapterSettings));
return serializer.Deserialize(reader) as CatchAdapterSettings;
}
else
{
// No settings found.
return null;
}
}

/// <summary>
/// Finds all runsettings files in folders above the provided one.
/// The files closest to root are returned first.
/// </summary>
/// <param name="initialPath"></param>
/// <param name="log"></param>
/// <returns></returns>
IEnumerable<string> FindSettingsInFoldersAbove(string initialPath,
ILogger log)
{
// Get the full path.
string fullPath = Path.GetFullPath(initialPath);

// Split the path to components.
var pathComponents = fullPath.Split(new char[] { Path.DirectorySeparatorChar },
StringSplitOptions.RemoveEmptyEntries);

// Append the path components to each other to process each intermediate folder.
var currentPath = "";
foreach (string component in pathComponents)
{
currentPath = currentPath + component + Path.DirectorySeparatorChar;
IEnumerable<string> files = new string[0];
try
{
// Find matching files.
// Force evaluation to ensure errors occur inside the try.
files = Directory.EnumerateFiles(currentPath, "*.runsettings").ToArray();
}
catch (IOException ex)
{
// We may not have permission or something. Ignore silently.
log.Log(MessageLevel.Informational,
$"Error looking for settings at path {currentPath}: {ex.ToString()}.");
}

// Yield the found files.
foreach (string file in files)
{
yield return file;
}
}
}
}
}
19 changes: 14 additions & 5 deletions TestAdapter/TestAdapter.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,10 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.TestPlatform.CoreUtilities, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.TestPlatform.ObjectModel.15.6.1\lib\net451\Microsoft.TestPlatform.CoreUtilities.dll</HintPath>
</Reference>
<Reference Include="Microsoft.TestPlatform.PlatformAbstractions, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.TestPlatform.ObjectModel.15.6.1\lib\net451\Microsoft.TestPlatform.PlatformAbstractions.dll</HintPath>
<Reference Include="EnvDTE, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<EmbedInteropTypes>True</EmbedInteropTypes>
<HintPath>..\packages\EnvDTE.8.0.2\lib\net10\EnvDTE.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.Shell.Immutable.10.0, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
<Reference Include="Microsoft.VisualStudio.Shell.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
Expand All @@ -44,6 +43,15 @@
<Reference Include="Microsoft.VisualStudio.TestPlatform.TestExecutor.Core">
<HintPath>$(DevEnvDir)\CommonExtensions\Microsoft\TestWindow\Microsoft.VisualStudio.TestPlatform.TestExecutor.Core.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestWindow.Interfaces, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.VisualStudio.TestWindow.Interfaces.11.0.61030\lib\net45\Microsoft.VisualStudio.TestWindow.Interfaces.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="stdole, Version=7.0.3300.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<EmbedInteropTypes>True</EmbedInteropTypes>
<HintPath>..\packages\stdole.7.0.3302\lib\net10\stdole.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Collections.Immutable, Version=1.2.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Collections.Immutable.1.2.0\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll</HintPath>
Expand All @@ -64,6 +72,7 @@
<Compile Include="ProcessRunner.cs" />
<Compile Include="Settings\CatchAdapterSettings.cs" />
<Compile Include="Settings\CatchSettingsProvider.cs" />
<Compile Include="Settings\CatchSettingsService.cs" />
<Compile Include="TestExecutor.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="TestDiscoverer.cs" />
Expand Down
3 changes: 3 additions & 0 deletions TestAdapter/TestDiscoverer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ public void DiscoverTests(IEnumerable<string> sources, IDiscoveryContext discove
// Load settings from the discovery context.
CatchAdapterSettings settings = CatchSettingsProvider.LoadSettings( discoveryContext.RunSettings );

logger.SendMessage( TestMessageLevel.Informational,
"Inclusion patterns: " + String.Join( ",", settings.TestExeInclude ) );

try
{
foreach (var src in sources.Where(src => settings.IncludeTestExe(src)))
Expand Down
3 changes: 3 additions & 0 deletions TestAdapter/packages.config
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.TestPlatform.ObjectModel" version="15.6.1" targetFramework="net452" />
<package id="EnvDTE" version="8.0.2" targetFramework="net452" />
<package id="Microsoft.VisualStudio.Shell.14.0" version="14.3.25407" targetFramework="net452" />
<package id="Microsoft.VisualStudio.Shell.Immutable.10.0" version="10.0.30319" targetFramework="net452" />
<package id="Microsoft.VisualStudio.Shell.Interop" version="7.10.6071" targetFramework="net452" />
<package id="Microsoft.VisualStudio.TestWindow.Interfaces" version="11.0.61030" targetFramework="net452" />
<package id="Microsoft.VSSDK.BuildTools" version="14.3.25407" targetFramework="net452" developmentDependency="true" />
<package id="stdole" version="7.0.3302" targetFramework="net452" />
<package id="System.Collections.Immutable" version="1.2.0" targetFramework="net452" />
<package id="System.Reflection.Metadata" version="1.3.0" targetFramework="net452" />
<package id="System.Runtime.InteropServices.RuntimeInformation" version="4.3.0" targetFramework="net452" />
Expand Down
5 changes: 5 additions & 0 deletions TestAdapterTest/TestAdapterTest.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@
<Reference Include="Microsoft.VisualStudio.TestPlatform.ObjectModel, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.TestPlatform.ObjectModel.15.6.1\lib\net451\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestWindow.Interfaces, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.VisualStudio.TestWindow.Interfaces.11.0.61030\lib\net45\Microsoft.VisualStudio.TestWindow.Interfaces.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Collections.Immutable, Version=1.2.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Collections.Immutable.1.2.0\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll</HintPath>
Expand Down Expand Up @@ -87,6 +91,7 @@
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="packages.config" />
</ItemGroup>
<Choose>
Expand Down
11 changes: 11 additions & 0 deletions TestAdapterTest/app.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Microsoft.VisualStudio.TestPlatform.ObjectModel" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-15.0.0.0" newVersion="15.0.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
1 change: 1 addition & 0 deletions TestAdapterTest/packages.config
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.TestPlatform.ObjectModel" version="15.6.1" targetFramework="net452" />
<package id="Microsoft.VisualStudio.TestWindow.Interfaces" version="11.0.61030" targetFramework="net452" />
<package id="System.Collections.Immutable" version="1.2.0" targetFramework="net452" />
<package id="System.Reflection.Metadata" version="1.3.0" targetFramework="net452" />
<package id="System.Runtime.InteropServices.RuntimeInformation" version="4.3.0" targetFramework="net452" />
Expand Down
11 changes: 11 additions & 0 deletions app.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Microsoft.VisualStudio.TestPlatform.ObjectModel" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-14.0.0.0" newVersion="14.0.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

0 comments on commit 4a029b9

Please sign in to comment.