| File: DataCollection\InProcDataCollectionExtensionManager.cs | Web Access |
| Project: src\vstest\src\Microsoft.TestPlatform.CrossPlatEngine\Microsoft.TestPlatform.CrossPlatEngine.csproj (Microsoft.TestPlatform.CrossPlatEngine) |
// 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.Collections.Generic; using System.IO; using System.Linq; 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; namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.DataCollection; /// <summary> /// The in process data collection extension manager. /// </summary> internal class InProcDataCollectionExtensionManager { private const string DataCollectorEndsWithPattern = @"Collector.dll"; private readonly IDataCollectionSink _inProcDataCollectionSink; private readonly string? _defaultCodeBase; internal /* for testing purposes */ readonly HashSet<string?> CodeBasePaths; private readonly IFileHelper _fileHelper; internal IDictionary<string, IInProcDataCollector> InProcDataCollectors; /// <summary> /// Loaded in-proc datacollectors collection /// </summary> private IEnumerable<DataCollectorSettings>? _inProcDataCollectorSettingsCollection; /// <summary> /// Initializes a new instance of the <see cref="InProcDataCollectionExtensionManager"/> class. /// </summary> /// <param name="runSettings"> /// The run settings. /// </param> /// <param name="testEventsPublisher"> /// The data collection test case event manager. /// </param> /// <param name="defaultCodeBase"> /// The default code base to be used by in-proc data collector /// </param> /// <param name="testPluginCache">The cache of test plugins.</param> public InProcDataCollectionExtensionManager(string? runSettings, ITestEventsPublisher testEventsPublisher, string? defaultCodeBase, TestPluginCache testPluginCache) : this(runSettings, testEventsPublisher, defaultCodeBase, testPluginCache, new FileHelper()) { } protected InProcDataCollectionExtensionManager(string? runSettings, ITestEventsPublisher testEventsPublisher, string? defaultCodeBase, TestPluginCache testPluginCache, IFileHelper fileHelper) { InProcDataCollectors = new Dictionary<string, IInProcDataCollector>(); _inProcDataCollectionSink = new InProcDataCollectionSink(); _defaultCodeBase = defaultCodeBase; _fileHelper = fileHelper; CodeBasePaths = new HashSet<string?>(StringComparer.OrdinalIgnoreCase) { _defaultCodeBase }; // Get Datacollector code base paths from test plugin cache var extensionPaths = testPluginCache.GetExtensionPaths(DataCollectorEndsWithPattern); foreach (var extensionPath in extensionPaths) { CodeBasePaths.Add(Path.GetDirectoryName(extensionPath)!); } // Initialize InProcDataCollectors InitializeInProcDataCollectors(runSettings); if (IsInProcDataCollectionEnabled) { testEventsPublisher.TestCaseEnd += TriggerTestCaseEnd; testEventsPublisher.TestCaseStart += TriggerTestCaseStart; testEventsPublisher.TestResult += TriggerUpdateTestResult; testEventsPublisher.SessionStart += TriggerTestSessionStart; testEventsPublisher.SessionEnd += TriggerTestSessionEnd; } } /// <summary> /// Gets a value indicating whether is in-proc data collection enabled. /// </summary> public bool IsInProcDataCollectionEnabled { get; private set; } /// <summary> /// Creates data collector instance based on datacollector settings provided. /// </summary> /// <returns> /// The <see cref="IInProcDataCollector"/>. /// </returns> protected virtual IInProcDataCollector CreateDataCollector(string assemblyQualifiedName, string codebase, XmlElement configuration, Type interfaceType) { var inProcDataCollector = new InProcDataCollector( codebase, assemblyQualifiedName, interfaceType, configuration?.OuterXml); inProcDataCollector.LoadDataCollector(_inProcDataCollectionSink); return inProcDataCollector; } /// <summary> /// The trigger test session start. /// </summary> /// <param name="sender"> /// The sender. /// </param> /// <param name="e"> /// The e. /// </param> private void TriggerTestSessionStart(object? sender, SessionStartEventArgs e) { TestSessionStartArgs testSessionStartArgs = new(GetSessionStartProperties(e)); TriggerInProcDataCollectionMethods(Constants.TestSessionStartMethodName, testSessionStartArgs); } /// <summary> /// The trigger session end. /// </summary> /// <param name="sender"> /// The sender. /// </param> /// <param name="e"> /// The e. /// </param> private void TriggerTestSessionEnd(object? sender, SessionEndEventArgs e) { var testSessionEndArgs = new TestSessionEndArgs(); TriggerInProcDataCollectionMethods(Constants.TestSessionEndMethodName, testSessionEndArgs); } /// <summary> /// The trigger test case start. /// </summary> /// <param name="sender"> /// The sender. /// </param> /// <param name="e"> /// The e. /// </param> private void TriggerTestCaseStart(object? sender, TestCaseStartEventArgs e) { var testCaseStartArgs = new TestCaseStartArgs(e.TestElement); TriggerInProcDataCollectionMethods(Constants.TestCaseStartMethodName, testCaseStartArgs); } /// <summary> /// The trigger test case end. /// </summary> /// <param name="sender"> /// The sender. /// </param> /// <param name="e"> /// The e. /// </param> private void TriggerTestCaseEnd(object? sender, TestCaseEndEventArgs e) { var dataCollectionContext = new DataCollectionContext(e.TestElement); var testCaseEndArgs = new TestCaseEndArgs(dataCollectionContext, e.TestOutcome); TriggerInProcDataCollectionMethods(Constants.TestCaseEndMethodName, testCaseEndArgs); } /// <summary> /// Triggers the send test result method /// </summary> /// <param name="sender"> /// The sender. /// </param> /// <param name="e"> /// The e. /// </param> private void TriggerUpdateTestResult(object? sender, TestResultEventArgs e) { // Just set the cached in-proc data if already exists SetInProcDataCollectionDataInTestResult(e.TestResult); } /// <summary> /// Loads all the in-proc data collector dlls /// </summary> /// <param name="runSettings"> /// The run Settings. /// </param> private void InitializeInProcDataCollectors(string? runSettings) { try { // Check if runsettings contains in-proc datacollector element var inProcDataCollectionRunSettings = XmlRunSettingsUtilities.GetInProcDataCollectionRunSettings(runSettings); var inProcDataCollectionSettingsPresentInRunSettings = inProcDataCollectionRunSettings?.IsCollectionEnabled ?? false; // Verify if it has any valid in-proc datacollectors or just a dummy element inProcDataCollectionSettingsPresentInRunSettings = inProcDataCollectionSettingsPresentInRunSettings && inProcDataCollectionRunSettings!.DataCollectorSettingsList.Count != 0; // Initialize if we have at least one if (!inProcDataCollectionSettingsPresentInRunSettings) { return; } _inProcDataCollectorSettingsCollection = inProcDataCollectionRunSettings!.DataCollectorSettingsList; var interfaceTypeInfo = typeof(InProcDataCollection); foreach (var inProcDc in _inProcDataCollectorSettingsCollection) { var codeBase = GetCodebase(inProcDc.CodeBase!); var assemblyQualifiedName = inProcDc.AssemblyQualifiedName!; var configuration = inProcDc.Configuration!; var inProcDataCollector = CreateDataCollector(assemblyQualifiedName, codeBase, configuration, interfaceTypeInfo); InProcDataCollectors[inProcDataCollector.AssemblyQualifiedName!] = inProcDataCollector; } } catch (Exception ex) { EqtTrace.Error("InProcDataCollectionExtensionManager: Error occurred while Initializing the datacollectors : {0}", ex); } finally { IsInProcDataCollectionEnabled = InProcDataCollectors.Any(); } } /// <summary> /// Gets code base for in-proc datacollector /// Uses all codebasePaths to check where the datacollector exists /// </summary> /// <param name="codeBase">The code base.</param> /// <returns> Code base </returns> private string GetCodebase(string codeBase) { if (Path.IsPathRooted(codeBase)) { return codeBase; } foreach (var extensionPath in CodeBasePaths) { if (extensionPath is null) { continue; } var assemblyPath = Path.Combine(extensionPath, codeBase); if (_fileHelper.Exists(assemblyPath)) { return assemblyPath; } } return codeBase; } private static IDictionary<string, object?> GetSessionStartProperties(SessionStartEventArgs sessionStartEventArgs) { var properties = new Dictionary<string, object?> { { Constants.TestSourcesPropertyName, sessionStartEventArgs.GetPropertyValue<IEnumerable<string>>(Constants.TestSourcesPropertyName) } }; return properties; } private void TriggerInProcDataCollectionMethods(string methodName, InProcDataCollectionArgs methodArg) { try { foreach (var inProcDc in InProcDataCollectors.Values) { inProcDc.TriggerInProcDataCollectionMethod(methodName, methodArg); } } catch (Exception ex) { EqtTrace.Error("InProcDataCollectionExtensionManager: Error occurred while Triggering the {0} method : {1}", methodName, ex); } } /// <summary> /// Set the data sent via datacollection sink in the testresult property for upstream applications to read. /// And removes the data from the dictionary. /// </summary> /// <param name="testResult"> /// The test Result. /// </param> private void SetInProcDataCollectionDataInTestResult(TestResult testResult) { // Loops through each datacollector reads the data collection data and sets as TestResult property. foreach (var entry in InProcDataCollectors) { var dataCollectionData = ((InProcDataCollectionSink)_inProcDataCollectionSink).GetDataCollectionDataSetForTestCase(testResult.TestCase.Id); foreach (var keyValuePair in dataCollectionData) { var testProperty = TestProperty.Register(id: keyValuePair.Key, label: keyValuePair.Key, category: string.Empty, description: string.Empty, valueType: typeof(string), validateValueCallback: null, attributes: TestPropertyAttributes.None, owner: typeof(TestCase)); testResult.SetPropertyValue(testProperty, keyValuePair.Value); } } } } internal static class Constants { /// <summary> /// The test session start method name. /// </summary> public const string TestSessionStartMethodName = "TestSessionStart"; /// <summary> /// The test session end method name. /// </summary> public const string TestSessionEndMethodName = "TestSessionEnd"; /// <summary> /// The test case start method name. /// </summary> public const string TestCaseStartMethodName = "TestCaseStart"; /// <summary> /// The test case end method name. /// </summary> public const string TestCaseEndMethodName = "TestCaseEnd"; /// <summary> /// Test sources property name /// </summary> public const string TestSourcesPropertyName = "TestSources"; /// <summary> /// Coverlet in-proc data collector code base /// </summary> public const string CoverletDataCollectorCodebase = "coverlet.collector.dll"; /// <summary> /// Coverlet in-proc data collector type name /// </summary> public const string CoverletDataCollectorTypeName = "Coverlet.Collector.DataCollection.CoverletInProcDataCollector"; }