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

Pass coverlet codebase in runsettings for inproc data collector initialization #2288

Merged
merged 11 commits into from
Jan 30, 2020
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,15 @@ public void ClearExtensions()
this.TestExtensions?.InvalidateCache();
}

/// <summary>
/// Add search directories to assembly resolver
/// </summary>
/// <param name="directories"></param>
public void AddResolverSearchDirectories(string[] directories)
{
assemblyResolver.AddSearchDirectories(directories);
}

#endregion

#region Utility methods
Expand Down Expand Up @@ -486,7 +495,7 @@ private Dictionary<string, TPluginInfo> GetTestExtensions<TPluginInfo, TExtensio
return discoverer.GetTestExtensionsInformation<TPluginInfo, TExtension>(extensionPaths);
}

private void SetupAssemblyResolver(string extensionAssembly)
protected void SetupAssemblyResolver(string extensionAssembly)
{
IList<string> resolutionPaths;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,6 @@ internal class ProxyExecutionManager : ProxyOperationManager, IProxyExecutionMan
private bool skipDefaultAdapters;
private readonly IFileHelper fileHelper;

// Only send coverlet inproc datacollector dll to be initialized via testhost,
// ideally this should get initialized via InProcessDC Node in runsettings, but
// somehow it is failing, hence putting this ugly HACK, to fix issues like
// https://developercommunity.visualstudio.com/content/problem/738856/could-not-load-file-or-assembly-microsoftintellitr.html
private const string CoverletDataCollector = "coverlet.collector.dll";

/// <inheritdoc/>
public bool IsInitialized { get; private set; } = false;

Expand All @@ -54,7 +48,7 @@ internal class ProxyExecutionManager : ProxyOperationManager, IProxyExecutionMan
/// <param name="requestData">The Request Data for providing services and data for Run.</param>
/// <param name="requestSender">Test request sender instance.</param>
/// <param name="testHostManager">Test host manager for this proxy.</param>
public ProxyExecutionManager(IRequestData requestData, ITestRequestSender requestSender, ITestRuntimeProvider testHostManager) :
public ProxyExecutionManager(IRequestData requestData, ITestRequestSender requestSender, ITestRuntimeProvider testHostManager) :
this(requestData, requestSender, testHostManager, JsonDataSerializer.Instance, new FileHelper())
{
}
Expand Down Expand Up @@ -108,7 +102,7 @@ public virtual int StartTestRun(TestRunCriteria testRunCriteria, ITestRunEventsH
{
EqtTrace.Verbose("ProxyExecutionManager: Test host is always Lazy initialize.");
}

var testSources = new List<string>(testRunCriteria.HasSpecificSources ? testRunCriteria.Sources :
// If the test execution is with a test filter, group them by sources
testRunCriteria.Tests.GroupBy(tc => tc.Source).Select(g => g.Key));
Expand All @@ -118,7 +112,7 @@ public virtual int StartTestRun(TestRunCriteria testRunCriteria, ITestRunEventsH
if (this.isCommunicationEstablished)
{
this.CancellationTokenSource.Token.ThrowTestPlatformExceptionIfCancellationRequested();

this.InitializeExtensions(testSources);

// This code should be in sync with InProcessProxyExecutionManager.StartTestRun executionContext
Expand Down Expand Up @@ -181,7 +175,7 @@ public virtual int StartTestRun(TestRunCriteria testRunCriteria, ITestRunEventsH
public virtual void Cancel(ITestRunEventsHandler eventHandler)
{
// Just in case ExecuteAsync isn't called yet, set the eventhandler
if(this.baseTestRunEventsHandler == null)
if (this.baseTestRunEventsHandler == null)
{
this.baseTestRunEventsHandler = eventHandler;
}
Expand All @@ -207,7 +201,7 @@ public virtual int LaunchProcessWithDebuggerAttached(TestProcessStartInfo testPr
public void Abort(ITestRunEventsHandler eventHandler)
{
// Just in case ExecuteAsync isn't called yet, set the eventhandler
if(this.baseTestRunEventsHandler == null)
if (this.baseTestRunEventsHandler == null)
{
this.baseTestRunEventsHandler = eventHandler;
}
Expand Down Expand Up @@ -238,7 +232,7 @@ public void HandleRawMessage(string rawMessage)
{
var message = this.dataSerializer.DeserializeMessage(rawMessage);

if(string.Equals(message.MessageType, MessageType.ExecutionComplete))
if (string.Equals(message.MessageType, MessageType.ExecutionComplete))
{
this.Close();
}
Expand Down Expand Up @@ -267,9 +261,6 @@ private void LogMessage(TestMessageLevel testMessageLevel, string message)
private void InitializeExtensions(IEnumerable<string> sources)
{
var extensions = TestPluginCache.Instance.GetExtensionPaths(TestPlatformConstants.TestAdapterEndsWithPattern, this.skipDefaultAdapters);

// remove this line once we figure out why coverlet inproc DC is not initialized via runsetting inproc node.
extensions = extensions.Concat(TestPluginCache.Instance.GetExtensionPaths(ProxyExecutionManager.CoverletDataCollector, true)).ToList();

// Filter out non existing extensions
var nonExistingExtensions = extensions.Where(extension => !this.fileHelper.Exists(extension));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.DataCollection
{
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;

using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework;
using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.DataCollection.Interfaces;
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection;
Expand Down Expand Up @@ -45,7 +46,7 @@ public InProcDataCollector(
string assemblyQualifiedName,
TypeInfo interfaceTypeInfo,
string configXml)
: this(codeBase, assemblyQualifiedName, interfaceTypeInfo, configXml, new PlatformAssemblyLoadContext())
: this(codeBase, assemblyQualifiedName, interfaceTypeInfo, configXml, new PlatformAssemblyLoadContext(), TestPluginCache.Instance)
{
}

Expand All @@ -62,7 +63,7 @@ public InProcDataCollector(
/// </param>
/// <param name="assemblyLoadContext">
/// </param>
internal InProcDataCollector(string codeBase, string assemblyQualifiedName, TypeInfo interfaceTypeInfo, string configXml, IAssemblyLoadContext assemblyLoadContext)
internal InProcDataCollector(string codeBase, string assemblyQualifiedName, TypeInfo interfaceTypeInfo, string configXml, IAssemblyLoadContext assemblyLoadContext, TestPluginCache testPluginCache)
{
this.configXml = configXml;
this.assemblyLoadContext = assemblyLoadContext;
Expand All @@ -75,6 +76,10 @@ internal InProcDataCollector(string codeBase, string assemblyQualifiedName, Type
// If we're loading coverlet collector we skip to check the version of assembly
// to allow upgrade throught nuget package
filterPredicate = (x) => x.FullName.Equals(Constants.CoverletDataCollectorTypeName) && interfaceTypeInfo.IsAssignableFrom(x.GetTypeInfo());

// Coverlet collector is consumed as nuget package we need to add assemblies directory to resolver to correctly load references.
Debug.Assert(Path.IsPathRooted(codeBase), "Absolute path expected");
testPluginCache.AddResolverSearchDirectories(new string[] { Path.GetDirectoryName(codeBase) });
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ public class DotnetTestHostManager : ITestRuntimeProvider
private const string DotnetTestHostUri = "HostProvider://DotnetTestHost";
private const string DotnetTestHostFriendlyName = "DotnetTestHost";
private const string TestAdapterRegexPattern = @"TestAdapter.dll";
private const string CoverletDataCollectorRegexPattern = @"coverlet.collector.dll";

private IDotnetHostHelper dotnetHostHelper;
private IEnvironment platformEnvironment;
Expand Down Expand Up @@ -316,11 +315,6 @@ public IEnumerable<string> GetTestPlatformExtensions(IEnumerable<string> sources
extensionPaths.AddRange(this.fileHelper.EnumerateFiles(sourceDirectory, SearchOption.TopDirectoryOnly, TestAdapterRegexPattern));
}

if (extensions != null && extensions.Any())
{
extensionPaths.AddRange(extensions.Where(x => x.EndsWith(CoverletDataCollectorRegexPattern, StringComparison.OrdinalIgnoreCase)));
}

return extensionPaths;
}

Expand Down
46 changes: 40 additions & 6 deletions src/vstest.console/Processors/CollectArgumentProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Processors
using System.Collections.Generic;

using System.Globalization;
using System.IO;
using Microsoft.VisualStudio.TestPlatform.Common;
using Microsoft.VisualStudio.TestPlatform.Common.Interfaces;
using Microsoft.VisualStudio.TestPlatform.Common.Utilities;
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities;
using Microsoft.VisualStudio.TestPlatform.Utilities;
using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers;
using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces;
using CommandLineResources = Microsoft.VisualStudio.TestPlatform.CommandLine.Resources.Resources;

/// <summary>
Expand Down Expand Up @@ -58,7 +61,7 @@ public Lazy<IArgumentExecutor> Executor
{
if (this.executor == null)
{
this.executor = new Lazy<IArgumentExecutor>(() => new CollectArgumentExecutor(RunSettingsManager.Instance));
this.executor = new Lazy<IArgumentExecutor>(() => new CollectArgumentExecutor(RunSettingsManager.Instance, new FileHelper()));
}

return this.executor;
Expand All @@ -71,7 +74,7 @@ public Lazy<IArgumentExecutor> Executor
}
}


internal class CollectArgumentProcessorCapabilities : BaseArgumentProcessorCapabilities
{
public override string CommandName => CollectArgumentProcessor.CommandName;
Expand All @@ -90,11 +93,13 @@ internal class CollectArgumentProcessorCapabilities : BaseArgumentProcessorCapab
/// <inheritdoc />
internal class CollectArgumentExecutor : IArgumentExecutor
{
private IRunSettingsProvider runSettingsManager;
private readonly IRunSettingsProvider runSettingsManager;
private readonly IFileHelper fileHelper;
internal static List<string> EnabledDataCollectors = new List<string>();
internal CollectArgumentExecutor(IRunSettingsProvider runSettingsManager)
internal CollectArgumentExecutor(IRunSettingsProvider runSettingsManager, IFileHelper fileHelper)
{
this.runSettingsManager = runSettingsManager;
this.fileHelper = fileHelper;
}

/// <inheritdoc />
Expand All @@ -113,16 +118,45 @@ public void Initialize(string argument)
argument));
}

if(InferRunSettingsHelper.IsTestSettingsEnabled(this.runSettingsManager.ActiveRunSettings.SettingsXml))
if (InferRunSettingsHelper.IsTestSettingsEnabled(this.runSettingsManager.ActiveRunSettings.SettingsXml))
{
throw new SettingsException(string.Format(CommandLineResources.CollectWithTestSettingErrorMessage, argument));
}
AddDataCollectorToRunSettings(argument, this.runSettingsManager);
}

/// <summary>
/// We try to fix inproc coverlet codebase searching coverlet.collector.dll assembly inside adaptersPaths
/// </summary>
private void FixCoverletInProcessCollectorCodeBase()
MarcoRossignoli marked this conversation as resolved.
Show resolved Hide resolved
{
DataCollectionRunSettings inProcDataCollectionRunSettings = XmlRunSettingsUtilities.GetInProcDataCollectionRunSettings(this.runSettingsManager.ActiveRunSettings.SettingsXml);

if (inProcDataCollectionRunSettings is null)
{
return;
}

if (DoesDataCollectorSettingsExist(CoverletConstants.CoverletDataCollectorFriendlyName, inProcDataCollectionRunSettings, out DataCollectorSettings inProcDataCollector))
{
foreach (string adapterPath in RunSettingsUtilities.GetTestAdaptersPaths(this.runSettingsManager.ActiveRunSettings.SettingsXml))
{
string collectorPath = Path.Combine(adapterPath, CoverletConstants.CoverletDataCollectorCodebase);
if (fileHelper.Exists(collectorPath))
{
inProcDataCollector.CodeBase = collectorPath;
runSettingsManager.UpdateRunSettingsNodeInnerXml(Constants.InProcDataCollectionRunSettingsName, inProcDataCollectionRunSettings.ToXml().InnerXml);
EqtTrace.Verbose("CoverletDataCollector in-process codeBase updated to '{0}'", inProcDataCollector.CodeBase);
break;
}
}
}
}

/// <inheritdoc />
public ArgumentProcessorResult Execute()
{
FixCoverletInProcessCollectorCodeBase();
return ArgumentProcessorResult.Success;
}

Expand Down Expand Up @@ -198,7 +232,7 @@ internal static void AddDataCollectorToRunSettings(string argument, IRunSettings
}

var dataCollectionRunSettings = XmlRunSettingsUtilities.GetDataCollectionRunSettings(settings) ?? new DataCollectionRunSettings();
var inProcDataCollectionRunSettings = XmlRunSettingsUtilities.GetInProcDataCollectionRunSettings(settings)
var inProcDataCollectionRunSettings = XmlRunSettingsUtilities.GetInProcDataCollectionRunSettings(settings)
?? new DataCollectionRunSettings(
Constants.InProcDataCollectionRunSettingsName,
Constants.InProcDataCollectorsSettingName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,8 @@ public TestAdapterPathArgumentExecutor(CommandLineOptions options, IRunSettingsP

/// <summary>
/// Initializes with the argument that was provided with the command.
/// Pay attention, move code out of Initialize will interfere with CollectArgumentProcessor.Execute(),
/// it expects initialized RunConfiguration.TestAdaptersPaths element.
/// </summary>
/// <param name="argument">Argument that was provided with the command.</param>
public void Initialize(string argument)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ public void GetExtensionPathsShouldConsolidateAllExtensions()
expectedExtensions.Add("default.dll");
TestPluginCache.Instance.UpdateExtensions(new[] { @"filter.dll" }, false);
TestPluginCache.Instance.UpdateExtensions(new[] { @"unfilter.dll" }, true);
TestPluginCache.Instance.DefaultExtensionPaths = new[] {"default.dll"};
TestPluginCache.Instance.DefaultExtensionPaths = new[] { "default.dll" };

var extensions = TestPluginCache.Instance.GetExtensionPaths("filter.dll");

Expand All @@ -163,7 +163,7 @@ public void GetExtensionPathsShouldFilterFilterableExtensions()
expectedExtensions.Add("default.dll");
TestPluginCache.Instance.UpdateExtensions(new[] { @"filter.dll", @"other.dll" }, false);
TestPluginCache.Instance.UpdateExtensions(new[] { @"unfilter.dll" }, true);
TestPluginCache.Instance.DefaultExtensionPaths = new[] {"default.dll"};
TestPluginCache.Instance.DefaultExtensionPaths = new[] { "default.dll" };

var extensions = TestPluginCache.Instance.GetExtensionPaths("filter.dll");

Expand All @@ -177,7 +177,7 @@ public void GetExtensionPathsShouldNotFilterIfEndsWithPatternIsNullOrEmpty()
expectedExtensions.Add("default.dll");
TestPluginCache.Instance.UpdateExtensions(new[] { @"filter.dll", @"other.dll" }, false);
TestPluginCache.Instance.UpdateExtensions(new[] { @"unfilter.dll" }, true);
TestPluginCache.Instance.DefaultExtensionPaths = new[] {"default.dll"};
TestPluginCache.Instance.DefaultExtensionPaths = new[] { "default.dll" };

var extensions = TestPluginCache.Instance.GetExtensionPaths(string.Empty);

Expand Down Expand Up @@ -459,6 +459,11 @@ protected override IEnumerable<string> GetFilteredExtensions(List<string> extens
this.Action?.Invoke();
return extensions;
}

new public void SetupAssemblyResolver(string extensionAssembly)
{
base.SetupAssemblyResolver(extensionAssembly);
AbhitejJohn marked this conversation as resolved.
Show resolved Hide resolved
}
}

#endregion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -302,36 +302,6 @@ public void StartTestRunShouldInitializeExtensionsWithExistingExtensionsOnly()
this.mockRequestSender.Verify(s => s.InitializeExecution(expectedOutputPaths), Times.Once);
}

[TestMethod]
public void StartTestRunShouldInitializeExtensionsOnlyWithCoverletDataCollectorExtensions()
{
TestPluginCache.Instance = null;
TestPluginCache.Instance.UpdateExtensions(new List<string> { "abc.TestAdapter.dll", "def.TestAdapter.dll", "xyz.TestAdapter.dll", "abc.DataCollector.dll", "xyz.coverlet.collector.dll" }, false);
var expectedOutputPaths = new[] { "abc.TestAdapter.dll", "xyz.TestAdapter.dll", "xyz.coverlet.collector.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);
this.mockFileHelper.Setup(fh => fh.Exists("xyz.coverlet.collector.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()
{
Expand Down
Loading