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

Data collector extensibility in Test Plugin Cache #1991

Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/Microsoft.TestPlatform.Client/TestPlatform.cs
Expand Up @@ -225,7 +225,12 @@ private void AddExtensionAssemblies(string runSettings)
continue;
}

var extensionAssemblies = new List<string>(this.fileHelper.EnumerateFiles(adapterPath, SearchOption.AllDirectories, TestPlatformConstants.TestAdapterEndsWithPattern, TestPlatformConstants.TestLoggerEndsWithPattern, TestPlatformConstants.RunTimeEndsWithPattern));
var extensionAssemblies = new List<string>(this.fileHelper.EnumerateFiles(adapterPath, SearchOption.AllDirectories,
TestPlatformConstants.TestAdapterEndsWithPattern,
TestPlatformConstants.TestLoggerEndsWithPattern,
TestPlatformConstants.DataCollectorEndsWithPattern,
TestPlatformConstants.RunTimeEndsWithPattern));

if (extensionAssemblies.Count > 0)
{
this.UpdateExtensions(extensionAssemblies, skipExtensionFilters: false);
Expand Down
Expand Up @@ -259,6 +259,7 @@ private void LogMessage(TestMessageLevel testMessageLevel, string message)
private void InitializeExtensions(IEnumerable<string> sources)
{
var extensions = TestPluginCache.Instance.GetExtensionPaths(TestPlatformConstants.TestAdapterEndsWithPattern, this.skipDefaultAdapters);
vagisha-nidhi marked this conversation as resolved.
Show resolved Hide resolved
extensions = extensions.Concat(TestPluginCache.Instance.GetExtensionPaths(TestPlatformConstants.DataCollectorEndsWithPattern, true)).ToList();

// Filter out non existing extensions
var nonExistingExtensions = extensions.Where(extension => !this.fileHelper.Exists(extension));
Expand Down
Expand Up @@ -9,12 +9,15 @@ namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.DataCollection
using System.Linq;
using System.Reflection;
using System.Xml;
using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework;
using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.DataCollection.Interfaces;
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollector.InProcDataCollector;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.InProcDataCollector;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities;
using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers;
using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces;

/// <summary>
/// The in process data collection extension manager.
Expand All @@ -25,8 +28,14 @@ internal class InProcDataCollectionExtensionManager

private IDataCollectionSink inProcDataCollectionSink;

private const string DataCollectorEndsWithPattern = @"Collector.dll";

private string defaultCodeBase;

private List<string> codeBasePaths;

private IFileHelper fileHelper;

/// <summary>
/// Loaded in-proc datacollectors collection
/// </summary>
Expand All @@ -45,10 +54,23 @@ internal class InProcDataCollectionExtensionManager
/// The default codebase to be used by inproc data collector
/// </param>
public InProcDataCollectionExtensionManager(string runSettings, ITestEventsPublisher testEventsPublisher, string defaultCodeBase)
: this(runSettings, testEventsPublisher, defaultCodeBase, new FileHelper())
{}

protected InProcDataCollectionExtensionManager(string runSettings, ITestEventsPublisher testEventsPublisher, string defaultCodeBase, IFileHelper fileHelper)
{
this.InProcDataCollectors = new Dictionary<string, IInProcDataCollector>();
this.inProcDataCollectionSink = new InProcDataCollectionSink();
this.defaultCodeBase = defaultCodeBase;
this.fileHelper = fileHelper;
this.codeBasePaths = new List<string> { this.defaultCodeBase };

// Get Datacollector codebase paths from test plugin cache
var extensionPaths = TestPluginCache.Instance.GetExtensionPaths(DataCollectorEndsWithPattern);
vagisha-nidhi marked this conversation as resolved.
Show resolved Hide resolved
foreach (var extensionPath in extensionPaths)
{
this.codeBasePaths.Add(Path.GetDirectoryName(extensionPath));
}

// Initialize InProcDataCollectors
this.InitializeInProcDataCollectors(runSettings);
Expand Down Expand Up @@ -215,13 +237,25 @@ private void InitializeInProcDataCollectors(string runSettings)

/// <summary>
/// Gets codebase for inproc datacollector
/// Uses default codebase if given path is not absolute path of inproc datacollector
/// Uses all codebasePaths to check where the datacollector exists
/// </summary>
/// <param name="codeBase">The run Settings.</param>
/// <param name="codeBase">The codebase.</param>
/// <returns> Codebase </returns>
private string GetCodebase(string codeBase)
{
return Path.IsPathRooted(codeBase) ? codeBase : Path.Combine(this.defaultCodeBase, codeBase);
if (!Path.IsPathRooted(codeBase))
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion: you can flip the condition. if (path.ispathrooted(,....)) { return codebase }

{
foreach (var extensionPath in this.codeBasePaths)
{
var path = Path.Combine(extensionPath, codeBase);
vagisha-nidhi marked this conversation as resolved.
Show resolved Hide resolved
if (this.fileHelper.Exists(path))
{
return path;
}
}
}

return codeBase;
}

private IDictionary<string, object> GetSessionStartProperties(SessionStartEventArgs sessionStartEventArgs)
Expand Down
Expand Up @@ -45,6 +45,7 @@ public class DotnetTestHostManager : ITestRuntimeProvider
private const string DotnetTestHostUri = "HostProvider://DotnetTestHost";
private const string DotnetTestHostFriendlyName = "DotnetTestHost";
private const string TestAdapterRegexPattern = @"TestAdapter.dll";
private const string DataCollectorRegexPattern = @"Collector.dll";

private IDotnetHostHelper dotnetHostHelper;

Expand Down Expand Up @@ -238,14 +239,20 @@ public async Task<bool> LaunchTestHostAsync(TestProcessStartInfo testHostStartIn
/// <inheritdoc/>
public IEnumerable<string> GetTestPlatformExtensions(IEnumerable<string> sources, IEnumerable<string> extensions)
{
List<string> extensionPaths = new List<string>();
var sourceDirectory = Path.GetDirectoryName(sources.Single());

if (!string.IsNullOrEmpty(sourceDirectory) && this.fileHelper.DirectoryExists(sourceDirectory))
{
return this.fileHelper.EnumerateFiles(sourceDirectory, SearchOption.TopDirectoryOnly, TestAdapterRegexPattern);
extensionPaths.AddRange(this.fileHelper.EnumerateFiles(sourceDirectory, SearchOption.TopDirectoryOnly, TestAdapterRegexPattern));
}

return Enumerable.Empty<string>();
if (extensions != null && extensions.Any())
{
extensionPaths.AddRange(extensions.Where(x => x.EndsWith(DataCollectorRegexPattern, StringComparison.OrdinalIgnoreCase)));
}

return extensionPaths;
}

/// <inheritdoc/>
Expand Down
Expand Up @@ -275,6 +275,7 @@ public void StartTestRunShouldInitializeExtensionsIfTestHostIsNotShared()
[TestMethod]
public void StartTestRunShouldInitializeExtensionsWithExistingExtensionsOnly()
{
TestPluginCache.Instance = null;
TestPluginCache.Instance.UpdateExtensions(new List<string> { "abc.TestAdapter.dll", "def.TestAdapter.dll", "xyz.TestAdapter.dll" }, false);
var expectedOutputPaths = new[] { "abc.TestAdapter.dll", "xyz.TestAdapter.dll" };

Expand All @@ -299,7 +300,36 @@ public void StartTestRunShouldInitializeExtensionsWithExistingExtensionsOnly()
this.mockRequestSender.Verify(s => s.InitializeExecution(expectedOutputPaths), Times.Once);
}

[TestMethod]
[TestMethod]
public void StartTestRunShouldInitializeExtensionsWithExistingDataCOllectorExtensions()
{
TestPluginCache.Instance = null;
TestPluginCache.Instance.UpdateExtensions(new List<string> { "abc.TestAdapter.dll", "def.TestAdapter.dll", "xyz.TestAdapter.dll", "abc.DataCollector.dll" }, false);
var expectedOutputPaths = new[] { "abc.TestAdapter.dll", "xyz.TestAdapter.dll", "abc.DataCollector.dll" };

this.mockTestHostManager.SetupGet(th => th.Shared).Returns(false);
this.mockRequestSender.Setup(s => s.WaitForRequestHandlerConnection(It.IsAny<int>(), It.IsAny<CancellationToken>())).Returns(true);
this.mockTestHostManager.Setup(th => th.GetTestPlatformExtensions(It.IsAny<IEnumerable<string>>(), It.IsAny<IEnumerable<string>>())).Returns((IEnumerable<string> sources, IEnumerable<string> extensions) =>
{
return extensions.Select(extension => { return Path.GetFileName(extension); });
});

this.mockFileHelper.Setup(fh => fh.Exists(It.IsAny<string>())).Returns((string extensionPath) =>
{
return !extensionPath.Contains("def.TestAdapter.dll");
});

this.mockFileHelper.Setup(fh => fh.Exists("abc.TestAdapter.dll")).Returns(true);
this.mockFileHelper.Setup(fh => fh.Exists("xyz.TestAdapter.dll")).Returns(true);
this.mockFileHelper.Setup(fh => fh.Exists("abc.DataCollector.dll")).Returns(true);

var mockTestRunEventsHandler = new Mock<ITestRunEventsHandler>();
this.testExecutionManager.StartTestRun(this.mockTestRunCriteria.Object, mockTestRunEventsHandler.Object);

this.mockRequestSender.Verify(s => s.InitializeExecution(expectedOutputPaths), Times.Once);
}

[TestMethod]
public void SetupChannelShouldThrowExceptionIfClientConnectionTimeout()
{
this.mockRequestSender.Setup(s => s.WaitForRequestHandlerConnection(It.IsAny<int>(), It.IsAny<CancellationToken>())).Returns(false);
Expand Down
Expand Up @@ -8,11 +8,13 @@ namespace TestPlatform.CrossPlatEngine.UnitTests.DataCollection
using System.Linq;
using System.Reflection;
using System.Xml;
using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework;
using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.DataCollection;
using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.DataCollection.Interfaces;
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollector.InProcDataCollector;
using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces;
using Microsoft.VisualStudio.TestTools.UnitTesting;

using Moq;
Expand All @@ -37,11 +39,20 @@ public class InProcDataCollectionExtensionManagerTests
private Mock<ITestEventsPublisher> mockTestEventsPublisher;
private TestableInProcDataCollectionExtensionManager inProcDataCollectionManager;
private string defaultCodebase = "E:\\repos\\MSTest\\src\\managed\\TestPlatform\\TestImpactListener.Tests\\bin\\Debug";
private Mock<IFileHelper> mockFileHelper;

public InProcDataCollectionExtensionManagerTests()
[TestInitialize]
public void TestInit()
{
this.mockTestEventsPublisher = new Mock<ITestEventsPublisher>();
this.inProcDataCollectionManager = new TestableInProcDataCollectionExtensionManager(this.settingsXml, this.mockTestEventsPublisher.Object, this.defaultCodebase);
this.mockFileHelper = new Mock<IFileHelper>();
this.inProcDataCollectionManager = new TestableInProcDataCollectionExtensionManager(this.settingsXml, this.mockTestEventsPublisher.Object, this.defaultCodebase, this.mockFileHelper.Object);
}

[TestCleanup]
public void TestCleanup()
{
TestPluginCache.Instance = null;
vagisha-nidhi marked this conversation as resolved.
Show resolved Hide resolved
}

[TestMethod]
Expand All @@ -58,7 +69,7 @@ public void InProcDataCollectionExtensionManagerShouldLoadsDataCollectorsFromRun
}

[TestMethod]
public void InProcDataCollectionExtensionManagerLoadsDataCollectorFromDefaultCodebaseIfCodebaseIsRelative()
public void InProcDataCollectionExtensionManagerLoadsDataCollectorFromDefaultCodebaseIfExistsAndCodebaseIsRelative()
{
string settingsXml = @"<RunSettings>
<InProcDataCollectionRunSettings>
Expand All @@ -71,12 +82,38 @@ public void InProcDataCollectionExtensionManagerLoadsDataCollectorFromDefaultCod
</InProcDataCollectors>
</InProcDataCollectionRunSettings>
</RunSettings>";
this.inProcDataCollectionManager = new TestableInProcDataCollectionExtensionManager(settingsXml, this.mockTestEventsPublisher.Object, this.defaultCodebase);

this.mockFileHelper.Setup(fh => fh.Exists(@"E:\repos\MSTest\src\managed\TestPlatform\TestImpactListener.Tests\bin\Debug\TestImpactListener.Tests.dll")).Returns(true);
this.inProcDataCollectionManager = new TestableInProcDataCollectionExtensionManager(settingsXml, this.mockTestEventsPublisher.Object, this.defaultCodebase, this.mockFileHelper.Object);

var codebase = (inProcDataCollectionManager.InProcDataCollectors.Values.First() as MockDataCollector).CodeBase;
Assert.AreEqual(codebase, @"E:\repos\MSTest\src\managed\TestPlatform\TestImpactListener.Tests\bin\Debug\TestImpactListener.Tests.dll");
}

[TestMethod]
public void InProcDataCollectionExtensionManagerLoadsDataCollectorFromTestPluginCacheIfExistsAndCodebaseIsRelative()
{
string settingsXml = @"<RunSettings>
<InProcDataCollectionRunSettings>
<InProcDataCollectors>
<InProcDataCollector friendlyName='Test Impact' uri='InProcDataCollector://Microsoft/TestImpact/1.0' assemblyQualifiedName='TestImpactListener.Tests, Version=1.0.0.0, Culture=neutral, PublicKeyToken=7ccb7239ffde675a' codebase='TestImpactListenerDataCollector.dll'>
<Configuration>
<Port>4312</Port>
</Configuration>
</InProcDataCollector>
</InProcDataCollectors>
</InProcDataCollectionRunSettings>
</RunSettings>";

TestPluginCache.Instance.UpdateExtensions(new List<string> { @"E:\source\.nuget\TestImpactListenerDataCollector.dll" }, true);
this.mockFileHelper.Setup(fh => fh.Exists(@"E:\source\.nuget\TestImpactListenerDataCollector.dll")).Returns(true);

this.inProcDataCollectionManager = new TestableInProcDataCollectionExtensionManager(settingsXml, this.mockTestEventsPublisher.Object, this.defaultCodebase, this.mockFileHelper.Object);

var codebase = (inProcDataCollectionManager.InProcDataCollectors.Values.First() as MockDataCollector).CodeBase;
Assert.AreEqual(codebase, @"E:\source\.nuget\TestImpactListenerDataCollector.dll");
}

[TestMethod]
public void InProcDataCollectionExtensionManagerLoadsDataCollectorFromGivenCodebaseIfCodebaseIsAbsolute()
{
Expand All @@ -91,7 +128,7 @@ public void InProcDataCollectionExtensionManagerLoadsDataCollectorFromGivenCodeb
</InProcDataCollectors>
</InProcDataCollectionRunSettings>
</RunSettings>";
this.inProcDataCollectionManager = new TestableInProcDataCollectionExtensionManager(settingsXml, this.mockTestEventsPublisher.Object, this.defaultCodebase);
this.inProcDataCollectionManager = new TestableInProcDataCollectionExtensionManager(settingsXml, this.mockTestEventsPublisher.Object, this.defaultCodebase, this.mockFileHelper.Object);

var codebase = (inProcDataCollectionManager.InProcDataCollectors.Values.First() as MockDataCollector).CodeBase;
Assert.AreEqual(codebase, "\\\\DummyPath\\TestImpactListener.Tests.dll");
Expand All @@ -117,7 +154,7 @@ public void InProcDataCollectorIsReadingMultipleDataCollector()
</InProcDataCollectionRunSettings>
</RunSettings>";

this.inProcDataCollectionManager = new TestableInProcDataCollectionExtensionManager(multiSettingsXml, this.mockTestEventsPublisher.Object, this.defaultCodebase);
this.inProcDataCollectionManager = new TestableInProcDataCollectionExtensionManager(multiSettingsXml, this.mockTestEventsPublisher.Object, this.defaultCodebase, this.mockFileHelper.Object);
bool secondOne = false;
MockDataCollector dataCollector1 = null;
MockDataCollector dataCollector2 = null;
Expand Down Expand Up @@ -234,7 +271,8 @@ public void TriggerTestCaseEndShouldtBeCalledMultipleTimesInDataDrivenScenario()

internal class TestableInProcDataCollectionExtensionManager : InProcDataCollectionExtensionManager
{
public TestableInProcDataCollectionExtensionManager(string runSettings, ITestEventsPublisher mockTestEventsPublisher, string defaultCodebase) : base(runSettings, mockTestEventsPublisher, defaultCodebase)
public TestableInProcDataCollectionExtensionManager(string runSettings, ITestEventsPublisher mockTestEventsPublisher, string defaultCodebase, IFileHelper fileHelper)
: base(runSettings, mockTestEventsPublisher, defaultCodebase, fileHelper)
{
}

Expand Down
Expand Up @@ -387,6 +387,16 @@ public void GetTestPlatformExtensionsShouldReturnEmptySetIfSourceDirectoryIsEmpt
Assert.AreEqual(0, extensions.Count());
}

[TestMethod]
public void GetTestPlatformExtensionsShouldAddDataCollectorsExtensionsIfPresent()
{
this.mockFileHelper.Setup(fh => fh.DirectoryExists(It.IsAny<string>())).Returns(true);
this.mockFileHelper.Setup(fh => fh.EnumerateFiles(It.IsAny<string>(), SearchOption.TopDirectoryOnly, It.IsAny<string[]>())).Returns(new[] { "foo.dll" });
var extensions = this.dotnetHostManager.GetTestPlatformExtensions(this.testSource, new List<string> { "abc.datacollector.dll" });

Assert.AreEqual(1, extensions.Count());
}

[TestMethod]
public async Task LaunchTestHostShouldLaunchProcessWithConnectionInfo()
{
Expand Down