From 64d43e60358fab8ce103199d57bdcd685c1472af Mon Sep 17 00:00:00 2001 From: Harsh Jain Date: Fri, 5 Aug 2016 02:03:12 +0530 Subject: [PATCH 1/5] Load and Initialize Data Collectors --- TestPlatform.sln | 16 +- .../Constants.cs | 5 + .../DataCollectorsSettingsProvider.cs | 42 + .../Interfaces/IDataCollectionManager.cs | 6 +- .../IDataCollectorsSettingsProvider.cs | 6 +- .../IRunConfigurationSettingsProvider.cs | 18 + .../RunConfigurationSettingsProvider.cs | 42 + src/Microsoft.TestPlatform.Common/Friends.cs | 5 + .../CollectorRequestedEnvironmentVariable.cs | 110 +++ .../DataCollectionManager.cs | 764 ++++++++++++++++++ .../DataCollectorDataMessage.cs | 171 ++++ .../Friends.cs | 7 + .../Interfaces/IMessageSink.cs | 31 + ...icrosoft.TestPlatform.DataCollection.xproj | 19 + .../Properties/AssemblyInfo.cs | 19 + .../Resource.Designer.cs | 199 +++++ .../Resource.resx | 167 ++++ .../TestPlatformDataCollectionEvents.cs | 707 ++++++++++++++++ .../TestPlatformDataCollectionLogger.cs | 164 ++++ .../TestPlatformDataCollectionSink.cs | 101 +++ .../TestPlatformDataCollectorDiscovery.cs | 315 ++++++++ .../TestPlatformDataCollectorInfo.cs | 183 +++++ .../project.json | 28 + .../project.json | 2 +- .../ClientUtilities.cs | 239 +++++- .../project.json | 4 +- .../DataCollectionCoordinator.cs | 8 +- src/datacollector.x86/Friends.cs | 7 - src/datacollector.x86/project.json | 4 +- src/datacollector/Friends.cs | 7 + .../DataCollectorsSettingsProviderTests.cs | 44 + .../RunConfigurationSettingsProviderTests.cs | 11 + ...lectorRequestedEnvironmentVariableTests.cs | 44 + .../DataCollectionPluginManagerTests.cs | 284 +++++++ ...estPlatform.DataCollection.UnitTests.xproj | 22 + .../Properties/AssemblyInfo.cs | 19 + .../TestPlatformDataCollectorInfoTests.cs | 117 +++ .../app.config | 7 + .../project.json | 29 + .../DataCollectionCoordinatorTests.cs | 73 +- .../Properties/AssemblyInfo.cs | 0 .../datacollector.UnitTests.xproj} | 0 .../project.json | 0 43 files changed, 3984 insertions(+), 62 deletions(-) create mode 100644 src/Microsoft.TestPlatform.Common/DataCollection/DataCollectorsSettingsProvider.cs rename src/{datacollector.x86 => Microsoft.TestPlatform.Common/DataCollection}/Interfaces/IDataCollectionManager.cs (90%) rename src/{Microsoft.TestPlatform.CrossPlatEngine => Microsoft.TestPlatform.Common}/DataCollection/Interfaces/IDataCollectorsSettingsProvider.cs (66%) create mode 100644 src/Microsoft.TestPlatform.Common/DataCollection/Interfaces/IRunConfigurationSettingsProvider.cs create mode 100644 src/Microsoft.TestPlatform.Common/DataCollection/RunConfigurationSettingsProvider.cs create mode 100644 src/Microsoft.TestPlatform.DataCollection/CollectorRequestedEnvironmentVariable.cs create mode 100644 src/Microsoft.TestPlatform.DataCollection/DataCollectionManager.cs create mode 100644 src/Microsoft.TestPlatform.DataCollection/DataCollectorDataMessage.cs create mode 100644 src/Microsoft.TestPlatform.DataCollection/Friends.cs create mode 100644 src/Microsoft.TestPlatform.DataCollection/Interfaces/IMessageSink.cs create mode 100644 src/Microsoft.TestPlatform.DataCollection/Microsoft.TestPlatform.DataCollection.xproj create mode 100644 src/Microsoft.TestPlatform.DataCollection/Properties/AssemblyInfo.cs create mode 100644 src/Microsoft.TestPlatform.DataCollection/Resource.Designer.cs create mode 100644 src/Microsoft.TestPlatform.DataCollection/Resource.resx create mode 100644 src/Microsoft.TestPlatform.DataCollection/TestPlatformDataCollectionEvents.cs create mode 100644 src/Microsoft.TestPlatform.DataCollection/TestPlatformDataCollectionLogger.cs create mode 100644 src/Microsoft.TestPlatform.DataCollection/TestPlatformDataCollectionSink.cs create mode 100644 src/Microsoft.TestPlatform.DataCollection/TestPlatformDataCollectorDiscovery.cs create mode 100644 src/Microsoft.TestPlatform.DataCollection/TestPlatformDataCollectorInfo.cs create mode 100644 src/Microsoft.TestPlatform.DataCollection/project.json delete mode 100644 src/datacollector.x86/Friends.cs create mode 100644 src/datacollector/Friends.cs create mode 100644 test/Microsoft.TestPlatform.Common.UnitTests/DataCollection/DataCollectorsSettingsProviderTests.cs create mode 100644 test/Microsoft.TestPlatform.Common.UnitTests/DataCollection/RunConfigurationSettingsProviderTests.cs create mode 100644 test/Microsoft.TestPlatform.DataCollection.UnitTests/CollectorRequestedEnvironmentVariableTests.cs create mode 100644 test/Microsoft.TestPlatform.DataCollection.UnitTests/DataCollectionPluginManagerTests.cs create mode 100644 test/Microsoft.TestPlatform.DataCollection.UnitTests/Microsoft.TestPlatform.DataCollection.UnitTests.xproj create mode 100644 test/Microsoft.TestPlatform.DataCollection.UnitTests/Properties/AssemblyInfo.cs create mode 100644 test/Microsoft.TestPlatform.DataCollection.UnitTests/TestPlatformDataCollectorInfoTests.cs create mode 100644 test/Microsoft.TestPlatform.DataCollection.UnitTests/app.config create mode 100644 test/Microsoft.TestPlatform.DataCollection.UnitTests/project.json rename test/{datacollector.x86.UnitTests => datacollector.UnitTests}/DataCollectionCoordinatorTests.cs (77%) rename test/{datacollector.x86.UnitTests => datacollector.UnitTests}/Properties/AssemblyInfo.cs (100%) rename test/{datacollector.x86.UnitTests/datacollector.x86.UnitTests.xproj => datacollector.UnitTests/datacollector.UnitTests.xproj} (100%) rename test/{datacollector.x86.UnitTests => datacollector.UnitTests}/project.json (100%) diff --git a/TestPlatform.sln b/TestPlatform.sln index a3190a0116..0c1ab70cb3 100644 --- a/TestPlatform.sln +++ b/TestPlatform.sln @@ -83,7 +83,11 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "datacollector", "src\dataco EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "datacollector.x86", "src\datacollector.x86\datacollector.x86.xproj", "{00DFB5C7-3850-4A65-986B-713F200482D4}" EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "datacollector.x86.UnitTests", "test\datacollector.x86.UnitTests\datacollector.x86.UnitTests.xproj", "{00AA21F3-31E4-4748-AC0B-C4EADB41CA24}" +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.TestPlatform.DataCollection", "src\Microsoft.TestPlatform.DataCollection\Microsoft.TestPlatform.DataCollection.xproj", "{D5C951F1-B6FF-4DE2-AD89-F5C5803DE126}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "datacollector.UnitTests", "test\datacollector.UnitTests\datacollector.UnitTests.xproj", "{00AA21F3-31E4-4748-AC0B-C4EADB41CA24}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.TestPlatform.DataCollection.UnitTests", "test\Microsoft.TestPlatform.DataCollection.UnitTests\Microsoft.TestPlatform.DataCollection.UnitTests.xproj", "{07D89FBD-F76D-40EF-A0CA-9213DEE61941}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -207,10 +211,18 @@ Global {00DFB5C7-3850-4A65-986B-713F200482D4}.Debug|Any CPU.Build.0 = Debug|Any CPU {00DFB5C7-3850-4A65-986B-713F200482D4}.Release|Any CPU.ActiveCfg = Release|Any CPU {00DFB5C7-3850-4A65-986B-713F200482D4}.Release|Any CPU.Build.0 = Release|Any CPU + {D5C951F1-B6FF-4DE2-AD89-F5C5803DE126}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D5C951F1-B6FF-4DE2-AD89-F5C5803DE126}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D5C951F1-B6FF-4DE2-AD89-F5C5803DE126}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D5C951F1-B6FF-4DE2-AD89-F5C5803DE126}.Release|Any CPU.Build.0 = Release|Any CPU {00AA21F3-31E4-4748-AC0B-C4EADB41CA24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {00AA21F3-31E4-4748-AC0B-C4EADB41CA24}.Debug|Any CPU.Build.0 = Debug|Any CPU {00AA21F3-31E4-4748-AC0B-C4EADB41CA24}.Release|Any CPU.ActiveCfg = Release|Any CPU {00AA21F3-31E4-4748-AC0B-C4EADB41CA24}.Release|Any CPU.Build.0 = Release|Any CPU + {07D89FBD-F76D-40EF-A0CA-9213DEE61941}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {07D89FBD-F76D-40EF-A0CA-9213DEE61941}.Debug|Any CPU.Build.0 = Debug|Any CPU + {07D89FBD-F76D-40EF-A0CA-9213DEE61941}.Release|Any CPU.ActiveCfg = Release|Any CPU + {07D89FBD-F76D-40EF-A0CA-9213DEE61941}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -247,6 +259,8 @@ Global {0CC51428-B665-47B0-A093-042D31785928} = {707096D0-DCFB-44A2-979D-178740E5DADE} {3572E78C-5AA5-4F68-876D-FC5322677263} = {D8EF073C-279A-4279-912D-E9D4B0635E17} {00DFB5C7-3850-4A65-986B-713F200482D4} = {D8EF073C-279A-4279-912D-E9D4B0635E17} + {D5C951F1-B6FF-4DE2-AD89-F5C5803DE126} = {D8EF073C-279A-4279-912D-E9D4B0635E17} {00AA21F3-31E4-4748-AC0B-C4EADB41CA24} = {463031A2-7F16-4E38-9944-1F5161D04933} + {07D89FBD-F76D-40EF-A0CA-9213DEE61941} = {463031A2-7F16-4E38-9944-1F5161D04933} EndGlobalSection EndGlobal diff --git a/src/Microsoft.TestPlatform.Common/Constants.cs b/src/Microsoft.TestPlatform.Common/Constants.cs index d0d87b4290..3ee84f1397 100644 --- a/src/Microsoft.TestPlatform.Common/Constants.cs +++ b/src/Microsoft.TestPlatform.Common/Constants.cs @@ -36,5 +36,10 @@ public static class TestPlatformDefaults /// Default value of the boolean that determines whether or not job queue should be bounded. /// public const bool DefaultEnableBoundsOnLoggerEventQueue = true; + + /// + /// Name of the key used in app settings for TestRunnerService.exe to specify location of plugin directory. + /// + public const string PluginDirectorySettingsKeyName = "DataCollectorPluginDirectory"; } } diff --git a/src/Microsoft.TestPlatform.Common/DataCollection/DataCollectorsSettingsProvider.cs b/src/Microsoft.TestPlatform.Common/DataCollection/DataCollectorsSettingsProvider.cs new file mode 100644 index 0000000000..875c298c8c --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/DataCollection/DataCollectorsSettingsProvider.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Common.DataCollection +{ + using System.Xml; + + using Microsoft.VisualStudio.TestPlatform.Common.DataCollection.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + + /// + /// Provides settings for data collectors. + /// + [SettingsName(DataCollectionRunSettingsName)] + internal class DataCollectorsSettingsProvider : IDataCollectorsSettingsProvider + { + /// + /// Name of the data collection settings node in RunSettings. + /// + public const string DataCollectionRunSettingsName = "DataCollectionRunSettings"; + + /// + /// Gets data collectors settings. + /// + public DataCollectionRunSettings Settings + { + get; + private set; + } + + /// + /// Loads the Run specific data collection settings from RunSettings. + /// + /// Xml reader. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0")] + public void Load(XmlReader reader) + { + ValidateArg.NotNull(reader, "reader"); + reader.Read(); + this.Settings = DataCollectionRunSettings.FromXml(reader); + } + } +} \ No newline at end of file diff --git a/src/datacollector.x86/Interfaces/IDataCollectionManager.cs b/src/Microsoft.TestPlatform.Common/DataCollection/Interfaces/IDataCollectionManager.cs similarity index 90% rename from src/datacollector.x86/Interfaces/IDataCollectionManager.cs rename to src/Microsoft.TestPlatform.Common/DataCollection/Interfaces/IDataCollectionManager.cs index 7e06b2f330..935a40d6ae 100644 --- a/src/datacollector.x86/Interfaces/IDataCollectionManager.cs +++ b/src/Microsoft.TestPlatform.Common/DataCollection/Interfaces/IDataCollectionManager.cs @@ -1,6 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.VisualStudio.TestPlatform.DataCollector.Interfaces +namespace Microsoft.VisualStudio.TestPlatform.Common.DataCollection.Interfaces { using System; using System.Collections.Generic; @@ -20,9 +20,9 @@ internal interface IDataCollectionManager : IDisposable /// /// Loads and initializes data collector plugins. /// - /// Run Settings which has DataCollector configuration. + /// Run Settings which has DataCollector configuration. /// Environment variables. - Dictionary LoadDataCollectors(RunSettings settingsXml); + IDictionary LoadDataCollectors(RunSettings runSettings); /// /// Raises TestCaseStart event to all data collectors configured for run. diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/DataCollection/Interfaces/IDataCollectorsSettingsProvider.cs b/src/Microsoft.TestPlatform.Common/DataCollection/Interfaces/IDataCollectorsSettingsProvider.cs similarity index 66% rename from src/Microsoft.TestPlatform.CrossPlatEngine/DataCollection/Interfaces/IDataCollectorsSettingsProvider.cs rename to src/Microsoft.TestPlatform.Common/DataCollection/Interfaces/IDataCollectorsSettingsProvider.cs index e86dcb7a91..76887ff276 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/DataCollection/Interfaces/IDataCollectorsSettingsProvider.cs +++ b/src/Microsoft.TestPlatform.Common/DataCollection/Interfaces/IDataCollectorsSettingsProvider.cs @@ -1,17 +1,17 @@ // Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.DataCollection.Interfaces +namespace Microsoft.VisualStudio.TestPlatform.Common.DataCollection.Interfaces { using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; /// - /// The DataCollectorsSettingsProvider interface. + /// Provides settings for data collectors. /// public interface IDataCollectorsSettingsProvider : ISettingsProvider { /// - /// Gets run specific data collection settings. + /// Gets data collectors settings. /// DataCollectionRunSettings Settings { get; } } diff --git a/src/Microsoft.TestPlatform.Common/DataCollection/Interfaces/IRunConfigurationSettingsProvider.cs b/src/Microsoft.TestPlatform.Common/DataCollection/Interfaces/IRunConfigurationSettingsProvider.cs new file mode 100644 index 0000000000..90c28b2a13 --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/DataCollection/Interfaces/IRunConfigurationSettingsProvider.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Common.DataCollection.Interfaces +{ + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; + + /// + /// Provides settings for test run configuration. + /// + public interface IRunConfigurationSettingsProvider : ISettingsProvider + { + /// + /// Gets run specific data collection settings. + /// + RunConfiguration Settings { get; } + } +} diff --git a/src/Microsoft.TestPlatform.Common/DataCollection/RunConfigurationSettingsProvider.cs b/src/Microsoft.TestPlatform.Common/DataCollection/RunConfigurationSettingsProvider.cs new file mode 100644 index 0000000000..38070c3cbf --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/DataCollection/RunConfigurationSettingsProvider.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Common.DataCollection +{ + using System.Xml; + + using Microsoft.VisualStudio.TestPlatform.Common.DataCollection.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + + /// + /// Provides settings for data collectors. + /// + [SettingsName(RunConfigurationSettingsName)] + public class RunConfigurationSettingsProvider : IRunConfigurationSettingsProvider + { + /// + /// Name of the data collection settings node in RunSettings. + /// + public const string RunConfigurationSettingsName = "RunConfiguration"; + + /// + /// Gets run configuration settings. + /// + public RunConfiguration Settings + { + get; + private set; + } + + /// + /// Loads the Run specific data collection settings from RunSettings. + /// + /// Xml reader + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0")] + public void Load(XmlReader reader) + { + ValidateArg.NotNull(reader, "reader"); + reader.Read(); + this.Settings = RunConfiguration.FromXml(reader); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Common/Friends.cs b/src/Microsoft.TestPlatform.Common/Friends.cs index 188d22c6e3..c18b41b2e1 100644 --- a/src/Microsoft.TestPlatform.Common/Friends.cs +++ b/src/Microsoft.TestPlatform.Common/Friends.cs @@ -5,6 +5,10 @@ #region Product Assemblies [assembly: InternalsVisibleTo("Microsoft.TestPlatform.CrossPlatEngine, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("vstest.console, PublicKey =002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] +[assembly: InternalsVisibleTo("datacollector.x86, PublicKey =002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] +[assembly: InternalsVisibleTo("datacollector, PublicKey =002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] +[assembly: InternalsVisibleTo("Microsoft.TestPlatform.DataCollection, PublicKey =002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] +[assembly: InternalsVisibleTo("datacollector.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] #endregion #region Test Assemblies @@ -12,4 +16,5 @@ [assembly: InternalsVisibleTo("Microsoft.TestPlatform.CrossPlatEngine.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("vstest.console.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] +[assembly: InternalsVisibleTo("Microsoft.TestPlatform.DataCollection.UnitTests, PublicKey =002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] #endregion \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.DataCollection/CollectorRequestedEnvironmentVariable.cs b/src/Microsoft.TestPlatform.DataCollection/CollectorRequestedEnvironmentVariable.cs new file mode 100644 index 0000000000..ccdc892054 --- /dev/null +++ b/src/Microsoft.TestPlatform.DataCollection/CollectorRequestedEnvironmentVariable.cs @@ -0,0 +1,110 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.DataCollection +{ + using System.Collections.Generic; + using System.Diagnostics; + + /// + /// An environment variable requested to be set in the test execution environment by a data collector, including the + /// friendly names of data collectors that requested it. + /// This is needed to find list of environment variables needed for test run after eliminating the duplicate name and keys. + /// For details check DataCollectionPluginManager.AddCollectorEnvironmentVariables() method. + /// + internal sealed class CollectorRequestedEnvironmentVariable + { + #region Fields + + /// + /// Variable name and requested value + /// + private readonly KeyValuePair variable; + + /// + /// Friendly names of data collectors that requested this environment variable + /// + private List dataCollectorsThatRequested; + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// + /// Variable name and requested value. + /// + /// + /// Friendly name of the data collector requesting it. + /// + public CollectorRequestedEnvironmentVariable( + KeyValuePair variable, + string requestingDataCollectorFriendlyName) + { + Debug.Assert(!string.IsNullOrEmpty(variable.Key), "'variable.Key' is null or empty"); + Debug.Assert( + !string.IsNullOrEmpty(requestingDataCollectorFriendlyName), + "'requestingDataCollectorFriendlyName' is null or empty"); + + this.variable = variable; + this.dataCollectorsThatRequested = new List { requestingDataCollectorFriendlyName }; + } + + #endregion + + #region Properties + + /// + /// Gets variable name. + /// + public string Name + { + get + { + return this.variable.Key; + } + } + + /// + /// Gets requested value + /// + public string Value + { + get + { + return this.variable.Value; + } + } + + /// + /// Gets friendly name of the first data collector that requested this environment variable + /// + public string FirstDataCollectorThatRequested + { + get + { + return this.dataCollectorsThatRequested[0]; + } + } + + #endregion + + #region Methods + + /// + /// Adds the data collector to the list of data collectors that requested this variable. + /// + /// Friendly name of requesting data collector. + public void AddRequestingDataCollector(string requestingDataCollectorFriendlyName) + { + Debug.Assert( + !this.dataCollectorsThatRequested.Contains(requestingDataCollectorFriendlyName), + "'dataCollectorsThatRequested' already contains the data collector '" + + requestingDataCollectorFriendlyName + "'"); + this.dataCollectorsThatRequested.Add(requestingDataCollectorFriendlyName); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.DataCollection/DataCollectionManager.cs b/src/Microsoft.TestPlatform.DataCollection/DataCollectionManager.cs new file mode 100644 index 0000000000..10070ba595 --- /dev/null +++ b/src/Microsoft.TestPlatform.DataCollection/DataCollectionManager.cs @@ -0,0 +1,764 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.DataCollection +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Diagnostics; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Reflection; + using System.Xml; + + using Microsoft.VisualStudio.TestPlatform.Common; + using Microsoft.VisualStudio.TestPlatform.Common.DataCollection; + using Microsoft.VisualStudio.TestPlatform.Common.DataCollection.Interfaces; + using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; + using Microsoft.VisualStudio.TestPlatform.DataCollection; + using Microsoft.VisualStudio.TestPlatform.DataCollection.Implementations; + using Microsoft.VisualStudio.TestPlatform.DataCollection.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + using Microsoft.VisualStudio.TestTools.Common; + + using CollectorDataEntry = Microsoft.VisualStudio.TestPlatform.ObjectModel.AttachmentSet; + using DataCollectionContext = Microsoft.VisualStudio.TestTools.Execution.DataCollectionContext; + using DataCollectionEnvironmentContext = Microsoft.VisualStudio.TestTools.Execution.DataCollectionEnvironmentContext; + using DataCollectionEventArgs = Microsoft.VisualStudio.TestTools.Execution.DataCollectionEventArgs; + using DataCollector = Microsoft.VisualStudio.TestTools.Execution.DataCollector; + using DataCollectorInformation = Microsoft.VisualStudio.TestTools.Execution.DataCollectorInformation; + using DataCollectorInvocationError = Microsoft.VisualStudio.TestTools.Execution.DataCollectorInvocationError; + using SessionEndEventArgs = Microsoft.VisualStudio.TestTools.Execution.SessionEndEventArgs; + using SessionId = Microsoft.VisualStudio.TestTools.Common.SessionId; + using UriDataAttachment = Microsoft.VisualStudio.TestPlatform.ObjectModel.UriDataAttachment; + + /// + /// The data collection plugin manager. + /// + internal class DataCollectionManager : IDataCollectionManager, IDisposable + { + /// + /// Cache of data collectors associated with the run. + /// + private Dictionary runDataCollectors; + + /// + /// Is data collection currently enabled. + /// + private bool collectionEnabled; + + /// + /// Data collection environment context. + /// + private DataCollectionEnvironmentContext dataCollectionEnvironmentContext; + + /// + /// Data collection and log messages are sent through message sink + /// + private IMessageSink messageSink; + + /// + /// Abort user work item factory, needed for DataCollectionEvents TestTool interface. + /// + private SafeAbortableUserWorkItemFactory userWorkItemFactory; + + /// + /// Specifies whether the object is disposed or not. + /// + private bool disposed; + + /// + /// Directory in which data collector collection files are copied. + /// + private string collectionOutputDirectory; + + /// + /// Initializes a new instance of the class. + /// + public DataCollectionManager() : this(default(IMessageSink)) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The message sink. + /// + internal DataCollectionManager(IMessageSink messageSink) + { + this.collectionEnabled = false; + this.runDataCollectors = new Dictionary(); + + this.messageSink = messageSink; + this.userWorkItemFactory = new SafeAbortableUserWorkItemFactory(); + this.ConfigureNewSession(); + } + + /// + /// Raises TestCaseStart event to all data collectors configured for run. + /// + /// TestCaseStart event. + public void TestCaseStarted(TestCaseStartEventArgs testCaseStartEventArgs) + { + throw new NotImplementedException(); + } + + /// + /// Raises TestCaseEnd event to all data collectors configured for run. + /// + /// Test case which is complete. + /// Outcome of the test case. + /// Collection of testCase attachmentSet. + public Collection TestCaseEnded(TestCase testCase, ObjectModel.TestOutcome testOutcome) + { + throw new NotImplementedException(); + } + + /// + /// Raises SessionStart event to all data collectors configured for run. + /// + /// Are test case level events required. + public bool SessionStarted() + { + throw new NotImplementedException(); + } + + /// + /// Raises SessionEnd event to all data collectors configured for run. + /// + /// Specified whether the run is cancelled or not. + /// Collection of session attachmentSet. + public Collection SessionEnded(bool isCancelled) + { + throw new NotImplementedException(); + } + + /// + /// Loads and initializes data collector plugins. + /// + /// Settings for test run. + /// Environment variables requested by data collectors + public IDictionary LoadDataCollectors(RunSettings testRunSettings) + { + ValidateArg.NotNull(testRunSettings, "testRunSettings"); + IDictionary executionEnvironmentVariables = new Dictionary(StringComparer.OrdinalIgnoreCase); + + var dataCollectorSettingsProvider = + (IDataCollectorsSettingsProvider)testRunSettings.GetSettings(Constants.DataCollectionRunSettingsName); + if (null == dataCollectorSettingsProvider) + { + // If config file didn't specify any settings than settings provider will not be loaded. No data collection enabled. + return executionEnvironmentVariables; + } + + var runCollectionSettings = dataCollectorSettingsProvider.Settings; + if (null == runCollectionSettings || !runCollectionSettings.IsCollectionEnabled) + { + return executionEnvironmentVariables; + } + + var runConfigurationSettingsProvider = + (IRunConfigurationSettingsProvider)testRunSettings.GetSettings(Constants.RunConfigurationSettingsName); + RunConfiguration runConfiguration = null; + if (null != runConfigurationSettingsProvider) + { + runConfiguration = runConfigurationSettingsProvider.Settings; + } + + this.collectionOutputDirectory = null; + if (null != runConfiguration) + { + this.collectionOutputDirectory = runConfiguration.ResultsDirectory; + } + + this.collectionEnabled = runCollectionSettings.IsCollectionEnabled; + + if (this.collectionEnabled) + { + executionEnvironmentVariables = this.LoadAndInitDataCollectors(runCollectionSettings); + } + + return executionEnvironmentVariables; + } + + /// + /// Dispose event object + /// + public void Dispose() + { + this.Dispose(true); + + // Use SupressFinalize in case a subclass + // of this type implements a finalizer. + GC.SuppressFinalize(this); + } + + /// + /// The dispose. + /// + /// + /// The disposing. + /// + protected virtual void Dispose(bool disposing) + { + if (!this.disposed) + { + if (disposing) + { + // todo : Dispose resources here. + } + + this.disposed = true; + } + } + + /// + /// Creates an instance of collector plugin of given type. + /// + /// type of collector plugin to instantiate. + /// The dataCollector. + private static DataCollector CreateDataCollector(Type dataCollectorType) + { + if (EqtTrace.IsInfoEnabled) + { + EqtTrace.Info("DataCollectionManager.CreateDataCollector: Attempting to load data collector: " + dataCollectorType); + } + + try + { + var rawPlugin = Activator.CreateInstance(dataCollectorType); + + // Check if this is a data collector. + var dataCollector = rawPlugin as DataCollector; + return dataCollector; + } + catch (SystemException ex) + { + if (EqtTrace.IsErrorEnabled) + { + EqtTrace.Error("DataCollectionManager.CreateDataCollector: Could not create instance of type: " + dataCollectorType.ToString() + " Exception: " + ex.Message); + } + + throw; + } + } + + /// + /// Helper method that gets the Type from typename string specified. + /// + /// + /// Type name of the collector + /// + /// + /// The data Collector Information. + /// + /// + /// Type of the collector type name + /// + private static Type GetCollectorType(string collectorTypeName, out DataCollectorInformation dataCollectorInformation) + { + try + { + dataCollectorInformation = null; + var pluginDirectoryPath = DataCollectorDiscoveryHelper.DataCollectorsDirectory; + + var basePath = Path.Combine(pluginDirectoryPath, collectorTypeName.Split(',')[1].Trim()); + + Assembly assembly = null; + + Type dcType = null; + dcType = GetDataCollectorInformationFromBinary(basePath, collectorTypeName, out assembly); + + // Not able to locate data collector binary. + if (dcType == null) + { + return dcType; + } + + var configuration = DataCollectorDiscoveryHelper.GetConfigurationForAssembly(assembly); + + dataCollectorInformation = new DataCollectorInformation(dcType, configuration); + + return dcType; + } + catch (Exception ex) + { + if (EqtTrace.IsErrorEnabled) + { + EqtTrace.Error("DataCollectionManager.GetCollectorType: Failed to get type for Collector '{0}': {1}", collectorTypeName, ex); + } + + throw; + } + } + + /// + /// The get data collector information from binary. + /// + /// + /// The binary path. + /// + /// + /// The collector type name. + /// + /// + /// The assembly. + /// + /// + /// The . + /// + private static Type GetDataCollectorInformationFromBinary(string binaryPath, string collectorTypeName, out Assembly assembly) + { + assembly = null; + Type dcType = null; + var dllPath = string.Concat(binaryPath, ".dll"); + if (File.Exists(dllPath)) + { + assembly = Assembly.LoadFrom(dllPath); + dcType = GetDataCollectorInformationFromAssembly(assembly, collectorTypeName); + } + + if (dcType == null) + { + var exePath = string.Concat(binaryPath, ".exe"); + if (File.Exists(exePath)) + { + assembly = Assembly.LoadFrom(exePath); + dcType = GetDataCollectorInformationFromAssembly(assembly, collectorTypeName); + } + } + + return dcType; + } + + /// + /// The get data collector information from assembly. + /// + /// + /// The assembly. + /// + /// + /// The collector type name. + /// + /// + /// The . + /// + private static Type GetDataCollectorInformationFromAssembly(Assembly assembly, string collectorTypeName) + { + Type dcType = null; + if (assembly != null) + { + var types = assembly.GetTypes(); + dcType = types.Where((type) => type.AssemblyQualifiedName.Equals(collectorTypeName)).FirstOrDefault(); + } + + return dcType; + } + + /// + /// Configures new session by + /// a. creating new session id + /// + private void ConfigureNewSession() + { + // todo : add stuff here required to configure new session + var dataCollectionContext = new DataCollectionContext(new SessionId(Guid.NewGuid())); + this.dataCollectionEnvironmentContext = DataCollectionEnvironmentContext.CreateForLocalEnvironment(dataCollectionContext); + } + + /// + /// Loads the data collector plugins and initializes them. + /// + /// data collection settings. + /// Environment variables that needs to be set in test process for data collection. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Need to catch all exception type to send as data collection error to client.")] + private IDictionary LoadAndInitDataCollectors(DataCollectionRunSettings dataCollectionSettings) + { + IDictionary variables = new Dictionary(StringComparer.OrdinalIgnoreCase); + + var enabledCollectors = this.GetDataCollectorsEnabledForRun(dataCollectionSettings); + if (enabledCollectors.Count == 0) + { + return variables; + } + + foreach (var collectorSettings in enabledCollectors) + { + this.LoadAndInitDataCollector(collectorSettings); + } + + // Once all data collectors have been initialized, query for environment variables + bool unloadedAnyCollector; + var dataCollectorEnvironmentVariables = this.GetEnvironmentVariables(out unloadedAnyCollector); + + foreach (var variable in dataCollectorEnvironmentVariables.Values) + { + variables.Add(variable.Name, variable.Value); + } + + return variables; + } + + /// + /// Finds data collector enabled for the run in data collection settings. + /// + /// data collection settings + /// List of enabled data collectors + private List GetDataCollectorsEnabledForRun(DataCollectionRunSettings dataCollectionSettings) + { + List runEnabledDataCollectors = new List(); + foreach (DataCollectorSettings settings in dataCollectionSettings.DataCollectorSettingsList) + { + if (settings.IsEnabled) + { + if (runEnabledDataCollectors.Any(dcSettings => dcSettings.Uri.Equals(settings.Uri) + || string.Equals(dcSettings.AssemblyQualifiedName, settings.AssemblyQualifiedName, StringComparison.OrdinalIgnoreCase))) + { + // If Uri or assembly qualified type name is repeated, consider data collector as duplicate and ignore it. + this.LogWarning(string.Format(CultureInfo.CurrentUICulture, Resource.IgnoredDuplicateConfiguration, settings.AssemblyQualifiedName, settings.Uri)); + continue; + } + + runEnabledDataCollectors.Add(settings); + } + } + + return runEnabledDataCollectors; + } + + /// + /// The get environment variables. + /// + /// + /// The unloaded any collector. + /// + /// + /// The . + /// + private Dictionary GetEnvironmentVariables(out bool unloadedAnyCollector) + { + var failedCollectors = new List(); + unloadedAnyCollector = false; + var dataCollectorEnvironmentVariables = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var dataCollectorInfo in this.GetDataCollectorsSnapshot()) + { + dataCollectorInfo.GetTestExecutionEnvironmentVariables(); + try + { + this.AddCollectorEnvironmentVariables(dataCollectorInfo, dataCollectorEnvironmentVariables); + } + catch (Exception ex) + { + unloadedAnyCollector = true; + + Type dataCollectorType = dataCollectorInfo.DataCollector.GetType(); + failedCollectors.Add(dataCollectorInfo); + dataCollectorInfo.Logger.LogError( + this.dataCollectionEnvironmentContext.SessionDataCollectionContext, + string.Format( + CultureInfo.CurrentCulture, + Resource.DataCollectorErrorOnGetVariable, + dataCollectorType, + ex.ToString())); + + if (EqtTrace.IsErrorEnabled) + { + EqtTrace.Error("DataCollectionManager.GetEnvironmentVariables: Failed to get variable for Collector '{0}': {1}", dataCollectorType, ex); + } + } + } + + this.RemoveDataCollectors(failedCollectors); + return dataCollectorEnvironmentVariables; + } + + /// + /// Gets a snapshot of current data collectors. + /// + /// + /// The . + /// + private List GetDataCollectorsSnapshot() + { + var datacollectorInfoList = new List(); + lock (this.runDataCollectors) + { + foreach (var dataCollectorInfo in this.runDataCollectors.Values) + { + if (dataCollectorInfo != null) + { + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose( + "DataCollectionManager.GetDataCollectorsSnapshot: DataCollector:{0}", + dataCollectorInfo.DataCollectorInformation.FriendlyName); + } + + datacollectorInfoList.Add(dataCollectorInfo); + } + else + { + if (EqtTrace.IsErrorEnabled) + { + EqtTrace.Error("DataCollectionManager.GetDataCollectorsSnapshot: got null data collector info from the data collector info collection (ignored)."); + } + } + } + } + + return datacollectorInfoList; + } + + /// + /// Collects environment variable to be set in test process by avoiding duplicates + /// and detecting override of variable value by multiple adapters. + /// + /// Data collector information for newly loaded plugin. + /// Environment variables required for already loaded plugin. + private void AddCollectorEnvironmentVariables( + TestPlatformDataCollectorInfo dataCollectorInfo, + Dictionary dataCollectorEnvironmentVariables) + { + if (null != dataCollectorInfo.TestExecutionEnvironmentVariables) + { + var collectorFriendlyName = dataCollectorInfo.DataCollectorInformation.FriendlyName; + foreach (var namevaluepair in dataCollectorInfo.TestExecutionEnvironmentVariables) + { + CollectorRequestedEnvironmentVariable alreadyRequestedVariable; + if (dataCollectorEnvironmentVariables.TryGetValue(namevaluepair.Key, out alreadyRequestedVariable)) + { + if (string.Equals(namevaluepair.Value, alreadyRequestedVariable.Value, StringComparison.Ordinal)) + { + alreadyRequestedVariable.AddRequestingDataCollector(collectorFriendlyName); + } + else + { + // Data collector is overriding an already requested variable, possibly an error. + dataCollectorInfo.Logger.LogError( + this.dataCollectionEnvironmentContext.SessionDataCollectionContext, + string.Format( + CultureInfo.CurrentUICulture, + Resource.DataCollectorRequestedDuplicateEnvironmentVariable, + collectorFriendlyName, + namevaluepair.Key, + namevaluepair.Value, + alreadyRequestedVariable.FirstDataCollectorThatRequested, + alreadyRequestedVariable.Value)); + } + } + else + { + if (EqtTrace.IsVerboseEnabled) + { + // New variable, add to the list. + EqtTrace.Verbose("DataCollectionManager.AddCollectionEnvironmentVariables: Adding Environment variable '{0}' value '{1}'", namevaluepair.Key, namevaluepair.Value); + } + + dataCollectorEnvironmentVariables.Add( + namevaluepair.Key, + new CollectorRequestedEnvironmentVariable(namevaluepair, collectorFriendlyName)); + } + } + } + } + + /// + /// Initializes data collector plugin. + /// + /// + /// data collector settings. + /// + /// + /// The . + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Need to catch all exception type to send as data collection error to client.")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Data collector object is disposed when plugins are cleaned up when test run ends.")] + private bool LoadAndInitDataCollector(DataCollectorSettings collectorSettings) + { + var codeBase = collectorSettings.CodeBase; + var collectorTypeName = collectorSettings.AssemblyQualifiedName; + var collectorDisplayName = string.IsNullOrWhiteSpace(collectorSettings.FriendlyName) ? collectorTypeName : collectorSettings.FriendlyName; + Type collectorType = null; + DataCollectorInformation dcInfo = null; + TestPlatformDataCollectorInfo dataCollectorInfo = null; + try + { + if (!string.IsNullOrWhiteSpace(codeBase)) + { + var fullyQualifiedAssemblyName = this.GetFullyQualifiedAssemblyNameFromFullTypeName(collectorSettings.AssemblyQualifiedName); // assemblyQualifiedname will have type name also. Get assemblyname only + var name = new AssemblyName(fullyQualifiedAssemblyName) { CodeBase = codeBase }; + + // Eg codebase="file://c:/TestImpact/Microsoft.VisualStudio.TraceCollector.dll" + + // Check if file is there. There can be a case data collector was loaded from some other path. If user has given a codebase we should ensure it is there + if (!File.Exists(new Uri(codeBase).LocalPath)) + { + throw new FileNotFoundException(codeBase); + } + + Assembly.Load(name); // This will do check for publicKeyToken etc + } + + collectorType = GetCollectorType(collectorTypeName, out dcInfo); + } + catch (FileNotFoundException) + { + this.LogWarning(string.Format(CultureInfo.CurrentCulture, Resource.DataCollectorAssemblyNotFound, collectorDisplayName)); + return false; + } + catch (Exception ex) + { + this.LogWarning(string.Format(CultureInfo.CurrentCulture, Resource.DataCollectorTypeNotFound, collectorDisplayName, ex.Message)); + return false; + } + + lock (this.runDataCollectors) + { + if (this.runDataCollectors.ContainsKey(collectorType)) + { + // Collector is already loaded (may be configured twice). Ignore duplicates and return. + return true; + } + } + + Debug.Assert(null != collectorType, string.Format(CultureInfo.CurrentCulture, "Could not find collector type '{0}'", collectorTypeName)); + + try + { + var dataCollector = CreateDataCollector(collectorType); + + // Attempt to get the data collector information verifying that all of the required metadata for the collector is available. + dataCollectorInfo = dcInfo == null ? null : new TestPlatformDataCollectorInfo( + dataCollector, + collectorSettings.Configuration, + this.messageSink, + dcInfo, + this.userWorkItemFactory); + + if (dataCollectorInfo == null || !dataCollectorInfo.DataCollectorInformation.TypeUri.Equals(collectorSettings.Uri)) + { + // If the data collector was not found, send an error. + this.LogWarning(string.Format(CultureInfo.CurrentCulture, Resource.DataCollectorNotFound, collectorType.FullName, collectorSettings.Uri)); + return false; + } + } + catch (Exception ex) + { + if (EqtTrace.IsErrorEnabled) + { + EqtTrace.Error("DataCollectionManager.LoadAndInitDataCollectors: exception while creating data collector {0}: " + ex, collectorTypeName); + } + + // No data collector info, so send the error with no direct association to the collector. + this.LogWarning(string.Format(CultureInfo.CurrentUICulture, Resource.DataCollectorInitializationError, collectorTypeName, ex.Message)); + return false; + } + + try + { + dataCollectorInfo.InitializeDataCollector(this.dataCollectionEnvironmentContext); + lock (this.runDataCollectors) + { + // Add data collectors to run cache. + this.runDataCollectors[collectorType] = dataCollectorInfo; + } + } + catch (Exception ex) + { + // data collector failed to initialize. Dispose it and mark it failed. + dataCollectorInfo.Logger.LogError( + this.dataCollectionEnvironmentContext.SessionDataCollectionContext, + string.Format( + CultureInfo.CurrentCulture, + Resource.DataCollectorInitializationError, + dataCollectorInfo.DataCollectorInformation.FriendlyName, + ex.Message)); + this.DisposeDataCollector(dataCollectorInfo); + return false; + } + + return true; + } + + /// + /// Sends a warning message against the session which is not associated with a data collector. + /// + /// + /// This should only be used when we do not have the data collector info yet. After we have the data + /// collector info we can use the data collectors logger for errors. + /// + /// The message to be logged. + private void LogWarning(string warningMessage) + { + this.messageSink.SendMessage(new DataCollectionMessageEventArgs(TestMessageLevel.Warning, warningMessage)); + } + + /// + /// Given assemblyQualifiedName of type get the fully qualified name of assembly. + /// + /// The assembly qualified name. + /// The fully qualified assembly name. + private string GetFullyQualifiedAssemblyNameFromFullTypeName(string assemblyQualifiedName) + { + // Below is assemblyQualifiedName + // Microsoft.VisualStudio.TraceCollector.TestImpactDataCollector, Microsoft.VisualStudio.TraceCollector, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + // FullyQualifiedAssemblyName will be + // Microsoft.VisualStudio.TraceCollector, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + var firstIndex = assemblyQualifiedName.IndexOf(",", StringComparison.Ordinal); + return assemblyQualifiedName.Substring(firstIndex + 1); + } + + private void RemoveDataCollectors(IEnumerable dataCollectorsToRemove) + { + if (null == dataCollectorsToRemove || dataCollectorsToRemove.Count() == 0) + { + return; + } + + lock (this.runDataCollectors) + { + foreach (var dataCollectorToRemove in dataCollectorsToRemove) + { + this.DisposeDataCollector(dataCollectorToRemove); + this.runDataCollectors.Remove(dataCollectorToRemove.DataCollector.GetType()); + } + if (this.runDataCollectors.Count == 0) + { + this.collectionEnabled = false; + } + } + } + + private void DisposeDataCollector(TestPlatformDataCollectorInfo dataCollectorInfo) + { + Type dataCollectorType = dataCollectorInfo.DataCollector.GetType(); + try + { + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose("DataCollectionManager.DisposeDataCollector: calling Dispose() on {0}", dataCollectorType); + } + dataCollectorInfo.DisposeDataCollector(); + } + catch (Exception ex) + { + if (EqtTrace.IsErrorEnabled) + { + EqtTrace.Error("DataCollectionManager.DisposeDataCollector: exception while calling Dispose() on {0}: " + ex, dataCollectorType); + } + + dataCollectorInfo.Logger.LogError( + this.dataCollectionEnvironmentContext.SessionDataCollectionContext, + string.Format( + CultureInfo.CurrentCulture, + Resource.DataCollectorDisposeError, + dataCollectorType, + ex.ToString())); + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.DataCollection/DataCollectorDataMessage.cs b/src/Microsoft.TestPlatform.DataCollection/DataCollectorDataMessage.cs new file mode 100644 index 0000000000..84b11b3d87 --- /dev/null +++ b/src/Microsoft.TestPlatform.DataCollection/DataCollectorDataMessage.cs @@ -0,0 +1,171 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.DataCollection +{ + using System; + using System.ComponentModel; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + + using DataCollectionContext = Microsoft.VisualStudio.TestTools.Execution.DataCollectionContext; + + /// + /// Base class for all message used in file transfer. + /// + internal class DataCollectorDataMessage + { + /// + /// Initializes a new instance of the class. + /// + /// Data collection context. + /// + /// Uri of data collector + /// + /// Friendly name of data collector + /// + internal DataCollectorDataMessage(DataCollectionContext context, Uri uri, string friendlyName) + { + ValidateArg.NotNull(context, "context"); + ValidateArg.NotNull(uri, "uri"); + ValidateArg.NotNullOrEmpty(friendlyName, "friendlyName"); + + this.DataCollectionContext = context; + this.Uri = uri; + this.FriendlyName = friendlyName; + } + + /// + /// Gets data collection context in which transfer is initiated. + /// + internal DataCollectionContext DataCollectionContext + { + get; + private set; + } + + /// + /// Gets Uri of data collector initiating the data transfer + /// + internal Uri Uri + { + get; + private set; + } + + /// + /// Gets friendly name of data collector initiating the data transfer. + /// + internal string FriendlyName + { + get; + private set; + } + } + + /// + /// Used for initiating a file transfer. This message includes all of the relevant information + /// about the file transfer. + /// + internal sealed class FileDataHeaderMessage : DataCollectorDataMessage + { + #region Constructor + + /// + /// Initializes a new instance of the class. + /// Initializes with all of the relevant information about the file transfer. + /// + /// + /// Content that the file is being sent for. + /// + /// + /// Path to the file. + /// + /// + /// A short description of the file. + /// + /// + /// Indicates if the file on the remote machine will be deleted after the file transfer is completed. + /// + /// User token + /// + /// + /// Handler that should be called when transfer is complete. + /// + /// + /// Uri of data collector initiating the transfer + /// + /// + /// Friendly name of the collector initiating the transfer + /// + internal FileDataHeaderMessage( + DataCollectionContext context, + string fileName, + string description, + bool deleteFile, + object userToken, + AsyncCompletedEventHandler fileTransferCompletedHandler, + Uri collectorUri, + string collectorFriendlyName) + : base(context, collectorUri, collectorFriendlyName) + { + ValidateArg.NotNullOrEmpty(fileName, "fileName"); + ValidateArg.NotNull(description, "description"); + + this.FileName = fileName; + this.PerformCleanup = deleteFile; + this.Description = description; + this.UserToken = userToken; + this.FileTransferCompletedHandler = fileTransferCompletedHandler; + } + + #endregion + + #region Public Properties + + /// + /// Gets the original path to the file on the client machine. + /// + public string FileName + { + get; + private set; + } + + /// + /// Gets display name to use for the file. + /// + public string Description + { + get; + private set; + } + + /// + /// Gets a value indicating whether the file on the remote machine will be deleted after the file transfer is completed. + /// + public bool PerformCleanup + { + get; + private set; + } + + /// + /// Gets called when file transfer is completed. + /// + public AsyncCompletedEventHandler FileTransferCompletedHandler + { + get; + private set; + } + + /// + /// Gets the user token. + /// + public object UserToken + { + get; + private set; + } + #endregion + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.DataCollection/Friends.cs b/src/Microsoft.TestPlatform.DataCollection/Friends.cs new file mode 100644 index 0000000000..37685a9a75 --- /dev/null +++ b/src/Microsoft.TestPlatform.DataCollection/Friends.cs @@ -0,0 +1,7 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Runtime.CompilerServices; + +#region TestAssemblies +[assembly: InternalsVisibleTo("Microsoft.TestPlatform.DataCollection.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] +#endregion \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.DataCollection/Interfaces/IMessageSink.cs b/src/Microsoft.TestPlatform.DataCollection/Interfaces/IMessageSink.cs new file mode 100644 index 0000000000..3078c42e32 --- /dev/null +++ b/src/Microsoft.TestPlatform.DataCollection/Interfaces/IMessageSink.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.DataCollection.Interfaces +{ + using System; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + + /// + /// Expose methods to be used by data collection process to send messages to Test Platform Client. + /// + internal interface IMessageSink + { + /// + /// Gets or sets event handler to be invoked on data collection message + /// + EventHandler OnDataCollectionMessage { get; set; } + + /// + /// Data collection message as sent by DataCollectionLogger. + /// + /// Data collection message event args. + void SendMessage(DataCollectionMessageEventArgs args); + + /// + /// Data collection data message as sent by DataCollectionSink. + /// + /// Data collector data message. + void SendMessage(DataCollectorDataMessage collectorDataMessage); + } +} diff --git a/src/Microsoft.TestPlatform.DataCollection/Microsoft.TestPlatform.DataCollection.xproj b/src/Microsoft.TestPlatform.DataCollection/Microsoft.TestPlatform.DataCollection.xproj new file mode 100644 index 0000000000..fc26d4cd0c --- /dev/null +++ b/src/Microsoft.TestPlatform.DataCollection/Microsoft.TestPlatform.DataCollection.xproj @@ -0,0 +1,19 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + d5c951f1-b6ff-4de2-ad89-f5c5803de126 + Microsoft.VisualStudio.TestPlatform.DataCollection + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\ + v4.5.2 + + + 2.0 + + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.DataCollection/Properties/AssemblyInfo.cs b/src/Microsoft.TestPlatform.DataCollection/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..9a0a27a583 --- /dev/null +++ b/src/Microsoft.TestPlatform.DataCollection/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Microsoft.TestPlatform.DataCollection.Legacy")] +[assembly: AssemblyTrademark("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("d5c951f1-b6ff-4de2-ad89-f5c5803de126")] diff --git a/src/Microsoft.TestPlatform.DataCollection/Resource.Designer.cs b/src/Microsoft.TestPlatform.DataCollection/Resource.Designer.cs new file mode 100644 index 0000000000..7a1d0016e8 --- /dev/null +++ b/src/Microsoft.TestPlatform.DataCollection/Resource.Designer.cs @@ -0,0 +1,199 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Microsoft.VisualStudio.TestPlatform.DataCollection { + using System; + using System.Reflection; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class Resource { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + internal Resource() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.TestPlatform.DataCollection.Resource", typeof(Resource).GetTypeInfo().Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Diagnostic data adapters could not be loaded. Failed to create required configuration. More details: {0}.. + /// + public static string ConfigureSessionError { + get { + return ResourceManager.GetString("ConfigureSessionError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Could not find diagnostic data adapter '{0}'. Make sure diagnostic data adapter is installed and try again.. + /// + public static string DataCollectorAssemblyNotFound { + get { + return ResourceManager.GetString("DataCollectorAssemblyNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Failed to dispose diagnostic data adapter '{0}'. Error: {1}.. + /// + public static string DataCollectorDisposeError { + get { + return ResourceManager.GetString("DataCollectorDisposeError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Diagnostic data adapter '{0}' failed to provide intialization information. Error: {1}. + /// + public static string DataCollectorErrorOnGetVariable { + get { + return ResourceManager.GetString("DataCollectorErrorOnGetVariable", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The diagnostic data adapter '{0}' threw an exception during type loading, construction, or initialization: {1}.. + /// + public static string DataCollectorInitializationError { + get { + return ResourceManager.GetString("DataCollectorInitializationError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Could not find diagnostic data adapter of type '{0}' and Uri '{1}'. + /// + public static string DataCollectorNotFound { + get { + return ResourceManager.GetString("DataCollectorNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The diagnostic data adapter '{0}' requested environment variable '{1}' with value '{2}' to be set in test execution environment, but another diagnostic data adapter '{3}' has already requested same environment variable with different value '{4}'.. + /// + public static string DataCollectorRequestedDuplicateEnvironmentVariable { + get { + return ResourceManager.GetString("DataCollectorRequestedDuplicateEnvironmentVariable", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to This adapter will not be used for this test run. The following error occurred: {0}.. + /// + public static string DataCollectorRunError { + get { + return ResourceManager.GetString("DataCollectorRunError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Failed to get type for diagnostic data adapter '{0}'. Error: {1}.. + /// + public static string DataCollectorTypeNotFound { + get { + return ResourceManager.GetString("DataCollectorTypeNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sending message of Message Type '{0}' is not supported.. + /// + public static string DataCollectorUnsupportedMessageType { + get { + return ResourceManager.GetString("DataCollectorUnsupportedMessageType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Timed out invoking diagnostic data adapter event handler '{0}'. + /// + ///To increase the timeout values for all diagnostic data adapters, see the following link: http://go.microsoft.com/fwlink/?LinkId=169311. + /// + public static string Execution_DataCollectorEventTimeout { + get { + return ResourceManager.GetString("Execution_DataCollectorEventTimeout", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to There are multiple configurations that have diagnostic data adapter type '{0}' or Uri '{1}'. Duplicate configurations will be ignored in the test run.. + /// + public static string IgnoredDuplicateConfiguration { + get { + return ResourceManager.GetString("IgnoredDuplicateConfiguration", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Diagnostic data adapter caught an exception of type '{0}': '{1}'. More details: {2}.. + /// + public static string ReportDataCollectorException { + get { + return ResourceManager.GetString("ReportDataCollectorException", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Diagnostic data adapter can send collection logs as files. Sending data as stream is not supported.. + /// + public static string SupportedTransferTypeIsFileTransfer { + get { + return ResourceManager.GetString("SupportedTransferTypeIsFileTransfer", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Types deriving from the data collection context cannot be used for sending data and messages. The DataCollectionContext used for sending data and messages must come from one of the events raised to the data collector.. + /// + public static string WrongDataCollectionContextType { + get { + return ResourceManager.GetString("WrongDataCollectionContextType", resourceCulture); + } + } + } +} diff --git a/src/Microsoft.TestPlatform.DataCollection/Resource.resx b/src/Microsoft.TestPlatform.DataCollection/Resource.resx new file mode 100644 index 0000000000..312f87cb15 --- /dev/null +++ b/src/Microsoft.TestPlatform.DataCollection/Resource.resx @@ -0,0 +1,167 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Diagnostic data adapters could not be loaded. Failed to create required configuration. More details: {0}. + + + Could not find diagnostic data adapter '{0}'. Make sure diagnostic data adapter is installed and try again. + + + Failed to dispose diagnostic data adapter '{0}'. Error: {1}. + + + Diagnostic data adapter '{0}' failed to provide intialization information. Error: {1} + + + The diagnostic data adapter '{0}' threw an exception during type loading, construction, or initialization: {1}. + + + Could not find diagnostic data adapter of type '{0}' and Uri '{1}' + + + The diagnostic data adapter '{0}' requested environment variable '{1}' with value '{2}' to be set in test execution environment, but another diagnostic data adapter '{3}' has already requested same environment variable with different value '{4}'. + + + This adapter will not be used for this test run. The following error occurred: {0}. + + + Failed to get type for diagnostic data adapter '{0}'. Error: {1}. + + + Sending message of Message Type '{0}' is not supported. + + + Timed out invoking diagnostic data adapter event handler '{0}'. + +To increase the timeout values for all diagnostic data adapters, see the following link: http://go.microsoft.com/fwlink/?LinkId=169311 + + + There are multiple configurations that have diagnostic data adapter type '{0}' or Uri '{1}'. Duplicate configurations will be ignored in the test run. + + + Diagnostic data adapter caught an exception of type '{0}': '{1}'. More details: {2}. + + + Diagnostic data adapter can send collection logs as files. Sending data as stream is not supported. + + + Types deriving from the data collection context cannot be used for sending data and messages. The DataCollectionContext used for sending data and messages must come from one of the events raised to the data collector. + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.DataCollection/TestPlatformDataCollectionEvents.cs b/src/Microsoft.TestPlatform.DataCollection/TestPlatformDataCollectionEvents.cs new file mode 100644 index 0000000000..73c0da2716 --- /dev/null +++ b/src/Microsoft.TestPlatform.DataCollection/TestPlatformDataCollectionEvents.cs @@ -0,0 +1,707 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.DataCollection +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.Linq; + using System.Reflection; + using System.Threading; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestTools.Common; + using Microsoft.VisualStudio.TestTools.DataCollection; + using Microsoft.VisualStudio.TestTools.Execution; + + + /// + /// Class defining execution events that will be registered for by collectors + /// + internal sealed class TestPlatformDataCollectionEvents : DataCollectionEvents + { + #region Fields + + /// + /// A factory for creating user work items + /// + private readonly SafeAbortableUserWorkItemFactory userWorkItemFactory; + + /// + /// Maps the type of event args to the multicast delegate for that event + /// + private Dictionary eventArgsToEventInvokerMap; + + #endregion + + #region Constructor + + /// + /// Initializes a new instance of the class by mapping the types of expected event args to the multicast + /// delegate that invokes the event on registered targets + /// + /// + /// A factory for creating user work items. + /// The user work items are used to invoke delegates on data collectors on worker threads. + /// + internal TestPlatformDataCollectionEvents(SafeAbortableUserWorkItemFactory userWorkItemFactory) + { + EqtAssert.ParameterNotNull(userWorkItemFactory, "userWorkItemFactory"); + this.userWorkItemFactory = userWorkItemFactory; + + this.eventArgsToEventInvokerMap = new Dictionary(12); + + this.eventArgsToEventInvokerMap[typeof(SessionStartEventArgs)] = this.OnSessionStart; + this.eventArgsToEventInvokerMap[typeof(SessionEndEventArgs)] = this.OnSessionEnd; + this.eventArgsToEventInvokerMap[typeof(SessionPauseEventArgs)] = this.OnSessionPause; + this.eventArgsToEventInvokerMap[typeof(SessionResumeEventArgs)] = this.OnSessionResume; + this.eventArgsToEventInvokerMap[typeof(TestCaseStartEventArgs)] = this.OnTestCaseStart; + this.eventArgsToEventInvokerMap[typeof(TestCaseEndEventArgs)] = this.OnTestCaseEnd; + this.eventArgsToEventInvokerMap[typeof(TestCasePauseEventArgs)] = this.OnTestCasePause; + this.eventArgsToEventInvokerMap[typeof(TestCaseResumeEventArgs)] = this.OnTestCaseResume; + this.eventArgsToEventInvokerMap[typeof(TestCaseResetEventArgs)] = this.OnTestCaseReset; + this.eventArgsToEventInvokerMap[typeof(TestCaseFailedEventArgs)] = this.OnTestCaseFailed; + this.eventArgsToEventInvokerMap[typeof(TestStepStartEventArgs)] = this.OnTestStepStart; + this.eventArgsToEventInvokerMap[typeof(TestStepEndEventArgs)] = this.OnTestStepEnd; + this.eventArgsToEventInvokerMap[typeof(DataRequestEventArgs)] = this.OnDataRequest; + this.eventArgsToEventInvokerMap[typeof(WrapperCustomNotificationEventArgs)] = this.OnCustomNotification; + } + + #endregion + + /// + /// Delegate for the event invoker methods (OnSessionStart, OnTestCaseResume, etc.) + /// + /// Contains the event data + /// List of invocation errors that occurred while raising the event + private delegate List EventInvoker(DataCollectionEventArgs e); + + #region Events + + #region Session events + + /// + /// Raised when a session is starting + /// + public override event EventHandler SessionStart; + + /// + /// Raised when a session is ending + /// + public override event EventHandler SessionEnd; + + /// + /// Raised when a session is paused + /// + public override event EventHandler SessionPause; + + /// + /// Raised when a session is resuming + /// + public override event EventHandler SessionResume; + #endregion + + #region Test case events + + /// + /// Raised when a test case is starting + /// + public override event EventHandler TestCaseStart; + + /// + /// Raised when a test case is ending + /// + public override event EventHandler TestCaseEnd; + + /// + /// Raised when a test case is pausing + /// + public override event EventHandler TestCasePause; + + /// + /// Raised when a test case is resuming + /// + public override event EventHandler TestCaseResume; + + /// + /// Raised when a test case is reset + /// + public override event EventHandler TestCaseReset; + + /// + /// Raised when a test case has failed. + /// + /// + /// This event is only raised for test types which send test failure notifications. + /// + public override event EventHandler TestCaseFailed; + + #endregion + + #region Test step events + + /// + /// Raised when a test step is starting + /// + public override event EventHandler TestStepStart; + + /// + /// Raised when a test step is ending + /// + public override event EventHandler TestStepEnd; + + #endregion + + #region Other events + + /// + /// Raised when intermediate data is requested. Can be a test case-specific event, or just + /// a session event. When sent with a test case-specific context, intermediate data for the + /// test case is requested, and when sent with only a session-specific context, + /// intermediate data for a session is requested. + /// + public override event EventHandler DataRequest; + + /// + /// Raised on a custom notification + /// + public override event EventHandler CustomNotification; + + #endregion + + #endregion + + #region Methods + + /// + /// Raises the event corresponding to the event arguments to all registered handlers + /// + /// Contains the event data + /// List of invocation errors that occurred while raising the event + internal List RaiseEvent(DataCollectionEventArgs e) + { + Debug.Assert(e != null, "'e' is null"); + + EventInvoker onEvent; + if (this.eventArgsToEventInvokerMap.TryGetValue(e.GetType(), out onEvent)) + { + return onEvent(e); + } + else if (e.GetType().IsSubclassOf(typeof(CustomNotificationEventArgs))) + { + return this.OnCustomNotification(e); + } + else + { + EqtTrace.Fail("InternalDataCollectionEvents.RaiseEvent: Unrecognized data collection event of type {0}.", e.GetType().FullName); + } + + return new List(); + } + + /// + /// Returns whether there is any event listener for test case start/end/failed events. + /// + /// + /// The value On Failure. + /// + /// + /// The . + /// + internal bool HasTestCaseStartOrEndOrFailedEventListener(bool valueOnFailure) + { + return HasEventListener(this.TestCaseStart, valueOnFailure) || HasEventListener(this.TestCaseEnd, valueOnFailure) || HasEventListener(this.TestCaseFailed, valueOnFailure); + } + + /// + /// Checks whether parameter event has any listener or not + /// + /// + /// The event To Check. + /// + /// + /// The value On Failure. + /// + /// + /// The . + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + private static bool HasEventListener(MulticastDelegate eventToCheck, bool valueOnFailure) + { + try + { + if (eventToCheck == null) + { + return false; + } + + Delegate[] listeners = eventToCheck.GetInvocationList(); + if (listeners == null || listeners.Count() == 0) + { + return false; + } + + return true; + } + catch (Exception ex) + { + EqtTrace.Error("Exception occured while checking whether event {0} has any listeners or not. {1}", eventToCheck, ex); + return valueOnFailure; + } + } + + /// + /// Raises the SessionStart event + /// + /// Contains the event data + /// List of invocation errors that occurred while raising the event + private List OnSessionStart(DataCollectionEventArgs e) + { + return this.On(this.SessionStart, e); + } + + /// + /// Raises the SessionEnd event + /// + /// Contains the event data + /// List of invocation errors that occurred while raising the event + private List OnSessionEnd(DataCollectionEventArgs e) + { + return this.On(this.SessionEnd, e); + } + + /// + /// Raises the SessionPause event + /// + /// Contains the event data + /// List of invocation errors that occurred while raising the event + private List OnSessionPause(DataCollectionEventArgs e) + { + return this.On(this.SessionPause, e); + } + + /// + /// Raises the SessionResume event + /// + /// Contains the event data + /// List of invocation errors that occurred while raising the event + private List OnSessionResume(DataCollectionEventArgs e) + { + return this.On(this.SessionResume, e); + } + + /// + /// Raises the TestCaseStart event + /// + /// Contains the event data + /// List of invocation errors that occurred while raising the event + private List OnTestCaseStart(DataCollectionEventArgs e) + { + return this.On(this.TestCaseStart, e); + } + + /// + /// Raises the TestCaseEnd event + /// + /// Contains the event data + /// List of invocation errors that occurred while raising the event + private List OnTestCaseEnd(DataCollectionEventArgs e) + { + return this.On(this.TestCaseEnd, e); + } + + /// + /// Raises the TestCasePause event + /// + /// Contains the event data + /// List of invocation errors that occurred while raising the event + private List OnTestCasePause(DataCollectionEventArgs e) + { + return this.On(this.TestCasePause, e); + } + + /// + /// Raises the TestCaseResume event + /// + /// Contains the event data + /// List of invocation errors that occurred while raising the event + private List OnTestCaseResume(DataCollectionEventArgs e) + { + return this.On(this.TestCaseResume, e); + } + + /// + /// Raises the TestCaseReset event + /// + /// Contains the event data + /// List of invocation errors that occurred while raising the event + private List OnTestCaseReset(DataCollectionEventArgs e) + { + return this.On(this.TestCaseReset, e); + } + + /// + /// Raises the TestCaseFailed event + /// + /// Contains the event data + /// List of invocation errors that occurred while raising the event + private List OnTestCaseFailed(DataCollectionEventArgs e) + { + return this.On(this.TestCaseFailed, e); + } + + /// + /// Raises the TestStepStart event + /// + /// Contains the event data + /// List of invocation errors that occurred while raising the event + private List OnTestStepStart(DataCollectionEventArgs e) + { + return this.On(this.TestStepStart, e); + } + + /// + /// Raises the TestStepEnd event + /// + /// Contains the event data + /// List of invocation errors that occurred while raising the event + private List OnTestStepEnd(DataCollectionEventArgs e) + { + return this.On(this.TestStepEnd, e); + } + + /// + /// Raises the DataRequest event + /// + /// Contains the event data + /// List of invocation errors that occurred while raising the event + private List OnDataRequest(DataCollectionEventArgs e) + { + return this.On(this.DataRequest, e); + } + + /// + /// Raises the CustomNotification event + /// + /// Contains the event data + /// List of invocation errors that occurred while raising the event + private List OnCustomNotification(DataCollectionEventArgs e) + { + // If this is a wrapper around the custom notification event arguments, + // then extract the actual event arguments. + var wrapperArgs = e as WrapperCustomNotificationEventArgs; + if (wrapperArgs != null) + { + // If the custom notification event arguments is null, + // the it can not be deseralized and we can not raise the event. + if (wrapperArgs.CustomNotificationEventArgs == null) + { + return new List(); + } + + e = wrapperArgs.CustomNotificationEventArgs; + } + + return this.On(this.CustomNotification, e); + } + + /// + /// Raises the event represented by the multicast delegate with the event data + /// + /// The multicast delegate representing the event to raise + /// Contains the event data + /// List of invocation errors that occurred while raising the event + private List On(MulticastDelegate multicastDel, DataCollectionEventArgs e) + { + MulticastDelegateInvoker invoker = new MulticastDelegateInvoker(multicastDel, e, this.userWorkItemFactory); + return invoker.Invoke(); + } + #endregion + + #region Types + + /// + /// A helper class for accumulating the errors that occur while invoking + /// the delegates in a . + /// + private sealed class MulticastDelegateInvoker + { + #region Fields + /// + /// The multicast delegate being invoked + /// + private readonly MulticastDelegate multicastDelegate; + + /// + /// The event args being passed as an argument to each delegate + /// + private readonly DataCollectionEventArgs eventArgs; + + /// + /// A factory for creating user work items + /// + private readonly SafeAbortableUserWorkItemFactory userWorkItemFactory; + + /// + /// The errors that have occurred while invoking the delegates + /// + private readonly List invocationErrors; + + /// + /// True if the time out value for invoking all of the delegates has been surpassed. + /// + private volatile bool timeout; + #endregion Fields + + #region Constructors + /// + /// Initializes a new instance of the class. + /// Constructor + /// + /// + /// The multicast delegate being invoked + /// + /// + /// The event args being passed as an argument to each delegate + /// + /// + /// A factory for creating user work items. + /// The user work items are used to invoke the delegates on worker threads. + /// + public MulticastDelegateInvoker( + MulticastDelegate multicastDelegate, + DataCollectionEventArgs eventArgs, + SafeAbortableUserWorkItemFactory userWorkItemFactory) + { + EqtAssert.ParameterNotNull(userWorkItemFactory, "userWorkItemFactory"); + + this.multicastDelegate = multicastDelegate; + this.eventArgs = eventArgs; + this.userWorkItemFactory = userWorkItemFactory; + this.invocationErrors = new List(); + } + #endregion Constructors + + #region Public Methods + + /// + /// Invokes the delegates in the multicast delegate in parallel, + /// passing the supplied event args as an argument. + /// + /// a list of the errors that occurred while invoking the delegates + public List Invoke() + { + if (this.multicastDelegate != null) + { + // A list of events on which we will wait after invoking the delegates asynchronously + var waitEvents = new List(); + + // A dictionary to keep track of the user work items and corresponding delegates + var userWorkItems = new Dictionary(); + + // Iterate through each delegate in the multicast delegate invocation list, and invoke + // each one individually, collecting any errors that occurred during the invocations + foreach (Delegate del in this.multicastDelegate.GetInvocationList()) + { + ISafeAbortableUserWorkItem newUserWorkItem; + ManualResetEvent waitEvent = this.InvokeDelegateAsync(del, out newUserWorkItem); + waitEvents.Add(waitEvent); + userWorkItems[newUserWorkItem] = del; + } + + if (!WaitHandle.WaitAll(waitEvents.ToArray(), ExecutionPluginManager.DataCollectionEventTimeout, false)) + { + this.HandleTimeout(userWorkItems); + } + } + + return this.invocationErrors; + } + #endregion Public Methods + + #region Private Methods + + /// + /// Invokes a single delegate in the multicast delegate on a thread pool thread. + /// + /// The delegate to be invoked + /// The user work item that is created to execute the delegate + /// A wait event that will be set when the invocation of the delegate is complete. + /// The caller is responsible for closing this handle. + private ManualResetEvent InvokeDelegateAsync( + Delegate del, + out ISafeAbortableUserWorkItem newUserWorkItem) + { + var completeEvent = new ManualResetEvent(false); + + var traceMessageFormat = + "DataCollectionEvents: {0} event " + + string.Format( + CultureInfo.CurrentCulture, + "'{0}' to '{1}'", + del, + del.Target == null ? "(static)" : del.Target); + + WaitCallback invokeDelegate = state => + { + EqtTrace.Verbose(traceMessageFormat, "Raising"); + del.DynamicInvoke(this, this.eventArgs); + EqtTrace.Verbose(traceMessageFormat, "Raised"); + }; + + ISafeAbortableUserWorkItem userWorkItem = this.userWorkItemFactory.Create(invokeDelegate); + + userWorkItem.Name = string.Format(CultureInfo.CurrentCulture, traceMessageFormat, "Raising"); + userWorkItem.Complete += (sender, e) => + { + try + { + if (userWorkItem.Exception != null) + { + this.HandleException(del, userWorkItem.Exception); + } + + completeEvent.Set(); + } + catch (ObjectDisposedException) + { + // Delegate already timed out + } + }; + + newUserWorkItem = userWorkItem; + + userWorkItem.Queue(); + + return completeEvent; + } + + /// + /// Handles the scenario in which not all delegates have completed prior to the time out interval expiring. + /// + /// the user work items used to execute the delegates on thread pool threads. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + private void HandleTimeout(Dictionary userWorkItems) + { + EqtTrace.Warning("DataCollectionEvents: Timed out raising event '{0}'", this.eventArgs == null ? "(null)" : this.eventArgs.GetType().Name); + + // Record an error for each invocation that timed out + foreach (var keyValuePair in userWorkItems.Where(pair => !pair.Key.IsComplete)) + { + this.AddTimeoutError(keyValuePair.Value); + } + + // Abort any threads that have not completed. This can be done on a background thread. + ThreadPool.QueueUserWorkItem(state => + { + foreach (var userWorkItem in userWorkItems.Keys) + { + if (!userWorkItem.IsComplete) + { + try + { + userWorkItem.Abort(); + } + catch (Exception ex) + { + EqtTrace.Warning( + "DataCollectionEvents: Exception while calling Abort on ISafeAbortableUserWorkItem '{0}': {1}", + userWorkItem.Name, + ex); + } + } + } + }); + } + + /// + /// Handles an exception thrown by the invocation of a delegate. + /// + /// the delegate that threw an exception + /// the exception that was thrown by the delegate + private void HandleException(Delegate del, Exception ex) + { + // Create an appropriate message, and unwrap the exception if it is a TargetInvocationException + string message; + var targetInvocationException = ex as TargetInvocationException; + if (targetInvocationException != null) + { + ex = targetInvocationException.InnerException; + message = "DataCollectionEvents: Target '{0}' event handler '{1}' invoked with event args '{2}' threw exception: {3}"; + } + else + { + message = "DataCollectionEvents: Exception occurred while invoking target '{0}' event handler '{1}' with event args '{2}': {3}"; + } + + EqtTrace.Warning( + message, + del.Target == null ? "(static)" : del.Target.GetType().FullName, + del.Method.Name, + this.eventArgs == null ? "(null)" : this.eventArgs.GetType().FullName, + ex); + + this.AddError(new DataCollectorInvocationError(del, this.eventArgs, ex)); + } + + /// + /// Adds an invocation error. Once the time out value for invoking all of the delegates + /// has been surpassed, this method becomes a no-op. + /// + /// the invocation error to be added to the list of errors + private void AddError(DataCollectorInvocationError error) + { + // After we've timed out, threads may still complete and attempt to report an invocation error. + // Since we don't our error list to be modified once we've returned to our caller, + // don't allow such errors to be added to the list. Rather, add an error recording each + // thread that timed out. + if (!this.timeout) + { + this.AddErrorImpl(error); + } + } + + /// + /// Adds an invocation error to indicate that the given delegate has timed out. + /// + /// the delegate that timed out + private void AddTimeoutError(Delegate del) + { + this.timeout = true; + + EqtTrace.Error( + "DataCollectionEvents: Timed out invoking target '{0}' event handler '{1}' with event args '{2}'", + del.Target == null ? "(static)" : del.Target.GetType().FullName, + del.Method.Name, + this.eventArgs == null ? "(null)" : this.eventArgs.GetType().FullName); + + var error = new DataCollectorInvocationError( + del, + this.eventArgs, + new TimeoutException( + string.Format( + CultureInfo.CurrentCulture, + Resource.Execution_DataCollectorEventTimeout, + del.Method.Name))); + + this.AddErrorImpl(error); + } + + /// + /// Implementation method for adding an invocation error + /// + /// the invocation error to be added to the list of errors + private void AddErrorImpl(DataCollectorInvocationError error) + { + lock (this.invocationErrors) + { + this.invocationErrors.Add(error); + } + } + + #endregion Private Methods + } + #endregion + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.DataCollection/TestPlatformDataCollectionLogger.cs b/src/Microsoft.TestPlatform.DataCollection/TestPlatformDataCollectionLogger.cs new file mode 100644 index 0000000000..7b0e4f6355 --- /dev/null +++ b/src/Microsoft.TestPlatform.DataCollection/TestPlatformDataCollectionLogger.cs @@ -0,0 +1,164 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.DataCollection.Implementations +{ + using System; + using System.Diagnostics; + using System.Globalization; + + using Microsoft.VisualStudio.TestPlatform.DataCollection.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + + using DataCollectionContext = Microsoft.VisualStudio.TestTools.Execution.DataCollectionContext; + using DataCollectionLogger = Microsoft.VisualStudio.TestTools.Execution.DataCollectionLogger; + using DataCollectorInformation = Microsoft.VisualStudio.TestTools.Execution.DataCollectorInformation; + + /// + /// Class used by data collectors to send messages to the client + /// + internal sealed class TestPlatformDataCollectionLogger : DataCollectionLogger + { + #region Private Fields + + private readonly DataCollectorInformation dataCollectorInformation; + private readonly IMessageSink sink; + + #endregion + + /// + /// Constructs a DataCollectionLogger + /// + /// + /// The underlying raw IMessageSink. Cannot be null. + /// + /// + /// The data Collector Information. + /// + internal TestPlatformDataCollectionLogger(IMessageSink sink, DataCollectorInformation dataCollectorInformation) + { + ValidateArg.NotNull(dataCollectorInformation, "dataCollectorInformation"); + ValidateArg.NotNull(sink, "sink"); + this.dataCollectorInformation = dataCollectorInformation; + this.sink = sink; + } + + #region Public Members + + /// + /// Logs an error message. + /// + /// The context in which the message is being sent. + /// The error text. Cannot be null. + public override void LogError(DataCollectionContext context, string text) + { + ValidateArg.NotNull(context, "context"); + ValidateArg.NotNull(text, "text"); + + if (EqtTrace.IsErrorEnabled) + { + EqtTrace.Error( + "Data collector '{0}' logged the following error: {1}", + this.dataCollectorInformation.TypeUri, + text); + } + + this.SendTextMessage(context, text, TestMessageLevel.Error); + } + + /// + /// Logs an error message for an exception. + /// + /// The context in which the message is being sent. + /// Text explaining the exception. Cannot be null. + /// The exception. Cannot be null. + public override void LogError(DataCollectionContext context, string text, Exception exception) + { + ValidateArg.NotNull(context, "context"); + ValidateArg.NotNull(text, "text"); + ValidateArg.NotNull(exception, "exception"); + + // Make sure the data collection context is not a derived data collection context. This + // is done to safeguard from 3rd parties creating their own data collection contexts. + if (context.GetType() != typeof(DataCollectionContext)) + { + throw new InvalidOperationException(Resource.WrongDataCollectionContextType); + } + + if (EqtTrace.IsErrorEnabled) + { + EqtTrace.Error( + "Data collector '{0}' logged the following error:" + Environment.NewLine + + "Description: {1}" + Environment.NewLine + + "Exception type: {2}" + Environment.NewLine + "Exception message: {3}" + + Environment.NewLine + "Exception stack trace: {4}", + this.dataCollectorInformation.TypeUri, + text, + exception.GetType(), + exception.Message, + exception.StackTrace); + } + + // Currently there is one type of DataCollectionMessage sent accross client for all message kind. + // If required new type can be created for different message type. + var message = string.Format( + CultureInfo.CurrentCulture, + Resource.ReportDataCollectorException, + exception.GetType(), + exception.Message, + text); + this.SendTextMessage(context, message, TestMessageLevel.Error); + } + + + /// + /// Logs a warning. + /// + /// The context in which the message is being sent. + /// The warning text. Cannot be null. + public override void LogWarning(DataCollectionContext context, string text) + { + ValidateArg.NotNull(context, "context"); + ValidateArg.NotNull(text, "text"); + EqtTrace.Warning( + "Data collector '{0}' logged the following warning: {1}", + this.dataCollectorInformation.TypeUri, + text); + + this.SendTextMessage(context, text, TestMessageLevel.Warning); + } + + #endregion + + #region Private Methods + + private void SendTextMessage(DataCollectionContext context, string text, TestMessageLevel level) + { + ValidateArg.NotNull(context, "context"); + ValidateArg.NotNull(text, "text"); + + Debug.Assert( + level >= TestMessageLevel.Informational && level <= TestMessageLevel.Error, + "Invalid level: " + level); + + // Make sure the data collection context is not a derived data collection context. This + // is done to safeguard from 3rd parties creating their own data collection contexts. + if (context.GetType() != typeof(DataCollectionContext)) + { + throw new InvalidOperationException(Resource.WrongDataCollectionContextType); + } + + DataCollectionMessageEventArgs args = new DataCollectionMessageEventArgs(level, text); + args.Uri = this.dataCollectorInformation.TypeUri; + args.FriendlyName = this.dataCollectorInformation.FriendlyName; + if (context.HasTestCase) + { + args.TestCaseId = context.TestExecId.Id; + } + + this.sink.SendMessage(args); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.DataCollection/TestPlatformDataCollectionSink.cs b/src/Microsoft.TestPlatform.DataCollection/TestPlatformDataCollectionSink.cs new file mode 100644 index 0000000000..c03c02f40f --- /dev/null +++ b/src/Microsoft.TestPlatform.DataCollection/TestPlatformDataCollectionSink.cs @@ -0,0 +1,101 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.DataCollection +{ + using System; + using System.ComponentModel; + using System.Diagnostics; + + using Microsoft.VisualStudio.TestPlatform.DataCollection.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + + using DataCollectionSink = Microsoft.VisualStudio.TestTools.Execution.DataCollectionSink; + using DataCollectorInformation = Microsoft.VisualStudio.TestTools.Execution.DataCollectorInformation; + using FileTransferInformation = Microsoft.VisualStudio.TestTools.Execution.FileTransferInformation; + using StreamTransferInformation = Microsoft.VisualStudio.TestTools.Execution.StreamTransferInformation; + + /// + /// The test platform data collection sink. + /// + internal sealed class TestPlatformDataCollectionSink : DataCollectionSink + { + /// + /// Initializes a new instance of the class. + /// Creates a data collector sink for data transfer. + /// + /// Message sink + /// + /// Data collector info. + /// + internal TestPlatformDataCollectionSink(IMessageSink messageSink, DataCollectorInformation dataCollectorInfo) + { + ValidateArg.NotNull(messageSink, "messageSink"); + ValidateArg.NotNull(dataCollectorInfo, "dataCollectorInfo"); + this.CollectorInfo = dataCollectorInfo; + this.MessageSink = messageSink; + } + + /// + /// Raised when asynchronous file transfer is complete. + /// + public override event AsyncCompletedEventHandler SendFileCompleted; +#pragma warning disable 0067 + /// + /// Raised when asynchronous stream transfer is complete. + /// + public override event AsyncCompletedEventHandler SendStreamCompleted; + + /// + /// Gets or sets message sink to transfer collection message. + /// + private IMessageSink MessageSink + { + get; set; + } + + /// + /// Gets or sets dataCollector with which this data sink is associated. + /// + private DataCollectorInformation CollectorInfo + { + get; set; + } + +#pragma warning restore 0067 + + /// + /// Sends a file asynchronously. + /// + /// Information about the file being transferred. + public override void SendFileAsync(FileTransferInformation fileTransferInformation) + { + ValidateArg.NotNull(fileTransferInformation, "fileTransferInformation"); + + var transferCompletedHandler = this.SendFileCompleted; + + Debug.Assert(System.IO.File.Exists(fileTransferInformation.Path), "DataCollector file '" + fileTransferInformation.Path + "' does not exist!"); + + var headerMsg = new FileDataHeaderMessage( + fileTransferInformation.Context, + fileTransferInformation.ClientFileName, + fileTransferInformation.Description, + fileTransferInformation.DeleteFile, + fileTransferInformation.UserToken, + transferCompletedHandler, + this.CollectorInfo.TypeUri, + this.CollectorInfo.FriendlyName); + + this.MessageSink.SendMessage(headerMsg); + } + + + /// + /// Sends a stream asynchronously. + /// + /// Information about stream being transferred. + public override void SendStreamAsync(StreamTransferInformation streamTransferInformation) + { + throw new NotSupportedException(Resource.SupportedTransferTypeIsFileTransfer); + } + } +} diff --git a/src/Microsoft.TestPlatform.DataCollection/TestPlatformDataCollectorDiscovery.cs b/src/Microsoft.TestPlatform.DataCollection/TestPlatformDataCollectorDiscovery.cs new file mode 100644 index 0000000000..d307de9f4d --- /dev/null +++ b/src/Microsoft.TestPlatform.DataCollection/TestPlatformDataCollectorDiscovery.cs @@ -0,0 +1,315 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.DataCollection.Implementations +{ + using System; + using System.Collections.Generic; + using System.Collections.Specialized; + using System.Configuration; + using System.Diagnostics; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Reflection; + using System.Threading.Tasks; + using System.Xml; + + using Microsoft.VisualStudio.TestPlatform.Common; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.Utilities; + + using DataCollectionEnvironmentContext = Microsoft.VisualStudio.TestTools.Execution.DataCollectionEnvironmentContext; + using DataCollector = Microsoft.VisualStudio.TestTools.Execution.DataCollector; + using DataCollectorInformation = Microsoft.VisualStudio.TestTools.Execution.DataCollectorInformation; + + /// + /// Discovers data collectors in a directory. + /// + internal class DataCollectorDiscoveryHelper + { + #region Fields + + /// + /// The data collectors directory name. + /// + public const string DataCollectorsDirectoryName = @"PrivateAssemblies\DataCollectors"; + + private static readonly string CurrentProcessLocation = Process.GetCurrentProcess().MainModule.FileName; + + private static readonly bool IsPortable = ClientUtilities.CheckIfTestProcessIsRunningInXcopyableMode(); + + #endregion + + /// + /// Gets the directory which contains the data collectors. + /// + public static string DataCollectorsDirectory + { + get + { + if (IsPortable) + { + return Path.GetDirectoryName(Path.GetFullPath(CurrentProcessLocation)); + } + else + { + return GetDataCollectorPluginDirectory(); + } + } + } + + /// + /// Gets the configuration file for the assembly. + /// + /// The assembly the configuration file is for. + /// The configuration file. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Shouldn't block loading of subsequent plugins.")] + internal static XmlDocument GetConfigurationForAssembly(Assembly assembly) + { + Debug.Assert(assembly != null, "null assembly"); + + // E.g. "C:\Program Files\Microsoft Visual Studio 14.0\Common7\IDE\PrivateAssemblies\DataCollectors" + var assemblyFolder = Path.GetDirectoryName(assembly.Location); + + // E.g. "Microsoft.VisualStudio.TraceCollector.dll.config" + var configFileName = Path.GetFileName(assembly.Location) + ".config"; + + // Search for the config file. + // Note that this config file is localized for some collectors (like Intellitrace), and is present in language specific sub-directory. + // The config file is unlocalized for some collectors, and is present in parent directory itself. + // So we need to look at all places. + // We go from more language specific to less language specific + + // VSTLM's UI is controlled by the OS's UI Language, which is available as current thread's UI Culture. We use it first. + var subFolders = GetOsNeutralCultureNames(); + + // On non-enu OS, if an enu product is installed, then we will not find the config in osUILanguages in which case + // we will fallback to assembly's default language. The default language can be null as well. + // + // TODO: Currently we are doing a partial fix by falling back to neutral language which might not work on jpn OS with german VS. + var assemblyUILanguage = GetAssemblyCultureLanguage(assembly); + if (assemblyUILanguage != null && !subFolders.Contains(assemblyUILanguage)) + { + subFolders.Add(assemblyUILanguage); + } + + // To search in current folder + subFolders.Add(string.Empty); + + var configFilePath = string.Empty; + int iFolder = 0; + for (; iFolder < subFolders.Count; iFolder++) + { + configFilePath = + Path.Combine( + Path.Combine(assemblyFolder, subFolders[iFolder]), + configFileName); + + if (File.Exists(configFilePath)) + { + break; + } + } + + // We couldn't find a config file for this assembly anywhere. Return null + if (iFolder >= subFolders.Count) + { + EqtTrace.Warning("DataCollectorDiscovery: No configuration file found for data collector collector assembly {0} in all {1} locations", assembly.FullName, subFolders.Count); + return null; + } + else + { + EqtTrace.Verbose( + "DataCollectorDiscovery: using configuration file {0}.", configFilePath); + } + + // Load the config file located + try + { + var configFile = new XmlDocument(); + using (var xmlReader = new XmlTextReader(configFilePath)) + { + xmlReader.DtdProcessing = DtdProcessing.Prohibit; + xmlReader.XmlResolver = null; + configFile.Load(xmlReader); + } + + return configFile; + } + catch (Exception e) + { + EqtTrace.Error("DataCollectorDiscovery: Error occurred while loading the configuration file {0}. Error: {1}", configFilePath, e.ToString()); + return null; + } + } + + #region Private Methods + + /// + /// The get data collector plugin directory. + /// + /// + /// The . + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Need to ignore failures to read the registry settings")] + private static string GetDataCollectorPluginDirectory() + { + // First option: Look into app settings. If specified and valid use it. + var settings = ConfigurationManager.AppSettings; + if (settings != null && settings.HasKeys()) + { + string directoryFromConfig = settings.Get(TestPlatformDefaults.PluginDirectorySettingsKeyName); + if (!string.IsNullOrEmpty(directoryFromConfig)) + { + directoryFromConfig = Environment.ExpandEnvironmentVariables(directoryFromConfig); + directoryFromConfig = Path.GetFullPath(directoryFromConfig); + if (Directory.Exists(directoryFromConfig)) + { + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose("TestPlatformDataCollectorDiscovery.GetDataCollectorPluginDirectory: Using config specified plugin directory '{0}'.", directoryFromConfig); + } + return directoryFromConfig; + } + else + { + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose("TestPlatformDataCollectorDiscovery.GetDataCollectorPluginDirectory: Config specified plugin directory '{0}' not found. Value ignored.", directoryFromConfig); + } + } + } + } + + // Second option: \PrivateAssemblies\DataCollectors + var currentProcessDirectory = Path.GetDirectoryName(Path.GetFullPath(CurrentProcessLocation)); + var directoryFromProcess = Path.Combine(currentProcessDirectory, DataCollectorDiscoveryHelper.DataCollectorsDirectoryName); + + if (Directory.Exists(directoryFromProcess)) + { + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose("TestPlatformDataCollectorDiscovery.GetDataCollectorPluginDirectory: Using plugin directory '{0}' relative to main module assembly location.", directoryFromProcess); + } + return directoryFromProcess; + } + else + { + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose("TestPlatformDataCollectorDiscovery.GetDataCollectorPluginDirectory: Can not find plugin directory realtive to main module assembly location."); + } + } + + // Third option: Look for VS Installation directory from registry key. If found and valid use that. + var path = GetInstallLocationFromRegistry(ClientUtilities.GetVSInstallPath()); + + if (!string.IsNullOrEmpty(path)) + { + return path; + } + + return string.Empty; + } + + /// + /// Get the OS UI Language's neutral culture names. + /// Typically, there is only one: + /// For the japanese (japan) ja-jp OS, return ja. + /// For english (us) en-us , return en. + /// But sometimes, there can be many: + /// For chinese-traditional (taiwan) zh-TW, this would return zh-CHT, zh-Hant, zh + /// + /// The OS Neutral Culture Names + private static List GetOsNeutralCultureNames() + { + var cultNames = new List(); + + // CurrentUICulture represents OS's UI language in the specific culture form. + // Parent represents the neutral culture form + var c = System.Globalization.CultureInfo.CurrentUICulture.Parent; + while (!string.IsNullOrEmpty(c.Name) && c.IsNeutralCulture) + { + cultNames.Add(c.Name); + c = c.Parent; + } + + Debug.Assert(cultNames.Count >= 1, "There is always a neutral culture"); + return cultNames; + } + + /// + /// Gets the language of the assembly + /// + /// The Assembly + /// The Assembly Culture Language + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + private static string GetAssemblyCultureLanguage(Assembly assembly) + { + try + { + System.Resources.NeutralResourcesLanguageAttribute neutralLangAttr = Attribute.GetCustomAttribute(assembly, typeof(System.Resources.NeutralResourcesLanguageAttribute)) as System.Resources.NeutralResourcesLanguageAttribute; /* Typically the value is en-US */ + if (neutralLangAttr != null) + { + return (new System.Globalization.CultureInfo(neutralLangAttr.CultureName)).TwoLetterISOLanguageName; + } + + return null; + } + catch (Exception ex) + { + if (EqtTrace.IsErrorEnabled) + { + EqtTrace.Error("TestPlatformDataCollectorDiscovery: Error occurred while finding the fallback language for assembly {0}. Error: {1}", assembly.FullName, ex); + } + + return null; + } + } + + /// + /// The get install location from registry. + /// + /// + /// The sku install location. + /// + /// + /// The . + /// + private static string GetInstallLocationFromRegistry(string skuInstallLocation) + { + try + { + var directoryFromRegistry = Path.Combine(skuInstallLocation, DataCollectorDiscoveryHelper.DataCollectorsDirectoryName); + if (Directory.Exists(directoryFromRegistry)) + { + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose("TestPlatformDataCollectorDiscovery.GetDataCollectorPluginDirectory: Using plugin directory '{0}' relative to directory location in registry.", directoryFromRegistry); + } + return directoryFromRegistry; + } + else + { + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose("TestPlatformDataCollectorDiscovery.GetDataCollectorPluginDirectory: Can not find plugin directory realtive to directory location in registry."); + } + } + } + catch (Exception ex) + { + if (EqtTrace.IsErrorEnabled) + { + EqtTrace.Error("TestPlatformDataCollectorDiscovery.GetDataCollectorPluginDirectory: Error finding plugin directory from registry. Error details:{0}.", ex.Message); + } + + // ignore the exception. + } + + return null; + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.DataCollection/TestPlatformDataCollectorInfo.cs b/src/Microsoft.TestPlatform.DataCollection/TestPlatformDataCollectorInfo.cs new file mode 100644 index 0000000000..c41f1365ca --- /dev/null +++ b/src/Microsoft.TestPlatform.DataCollection/TestPlatformDataCollectorInfo.cs @@ -0,0 +1,183 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.DataCollection +{ + using System; + using System.Collections.Generic; + using System.Xml; + + using Microsoft.VisualStudio.TestPlatform.DataCollection.Implementations; + + using Microsoft.VisualStudio.TestPlatform.DataCollection.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestTools.Common; + + using DataCollectionEnvironmentContext = Microsoft.VisualStudio.TestTools.Execution.DataCollectionEnvironmentContext; + using DataCollector = Microsoft.VisualStudio.TestTools.Execution.DataCollector; + using DataCollectorInformation = Microsoft.VisualStudio.TestTools.Execution.DataCollectorInformation; + using ITestExecutionEnvironmentSpecifier = Microsoft.VisualStudio.TestTools.Execution.ITestExecutionEnvironmentSpecifier; + + /// + /// The test platform data collector info. + /// + public class TestPlatformDataCollectorInfo + { + #region Constructor + + /// + /// Initializes a new instance of the class by creating a message sink, data sink, and logger for the + /// data collector + /// + /// + /// The data collector + /// + /// + /// XML element containing configuration information for the collector, or null if + /// there is no configuration + /// + /// + /// the message sink to send messages to + /// + /// + /// Information about the data collector. + /// + /// + /// A factory for creating user work items + /// + internal TestPlatformDataCollectorInfo( + DataCollector dataCollector, + XmlElement configurationElement, + IMessageSink messageSink, + DataCollectorInformation dataCollectorInformation, + SafeAbortableUserWorkItemFactory userWorkItemFactory) + { + ValidateArg.NotNull(dataCollector, "dataCollector"); + ValidateArg.NotNull(messageSink, "messageSink"); + ValidateArg.NotNull(dataCollectorInformation, "dataCollectorInformation"); + ValidateArg.NotNull(userWorkItemFactory, "userWorkItemFactory"); + + this.DataCollectorInformation = dataCollectorInformation; + this.DataCollector = dataCollector; + this.ConfigurationElement = configurationElement; + this.Events = new TestPlatformDataCollectionEvents(userWorkItemFactory); + this.Logger = new TestPlatformDataCollectionLogger(messageSink, dataCollectorInformation); + this.DataSink = new TestPlatformDataCollectionSink(messageSink, dataCollectorInformation); + } + + #endregion + + /// + /// Gets the data collector + /// + internal DataCollector DataCollector + { + get; + private set; + } + + /// + /// Gets the configuration XML element + /// + internal XmlElement ConfigurationElement + { + get; + private set; + } + + /// + /// Gets the events object on which the collector registers for events + /// + internal TestPlatformDataCollectionEvents Events + { + get; + private set; + } + + /// + /// Gets or sets environment variables supplied by the data collector. + /// These are available after the collector has been initialized. + /// + internal IEnumerable> TestExecutionEnvironmentVariables + { + get; + set; + } + + /// + /// Gets the data sink + /// + internal TestPlatformDataCollectionSink DataSink + { + get; + private set; + } + + /// + /// Gets the logger + /// + internal TestPlatformDataCollectionLogger Logger + { + get; + private set; + } + + /// + /// Gets information about the data collector. + /// + internal DataCollectorInformation DataCollectorInformation + { + get; + private set; + } + + #region Public methods + + /// + /// Initializes the data collector synchronously + /// + /// Context provided to the data collector + public void InitializeDataCollector(DataCollectionEnvironmentContext dataCollectionEnvironmentContext) + { + this.DataCollector.Initialize(this.ConfigurationElement, this.Events, this.DataSink, this.Logger, dataCollectionEnvironmentContext); + } + + /// + /// The get test execution environment variables sync. + /// + public void GetTestExecutionEnvironmentVariables() + { + var testExecutionEnvironmentSpecifier = this.DataCollector as ITestExecutionEnvironmentSpecifier; + if (testExecutionEnvironmentSpecifier != null) + { + // Get the environment variables the data collector wants set in the test execution environment + this.TestExecutionEnvironmentVariables = testExecutionEnvironmentSpecifier.GetTestExecutionEnvironmentVariables(); + } + } + + /// + /// Disposes the data collector + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + public void DisposeDataCollector() + { + try + { + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose("DataCollectorInfo.DisposeDataCollector: calling Dispose() on {0}", this.DataCollector.GetType()); + } + + this.DataCollector.Dispose(); + } + catch (Exception ex) + { + if (EqtTrace.IsErrorEnabled) + { + EqtTrace.Error("DataCollectorInfo.DisposeDataCollector: exception while calling Dispose() on {0}: " + ex, this.DataCollector.GetType()); + } + } + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.DataCollection/project.json b/src/Microsoft.TestPlatform.DataCollection/project.json new file mode 100644 index 0000000000..4d02fcfd6d --- /dev/null +++ b/src/Microsoft.TestPlatform.DataCollection/project.json @@ -0,0 +1,28 @@ +{ + "version": "15.0.0-*", + "buildOptions": { + "delaySign": true, + "keyFile": "../../scripts/key.snk", + "warningsAsErrors": true + //"copyToOutput": "Microsoft.TestPlatform.DataCollection.Legacy" + }, + + "runtimes": { + "win7-x64": {}, + "win7-x86": {} + }, + + "dependencies": { + "datacollector": "15.0.0-*", + "datacollector.x86": "15.0.0-*", + "Microsoft.TestPlatform.Common": "15.0.0-*", + "Microsoft.TestPlatform.ObjectModel": "15.0.0-*", + "Microsoft.TestPlatform.Utilities": "15.0.0-*", + "Microsoft.VisualStudio.QualityTools.ExecutionCommon": "1.0.0" + }, + + "frameworks": { + "net461": { + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/project.json b/src/Microsoft.TestPlatform.ObjectModel/project.json index 622c76a107..adcc122f6d 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/project.json +++ b/src/Microsoft.TestPlatform.ObjectModel/project.json @@ -1,7 +1,7 @@ { "version": "15.0.0-*", "buildOptions": { - //"outputName": "Microsoft.VisualStudio.TestPlatform.ObjectModel", + "outputName": "Microsoft.VisualStudio.TestPlatform.ObjectModel", "delaySign": true, "keyFile": "../../scripts/key.snk", "warningsAsErrors": true diff --git a/src/Microsoft.TestPlatform.Utilities/ClientUtilities.cs b/src/Microsoft.TestPlatform.Utilities/ClientUtilities.cs index d8681cdbf0..481aaf4511 100644 --- a/src/Microsoft.TestPlatform.Utilities/ClientUtilities.cs +++ b/src/Microsoft.TestPlatform.Utilities/ClientUtilities.cs @@ -3,10 +3,14 @@ namespace Microsoft.VisualStudio.TestPlatform.Utilities { using System; + using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Xml; + using Microsoft.Win32; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + /// /// Utilities used by the client to understand the environment of the current run. /// @@ -15,6 +19,52 @@ public static class ClientUtilities private const string TestSettingsFileXPath = "RunSettings/MSTest/SettingsFile"; private const string ResultsDirectoryXPath = "RunSettings/RunConfiguration/ResultsDirectory"; + /// + /// Manifest file name to check if vstest.console.exe is running in portable mode + /// + private const string PortableVsTestManifestFilename = "Portable.VsTest.Manifest"; + + /// + /// Registry Subkey for VisualStudio Root under HKLM Registry node for 32 bit process. + /// + private const string TaRootRegKey32 = @"Software\Microsoft\VisualStudio\15.0\Setup\VSTF\TestAgent"; + + /// + /// Registry Subkey for VisualStudio Root under HKLM Registry node for 64 bit process. + /// + private const string VsRootRegKey64 = @"Software\Wow6432Node\Microsoft\VisualStudio\15.0"; + + + /// + /// Registry Subkey for VisualStudio Root under HKLM Registry node for 32 bit process. + /// + private const string VsRootRegKey32 = @"Software\Microsoft\VisualStudio\15.0"; + + /// + /// Registry Subkey for VisualStudio Root under HKLM Registry node for 64 bit process. + /// + private const string TaRootRegKey64 = @"Software\Wow6432Node\Microsoft\VisualStudio\15.0\Setup\VSTF\TestAgent"; + + /// + /// Registry key name for the TestAgent Install Directory. + /// + private const string ProductDirRegistryKeyName = "ProductDir"; + + /// + /// Environment variable which specifies the registry root + /// (This key will be primarily used in rascalPro) + /// + private const string RegistryRootEnvironmentVariableName = @"VisualStudio_RootRegistryKey"; + + /// + /// Registry key name for the visual studio Install Directory. + /// + private const string InstallDirRegistryKeyName = "InstallDir"; + + /// + /// Converts the relative paths in a runsetting file to absolute ones. + /// + /// /// Converts the relative paths in a runsetting file to absolue ones. /// @@ -33,7 +83,7 @@ public static void FixRelativePathsInRunSettings(XmlDocument xmlDocument, string throw new ArgumentNullException("path"); } - string root = Path.GetDirectoryName(path); + var root = Path.GetDirectoryName(path); var testRunSettingsNode = xmlDocument.SelectSingleNode(TestSettingsFileXPath); if (testRunSettingsNode != null) { @@ -45,11 +95,192 @@ public static void FixRelativePathsInRunSettings(XmlDocument xmlDocument, string { FixNodeFilePath(resultsDirectoryNode, root); } + } + + /// + /// Check if Vstest.console is running in xcopyable mode + /// + /// true if vstest is running in xcopyable mode + public static bool CheckIfTestProcessIsRunningInXcopyableMode() + { + return CheckIfTestProcessIsRunningInXcopyableMode(Process.GetCurrentProcess().MainModule.FileName); + } + + /// + /// Check if Vstest.console is running in xcopyable mode given exe path + /// + /// + /// The exe Name. + /// + /// + /// true if vstest is running in xcopyable mode + /// + public static bool CheckIfTestProcessIsRunningInXcopyableMode(string exeName) + { + // Get the directory of the exe + var exeDir = Path.GetDirectoryName(exeName); + return File.Exists(Path.Combine(exeDir, PortableVsTestManifestFilename)); + } + + /// + /// Gets the Visual studio install location. + /// + /// VS install path + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + public static string GetVSInstallPath() + { + // Try custom Vs install path if available. This is done for rascal pro. + var vsInstallPathFromCustomRoot = GetVsInstallPathFromCustomRoot(); + if (!string.IsNullOrEmpty(vsInstallPathFromCustomRoot)) + { + return vsInstallPathFromCustomRoot; + } + + string path = null; + var subKey = VsRootRegKey32; + // Changing the key appropriately for 64 bit process. + if (Is64BitProcess()) + { + subKey = VsRootRegKey64; + } + using (var hklmKey = Registry.LocalMachine) + { + try + { + RegistryKey visualstudioSubKey = hklmKey.OpenSubKey(subKey); + var registryValue = visualstudioSubKey.GetValue(InstallDirRegistryKeyName).ToString(); + if (Directory.Exists(registryValue)) + { + path = registryValue; + } + } + catch (Exception) + { + //ignore the exception. + } + } + + return path; + } + + /// + /// Gets the TestAgent install location. + /// + /// TestAgent install path + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + public static string GetTestAgentInstallPath() + { + string path = null; + var subKey = TaRootRegKey32; + + // Changing the key appropriately for 64 bit process. + if (Is64BitProcess()) + { + subKey = TaRootRegKey64; + } + using (var hklmKey = Registry.LocalMachine) + { + try + { + using (RegistryKey visualstudioSubKey = hklmKey.OpenSubKey(subKey)) + { + string registryValue = visualstudioSubKey.GetValue(ProductDirRegistryKeyName).ToString(); + var installLocation = Path.Combine(registryValue, @"Common7\IDE"); + if (Directory.Exists(installLocation)) + { + path = installLocation; + } + } + } + catch (Exception ex) + { + // ignore the exception. + EqtTrace.Verbose("Got following exception while searching for testAgent key {0}", ex.Message); + } + } + + return path; + } + + + + /// + /// Return true if the process executing is 64 bit process. + /// In 32 bit processes, IntPtr size is 4 and not 8 + /// + /// The bool + private static bool Is64BitProcess() + { + return IntPtr.Size == 8; + } + + + /// + /// Get Vs install path from custom root + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Need to ignore failures to read the registry settings")] + private static string GetVsInstallPathFromCustomRoot() + { + try + { + var registryKeyWhichContainsVsInstallPath = GetEnvironmentVariable(RegistryRootEnvironmentVariableName); + + if (string.IsNullOrEmpty(registryKeyWhichContainsVsInstallPath)) + { + return null; + } + + // Rascal always uses currentUser hive + // todo: OpenremoteBaseKey method doesn't exist in dotnet core. +#if NET46 + using (RegistryKey hiveKey = RegistryKey.OpenRemoteBaseKey(RegistryHive.CurrentUser, string.Empty)) + { + var visualstudioSubKey = hiveKey.OpenSubKey(registryKeyWhichContainsVsInstallPath); + var registryValue = visualstudioSubKey.GetValue("InstallDir").ToString(); + if (Directory.Exists(registryValue)) + { + return registryValue; + } + } +#endif + } + catch (Exception) + { + // ignore the exception. + } + + return null; + } + + /// + /// Returns the value of the environment variable + /// + /// + /// The key Name. + /// + /// + /// The . + /// + private static string GetEnvironmentVariable(string keyName) + { + var value = Environment.GetEnvironmentVariable(keyName); + if (!string.IsNullOrEmpty(value)) + { + return value; + } + + using (var key = Registry.CurrentUser.OpenSubKey("Environment", false)) + { + return key?.GetValue(keyName) as string; + } } private static void FixNodeFilePath(XmlNode node, string root) { - string fileName = node.InnerXml; + var fileName = node.InnerXml; if (!string.IsNullOrEmpty(fileName) && !Path.IsPathRooted(fileName)) @@ -60,6 +291,6 @@ private static void FixNodeFilePath(XmlNode node, string root) node.InnerXml = fileName; } - } + } } -} +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Utilities/project.json b/src/Microsoft.TestPlatform.Utilities/project.json index d8e79b35c2..3e67f8ae42 100644 --- a/src/Microsoft.TestPlatform.Utilities/project.json +++ b/src/Microsoft.TestPlatform.Utilities/project.json @@ -9,7 +9,9 @@ "dependencies": { "Microsoft.TestPlatform.CoreUtilities": "15.0.0-*", - "Microsoft.TestPlatform.ObjectModel": "15.0.0-*" + "Microsoft.TestPlatform.ObjectModel": "15.0.0-*", + "Microsoft.Win32.Registry": "4.0.0", + "System.Diagnostics.Process": "4.1.0" }, "runtimes": { diff --git a/src/datacollector.x86/DataCollectionCoordinator.cs b/src/datacollector.x86/DataCollectionCoordinator.cs index c1d2b70a8c..ce5e9a8856 100644 --- a/src/datacollector.x86/DataCollectionCoordinator.cs +++ b/src/datacollector.x86/DataCollectionCoordinator.cs @@ -7,11 +7,11 @@ namespace Microsoft.VisualStudio.TestPlatform.DataCollector using System.Collections.ObjectModel; using System.Threading.Tasks; - using Microsoft.VisualStudio.TestPlatform.DataCollector.Interfaces; using Microsoft.VisualStudio.TestPlatform.Common.DataCollection; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.Common; using Microsoft.VisualStudio.TestPlatform.Common.Utilities; + using Microsoft.VisualStudio.TestPlatform.Common.DataCollection.Interfaces; /// /// Coordinates the Data Collection for V1 and V2 DataCollectors @@ -29,7 +29,7 @@ public DataCollectionCoordinator() : this(default(IDataCollectionManager[])) } /// - /// Constructor with Dependency injection. Used for unit testing. + /// Constructor with dependency injection. Used for unit testing. /// /// Array of IDataCollectionManagers for handling various versions of DataCollectors (Legacy,V2) internal DataCollectionCoordinator(IDataCollectionManager[] dataCollectionManagers) @@ -154,11 +154,11 @@ private void Dispose(bool disposing) private Dictionary LoadDataCollectors(RunSettings runSettings) { var envVars = new Dictionary(); - var tasks = new List>>(this.dataCollectionManagers.Length); + var tasks = new List>>(this.dataCollectionManagers.Length); foreach (var dataCollectionManager in this.dataCollectionManagers) { - tasks.Add(Task>.Factory.StartNew(() => dataCollectionManager.LoadDataCollectors(runSettings))); + tasks.Add(Task>.Factory.StartNew(() => dataCollectionManager.LoadDataCollectors(runSettings))); } Task.WaitAll(tasks.ToArray()); diff --git a/src/datacollector.x86/Friends.cs b/src/datacollector.x86/Friends.cs deleted file mode 100644 index 9a3b255746..0000000000 --- a/src/datacollector.x86/Friends.cs +++ /dev/null @@ -1,7 +0,0 @@ -using System.Runtime.CompilerServices; - -#region Test Assemblies - -[assembly: InternalsVisibleTo("datacollector.x86.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] - -#endregion \ No newline at end of file diff --git a/src/datacollector.x86/project.json b/src/datacollector.x86/project.json index ded8cc6110..4ebae5a112 100644 --- a/src/datacollector.x86/project.json +++ b/src/datacollector.x86/project.json @@ -12,7 +12,9 @@ "dependencies": { "Microsoft.TestPlatform.CommunicationUtilities": "15.0.0-*", "Microsoft.TestPlatform.ObjectModel": "15.0.0-*", - "Microsoft.TestPlatform.CrossPlatEngine": "15.0.0-*" + "Microsoft.TestPlatform.CrossPlatEngine": "15.0.0-*", + "Microsoft.TestPlatform.Common": "15.0.0-*" + }, "runtimes": { diff --git a/src/datacollector/Friends.cs b/src/datacollector/Friends.cs new file mode 100644 index 0000000000..d8779df571 --- /dev/null +++ b/src/datacollector/Friends.cs @@ -0,0 +1,7 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Runtime.CompilerServices; + +#region Test Assemblies +[assembly: InternalsVisibleTo("datacollector.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] +#endregion \ No newline at end of file diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/DataCollection/DataCollectorsSettingsProviderTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/DataCollection/DataCollectorsSettingsProviderTests.cs new file mode 100644 index 0000000000..60203a16fd --- /dev/null +++ b/test/Microsoft.TestPlatform.Common.UnitTests/DataCollection/DataCollectorsSettingsProviderTests.cs @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.Common.UnitTests.DataCollection +{ + using System.IO; + using System.Xml; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Microsoft.VisualStudio.TestPlatform.Common.DataCollection; + + [TestClass] + public class DataCollectorsSettingsProviderTests + { + private string xmlSettings = + "\r\n "; + + [TestMethod] + public void LoadShouldLoadDataCollectorRunSettingsFromXML() + { + var dcSettingsProvider = new DataCollectorsSettingsProvider(); + var reader = XmlReader.Create(new StringReader(this.xmlSettings)); + dcSettingsProvider.Load(reader); + Assert.IsNotNull(dcSettingsProvider.Settings); + Assert.AreEqual(dcSettingsProvider.Settings.Name, "DataCollectionRunSettings"); + Assert.AreEqual(dcSettingsProvider.Settings.DataCollectorSettingsList.Count, 1); + Assert.AreEqual(dcSettingsProvider.Settings.DataCollectorSettingsList[0].AssemblyQualifiedName, "MyDataCollector.CustomDataCollector, MyDataCollector, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"); + Assert.AreEqual(dcSettingsProvider.Settings.DataCollectorSettingsList[0].FriendlyName, "Custom DataCollector"); + Assert.AreEqual(dcSettingsProvider.Settings.DataCollectorSettingsList[0].Uri, "datacollector://MyCompany/MyDataCollector/1.0"); + } + + [TestMethod] + public void LoadShouldThrowExceptionIfXmlWithoutDataCollectionRunSettingsIsPassed() + { + Assert.ThrowsException( + () => + { + var dcSettingsProvider = new DataCollectorsSettingsProvider(); + var reader = XmlReader.Create(new StringReader(")")); + dcSettingsProvider.Load(reader); + }); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/DataCollection/RunConfigurationSettingsProviderTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/DataCollection/RunConfigurationSettingsProviderTests.cs new file mode 100644 index 0000000000..3b1d7861f7 --- /dev/null +++ b/test/Microsoft.TestPlatform.Common.UnitTests/DataCollection/RunConfigurationSettingsProviderTests.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace TestPlatform.Common.UnitTests.DataCollection +{ + public class RunConfigurationSettingsProviderTests + { + } +} diff --git a/test/Microsoft.TestPlatform.DataCollection.UnitTests/CollectorRequestedEnvironmentVariableTests.cs b/test/Microsoft.TestPlatform.DataCollection.UnitTests/CollectorRequestedEnvironmentVariableTests.cs new file mode 100644 index 0000000000..11eeb98c6e --- /dev/null +++ b/test/Microsoft.TestPlatform.DataCollection.UnitTests/CollectorRequestedEnvironmentVariableTests.cs @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.DataCollection.UnitTests.Implementations +{ + using System.Collections.Generic; + + using Microsoft.VisualStudio.TestPlatform.DataCollection; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class CollectorRequestedEnvironmentVariableTests + { + [TestMethod] + public void AddRequestingDataCollectorShouldAddDataCollectorName() + { + var friendlyName = "DataCollectorFriendlyName"; + var kv = new KeyValuePair("key", "value"); + + var collectorRequestedEnvironmentVariable = new CollectorRequestedEnvironmentVariable(kv, friendlyName); + + Assert.AreEqual("key", collectorRequestedEnvironmentVariable.Name); + Assert.AreEqual("value", collectorRequestedEnvironmentVariable.Value); + Assert.AreEqual("key", collectorRequestedEnvironmentVariable.Name); + Assert.AreEqual("value", collectorRequestedEnvironmentVariable.Value); + Assert.AreEqual(friendlyName, collectorRequestedEnvironmentVariable.FirstDataCollectorThatRequested); + } + + [TestMethod] + public void FirstDataCollectorThatRequestedShouldReturnTheNameOfFirstRequestingDataCollector() + { + var friendlyName = "DataCollectorFriendlyName"; + var kv = new KeyValuePair("key", "value"); + + var collectorRequestedEnvironmentVariable = new CollectorRequestedEnvironmentVariable(kv, friendlyName); + collectorRequestedEnvironmentVariable.AddRequestingDataCollector("DataCollectorFriendlyName1"); + + Assert.AreEqual("key", collectorRequestedEnvironmentVariable.Name); + Assert.AreEqual("value", collectorRequestedEnvironmentVariable.Value); + Assert.AreEqual("key", collectorRequestedEnvironmentVariable.Name); + Assert.AreEqual("value", collectorRequestedEnvironmentVariable.Value); + Assert.AreEqual(friendlyName, collectorRequestedEnvironmentVariable.FirstDataCollectorThatRequested); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.TestPlatform.DataCollection.UnitTests/DataCollectionPluginManagerTests.cs b/test/Microsoft.TestPlatform.DataCollection.UnitTests/DataCollectionPluginManagerTests.cs new file mode 100644 index 0000000000..061790d485 --- /dev/null +++ b/test/Microsoft.TestPlatform.DataCollection.UnitTests/DataCollectionPluginManagerTests.cs @@ -0,0 +1,284 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.DataCollection.UnitTests +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using System.Xml; + + using Microsoft.VisualStudio.TestPlatform.Common; + using Microsoft.VisualStudio.TestPlatform.Common.DataCollection; + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework; + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.Utilities; + using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; + using Microsoft.VisualStudio.TestPlatform.DataCollection; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + using Microsoft.VisualStudio.TestTools.Execution; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + using Moq; + + using IMessageSink = Microsoft.VisualStudio.TestPlatform.DataCollection.Interfaces.IMessageSink; + + [TestClass] + public class DataCollectionManagerTests + { + private DataCollectionManager dataCollectionManager; + private DummyMessageSink mockMessageSink; + private RunSettings runSettings; + private string xmlSettings = + "\r\n\r\n \r\n 1\r\n .\\TestResults\r\n x86\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n"; + + [TestInitialize] + public void Init() + { + SetupMockExtensions(new string[] { typeof(DataCollectorsSettingsProvider).GetTypeInfo().Assembly.Location }, () => { }); + + this.mockMessageSink = new DummyMessageSink(); + this.dataCollectionManager = new DataCollectionManager(this.mockMessageSink); + this.runSettings = new RunSettings(); + this.runSettings.LoadSettingsXml(this.xmlSettings); + } + + [TestCleanup] + public void Cleanup() + { + MockDataCollector.EnvVarList = null; + MockDataCollector.ThrowExceptionWhenInitialized = false; + ResetExtensionsCache(); + } + + [TestMethod] + public void LoadDataCollectorShouldLoadDataCollectorAndReturnEnvironmentVariables() + { + var envVarList = new List>(); + envVarList.Add(new KeyValuePair("key", "value")); + MockDataCollector.EnvVarList = envVarList; + this.runSettings.InitializeSettingsProviders(this.xmlSettings); + + var result = this.dataCollectionManager.LoadDataCollectors(this.runSettings); + + Assert.IsNotNull(result); + Assert.AreEqual(1, result.Count); + Assert.AreEqual("key", result.Keys.First()); + Assert.AreEqual("value", result.Values.First()); + } + + [TestMethod] + public void LoadDataCollectorShouldThrowExceptionIfRunSettingsIsNull() + { + Assert.ThrowsException(() => + { + this.dataCollectionManager.LoadDataCollectors(null); + }); + } + + [TestMethod] + public void LoadDataCollectorsShouldReturnEmptyDictionaryIfDataCollectionSettingsProviderIsNotRegistered() + { + var result = this.dataCollectionManager.LoadDataCollectors(this.runSettings); + + Assert.AreEqual(0, result.Count); + } + + [TestMethod] + public void LoadDataCollectorsShouldInitializeDataCollector() + { + this.runSettings.InitializeSettingsProviders(this.xmlSettings); + + var result = this.dataCollectionManager.LoadDataCollectors(this.runSettings); + + Assert.IsTrue(MockDataCollector.IsInitializeInvoked); + } + + [TestMethod] + public void LoadDataCollectorsShouldLogExceptionToMessageSinkIfInitializationFails() + { + this.runSettings.InitializeSettingsProviders(this.xmlSettings); + MockDataCollector.ThrowExceptionWhenInitialized = true; + + var result = this.dataCollectionManager.LoadDataCollectors(this.runSettings); + + Assert.IsTrue(this.mockMessageSink.IsSendMessageInvoked); + Assert.IsTrue(MockDataCollector.IsDisposeInvoked); + } + + [TestMethod] + public void LaodDataCollectorsShouldReturnEnviornmentVariables() + { + this.runSettings.InitializeSettingsProviders(this.xmlSettings); + var envVarList = new List>(); + var kvp = new KeyValuePair("key", "value"); + envVarList.Add(kvp); + MockDataCollector.EnvVarList = envVarList; + + var result = this.dataCollectionManager.LoadDataCollectors(this.runSettings); + + Assert.IsTrue(MockDataCollector.IsGetTestExecutionEnvironmentVariablesInvoked); + Assert.AreEqual(1, result.Count); + Assert.AreEqual("key", result.Keys.First()); + Assert.AreEqual("value", result.Values.First()); + } + + [TestMethod] + public void LoadDataCollectorsShouldReturnOnlyOneEnvirnmentVariableIfMoreThanOneVariablesWithSameKeyIsSpecified() + { + this.runSettings.InitializeSettingsProviders(this.xmlSettings); + var envVarList = new List>(); + envVarList.Add(new KeyValuePair("key", "value")); + envVarList.Add(new KeyValuePair("key", "value1")); + MockDataCollector.EnvVarList = envVarList; + + var result = this.dataCollectionManager.LoadDataCollectors(this.runSettings); + + Assert.IsTrue(MockDataCollector.IsGetTestExecutionEnvironmentVariablesInvoked); + Assert.AreEqual(1, result.Count); + Assert.AreEqual("key", result.Keys.First()); + Assert.AreEqual("value", result.Values.First()); + Assert.IsTrue(this.mockMessageSink.IsSendMessageInvoked); + } + + public static void SetupMockExtensions(string[] extensions, Action callback) + { + // Setup mocks. + var testableTestPluginCache = new TestableTestPluginCache(new Mock().Object); + testableTestPluginCache.DoesDirectoryExistSetter = true; + + testableTestPluginCache.FilesInDirectory = (path, pattern) => + { + if (pattern.Equals("*.dll")) + { + callback.Invoke(); + return extensions; + } + return new string[] { }; + }; + + // Setup the testable instance. + TestPluginCache.Instance = testableTestPluginCache; + } + + public static void ResetExtensionsCache() + { + TestPluginCache.Instance = null; + } + } + + [DataCollectorTypeUri("datacollector://Company/Product/Version")] + [DataCollectorFriendlyName("Collect Log Files", false)] + public class MockDataCollector : DataCollector, ITestExecutionEnvironmentSpecifier + { + public static bool IsInitializeInvoked; + public static bool ThrowExceptionWhenInitialized; + public static bool IsDisposeInvoked; + public static bool IsGetTestExecutionEnvironmentVariablesInvoked; + public static bool GetTestExecutionEnvironmentVariablesThrowException; + public static bool DisposeShouldThrowException; + + public static IEnumerable> EnvVarList; + + + public override void Initialize(XmlElement configurationElement, DataCollectionEvents events, DataCollectionSink dataSink, DataCollectionLogger logger, DataCollectionEnvironmentContext environmentContext) + { + if (ThrowExceptionWhenInitialized) + { + throw new Exception("DataCollectorException"); + } + IsInitializeInvoked = true; + } + + protected override void Dispose(bool disposing) + { + IsDisposeInvoked = true; + if (DisposeShouldThrowException) + { + throw new Exception("DataCollectorException"); + } + } + + public IEnumerable> GetTestExecutionEnvironmentVariables() + { + if (GetTestExecutionEnvironmentVariablesThrowException) + { + throw new Exception("DataCollectorException"); + } + + IsGetTestExecutionEnvironmentVariablesInvoked = true; + return EnvVarList; + } + } + + public class TestableTestPluginCache : TestPluginCache + { + public TestableTestPluginCache(IPathUtilities pathUtilities) + : base(pathUtilities) + { + } + + internal Func FilesInDirectory + { + get; + set; + } + + public bool DoesDirectoryExistSetter + { + get; + set; + } + + public Func, TestExtensions> TestExtensionsSetter { get; set; } + + internal override bool DoesDirectoryExist(string path) + { + return this.DoesDirectoryExistSetter; + } + + internal override string[] GetFilesInDirectory(string path, string searchPattern) + { + return this.FilesInDirectory.Invoke(path, searchPattern); + } + + internal override TestExtensions GetTestExtensions(IEnumerable extensions) + { + if (this.TestExtensionsSetter == null) + { + return base.GetTestExtensions(extensions); + } + else + { + return this.TestExtensionsSetter.Invoke(extensions); + } + } + } + + internal class DummyMessageSink : IMessageSink + { + public bool IsSendMessageInvoked; + public EventHandler OnDataCollectionMessage + { + get + { + throw new NotImplementedException(); + } + + set + { + throw new NotImplementedException(); + } + } + + public void SendMessage(DataCollectorDataMessage collectorDataMessage) + { + throw new NotImplementedException(); + } + + public void SendMessage(DataCollectionMessageEventArgs args) + { + this.IsSendMessageInvoked = true; + } + } +} + diff --git a/test/Microsoft.TestPlatform.DataCollection.UnitTests/Microsoft.TestPlatform.DataCollection.UnitTests.xproj b/test/Microsoft.TestPlatform.DataCollection.UnitTests/Microsoft.TestPlatform.DataCollection.UnitTests.xproj new file mode 100644 index 0000000000..2038075764 --- /dev/null +++ b/test/Microsoft.TestPlatform.DataCollection.UnitTests/Microsoft.TestPlatform.DataCollection.UnitTests.xproj @@ -0,0 +1,22 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 07d89fbd-f76d-40ef-a0ca-9213dee61941 + Microsoft.TestPlatform.DataCollection.UnitTests + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\ + v4.5.2 + + + 2.0 + + + + + + \ No newline at end of file diff --git a/test/Microsoft.TestPlatform.DataCollection.UnitTests/Properties/AssemblyInfo.cs b/test/Microsoft.TestPlatform.DataCollection.UnitTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..075d3dae62 --- /dev/null +++ b/test/Microsoft.TestPlatform.DataCollection.UnitTests/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Microsoft.TestPlatform.DataCollection.UnitTests")] +[assembly: AssemblyTrademark("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("07d89fbd-f76d-40ef-a0ca-9213dee61941")] diff --git a/test/Microsoft.TestPlatform.DataCollection.UnitTests/TestPlatformDataCollectorInfoTests.cs b/test/Microsoft.TestPlatform.DataCollection.UnitTests/TestPlatformDataCollectorInfoTests.cs new file mode 100644 index 0000000000..e707e06989 --- /dev/null +++ b/test/Microsoft.TestPlatform.DataCollection.UnitTests/TestPlatformDataCollectorInfoTests.cs @@ -0,0 +1,117 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.DataCollection.UnitTests +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using System.Xml; + + using Microsoft.VisualStudio.TestPlatform.DataCollection; + using Microsoft.VisualStudio.TestPlatform.DataCollection.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + using DataCollectionContext = Microsoft.VisualStudio.TestTools.Execution.DataCollectionContext; + using DataCollectionEnvironmentContext = Microsoft.VisualStudio.TestTools.Execution.DataCollectionEnvironmentContext; + using DataCollector = Microsoft.VisualStudio.TestTools.Execution.DataCollector; + using DataCollectorInformation = Microsoft.VisualStudio.TestTools.Execution.DataCollectorInformation; + using ITestExecutionEnvironmentSpecifier = Microsoft.VisualStudio.TestTools.Execution.ITestExecutionEnvironmentSpecifier; + using SafeAbortableUserWorkItemFactory = Microsoft.VisualStudio.TestTools.Common.SafeAbortableUserWorkItemFactory; + using SessionId = Microsoft.VisualStudio.TestTools.Common.SessionId; + + [TestClass] + public class TestPlatformDataCollectorInfoTests + { + private MockDataCollector mockDataCollector; + private TestPlatformDataCollectorInfo testPlatformDataCollectorInfo; + private IMessageSink mockMessageSink; + private DataCollectorInformation mockDataCollectorInformation; + private XmlElement dummyXmlElement; + + [TestInitialize] + public void Init() + { + this.mockDataCollector = new MockDataCollector(); + this.mockMessageSink = new DummyMessageSink(); + this.mockDataCollectorInformation = new DataCollectorInformation(); + this.dummyXmlElement = new XmlDocument().CreateElement("Root"); + this.testPlatformDataCollectorInfo = new TestPlatformDataCollectorInfo(this.mockDataCollector, this.dummyXmlElement, this.mockMessageSink, this.mockDataCollectorInformation, new SafeAbortableUserWorkItemFactory()); + } + + [TestCleanup] + public void Cleanup() + { + MockDataCollector.IsInitializeInvoked = false; + MockDataCollector.GetTestExecutionEnvironmentVariablesThrowException = false; + MockDataCollector.EnvVarList = null; + MockDataCollector.DisposeShouldThrowException = false; + MockDataCollector.IsDisposeInvoked = false; + } + + [TestMethod] + public void InitializeDataCollectorShouldInitializeDataCollector() + { + var envContext = DataCollectionEnvironmentContext.CreateForLocalEnvironment(new DataCollectionContext(new SessionId(Guid.NewGuid()))); + this.testPlatformDataCollectorInfo.InitializeDataCollector(envContext); + + Assert.IsTrue(MockDataCollector.IsInitializeInvoked); + } + + [TestMethod] + public void InitializeDataCollectorShouldThrowExecptionIfDataCollectorInitThrowsException() + { + MockDataCollector.ThrowExceptionWhenInitialized = true; + + Assert.ThrowsException(() => + { + var envContext = DataCollectionEnvironmentContext.CreateForLocalEnvironment(new DataCollectionContext(new SessionId(Guid.NewGuid()))); + this.testPlatformDataCollectorInfo.InitializeDataCollector(envContext); + }); + } + + [TestMethod] + public void GetTestExecutionEnvironmentVariablesShouldGetEnvVariablesFromDataCollector() + { + var envVarList = new List>(); + envVarList.Add(new KeyValuePair("key", "value")); + MockDataCollector.EnvVarList = envVarList; + + this.testPlatformDataCollectorInfo.GetTestExecutionEnvironmentVariables(); + + Assert.IsTrue(MockDataCollector.IsGetTestExecutionEnvironmentVariablesInvoked); + Assert.AreEqual(1, this.testPlatformDataCollectorInfo.TestExecutionEnvironmentVariables.Count()); + Assert.AreEqual("key", this.testPlatformDataCollectorInfo.TestExecutionEnvironmentVariables.First().Key); + Assert.AreEqual("value", this.testPlatformDataCollectorInfo.TestExecutionEnvironmentVariables.First().Value); + } + + [TestMethod] + public void GetTestExecutionEnvironmentVariablesShouldThrowExceptionIfCallFails() + { + MockDataCollector.GetTestExecutionEnvironmentVariablesThrowException = true; + Assert.ThrowsException(() => + { + this.testPlatformDataCollectorInfo.GetTestExecutionEnvironmentVariables(); + }); + } + + [TestMethod] + public void DisposeDataCollectorShouldDisposeDataCollecdtor() + { + this.testPlatformDataCollectorInfo.DisposeDataCollector(); + + Assert.IsTrue(MockDataCollector.IsDisposeInvoked); + } + + [TestMethod] + public void DisposeDataCollectorShouldEatExceptionIfDisposingDataCollectorFails() + { + MockDataCollector.DisposeShouldThrowException = true; + + this.testPlatformDataCollectorInfo.DisposeDataCollector(); + + Assert.IsTrue(MockDataCollector.IsDisposeInvoked); + } + } +} diff --git a/test/Microsoft.TestPlatform.DataCollection.UnitTests/app.config b/test/Microsoft.TestPlatform.DataCollection.UnitTests/app.config new file mode 100644 index 0000000000..67d96ac21a --- /dev/null +++ b/test/Microsoft.TestPlatform.DataCollection.UnitTests/app.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/test/Microsoft.TestPlatform.DataCollection.UnitTests/project.json b/test/Microsoft.TestPlatform.DataCollection.UnitTests/project.json new file mode 100644 index 0000000000..e262758114 --- /dev/null +++ b/test/Microsoft.TestPlatform.DataCollection.UnitTests/project.json @@ -0,0 +1,29 @@ +{ + "version": "15.0.0-*", + + "buildOptions": { + "delaySign": true, + "keyFile": "../../scripts/key.snk", + "warningsAsErrors": true, + + "copyToOutput": "app.config" + }, + + "dependencies": { + "dotnet-test-mstest": { + "version": "1.0.1-preview", + "exclude": "compile" + }, + "MSTest.TestFramework": "1.0.0-preview", + "moq.netcore": "4.4.0-beta8", + "System.Diagnostics.TraceSource": "4.0.0-rc2-24015", + "Microsoft.TestPlatform.DataCollection": "15.0.0-*", + "Microsoft.VisualStudio.QualityTools.ExecutionCommon": "1.0.0" + }, + + "frameworks": { + "net461": { } + }, + + "testRunner": "mstest" +} \ No newline at end of file diff --git a/test/datacollector.x86.UnitTests/DataCollectionCoordinatorTests.cs b/test/datacollector.UnitTests/DataCollectionCoordinatorTests.cs similarity index 77% rename from test/datacollector.x86.UnitTests/DataCollectionCoordinatorTests.cs rename to test/datacollector.UnitTests/DataCollectionCoordinatorTests.cs index 7c7ec571ce..35aeb46fd0 100644 --- a/test/datacollector.x86.UnitTests/DataCollectionCoordinatorTests.cs +++ b/test/datacollector.UnitTests/DataCollectionCoordinatorTests.cs @@ -10,8 +10,7 @@ namespace Microsoft.VisualStudio.TestPlatform.DataCollector.UnitTests using System.Threading; using Microsoft.VisualStudio.TestPlatform.Common; - using Microsoft.VisualStudio.TestPlatform.DataCollector.Interfaces; - using Microsoft.VisualStudio.TestPlatform.DataCollector.x86; + using Microsoft.VisualStudio.TestPlatform.Common.DataCollection.Interfaces; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -35,15 +34,15 @@ public void BeforeTestRunStartShouldReturnBeforeTestRunStartResult() { var envVars = new Dictionary(); envVars.Add("key", "value"); - this.dummyDataCollectionManagerV1.envVariables = envVars; - this.dummyDataCollectionManagerV2.envVariables = new Dictionary(); + this.dummyDataCollectionManagerV1.EnvVariables = envVars; + this.dummyDataCollectionManagerV2.EnvVariables = new Dictionary(); var result = this.dataCollectionCoordinator.BeforeTestRunStart(settingsXml: string.Empty, resetDataCollectors: true, isRunStartingNow: true); - Assert.IsTrue(this.dummyDataCollectionManagerV1.isLoadCollectorsInvoked); - Assert.IsTrue(this.dummyDataCollectionManagerV2.isLoadCollectorsInvoked); - Assert.IsTrue(this.dummyDataCollectionManagerV1.isSessionStartedInvoked); - Assert.IsTrue(this.dummyDataCollectionManagerV2.isSessionStartedInvoked); + Assert.IsTrue(this.dummyDataCollectionManagerV1.IsLoadCollectorsInvoked); + Assert.IsTrue(this.dummyDataCollectionManagerV2.IsLoadCollectorsInvoked); + Assert.IsTrue(this.dummyDataCollectionManagerV1.IsSessionStartedInvoked); + Assert.IsTrue(this.dummyDataCollectionManagerV2.IsSessionStartedInvoked); Assert.AreEqual(1, result.EnvironmentVariables.Count); Assert.AreEqual(envVars.Keys.First(), result.EnvironmentVariables.Keys.First()); Assert.AreEqual(envVars.Values.First(), result.EnvironmentVariables.Values.First()); @@ -54,8 +53,8 @@ public void BeforeTestRunStartShouldLoadTwoDataCollectorsInParallel() { var envVars = new Dictionary(); envVars.Add("key", "value"); - this.dummyDataCollectionManagerV1.envVariables = envVars; - this.dummyDataCollectionManagerV2.envVariables = new Dictionary(); + this.dummyDataCollectionManagerV1.EnvVariables = envVars; + this.dummyDataCollectionManagerV2.EnvVariables = new Dictionary(); var result = this.dataCollectionCoordinator.BeforeTestRunStart(settingsXml: string.Empty, resetDataCollectors: true, isRunStartingNow: true); @@ -78,7 +77,7 @@ public void BeforeTestRunStartShouldReturnNullIfNoDataCollectorManagersAreProvid [TestMethod] public void BeforeTestRunStartShouldThrowExceptionIfExceptionIsThrownByDataCollectionManager() { - this.dummyDataCollectionManagerV1.loadDataCollectorsThrowException = true; + this.dummyDataCollectionManagerV1.LoadDataCollectorsThrowException = true; Assert.ThrowsException( () => @@ -95,14 +94,14 @@ public void AfterTestRunEndShouldReturnAttachments() attachmentset1.Attachments.Add(new UriDataAttachment(new Uri("DataCollection://Attachment/v11"), "AttachmentV1-Attachment1")); attachments1.Add(attachmentset1); - this.dummyDataCollectionManagerV1.attachments = attachments1; - this.dummyDataCollectionManagerV2.attachments = attachments1; + this.dummyDataCollectionManagerV1.Attachments = attachments1; + this.dummyDataCollectionManagerV2.Attachments = attachments1; var result = this.dataCollectionCoordinator.AfterTestRunEnd(isCancelled: false); Assert.IsNotNull(result); - Assert.IsTrue(this.dummyDataCollectionManagerV1.isSessionEndedInvoked); - Assert.IsTrue(this.dummyDataCollectionManagerV2.isSessionEndedInvoked); + Assert.IsTrue(this.dummyDataCollectionManagerV1.IsSessionEndedInvoked); + Assert.IsTrue(this.dummyDataCollectionManagerV2.IsSessionEndedInvoked); Assert.AreEqual(2, result.Count()); } @@ -114,8 +113,8 @@ public void AfterTestRunEndShouldGetAttachmentsFromDataCollectorManagersInParall attachmentset1.Attachments.Add(new UriDataAttachment(new Uri("DataCollection://Attachment/v11"), "AttachmentV1-Attachment1")); attachments1.Add(attachmentset1); - this.dummyDataCollectionManagerV1.attachments = attachments1; - this.dummyDataCollectionManagerV2.attachments = attachments1; + this.dummyDataCollectionManagerV1.Attachments = attachments1; + this.dummyDataCollectionManagerV2.Attachments = attachments1; var result = this.dataCollectionCoordinator.AfterTestRunEnd(isCancelled: false); @@ -138,7 +137,7 @@ public void AfterTestRunEndShouldReturnNullIfNoDataCollectorManagersAreProvided( [TestMethod] public void AfterTestRunEndShouldThrowExceptionIfExceptionIsThrownByDataCollectionManager() { - this.dummyDataCollectionManagerV1.sessionEndedThrowsException = true; + this.dummyDataCollectionManagerV1.SessionEndedThrowsException = true; Assert.ThrowsException( () => @@ -152,8 +151,8 @@ public void DisposeShouldCallDisposeOfDataCollectionManagers() { this.dataCollectionCoordinator.Dispose(); - Assert.IsTrue(this.dummyDataCollectionManagerV1.isDisposedInvoked); - Assert.IsTrue(this.dummyDataCollectionManagerV2.isDisposedInvoked); + Assert.IsTrue(this.dummyDataCollectionManagerV1.IsDisposedInvoked); + Assert.IsTrue(this.dummyDataCollectionManagerV2.IsDisposedInvoked); } [TestMethod] @@ -167,32 +166,32 @@ public void DisposeShouldDisposeResourcesIfNoDataCollectionManagersAreProvided() internal class DummyDataCollectionManager : IDataCollectionManager { - public bool isLoadCollectorsInvoked; - public bool isSessionStartedInvoked; - public bool isSessionEndedInvoked; - public Dictionary envVariables; - public bool loadDataCollectorsThrowException; - public Collection attachments; - public bool sessionEndedThrowsException; - public bool isDisposedInvoked; + public bool IsLoadCollectorsInvoked; + public bool IsSessionStartedInvoked; + public bool IsSessionEndedInvoked; + public Dictionary EnvVariables; + public bool LoadDataCollectorsThrowException; + public Collection Attachments; + public bool SessionEndedThrowsException; + public bool IsDisposedInvoked; public int ThreadId; public void Dispose() { - this.isDisposedInvoked = true; + this.IsDisposedInvoked = true; } - public Dictionary LoadDataCollectors(RunSettings settingsXml) + public IDictionary LoadDataCollectors(RunSettings settingsXml) { this.ThreadId = Thread.CurrentThread.ManagedThreadId; - if (this.loadDataCollectorsThrowException) + if (this.LoadDataCollectorsThrowException) { throw new Exception("DataCollectionManagerException"); } - this.isLoadCollectorsInvoked = true; - return this.envVariables; + this.IsLoadCollectorsInvoked = true; + return this.EnvVariables; } @@ -205,19 +204,19 @@ public Collection SessionEnded(bool isCancelled) { this.ThreadId = Thread.CurrentThread.ManagedThreadId; - if (this.sessionEndedThrowsException) + if (this.SessionEndedThrowsException) { throw new Exception("DataCollectionManagerException"); } - this.isSessionEndedInvoked = true; - return this.attachments; + this.IsSessionEndedInvoked = true; + return this.Attachments; } public bool SessionStarted() { this.ThreadId = Thread.CurrentThread.ManagedThreadId; - this.isSessionStartedInvoked = true; + this.IsSessionStartedInvoked = true; return true; } diff --git a/test/datacollector.x86.UnitTests/Properties/AssemblyInfo.cs b/test/datacollector.UnitTests/Properties/AssemblyInfo.cs similarity index 100% rename from test/datacollector.x86.UnitTests/Properties/AssemblyInfo.cs rename to test/datacollector.UnitTests/Properties/AssemblyInfo.cs diff --git a/test/datacollector.x86.UnitTests/datacollector.x86.UnitTests.xproj b/test/datacollector.UnitTests/datacollector.UnitTests.xproj similarity index 100% rename from test/datacollector.x86.UnitTests/datacollector.x86.UnitTests.xproj rename to test/datacollector.UnitTests/datacollector.UnitTests.xproj diff --git a/test/datacollector.x86.UnitTests/project.json b/test/datacollector.UnitTests/project.json similarity index 100% rename from test/datacollector.x86.UnitTests/project.json rename to test/datacollector.UnitTests/project.json From 73b11f84dc2361b61469ea9983c4d2a1747f091e Mon Sep 17 00:00:00 2001 From: Harsh Jain Date: Sat, 6 Aug 2016 00:35:57 +0530 Subject: [PATCH 2/5] Renamed Legacy DataCollection project. --- Nuget.config | 3 +- TestPlatform.sln | 16 +- src/Microsoft.TestPlatform.Common/Friends.cs | 4 +- .../CollectorRequestedEnvironmentVariable.cs | 2 +- .../DataCollectionManager.cs | 383 +++++++++--------- .../DataCollectorDataMessage.cs | 26 +- .../Friends.cs | 7 + .../Interfaces/IMessageSink.cs | 2 +- ...soft.TestPlatform.DataCollection.v1.xproj} | 2 +- .../Properties/AssemblyInfo.cs | 0 .../Resource.Designer.cs | 4 +- .../Resource.resx | 0 .../TestPlatformDataCollectionEvents.cs | 5 +- .../TestPlatformDataCollectionLogger.cs | 50 ++- .../TestPlatformDataCollectionSink.cs | 11 +- .../TestPlatformDataCollectorDiscovery.cs | 28 +- .../TestPlatformDataCollectorInfo.cs | 14 +- .../project.json | 3 +- .../Friends.cs | 7 - .../project.json | 2 +- ...lectorRequestedEnvironmentVariableTests.cs | 18 +- .../DataCollectionPluginManagerTests.cs | 13 +- ...latform.DataCollection.v1.UnitTests.xproj} | 2 +- .../Properties/AssemblyInfo.cs | 0 .../TestPlatformDataCollectorInfoTests.cs | 6 +- .../app.config | 0 .../project.json | 4 +- .../DataCollectionCoordinatorTests.cs | 5 +- 28 files changed, 314 insertions(+), 303 deletions(-) rename src/{Microsoft.TestPlatform.DataCollection => Microsoft.TestPlatform.DataCollection.v1}/CollectorRequestedEnvironmentVariable.cs (98%) rename src/{Microsoft.TestPlatform.DataCollection => Microsoft.TestPlatform.DataCollection.v1}/DataCollectionManager.cs (87%) rename src/{Microsoft.TestPlatform.DataCollection => Microsoft.TestPlatform.DataCollection.v1}/DataCollectorDataMessage.cs (89%) create mode 100644 src/Microsoft.TestPlatform.DataCollection.v1/Friends.cs rename src/{Microsoft.TestPlatform.DataCollection => Microsoft.TestPlatform.DataCollection.v1}/Interfaces/IMessageSink.cs (93%) rename src/{Microsoft.TestPlatform.DataCollection/Microsoft.TestPlatform.DataCollection.xproj => Microsoft.TestPlatform.DataCollection.v1/Microsoft.TestPlatform.DataCollection.v1.xproj} (97%) rename src/{Microsoft.TestPlatform.DataCollection => Microsoft.TestPlatform.DataCollection.v1}/Properties/AssemblyInfo.cs (100%) rename src/{Microsoft.TestPlatform.DataCollection => Microsoft.TestPlatform.DataCollection.v1}/Resource.Designer.cs (98%) rename src/{Microsoft.TestPlatform.DataCollection => Microsoft.TestPlatform.DataCollection.v1}/Resource.resx (100%) rename src/{Microsoft.TestPlatform.DataCollection => Microsoft.TestPlatform.DataCollection.v1}/TestPlatformDataCollectionEvents.cs (99%) rename src/{Microsoft.TestPlatform.DataCollection => Microsoft.TestPlatform.DataCollection.v1}/TestPlatformDataCollectionLogger.cs (80%) rename src/{Microsoft.TestPlatform.DataCollection => Microsoft.TestPlatform.DataCollection.v1}/TestPlatformDataCollectionSink.cs (89%) rename src/{Microsoft.TestPlatform.DataCollection => Microsoft.TestPlatform.DataCollection.v1}/TestPlatformDataCollectorDiscovery.cs (93%) rename src/{Microsoft.TestPlatform.DataCollection => Microsoft.TestPlatform.DataCollection.v1}/TestPlatformDataCollectorInfo.cs (91%) rename src/{Microsoft.TestPlatform.DataCollection => Microsoft.TestPlatform.DataCollection.v1}/project.json (86%) delete mode 100644 src/Microsoft.TestPlatform.DataCollection/Friends.cs rename test/{Microsoft.TestPlatform.DataCollection.UnitTests => Microsoft.TestPlatform.DataCollection.v1.UnitTests}/CollectorRequestedEnvironmentVariableTests.cs (78%) rename test/{Microsoft.TestPlatform.DataCollection.UnitTests => Microsoft.TestPlatform.DataCollection.v1.UnitTests}/DataCollectionPluginManagerTests.cs (95%) rename test/{Microsoft.TestPlatform.DataCollection.UnitTests/Microsoft.TestPlatform.DataCollection.UnitTests.xproj => Microsoft.TestPlatform.DataCollection.v1.UnitTests/Microsoft.TestPlatform.DataCollection.v1.UnitTests.xproj} (92%) rename test/{Microsoft.TestPlatform.DataCollection.UnitTests => Microsoft.TestPlatform.DataCollection.v1.UnitTests}/Properties/AssemblyInfo.cs (100%) rename test/{Microsoft.TestPlatform.DataCollection.UnitTests => Microsoft.TestPlatform.DataCollection.v1.UnitTests}/TestPlatformDataCollectorInfoTests.cs (96%) rename test/{Microsoft.TestPlatform.DataCollection.UnitTests => Microsoft.TestPlatform.DataCollection.v1.UnitTests}/app.config (100%) rename test/{Microsoft.TestPlatform.DataCollection.UnitTests => Microsoft.TestPlatform.DataCollection.v1.UnitTests}/project.json (87%) diff --git a/Nuget.config b/Nuget.config index aa7bc03ce4..be7057cd5c 100644 --- a/Nuget.config +++ b/Nuget.config @@ -6,9 +6,8 @@ - - + diff --git a/TestPlatform.sln b/TestPlatform.sln index 0c1ab70cb3..c6a7d75a47 100644 --- a/TestPlatform.sln +++ b/TestPlatform.sln @@ -83,11 +83,11 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "datacollector", "src\dataco EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "datacollector.x86", "src\datacollector.x86\datacollector.x86.xproj", "{00DFB5C7-3850-4A65-986B-713F200482D4}" EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.TestPlatform.DataCollection", "src\Microsoft.TestPlatform.DataCollection\Microsoft.TestPlatform.DataCollection.xproj", "{D5C951F1-B6FF-4DE2-AD89-F5C5803DE126}" -EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "datacollector.UnitTests", "test\datacollector.UnitTests\datacollector.UnitTests.xproj", "{00AA21F3-31E4-4748-AC0B-C4EADB41CA24}" EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.TestPlatform.DataCollection.UnitTests", "test\Microsoft.TestPlatform.DataCollection.UnitTests\Microsoft.TestPlatform.DataCollection.UnitTests.xproj", "{07D89FBD-F76D-40EF-A0CA-9213DEE61941}" +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.TestPlatform.DataCollection.v1", "src\Microsoft.TestPlatform.DataCollection.v1\Microsoft.TestPlatform.DataCollection.v1.xproj", "{D5C951F1-B6FF-4DE2-AD89-F5C5803DE126}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.TestPlatform.DataCollection.v1.UnitTests", "test\Microsoft.TestPlatform.DataCollection.v1.UnitTests\Microsoft.TestPlatform.DataCollection.v1.UnitTests.xproj", "{07D89FBD-F76D-40EF-A0CA-9213DEE61941}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -211,14 +211,14 @@ Global {00DFB5C7-3850-4A65-986B-713F200482D4}.Debug|Any CPU.Build.0 = Debug|Any CPU {00DFB5C7-3850-4A65-986B-713F200482D4}.Release|Any CPU.ActiveCfg = Release|Any CPU {00DFB5C7-3850-4A65-986B-713F200482D4}.Release|Any CPU.Build.0 = Release|Any CPU - {D5C951F1-B6FF-4DE2-AD89-F5C5803DE126}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D5C951F1-B6FF-4DE2-AD89-F5C5803DE126}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D5C951F1-B6FF-4DE2-AD89-F5C5803DE126}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D5C951F1-B6FF-4DE2-AD89-F5C5803DE126}.Release|Any CPU.Build.0 = Release|Any CPU {00AA21F3-31E4-4748-AC0B-C4EADB41CA24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {00AA21F3-31E4-4748-AC0B-C4EADB41CA24}.Debug|Any CPU.Build.0 = Debug|Any CPU {00AA21F3-31E4-4748-AC0B-C4EADB41CA24}.Release|Any CPU.ActiveCfg = Release|Any CPU {00AA21F3-31E4-4748-AC0B-C4EADB41CA24}.Release|Any CPU.Build.0 = Release|Any CPU + {D5C951F1-B6FF-4DE2-AD89-F5C5803DE126}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D5C951F1-B6FF-4DE2-AD89-F5C5803DE126}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D5C951F1-B6FF-4DE2-AD89-F5C5803DE126}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D5C951F1-B6FF-4DE2-AD89-F5C5803DE126}.Release|Any CPU.Build.0 = Release|Any CPU {07D89FBD-F76D-40EF-A0CA-9213DEE61941}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {07D89FBD-F76D-40EF-A0CA-9213DEE61941}.Debug|Any CPU.Build.0 = Debug|Any CPU {07D89FBD-F76D-40EF-A0CA-9213DEE61941}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -259,8 +259,8 @@ Global {0CC51428-B665-47B0-A093-042D31785928} = {707096D0-DCFB-44A2-979D-178740E5DADE} {3572E78C-5AA5-4F68-876D-FC5322677263} = {D8EF073C-279A-4279-912D-E9D4B0635E17} {00DFB5C7-3850-4A65-986B-713F200482D4} = {D8EF073C-279A-4279-912D-E9D4B0635E17} - {D5C951F1-B6FF-4DE2-AD89-F5C5803DE126} = {D8EF073C-279A-4279-912D-E9D4B0635E17} {00AA21F3-31E4-4748-AC0B-C4EADB41CA24} = {463031A2-7F16-4E38-9944-1F5161D04933} + {D5C951F1-B6FF-4DE2-AD89-F5C5803DE126} = {D8EF073C-279A-4279-912D-E9D4B0635E17} {07D89FBD-F76D-40EF-A0CA-9213DEE61941} = {463031A2-7F16-4E38-9944-1F5161D04933} EndGlobalSection EndGlobal diff --git a/src/Microsoft.TestPlatform.Common/Friends.cs b/src/Microsoft.TestPlatform.Common/Friends.cs index c18b41b2e1..23d05e247b 100644 --- a/src/Microsoft.TestPlatform.Common/Friends.cs +++ b/src/Microsoft.TestPlatform.Common/Friends.cs @@ -7,7 +7,7 @@ [assembly: InternalsVisibleTo("vstest.console, PublicKey =002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("datacollector.x86, PublicKey =002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("datacollector, PublicKey =002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] -[assembly: InternalsVisibleTo("Microsoft.TestPlatform.DataCollection, PublicKey =002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] +[assembly: InternalsVisibleTo("Microsoft.TestPlatform.DataCollection.v1, PublicKey =002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("datacollector.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] #endregion @@ -16,5 +16,5 @@ [assembly: InternalsVisibleTo("Microsoft.TestPlatform.CrossPlatEngine.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("vstest.console.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] -[assembly: InternalsVisibleTo("Microsoft.TestPlatform.DataCollection.UnitTests, PublicKey =002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] +[assembly: InternalsVisibleTo("Microsoft.TestPlatform.DataCollection.v1.UnitTests, PublicKey =002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] #endregion \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.DataCollection/CollectorRequestedEnvironmentVariable.cs b/src/Microsoft.TestPlatform.DataCollection.v1/CollectorRequestedEnvironmentVariable.cs similarity index 98% rename from src/Microsoft.TestPlatform.DataCollection/CollectorRequestedEnvironmentVariable.cs rename to src/Microsoft.TestPlatform.DataCollection.v1/CollectorRequestedEnvironmentVariable.cs index ccdc892054..506eb6a3de 100644 --- a/src/Microsoft.TestPlatform.DataCollection/CollectorRequestedEnvironmentVariable.cs +++ b/src/Microsoft.TestPlatform.DataCollection.v1/CollectorRequestedEnvironmentVariable.cs @@ -1,6 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.VisualStudio.TestPlatform.DataCollection +namespace Microsoft.VisualStudio.TestPlatform.DataCollection.V1 { using System.Collections.Generic; using System.Diagnostics; diff --git a/src/Microsoft.TestPlatform.DataCollection/DataCollectionManager.cs b/src/Microsoft.TestPlatform.DataCollection.v1/DataCollectionManager.cs similarity index 87% rename from src/Microsoft.TestPlatform.DataCollection/DataCollectionManager.cs rename to src/Microsoft.TestPlatform.DataCollection.v1/DataCollectionManager.cs index 10070ba595..5396c66b5d 100644 --- a/src/Microsoft.TestPlatform.DataCollection/DataCollectionManager.cs +++ b/src/Microsoft.TestPlatform.DataCollection.v1/DataCollectionManager.cs @@ -1,6 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.VisualStudio.TestPlatform.DataCollection +namespace Microsoft.VisualStudio.TestPlatform.DataCollection.V1 { using System; using System.Collections.Generic; @@ -10,15 +10,10 @@ namespace Microsoft.VisualStudio.TestPlatform.DataCollection using System.IO; using System.Linq; using System.Reflection; - using System.Xml; using Microsoft.VisualStudio.TestPlatform.Common; - using Microsoft.VisualStudio.TestPlatform.Common.DataCollection; using Microsoft.VisualStudio.TestPlatform.Common.DataCollection.Interfaces; - using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; - using Microsoft.VisualStudio.TestPlatform.DataCollection; - using Microsoft.VisualStudio.TestPlatform.DataCollection.Implementations; - using Microsoft.VisualStudio.TestPlatform.DataCollection.Interfaces; + using Microsoft.VisualStudio.TestPlatform.DataCollection.V1.Interfaces; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; @@ -27,19 +22,17 @@ namespace Microsoft.VisualStudio.TestPlatform.DataCollection using CollectorDataEntry = Microsoft.VisualStudio.TestPlatform.ObjectModel.AttachmentSet; using DataCollectionContext = Microsoft.VisualStudio.TestTools.Execution.DataCollectionContext; using DataCollectionEnvironmentContext = Microsoft.VisualStudio.TestTools.Execution.DataCollectionEnvironmentContext; - using DataCollectionEventArgs = Microsoft.VisualStudio.TestTools.Execution.DataCollectionEventArgs; using DataCollector = Microsoft.VisualStudio.TestTools.Execution.DataCollector; using DataCollectorInformation = Microsoft.VisualStudio.TestTools.Execution.DataCollectorInformation; - using DataCollectorInvocationError = Microsoft.VisualStudio.TestTools.Execution.DataCollectorInvocationError; - using SessionEndEventArgs = Microsoft.VisualStudio.TestTools.Execution.SessionEndEventArgs; using SessionId = Microsoft.VisualStudio.TestTools.Common.SessionId; - using UriDataAttachment = Microsoft.VisualStudio.TestPlatform.ObjectModel.UriDataAttachment; /// /// The data collection plugin manager. /// - internal class DataCollectionManager : IDataCollectionManager, IDisposable + internal class DataCollectionManager : IDataCollectionManager { + #region Private Variables + /// /// Cache of data collectors associated with the run. /// @@ -48,7 +41,7 @@ internal class DataCollectionManager : IDataCollectionManager, IDisposable /// /// Is data collection currently enabled. /// - private bool collectionEnabled; + private bool isDataCollectionEnabled; /// /// Data collection environment context. @@ -75,6 +68,8 @@ internal class DataCollectionManager : IDataCollectionManager, IDisposable /// private string collectionOutputDirectory; + #endregion + /// /// Initializes a new instance of the class. /// @@ -83,14 +78,14 @@ public DataCollectionManager() : this(default(IMessageSink)) } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class with dependency injection. Used for unit testing. /// /// /// The message sink. /// internal DataCollectionManager(IMessageSink messageSink) { - this.collectionEnabled = false; + this.isDataCollectionEnabled = false; this.runDataCollectors = new Dictionary(); this.messageSink = messageSink; @@ -144,7 +139,7 @@ public Collection SessionEnded(bool isCancelled) /// Environment variables requested by data collectors public IDictionary LoadDataCollectors(RunSettings testRunSettings) { - ValidateArg.NotNull(testRunSettings, "testRunSettings"); + ValidateArg.NotNull(testRunSettings, "testRunSettings"); IDictionary executionEnvironmentVariables = new Dictionary(StringComparer.OrdinalIgnoreCase); var dataCollectorSettingsProvider = @@ -175,9 +170,9 @@ public Collection SessionEnded(bool isCancelled) this.collectionOutputDirectory = runConfiguration.ResultsDirectory; } - this.collectionEnabled = runCollectionSettings.IsCollectionEnabled; + this.isDataCollectionEnabled = runCollectionSettings.IsCollectionEnabled; - if (this.collectionEnabled) + if (this.isDataCollectionEnabled) { executionEnvironmentVariables = this.LoadAndInitDataCollectors(runCollectionSettings); } @@ -216,6 +211,8 @@ protected virtual void Dispose(bool disposing) } } + #region Private Methods + /// /// Creates an instance of collector plugin of given type. /// @@ -240,7 +237,7 @@ private static DataCollector CreateDataCollector(Type dataCollectorType) { if (EqtTrace.IsErrorEnabled) { - EqtTrace.Error("DataCollectionManager.CreateDataCollector: Could not create instance of type: " + dataCollectorType.ToString() + " Exception: " + ex.Message); + EqtTrace.Error("DataCollectionManager.CreateDataCollector: Could not create instance of type: " + dataCollectorType + " Exception: " + ex.Message); } throw; @@ -248,7 +245,7 @@ private static DataCollector CreateDataCollector(Type dataCollectorType) } /// - /// Helper method that gets the Type from typename string specified. + /// Helper method that gets the Type from type name string specified. /// /// /// Type name of the collector @@ -268,22 +265,21 @@ private static Type GetCollectorType(string collectorTypeName, out DataCollector var basePath = Path.Combine(pluginDirectoryPath, collectorTypeName.Split(',')[1].Trim()); - Assembly assembly = null; + Assembly assembly; - Type dcType = null; - dcType = GetDataCollectorInformationFromBinary(basePath, collectorTypeName, out assembly); + Type dctype = GetDataCollectorType(basePath, collectorTypeName, out assembly); // Not able to locate data collector binary. - if (dcType == null) + if (dctype == null) { - return dcType; + return null; } var configuration = DataCollectorDiscoveryHelper.GetConfigurationForAssembly(assembly); - dataCollectorInformation = new DataCollectorInformation(dcType, configuration); + dataCollectorInformation = new DataCollectorInformation(dctype, configuration); - return dcType; + return dctype; } catch (Exception ex) { @@ -311,28 +307,28 @@ private static Type GetCollectorType(string collectorTypeName, out DataCollector /// /// The . /// - private static Type GetDataCollectorInformationFromBinary(string binaryPath, string collectorTypeName, out Assembly assembly) + private static Type GetDataCollectorType(string binaryPath, string collectorTypeName, out Assembly assembly) { assembly = null; - Type dcType = null; + Type dctype = null; var dllPath = string.Concat(binaryPath, ".dll"); if (File.Exists(dllPath)) { assembly = Assembly.LoadFrom(dllPath); - dcType = GetDataCollectorInformationFromAssembly(assembly, collectorTypeName); + dctype = GetDataCollectorInformationFromAssembly(assembly, collectorTypeName); } - if (dcType == null) + if (dctype == null) { var exePath = string.Concat(binaryPath, ".exe"); if (File.Exists(exePath)) { assembly = Assembly.LoadFrom(exePath); - dcType = GetDataCollectorInformationFromAssembly(assembly, collectorTypeName); + dctype = GetDataCollectorInformationFromAssembly(assembly, collectorTypeName); } } - return dcType; + return dctype; } /// @@ -349,19 +345,21 @@ private static Type GetDataCollectorInformationFromBinary(string binaryPath, str /// private static Type GetDataCollectorInformationFromAssembly(Assembly assembly, string collectorTypeName) { - Type dcType = null; + Type dctype = null; if (assembly != null) { var types = assembly.GetTypes(); - dcType = types.Where((type) => type.AssemblyQualifiedName.Equals(collectorTypeName)).FirstOrDefault(); + dctype = types.FirstOrDefault(type => type.AssemblyQualifiedName != null && type.AssemblyQualifiedName.Equals(collectorTypeName)); } - return dcType; + return dctype; } /// /// Configures new session by /// a. creating new session id + /// b. creating new data collection context. + /// c. creating new data collection environment context for local run. /// private void ConfigureNewSession() { @@ -410,7 +408,7 @@ private void ConfigureNewSession() /// List of enabled data collectors private List GetDataCollectorsEnabledForRun(DataCollectionRunSettings dataCollectionSettings) { - List runEnabledDataCollectors = new List(); + var runEnabledDataCollectors = new List(); foreach (DataCollectorSettings settings in dataCollectionSettings.DataCollectorSettingsList) { if (settings.IsEnabled) @@ -430,142 +428,6 @@ private List GetDataCollectorsEnabledForRun(DataCollectio return runEnabledDataCollectors; } - /// - /// The get environment variables. - /// - /// - /// The unloaded any collector. - /// - /// - /// The . - /// - private Dictionary GetEnvironmentVariables(out bool unloadedAnyCollector) - { - var failedCollectors = new List(); - unloadedAnyCollector = false; - var dataCollectorEnvironmentVariables = new Dictionary(StringComparer.OrdinalIgnoreCase); - foreach (var dataCollectorInfo in this.GetDataCollectorsSnapshot()) - { - dataCollectorInfo.GetTestExecutionEnvironmentVariables(); - try - { - this.AddCollectorEnvironmentVariables(dataCollectorInfo, dataCollectorEnvironmentVariables); - } - catch (Exception ex) - { - unloadedAnyCollector = true; - - Type dataCollectorType = dataCollectorInfo.DataCollector.GetType(); - failedCollectors.Add(dataCollectorInfo); - dataCollectorInfo.Logger.LogError( - this.dataCollectionEnvironmentContext.SessionDataCollectionContext, - string.Format( - CultureInfo.CurrentCulture, - Resource.DataCollectorErrorOnGetVariable, - dataCollectorType, - ex.ToString())); - - if (EqtTrace.IsErrorEnabled) - { - EqtTrace.Error("DataCollectionManager.GetEnvironmentVariables: Failed to get variable for Collector '{0}': {1}", dataCollectorType, ex); - } - } - } - - this.RemoveDataCollectors(failedCollectors); - return dataCollectorEnvironmentVariables; - } - - /// - /// Gets a snapshot of current data collectors. - /// - /// - /// The . - /// - private List GetDataCollectorsSnapshot() - { - var datacollectorInfoList = new List(); - lock (this.runDataCollectors) - { - foreach (var dataCollectorInfo in this.runDataCollectors.Values) - { - if (dataCollectorInfo != null) - { - if (EqtTrace.IsVerboseEnabled) - { - EqtTrace.Verbose( - "DataCollectionManager.GetDataCollectorsSnapshot: DataCollector:{0}", - dataCollectorInfo.DataCollectorInformation.FriendlyName); - } - - datacollectorInfoList.Add(dataCollectorInfo); - } - else - { - if (EqtTrace.IsErrorEnabled) - { - EqtTrace.Error("DataCollectionManager.GetDataCollectorsSnapshot: got null data collector info from the data collector info collection (ignored)."); - } - } - } - } - - return datacollectorInfoList; - } - - /// - /// Collects environment variable to be set in test process by avoiding duplicates - /// and detecting override of variable value by multiple adapters. - /// - /// Data collector information for newly loaded plugin. - /// Environment variables required for already loaded plugin. - private void AddCollectorEnvironmentVariables( - TestPlatformDataCollectorInfo dataCollectorInfo, - Dictionary dataCollectorEnvironmentVariables) - { - if (null != dataCollectorInfo.TestExecutionEnvironmentVariables) - { - var collectorFriendlyName = dataCollectorInfo.DataCollectorInformation.FriendlyName; - foreach (var namevaluepair in dataCollectorInfo.TestExecutionEnvironmentVariables) - { - CollectorRequestedEnvironmentVariable alreadyRequestedVariable; - if (dataCollectorEnvironmentVariables.TryGetValue(namevaluepair.Key, out alreadyRequestedVariable)) - { - if (string.Equals(namevaluepair.Value, alreadyRequestedVariable.Value, StringComparison.Ordinal)) - { - alreadyRequestedVariable.AddRequestingDataCollector(collectorFriendlyName); - } - else - { - // Data collector is overriding an already requested variable, possibly an error. - dataCollectorInfo.Logger.LogError( - this.dataCollectionEnvironmentContext.SessionDataCollectionContext, - string.Format( - CultureInfo.CurrentUICulture, - Resource.DataCollectorRequestedDuplicateEnvironmentVariable, - collectorFriendlyName, - namevaluepair.Key, - namevaluepair.Value, - alreadyRequestedVariable.FirstDataCollectorThatRequested, - alreadyRequestedVariable.Value)); - } - } - else - { - if (EqtTrace.IsVerboseEnabled) - { - // New variable, add to the list. - EqtTrace.Verbose("DataCollectionManager.AddCollectionEnvironmentVariables: Adding Environment variable '{0}' value '{1}'", namevaluepair.Key, namevaluepair.Value); - } - - dataCollectorEnvironmentVariables.Add( - namevaluepair.Key, - new CollectorRequestedEnvironmentVariable(namevaluepair, collectorFriendlyName)); - } - } - } - } - /// /// Initializes data collector plugin. /// @@ -582,9 +444,9 @@ private bool LoadAndInitDataCollector(DataCollectorSettings collectorSettings) var codeBase = collectorSettings.CodeBase; var collectorTypeName = collectorSettings.AssemblyQualifiedName; var collectorDisplayName = string.IsNullOrWhiteSpace(collectorSettings.FriendlyName) ? collectorTypeName : collectorSettings.FriendlyName; - Type collectorType = null; - DataCollectorInformation dcInfo = null; - TestPlatformDataCollectorInfo dataCollectorInfo = null; + Type collectorType; + DataCollectorInformation dcinfo; + TestPlatformDataCollectorInfo dataCollectorInfo; try { if (!string.IsNullOrWhiteSpace(codeBase)) @@ -603,7 +465,7 @@ private bool LoadAndInitDataCollector(DataCollectorSettings collectorSettings) Assembly.Load(name); // This will do check for publicKeyToken etc } - collectorType = GetCollectorType(collectorTypeName, out dcInfo); + collectorType = GetCollectorType(collectorTypeName, out dcinfo); } catch (FileNotFoundException) { @@ -632,11 +494,11 @@ private bool LoadAndInitDataCollector(DataCollectorSettings collectorSettings) var dataCollector = CreateDataCollector(collectorType); // Attempt to get the data collector information verifying that all of the required metadata for the collector is available. - dataCollectorInfo = dcInfo == null ? null : new TestPlatformDataCollectorInfo( + dataCollectorInfo = dcinfo == null ? null : new TestPlatformDataCollectorInfo( dataCollector, collectorSettings.Configuration, this.messageSink, - dcInfo, + dcinfo, this.userWorkItemFactory); if (dataCollectorInfo == null || !dataCollectorInfo.DataCollectorInformation.TypeUri.Equals(collectorSettings.Uri)) @@ -672,11 +534,8 @@ private bool LoadAndInitDataCollector(DataCollectorSettings collectorSettings) // data collector failed to initialize. Dispose it and mark it failed. dataCollectorInfo.Logger.LogError( this.dataCollectionEnvironmentContext.SessionDataCollectionContext, - string.Format( - CultureInfo.CurrentCulture, - Resource.DataCollectorInitializationError, - dataCollectorInfo.DataCollectorInformation.FriendlyName, - ex.Message)); + string.Format(CultureInfo.CurrentCulture, Resource.DataCollectorInitializationError, dataCollectorInfo.DataCollectorInformation.FriendlyName, ex.Message)); + this.DisposeDataCollector(dataCollectorInfo); return false; } @@ -712,9 +571,15 @@ private string GetFullyQualifiedAssemblyNameFromFullTypeName(string assemblyQual return assemblyQualifiedName.Substring(firstIndex + 1); } - private void RemoveDataCollectors(IEnumerable dataCollectorsToRemove) + /// + /// The remove data collectors. + /// + /// + /// The data collectors to remove. + /// + private void RemoveDataCollectors(IReadOnlyCollection dataCollectorsToRemove) { - if (null == dataCollectorsToRemove || dataCollectorsToRemove.Count() == 0) + if (dataCollectorsToRemove == null || !dataCollectorsToRemove.Any()) { return; } @@ -726,22 +591,30 @@ private void RemoveDataCollectors(IEnumerable dat this.DisposeDataCollector(dataCollectorToRemove); this.runDataCollectors.Remove(dataCollectorToRemove.DataCollector.GetType()); } + if (this.runDataCollectors.Count == 0) { - this.collectionEnabled = false; + this.isDataCollectionEnabled = false; } } } + /// + /// The dispose data collector. + /// + /// + /// The data collector info. + /// private void DisposeDataCollector(TestPlatformDataCollectorInfo dataCollectorInfo) { - Type dataCollectorType = dataCollectorInfo.DataCollector.GetType(); + var dataCollectorType = dataCollectorInfo.DataCollector.GetType(); try { if (EqtTrace.IsVerboseEnabled) { EqtTrace.Verbose("DataCollectionManager.DisposeDataCollector: calling Dispose() on {0}", dataCollectorType); } + dataCollectorInfo.DisposeDataCollector(); } catch (Exception ex) @@ -753,12 +626,136 @@ private void DisposeDataCollector(TestPlatformDataCollectorInfo dataCollectorInf dataCollectorInfo.Logger.LogError( this.dataCollectionEnvironmentContext.SessionDataCollectionContext, - string.Format( - CultureInfo.CurrentCulture, - Resource.DataCollectorDisposeError, - dataCollectorType, - ex.ToString())); + string.Format(CultureInfo.CurrentCulture, Resource.DataCollectorDisposeError, dataCollectorType, ex.ToString())); + } + } + + /// + /// The get environment variables. + /// + /// + /// The unloaded any collector. + /// + /// + /// Dictionary of variable name as key and collector requested environment variable as value. + /// + private Dictionary GetEnvironmentVariables(out bool unloadedAnyCollector) + { + var failedCollectors = new List(); + unloadedAnyCollector = false; + var dataCollectorEnvironmentVariables = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var dataCollectorInfo in this.GetDataCollectorsSnapshot()) + { + dataCollectorInfo.GetTestExecutionEnvironmentVariables(); + try + { + this.AddCollectorEnvironmentVariables(dataCollectorInfo, dataCollectorEnvironmentVariables); + } + catch (Exception ex) + { + unloadedAnyCollector = true; + + Type dataCollectorType = dataCollectorInfo.DataCollector.GetType(); + failedCollectors.Add(dataCollectorInfo); + dataCollectorInfo.Logger.LogError( + this.dataCollectionEnvironmentContext.SessionDataCollectionContext, + string.Format(CultureInfo.CurrentCulture, Resource.DataCollectorErrorOnGetVariable, dataCollectorType, ex.ToString())); + + if (EqtTrace.IsErrorEnabled) + { + EqtTrace.Error("DataCollectionManager.GetEnvironmentVariables: Failed to get variable for Collector '{0}': {1}", dataCollectorType, ex); + } + } } + + this.RemoveDataCollectors(failedCollectors); + return dataCollectorEnvironmentVariables; } + + /// + /// Gets a snapshot of current data collectors. + /// + /// + /// Collection of TestPlatformDataCollectorInfo. + /// + private List GetDataCollectorsSnapshot() + { + var datacollectorInfoList = new List(); + lock (this.runDataCollectors) + { + foreach (var dataCollectorInfo in this.runDataCollectors.Values) + { + if (dataCollectorInfo != null) + { + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose( + "DataCollectionManager.GetDataCollectorsSnapshot: DataCollector:{0}", + dataCollectorInfo.DataCollectorInformation.FriendlyName); + } + + datacollectorInfoList.Add(dataCollectorInfo); + } + else + { + if (EqtTrace.IsErrorEnabled) + { + EqtTrace.Error("DataCollectionManager.GetDataCollectorsSnapshot: got null data collector info from the data collector info collection (ignored)."); + } + } + } + } + + return datacollectorInfoList; + } + + /// + /// Collects environment variable to be set in test process by avoiding duplicates + /// and detecting override of variable value by multiple adapters. + /// + /// Data collector information for newly loaded plugin. + /// Environment variables required for already loaded plugin. + private void AddCollectorEnvironmentVariables( + TestPlatformDataCollectorInfo dataCollectorInfo, + Dictionary dataCollectorEnvironmentVariables) + { + if (null != dataCollectorInfo.TestExecutionEnvironmentVariables) + { + var collectorFriendlyName = dataCollectorInfo.DataCollectorInformation.FriendlyName; + foreach (var namevaluepair in dataCollectorInfo.TestExecutionEnvironmentVariables) + { + CollectorRequestedEnvironmentVariable alreadyRequestedVariable; + if (dataCollectorEnvironmentVariables.TryGetValue(namevaluepair.Key, out alreadyRequestedVariable)) + { + if (string.Equals(namevaluepair.Value, alreadyRequestedVariable.Value, StringComparison.Ordinal)) + { + alreadyRequestedVariable.AddRequestingDataCollector(collectorFriendlyName); + } + else + { + // Data collector is overriding an already requested variable, possibly an error. + var text = string.Format(CultureInfo.CurrentUICulture, Resource.DataCollectorRequestedDuplicateEnvironmentVariable, collectorFriendlyName, namevaluepair.Key, namevaluepair.Value, alreadyRequestedVariable.FirstDataCollectorThatRequested, alreadyRequestedVariable.Value); + dataCollectorInfo.Logger.LogError( + this.dataCollectionEnvironmentContext.SessionDataCollectionContext, + text); + } + } + else + { + if (EqtTrace.IsVerboseEnabled) + { + // New variable, add to the list. + EqtTrace.Verbose("DataCollectionManager.AddCollectionEnvironmentVariables: Adding Environment variable '{0}' value '{1}'", namevaluepair.Key, namevaluepair.Value); + } + + dataCollectorEnvironmentVariables.Add( + namevaluepair.Key, + new CollectorRequestedEnvironmentVariable(namevaluepair, collectorFriendlyName)); + } + } + } + } + + #endregion } } \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.DataCollection/DataCollectorDataMessage.cs b/src/Microsoft.TestPlatform.DataCollection.v1/DataCollectorDataMessage.cs similarity index 89% rename from src/Microsoft.TestPlatform.DataCollection/DataCollectorDataMessage.cs rename to src/Microsoft.TestPlatform.DataCollection.v1/DataCollectorDataMessage.cs index 84b11b3d87..854bbb27d1 100644 --- a/src/Microsoft.TestPlatform.DataCollection/DataCollectorDataMessage.cs +++ b/src/Microsoft.TestPlatform.DataCollection.v1/DataCollectorDataMessage.cs @@ -1,13 +1,11 @@ // Copyright (c) Microsoft. All rights reserved. - -namespace Microsoft.VisualStudio.TestPlatform.DataCollection +namespace Microsoft.VisualStudio.TestPlatform.DataCollection.V1 { using System; using System.ComponentModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel; - - using DataCollectionContext = Microsoft.VisualStudio.TestTools.Execution.DataCollectionContext; + using Microsoft.VisualStudio.TestTools.Execution; /// /// Base class for all message used in file transfer. @@ -25,8 +23,8 @@ internal class DataCollectorDataMessage /// internal DataCollectorDataMessage(DataCollectionContext context, Uri uri, string friendlyName) { - ValidateArg.NotNull(context, "context"); - ValidateArg.NotNull(uri, "uri"); + ValidateArg.NotNull(context, "context"); + ValidateArg.NotNull(uri, "uri"); ValidateArg.NotNullOrEmpty(friendlyName, "friendlyName"); this.DataCollectionContext = context; @@ -98,18 +96,18 @@ internal sealed class FileDataHeaderMessage : DataCollectorDataMessage /// Friendly name of the collector initiating the transfer /// internal FileDataHeaderMessage( - DataCollectionContext context, - string fileName, - string description, - bool deleteFile, - object userToken, - AsyncCompletedEventHandler fileTransferCompletedHandler, - Uri collectorUri, + DataCollectionContext context, + string fileName, + string description, + bool deleteFile, + object userToken, + AsyncCompletedEventHandler fileTransferCompletedHandler, + Uri collectorUri, string collectorFriendlyName) : base(context, collectorUri, collectorFriendlyName) { ValidateArg.NotNullOrEmpty(fileName, "fileName"); - ValidateArg.NotNull(description, "description"); + ValidateArg.NotNull(description, "description"); this.FileName = fileName; this.PerformCleanup = deleteFile; diff --git a/src/Microsoft.TestPlatform.DataCollection.v1/Friends.cs b/src/Microsoft.TestPlatform.DataCollection.v1/Friends.cs new file mode 100644 index 0000000000..12b1f42946 --- /dev/null +++ b/src/Microsoft.TestPlatform.DataCollection.v1/Friends.cs @@ -0,0 +1,7 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Runtime.CompilerServices; + +#region TestAssemblies +[assembly: InternalsVisibleTo("Microsoft.TestPlatform.DataCollection.v1.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] +#endregion \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.DataCollection/Interfaces/IMessageSink.cs b/src/Microsoft.TestPlatform.DataCollection.v1/Interfaces/IMessageSink.cs similarity index 93% rename from src/Microsoft.TestPlatform.DataCollection/Interfaces/IMessageSink.cs rename to src/Microsoft.TestPlatform.DataCollection.v1/Interfaces/IMessageSink.cs index 3078c42e32..4166005a5c 100644 --- a/src/Microsoft.TestPlatform.DataCollection/Interfaces/IMessageSink.cs +++ b/src/Microsoft.TestPlatform.DataCollection.v1/Interfaces/IMessageSink.cs @@ -1,6 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.VisualStudio.TestPlatform.DataCollection.Interfaces +namespace Microsoft.VisualStudio.TestPlatform.DataCollection.V1.Interfaces { using System; diff --git a/src/Microsoft.TestPlatform.DataCollection/Microsoft.TestPlatform.DataCollection.xproj b/src/Microsoft.TestPlatform.DataCollection.v1/Microsoft.TestPlatform.DataCollection.v1.xproj similarity index 97% rename from src/Microsoft.TestPlatform.DataCollection/Microsoft.TestPlatform.DataCollection.xproj rename to src/Microsoft.TestPlatform.DataCollection.v1/Microsoft.TestPlatform.DataCollection.v1.xproj index fc26d4cd0c..75df9077f6 100644 --- a/src/Microsoft.TestPlatform.DataCollection/Microsoft.TestPlatform.DataCollection.xproj +++ b/src/Microsoft.TestPlatform.DataCollection.v1/Microsoft.TestPlatform.DataCollection.v1.xproj @@ -7,7 +7,7 @@ d5c951f1-b6ff-4de2-ad89-f5c5803de126 - Microsoft.VisualStudio.TestPlatform.DataCollection + Microsoft.VisualStudio.TestPlatform.DataCollection.V1 ..\..\artifacts\obj\$(MSBuildProjectName) ..\..\artifacts\ v4.5.2 diff --git a/src/Microsoft.TestPlatform.DataCollection/Properties/AssemblyInfo.cs b/src/Microsoft.TestPlatform.DataCollection.v1/Properties/AssemblyInfo.cs similarity index 100% rename from src/Microsoft.TestPlatform.DataCollection/Properties/AssemblyInfo.cs rename to src/Microsoft.TestPlatform.DataCollection.v1/Properties/AssemblyInfo.cs diff --git a/src/Microsoft.TestPlatform.DataCollection/Resource.Designer.cs b/src/Microsoft.TestPlatform.DataCollection.v1/Resource.Designer.cs similarity index 98% rename from src/Microsoft.TestPlatform.DataCollection/Resource.Designer.cs rename to src/Microsoft.TestPlatform.DataCollection.v1/Resource.Designer.cs index 7a1d0016e8..c3be4bca9c 100644 --- a/src/Microsoft.TestPlatform.DataCollection/Resource.Designer.cs +++ b/src/Microsoft.TestPlatform.DataCollection.v1/Resource.Designer.cs @@ -8,7 +8,7 @@ // //------------------------------------------------------------------------------ -namespace Microsoft.VisualStudio.TestPlatform.DataCollection { +namespace Microsoft.VisualStudio.TestPlatform.DataCollection.V1 { using System; using System.Reflection; @@ -38,7 +38,7 @@ public class Resource { public static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.TestPlatform.DataCollection.Resource", typeof(Resource).GetTypeInfo().Assembly); + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.TestPlatform.DataCollection.v1.Resource", typeof(Resource).GetTypeInfo().Assembly); resourceMan = temp; } return resourceMan; diff --git a/src/Microsoft.TestPlatform.DataCollection/Resource.resx b/src/Microsoft.TestPlatform.DataCollection.v1/Resource.resx similarity index 100% rename from src/Microsoft.TestPlatform.DataCollection/Resource.resx rename to src/Microsoft.TestPlatform.DataCollection.v1/Resource.resx diff --git a/src/Microsoft.TestPlatform.DataCollection/TestPlatformDataCollectionEvents.cs b/src/Microsoft.TestPlatform.DataCollection.v1/TestPlatformDataCollectionEvents.cs similarity index 99% rename from src/Microsoft.TestPlatform.DataCollection/TestPlatformDataCollectionEvents.cs rename to src/Microsoft.TestPlatform.DataCollection.v1/TestPlatformDataCollectionEvents.cs index 73c0da2716..a16bf81f55 100644 --- a/src/Microsoft.TestPlatform.DataCollection/TestPlatformDataCollectionEvents.cs +++ b/src/Microsoft.TestPlatform.DataCollection.v1/TestPlatformDataCollectionEvents.cs @@ -1,6 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.VisualStudio.TestPlatform.DataCollection +namespace Microsoft.VisualStudio.TestPlatform.DataCollection.V1 { using System; using System.Collections.Generic; @@ -15,7 +15,6 @@ namespace Microsoft.VisualStudio.TestPlatform.DataCollection using Microsoft.VisualStudio.TestTools.DataCollection; using Microsoft.VisualStudio.TestTools.Execution; - /// /// Class defining execution events that will be registered for by collectors /// @@ -50,7 +49,7 @@ internal TestPlatformDataCollectionEvents(SafeAbortableUserWorkItemFactory userW EqtAssert.ParameterNotNull(userWorkItemFactory, "userWorkItemFactory"); this.userWorkItemFactory = userWorkItemFactory; - this.eventArgsToEventInvokerMap = new Dictionary(12); + this.eventArgsToEventInvokerMap = new Dictionary(14); this.eventArgsToEventInvokerMap[typeof(SessionStartEventArgs)] = this.OnSessionStart; this.eventArgsToEventInvokerMap[typeof(SessionEndEventArgs)] = this.OnSessionEnd; diff --git a/src/Microsoft.TestPlatform.DataCollection/TestPlatformDataCollectionLogger.cs b/src/Microsoft.TestPlatform.DataCollection.v1/TestPlatformDataCollectionLogger.cs similarity index 80% rename from src/Microsoft.TestPlatform.DataCollection/TestPlatformDataCollectionLogger.cs rename to src/Microsoft.TestPlatform.DataCollection.v1/TestPlatformDataCollectionLogger.cs index 7b0e4f6355..512acdbeb1 100644 --- a/src/Microsoft.TestPlatform.DataCollection/TestPlatformDataCollectionLogger.cs +++ b/src/Microsoft.TestPlatform.DataCollection.v1/TestPlatformDataCollectionLogger.cs @@ -1,12 +1,12 @@ // Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.VisualStudio.TestPlatform.DataCollection.Implementations +namespace Microsoft.VisualStudio.TestPlatform.DataCollection.V1 { using System; using System.Diagnostics; using System.Globalization; - using Microsoft.VisualStudio.TestPlatform.DataCollection.Interfaces; + using Microsoft.VisualStudio.TestPlatform.DataCollection.V1.Interfaces; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; @@ -21,13 +21,20 @@ internal sealed class TestPlatformDataCollectionLogger : DataCollectionLogger { #region Private Fields + /// + /// The data collector information. + /// private readonly DataCollectorInformation dataCollectorInformation; + + /// + /// The sink. + /// private readonly IMessageSink sink; #endregion /// - /// Constructs a DataCollectionLogger + /// Initializes a new instance of the class. /// /// /// The underlying raw IMessageSink. Cannot be null. @@ -37,8 +44,8 @@ internal sealed class TestPlatformDataCollectionLogger : DataCollectionLogger /// internal TestPlatformDataCollectionLogger(IMessageSink sink, DataCollectorInformation dataCollectorInformation) { - ValidateArg.NotNull(dataCollectorInformation, "dataCollectorInformation"); - ValidateArg.NotNull(sink, "sink"); + ValidateArg.NotNull(dataCollectorInformation, "dataCollectorInformation"); + ValidateArg.NotNull(sink, "sink"); this.dataCollectorInformation = dataCollectorInformation; this.sink = sink; } @@ -52,8 +59,8 @@ internal TestPlatformDataCollectionLogger(IMessageSink sink, DataCollectorInform /// The error text. Cannot be null. public override void LogError(DataCollectionContext context, string text) { - ValidateArg.NotNull(context, "context"); - ValidateArg.NotNull(text, "text"); + ValidateArg.NotNull(context, "context"); + ValidateArg.NotNull(text, "text"); if (EqtTrace.IsErrorEnabled) { @@ -74,9 +81,9 @@ public override void LogError(DataCollectionContext context, string text) /// The exception. Cannot be null. public override void LogError(DataCollectionContext context, string text, Exception exception) { - ValidateArg.NotNull(context, "context"); - ValidateArg.NotNull(text, "text"); - ValidateArg.NotNull(exception, "exception"); + ValidateArg.NotNull(context, "context"); + ValidateArg.NotNull(text, "text"); + ValidateArg.NotNull(exception, "exception"); // Make sure the data collection context is not a derived data collection context. This // is done to safeguard from 3rd parties creating their own data collection contexts. @@ -110,7 +117,6 @@ public override void LogError(DataCollectionContext context, string text, Except this.SendTextMessage(context, message, TestMessageLevel.Error); } - /// /// Logs a warning. /// @@ -118,8 +124,8 @@ public override void LogError(DataCollectionContext context, string text, Except /// The warning text. Cannot be null. public override void LogWarning(DataCollectionContext context, string text) { - ValidateArg.NotNull(context, "context"); - ValidateArg.NotNull(text, "text"); + ValidateArg.NotNull(context, "context"); + ValidateArg.NotNull(text, "text"); EqtTrace.Warning( "Data collector '{0}' logged the following warning: {1}", this.dataCollectorInformation.TypeUri, @@ -132,10 +138,24 @@ public override void LogWarning(DataCollectionContext context, string text) #region Private Methods + /// + /// The send text message. + /// + /// + /// The context. + /// + /// + /// The text. + /// + /// + /// The level. + /// + /// Throws InvalidOperationException. + /// private void SendTextMessage(DataCollectionContext context, string text, TestMessageLevel level) { - ValidateArg.NotNull(context, "context"); - ValidateArg.NotNull(text, "text"); + ValidateArg.NotNull(context, "context"); + ValidateArg.NotNull(text, "text"); Debug.Assert( level >= TestMessageLevel.Informational && level <= TestMessageLevel.Error, diff --git a/src/Microsoft.TestPlatform.DataCollection/TestPlatformDataCollectionSink.cs b/src/Microsoft.TestPlatform.DataCollection.v1/TestPlatformDataCollectionSink.cs similarity index 89% rename from src/Microsoft.TestPlatform.DataCollection/TestPlatformDataCollectionSink.cs rename to src/Microsoft.TestPlatform.DataCollection.v1/TestPlatformDataCollectionSink.cs index c03c02f40f..64efde01e3 100644 --- a/src/Microsoft.TestPlatform.DataCollection/TestPlatformDataCollectionSink.cs +++ b/src/Microsoft.TestPlatform.DataCollection.v1/TestPlatformDataCollectionSink.cs @@ -1,12 +1,12 @@ // Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.VisualStudio.TestPlatform.DataCollection +namespace Microsoft.VisualStudio.TestPlatform.DataCollection.V1 { using System; using System.ComponentModel; using System.Diagnostics; - using Microsoft.VisualStudio.TestPlatform.DataCollection.Interfaces; + using Microsoft.VisualStudio.TestPlatform.DataCollection.V1.Interfaces; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using DataCollectionSink = Microsoft.VisualStudio.TestTools.Execution.DataCollectionSink; @@ -29,8 +29,8 @@ internal sealed class TestPlatformDataCollectionSink : DataCollectionSink /// internal TestPlatformDataCollectionSink(IMessageSink messageSink, DataCollectorInformation dataCollectorInfo) { - ValidateArg.NotNull(messageSink, "messageSink"); - ValidateArg.NotNull(dataCollectorInfo, "dataCollectorInfo"); + ValidateArg.NotNull(messageSink, "messageSink"); + ValidateArg.NotNull(dataCollectorInfo, "dataCollectorInfo"); this.CollectorInfo = dataCollectorInfo; this.MessageSink = messageSink; } @@ -69,7 +69,7 @@ private DataCollectorInformation CollectorInfo /// Information about the file being transferred. public override void SendFileAsync(FileTransferInformation fileTransferInformation) { - ValidateArg.NotNull(fileTransferInformation, "fileTransferInformation"); + ValidateArg.NotNull(fileTransferInformation, "fileTransferInformation"); var transferCompletedHandler = this.SendFileCompleted; @@ -88,7 +88,6 @@ public override void SendFileAsync(FileTransferInformation fileTransferInformati this.MessageSink.SendMessage(headerMsg); } - /// /// Sends a stream asynchronously. /// diff --git a/src/Microsoft.TestPlatform.DataCollection/TestPlatformDataCollectorDiscovery.cs b/src/Microsoft.TestPlatform.DataCollection.v1/TestPlatformDataCollectorDiscovery.cs similarity index 93% rename from src/Microsoft.TestPlatform.DataCollection/TestPlatformDataCollectorDiscovery.cs rename to src/Microsoft.TestPlatform.DataCollection.v1/TestPlatformDataCollectorDiscovery.cs index d307de9f4d..557128e63d 100644 --- a/src/Microsoft.TestPlatform.DataCollection/TestPlatformDataCollectorDiscovery.cs +++ b/src/Microsoft.TestPlatform.DataCollection.v1/TestPlatformDataCollectorDiscovery.cs @@ -1,27 +1,19 @@ // Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.VisualStudio.TestPlatform.DataCollection.Implementations +namespace Microsoft.VisualStudio.TestPlatform.DataCollection.V1 { using System; using System.Collections.Generic; - using System.Collections.Specialized; using System.Configuration; using System.Diagnostics; - using System.Globalization; using System.IO; - using System.Linq; using System.Reflection; - using System.Threading.Tasks; using System.Xml; using Microsoft.VisualStudio.TestPlatform.Common; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.Utilities; - using DataCollectionEnvironmentContext = Microsoft.VisualStudio.TestTools.Execution.DataCollectionEnvironmentContext; - using DataCollector = Microsoft.VisualStudio.TestTools.Execution.DataCollector; - using DataCollectorInformation = Microsoft.VisualStudio.TestTools.Execution.DataCollectorInformation; - /// /// Discovers data collectors in a directory. /// @@ -34,8 +26,14 @@ internal class DataCollectorDiscoveryHelper /// public const string DataCollectorsDirectoryName = @"PrivateAssemblies\DataCollectors"; + /// + /// The current process location. + /// private static readonly string CurrentProcessLocation = Process.GetCurrentProcess().MainModule.FileName; + /// + /// The is portable. + /// private static readonly bool IsPortable = ClientUtilities.CheckIfTestProcessIsRunningInXcopyableMode(); #endregion @@ -87,17 +85,17 @@ internal static XmlDocument GetConfigurationForAssembly(Assembly assembly) // we will fallback to assembly's default language. The default language can be null as well. // // TODO: Currently we are doing a partial fix by falling back to neutral language which might not work on jpn OS with german VS. - var assemblyUILanguage = GetAssemblyCultureLanguage(assembly); - if (assemblyUILanguage != null && !subFolders.Contains(assemblyUILanguage)) + var assemblyUiLanguage = GetAssemblyCultureLanguage(assembly); + if (assemblyUiLanguage != null && !subFolders.Contains(assemblyUiLanguage)) { - subFolders.Add(assemblyUILanguage); + subFolders.Add(assemblyUiLanguage); } // To search in current folder subFolders.Add(string.Empty); var configFilePath = string.Empty; - int iFolder = 0; + var iFolder = 0; for (; iFolder < subFolders.Count; iFolder++) { configFilePath = @@ -169,6 +167,7 @@ private static string GetDataCollectorPluginDirectory() { EqtTrace.Verbose("TestPlatformDataCollectorDiscovery.GetDataCollectorPluginDirectory: Using config specified plugin directory '{0}'.", directoryFromConfig); } + return directoryFromConfig; } else @@ -280,13 +279,14 @@ private static string GetInstallLocationFromRegistry(string skuInstallLocation) { try { - var directoryFromRegistry = Path.Combine(skuInstallLocation, DataCollectorDiscoveryHelper.DataCollectorsDirectoryName); + var directoryFromRegistry = Path.Combine(skuInstallLocation, DataCollectorsDirectoryName); if (Directory.Exists(directoryFromRegistry)) { if (EqtTrace.IsVerboseEnabled) { EqtTrace.Verbose("TestPlatformDataCollectorDiscovery.GetDataCollectorPluginDirectory: Using plugin directory '{0}' relative to directory location in registry.", directoryFromRegistry); } + return directoryFromRegistry; } else diff --git a/src/Microsoft.TestPlatform.DataCollection/TestPlatformDataCollectorInfo.cs b/src/Microsoft.TestPlatform.DataCollection.v1/TestPlatformDataCollectorInfo.cs similarity index 91% rename from src/Microsoft.TestPlatform.DataCollection/TestPlatformDataCollectorInfo.cs rename to src/Microsoft.TestPlatform.DataCollection.v1/TestPlatformDataCollectorInfo.cs index c41f1365ca..de90ebaee0 100644 --- a/src/Microsoft.TestPlatform.DataCollection/TestPlatformDataCollectorInfo.cs +++ b/src/Microsoft.TestPlatform.DataCollection.v1/TestPlatformDataCollectorInfo.cs @@ -1,14 +1,12 @@ // Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.VisualStudio.TestPlatform.DataCollection +namespace Microsoft.VisualStudio.TestPlatform.DataCollection.V1 { using System; using System.Collections.Generic; using System.Xml; - using Microsoft.VisualStudio.TestPlatform.DataCollection.Implementations; - - using Microsoft.VisualStudio.TestPlatform.DataCollection.Interfaces; + using Microsoft.VisualStudio.TestPlatform.DataCollection.V1.Interfaces; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestTools.Common; @@ -51,10 +49,10 @@ public class TestPlatformDataCollectorInfo DataCollectorInformation dataCollectorInformation, SafeAbortableUserWorkItemFactory userWorkItemFactory) { - ValidateArg.NotNull(dataCollector, "dataCollector"); - ValidateArg.NotNull(messageSink, "messageSink"); - ValidateArg.NotNull(dataCollectorInformation, "dataCollectorInformation"); - ValidateArg.NotNull(userWorkItemFactory, "userWorkItemFactory"); + ValidateArg.NotNull(dataCollector, "dataCollector"); + ValidateArg.NotNull(messageSink, "messageSink"); + ValidateArg.NotNull(dataCollectorInformation, "dataCollectorInformation"); + ValidateArg.NotNull(userWorkItemFactory, "userWorkItemFactory"); this.DataCollectorInformation = dataCollectorInformation; this.DataCollector = dataCollector; diff --git a/src/Microsoft.TestPlatform.DataCollection/project.json b/src/Microsoft.TestPlatform.DataCollection.v1/project.json similarity index 86% rename from src/Microsoft.TestPlatform.DataCollection/project.json rename to src/Microsoft.TestPlatform.DataCollection.v1/project.json index 4d02fcfd6d..7990bb05ce 100644 --- a/src/Microsoft.TestPlatform.DataCollection/project.json +++ b/src/Microsoft.TestPlatform.DataCollection.v1/project.json @@ -4,7 +4,6 @@ "delaySign": true, "keyFile": "../../scripts/key.snk", "warningsAsErrors": true - //"copyToOutput": "Microsoft.TestPlatform.DataCollection.Legacy" }, "runtimes": { @@ -22,7 +21,7 @@ }, "frameworks": { - "net461": { + "net46": { } } } diff --git a/src/Microsoft.TestPlatform.DataCollection/Friends.cs b/src/Microsoft.TestPlatform.DataCollection/Friends.cs deleted file mode 100644 index 37685a9a75..0000000000 --- a/src/Microsoft.TestPlatform.DataCollection/Friends.cs +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.Runtime.CompilerServices; - -#region TestAssemblies -[assembly: InternalsVisibleTo("Microsoft.TestPlatform.DataCollection.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] -#endregion \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.ObjectModel/project.json b/src/Microsoft.TestPlatform.ObjectModel/project.json index adcc122f6d..622c76a107 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/project.json +++ b/src/Microsoft.TestPlatform.ObjectModel/project.json @@ -1,7 +1,7 @@ { "version": "15.0.0-*", "buildOptions": { - "outputName": "Microsoft.VisualStudio.TestPlatform.ObjectModel", + //"outputName": "Microsoft.VisualStudio.TestPlatform.ObjectModel", "delaySign": true, "keyFile": "../../scripts/key.snk", "warningsAsErrors": true diff --git a/test/Microsoft.TestPlatform.DataCollection.UnitTests/CollectorRequestedEnvironmentVariableTests.cs b/test/Microsoft.TestPlatform.DataCollection.v1.UnitTests/CollectorRequestedEnvironmentVariableTests.cs similarity index 78% rename from test/Microsoft.TestPlatform.DataCollection.UnitTests/CollectorRequestedEnvironmentVariableTests.cs rename to test/Microsoft.TestPlatform.DataCollection.v1.UnitTests/CollectorRequestedEnvironmentVariableTests.cs index 11eeb98c6e..7befb188ab 100644 --- a/test/Microsoft.TestPlatform.DataCollection.UnitTests/CollectorRequestedEnvironmentVariableTests.cs +++ b/test/Microsoft.TestPlatform.DataCollection.v1.UnitTests/CollectorRequestedEnvironmentVariableTests.cs @@ -1,22 +1,28 @@ // Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.TestPlatform.DataCollection.UnitTests.Implementations +namespace Microsoft.TestPlatform.DataCollection.V1.UnitTests { using System.Collections.Generic; - using Microsoft.VisualStudio.TestPlatform.DataCollection; + using Microsoft.VisualStudio.TestPlatform.DataCollection.V1; using Microsoft.VisualStudio.TestTools.UnitTesting; [TestClass] public class CollectorRequestedEnvironmentVariableTests { + [TestInitialize] + public void Init() + { + + } + [TestMethod] public void AddRequestingDataCollectorShouldAddDataCollectorName() { var friendlyName = "DataCollectorFriendlyName"; - var kv = new KeyValuePair("key", "value"); + var kvpair = new KeyValuePair("key", "value"); - var collectorRequestedEnvironmentVariable = new CollectorRequestedEnvironmentVariable(kv, friendlyName); + var collectorRequestedEnvironmentVariable = new CollectorRequestedEnvironmentVariable(kvpair, friendlyName); Assert.AreEqual("key", collectorRequestedEnvironmentVariable.Name); Assert.AreEqual("value", collectorRequestedEnvironmentVariable.Value); @@ -29,9 +35,9 @@ public void AddRequestingDataCollectorShouldAddDataCollectorName() public void FirstDataCollectorThatRequestedShouldReturnTheNameOfFirstRequestingDataCollector() { var friendlyName = "DataCollectorFriendlyName"; - var kv = new KeyValuePair("key", "value"); + var kvpair = new KeyValuePair("key", "value"); - var collectorRequestedEnvironmentVariable = new CollectorRequestedEnvironmentVariable(kv, friendlyName); + var collectorRequestedEnvironmentVariable = new CollectorRequestedEnvironmentVariable(kvpair, friendlyName); collectorRequestedEnvironmentVariable.AddRequestingDataCollector("DataCollectorFriendlyName1"); Assert.AreEqual("key", collectorRequestedEnvironmentVariable.Name); diff --git a/test/Microsoft.TestPlatform.DataCollection.UnitTests/DataCollectionPluginManagerTests.cs b/test/Microsoft.TestPlatform.DataCollection.v1.UnitTests/DataCollectionPluginManagerTests.cs similarity index 95% rename from test/Microsoft.TestPlatform.DataCollection.UnitTests/DataCollectionPluginManagerTests.cs rename to test/Microsoft.TestPlatform.DataCollection.v1.UnitTests/DataCollectionPluginManagerTests.cs index 061790d485..e2b3985f42 100644 --- a/test/Microsoft.TestPlatform.DataCollection.UnitTests/DataCollectionPluginManagerTests.cs +++ b/test/Microsoft.TestPlatform.DataCollection.v1.UnitTests/DataCollectionPluginManagerTests.cs @@ -1,6 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.TestPlatform.DataCollection.UnitTests +namespace Microsoft.TestPlatform.DataCollection.V1.UnitTests { using System; using System.Collections.Generic; @@ -13,14 +13,14 @@ namespace Microsoft.TestPlatform.DataCollection.UnitTests using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework; using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.Utilities; using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; - using Microsoft.VisualStudio.TestPlatform.DataCollection; + using Microsoft.VisualStudio.TestPlatform.DataCollection.V1; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; using Microsoft.VisualStudio.TestTools.Execution; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; - using IMessageSink = Microsoft.VisualStudio.TestPlatform.DataCollection.Interfaces.IMessageSink; + using IMessageSink = Microsoft.VisualStudio.TestPlatform.DataCollection.V1.Interfaces.IMessageSink; [TestClass] public class DataCollectionManagerTests @@ -29,7 +29,7 @@ public class DataCollectionManagerTests private DummyMessageSink mockMessageSink; private RunSettings runSettings; private string xmlSettings = - "\r\n\r\n \r\n 1\r\n .\\TestResults\r\n x86\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n"; + "\r\n\r\n \r\n 1\r\n .\\TestResults\r\n x86\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n"; [TestInitialize] public void Init() @@ -53,8 +53,7 @@ public void Cleanup() [TestMethod] public void LoadDataCollectorShouldLoadDataCollectorAndReturnEnvironmentVariables() { - var envVarList = new List>(); - envVarList.Add(new KeyValuePair("key", "value")); + var envVarList = new List> { new KeyValuePair("key", "value") }; MockDataCollector.EnvVarList = envVarList; this.runSettings.InitializeSettingsProviders(this.xmlSettings); @@ -176,10 +175,8 @@ public class MockDataCollector : DataCollector, ITestExecutionEnvironmentSpecifi public static bool IsGetTestExecutionEnvironmentVariablesInvoked; public static bool GetTestExecutionEnvironmentVariablesThrowException; public static bool DisposeShouldThrowException; - public static IEnumerable> EnvVarList; - public override void Initialize(XmlElement configurationElement, DataCollectionEvents events, DataCollectionSink dataSink, DataCollectionLogger logger, DataCollectionEnvironmentContext environmentContext) { if (ThrowExceptionWhenInitialized) diff --git a/test/Microsoft.TestPlatform.DataCollection.UnitTests/Microsoft.TestPlatform.DataCollection.UnitTests.xproj b/test/Microsoft.TestPlatform.DataCollection.v1.UnitTests/Microsoft.TestPlatform.DataCollection.v1.UnitTests.xproj similarity index 92% rename from test/Microsoft.TestPlatform.DataCollection.UnitTests/Microsoft.TestPlatform.DataCollection.UnitTests.xproj rename to test/Microsoft.TestPlatform.DataCollection.v1.UnitTests/Microsoft.TestPlatform.DataCollection.v1.UnitTests.xproj index 2038075764..730b72e156 100644 --- a/test/Microsoft.TestPlatform.DataCollection.UnitTests/Microsoft.TestPlatform.DataCollection.UnitTests.xproj +++ b/test/Microsoft.TestPlatform.DataCollection.v1.UnitTests/Microsoft.TestPlatform.DataCollection.v1.UnitTests.xproj @@ -7,7 +7,7 @@ 07d89fbd-f76d-40ef-a0ca-9213dee61941 - Microsoft.TestPlatform.DataCollection.UnitTests + Microsoft.TestPlatform.DataCollection.V1.UnitTests ..\..\artifacts\obj\$(MSBuildProjectName) ..\..\artifacts\ v4.5.2 diff --git a/test/Microsoft.TestPlatform.DataCollection.UnitTests/Properties/AssemblyInfo.cs b/test/Microsoft.TestPlatform.DataCollection.v1.UnitTests/Properties/AssemblyInfo.cs similarity index 100% rename from test/Microsoft.TestPlatform.DataCollection.UnitTests/Properties/AssemblyInfo.cs rename to test/Microsoft.TestPlatform.DataCollection.v1.UnitTests/Properties/AssemblyInfo.cs diff --git a/test/Microsoft.TestPlatform.DataCollection.UnitTests/TestPlatformDataCollectorInfoTests.cs b/test/Microsoft.TestPlatform.DataCollection.v1.UnitTests/TestPlatformDataCollectorInfoTests.cs similarity index 96% rename from test/Microsoft.TestPlatform.DataCollection.UnitTests/TestPlatformDataCollectorInfoTests.cs rename to test/Microsoft.TestPlatform.DataCollection.v1.UnitTests/TestPlatformDataCollectorInfoTests.cs index e707e06989..2466575cab 100644 --- a/test/Microsoft.TestPlatform.DataCollection.UnitTests/TestPlatformDataCollectorInfoTests.cs +++ b/test/Microsoft.TestPlatform.DataCollection.v1.UnitTests/TestPlatformDataCollectorInfoTests.cs @@ -1,6 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.TestPlatform.DataCollection.UnitTests +namespace Microsoft.TestPlatform.DataCollection.V1.UnitTests { using System; using System.Collections.Generic; @@ -8,8 +8,8 @@ namespace Microsoft.TestPlatform.DataCollection.UnitTests using System.Threading.Tasks; using System.Xml; - using Microsoft.VisualStudio.TestPlatform.DataCollection; - using Microsoft.VisualStudio.TestPlatform.DataCollection.Interfaces; + using Microsoft.VisualStudio.TestPlatform.DataCollection.V1; + using Microsoft.VisualStudio.TestPlatform.DataCollection.V1.Interfaces; using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection; using Microsoft.VisualStudio.TestTools.UnitTesting; diff --git a/test/Microsoft.TestPlatform.DataCollection.UnitTests/app.config b/test/Microsoft.TestPlatform.DataCollection.v1.UnitTests/app.config similarity index 100% rename from test/Microsoft.TestPlatform.DataCollection.UnitTests/app.config rename to test/Microsoft.TestPlatform.DataCollection.v1.UnitTests/app.config diff --git a/test/Microsoft.TestPlatform.DataCollection.UnitTests/project.json b/test/Microsoft.TestPlatform.DataCollection.v1.UnitTests/project.json similarity index 87% rename from test/Microsoft.TestPlatform.DataCollection.UnitTests/project.json rename to test/Microsoft.TestPlatform.DataCollection.v1.UnitTests/project.json index e262758114..d505a62f2e 100644 --- a/test/Microsoft.TestPlatform.DataCollection.UnitTests/project.json +++ b/test/Microsoft.TestPlatform.DataCollection.v1.UnitTests/project.json @@ -17,12 +17,12 @@ "MSTest.TestFramework": "1.0.0-preview", "moq.netcore": "4.4.0-beta8", "System.Diagnostics.TraceSource": "4.0.0-rc2-24015", - "Microsoft.TestPlatform.DataCollection": "15.0.0-*", + "Microsoft.TestPlatform.DataCollection.v1": "15.0.0-*", "Microsoft.VisualStudio.QualityTools.ExecutionCommon": "1.0.0" }, "frameworks": { - "net461": { } + "net46": { } }, "testRunner": "mstest" diff --git a/test/datacollector.UnitTests/DataCollectionCoordinatorTests.cs b/test/datacollector.UnitTests/DataCollectionCoordinatorTests.cs index 35aeb46fd0..82bf15053c 100644 --- a/test/datacollector.UnitTests/DataCollectionCoordinatorTests.cs +++ b/test/datacollector.UnitTests/DataCollectionCoordinatorTests.cs @@ -26,7 +26,7 @@ public void Initialize() { this.dummyDataCollectionManagerV1 = new DummyDataCollectionManager(); this.dummyDataCollectionManagerV2 = new DummyDataCollectionManager(); - this.dataCollectionCoordinator = new DataCollectionCoordinator(new[] { dummyDataCollectionManagerV1, dummyDataCollectionManagerV2 }); + this.dataCollectionCoordinator = new DataCollectionCoordinator(new[] { this.dummyDataCollectionManagerV1, this.dummyDataCollectionManagerV2 }); } [TestMethod] @@ -156,7 +156,7 @@ public void DisposeShouldCallDisposeOfDataCollectionManagers() } [TestMethod] - public void DisposeShouldDisposeResourcesIfNoDataCollectionManagersAreProvided() + public void DisposeShouldDisposeResourcesIfDataCollectionManagersAreNotProvided() { this.dataCollectionCoordinator = new DataCollectionCoordinator(null); @@ -192,7 +192,6 @@ public void Dispose() this.IsLoadCollectorsInvoked = true; return this.EnvVariables; - } public void TestCaseStarted(TestCaseStartEventArgs testCaseStartEventArgs) From 3f1c5ce6eec4d9c650a55e95513c310cc12301e8 Mon Sep 17 00:00:00 2001 From: Harsh Jain Date: Sat, 6 Aug 2016 11:29:25 +0530 Subject: [PATCH 3/5] Added tests for Client Utilities. --- ...ery.cs => DataCollectorDiscoveryHelper.cs} | 9 +- .../project.json | 2 +- .../ClientUtilities.cs | 216 ++---------------- .../project.json | 11 +- .../ClientUtilitiesTests.cs | 28 +++ 5 files changed, 52 insertions(+), 214 deletions(-) rename src/Microsoft.TestPlatform.DataCollection.v1/{TestPlatformDataCollectorDiscovery.cs => DataCollectorDiscoveryHelper.cs} (98%) diff --git a/src/Microsoft.TestPlatform.DataCollection.v1/TestPlatformDataCollectorDiscovery.cs b/src/Microsoft.TestPlatform.DataCollection.v1/DataCollectorDiscoveryHelper.cs similarity index 98% rename from src/Microsoft.TestPlatform.DataCollection.v1/TestPlatformDataCollectorDiscovery.cs rename to src/Microsoft.TestPlatform.DataCollection.v1/DataCollectorDiscoveryHelper.cs index 557128e63d..004838c087 100644 --- a/src/Microsoft.TestPlatform.DataCollection.v1/TestPlatformDataCollectorDiscovery.cs +++ b/src/Microsoft.TestPlatform.DataCollection.v1/DataCollectorDiscoveryHelper.cs @@ -34,7 +34,7 @@ internal class DataCollectorDiscoveryHelper /// /// The is portable. /// - private static readonly bool IsPortable = ClientUtilities.CheckIfTestProcessIsRunningInXcopyableMode(); + private static readonly bool IsPortable = ClientUtilities.IsTestProcessRunningInXcopyableMode(); #endregion @@ -201,12 +201,7 @@ private static string GetDataCollectorPluginDirectory() } // Third option: Look for VS Installation directory from registry key. If found and valid use that. - var path = GetInstallLocationFromRegistry(ClientUtilities.GetVSInstallPath()); - - if (!string.IsNullOrEmpty(path)) - { - return path; - } + // todo : write code to get VS Installation Path. return string.Empty; } diff --git a/src/Microsoft.TestPlatform.ObjectModel/project.json b/src/Microsoft.TestPlatform.ObjectModel/project.json index 622c76a107..adcc122f6d 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/project.json +++ b/src/Microsoft.TestPlatform.ObjectModel/project.json @@ -1,7 +1,7 @@ { "version": "15.0.0-*", "buildOptions": { - //"outputName": "Microsoft.VisualStudio.TestPlatform.ObjectModel", + "outputName": "Microsoft.VisualStudio.TestPlatform.ObjectModel", "delaySign": true, "keyFile": "../../scripts/key.snk", "warningsAsErrors": true diff --git a/src/Microsoft.TestPlatform.Utilities/ClientUtilities.cs b/src/Microsoft.TestPlatform.Utilities/ClientUtilities.cs index 481aaf4511..b109f356d6 100644 --- a/src/Microsoft.TestPlatform.Utilities/ClientUtilities.cs +++ b/src/Microsoft.TestPlatform.Utilities/ClientUtilities.cs @@ -8,66 +8,29 @@ namespace Microsoft.VisualStudio.TestPlatform.Utilities using System.IO; using System.Xml; - using Microsoft.Win32; - using Microsoft.VisualStudio.TestPlatform.ObjectModel; - /// /// Utilities used by the client to understand the environment of the current run. /// public static class ClientUtilities { - private const string TestSettingsFileXPath = "RunSettings/MSTest/SettingsFile"; - private const string ResultsDirectoryXPath = "RunSettings/RunConfiguration/ResultsDirectory"; - /// /// Manifest file name to check if vstest.console.exe is running in portable mode /// - private const string PortableVsTestManifestFilename = "Portable.VsTest.Manifest"; - - /// - /// Registry Subkey for VisualStudio Root under HKLM Registry node for 32 bit process. - /// - private const string TaRootRegKey32 = @"Software\Microsoft\VisualStudio\15.0\Setup\VSTF\TestAgent"; - - /// - /// Registry Subkey for VisualStudio Root under HKLM Registry node for 64 bit process. - /// - private const string VsRootRegKey64 = @"Software\Wow6432Node\Microsoft\VisualStudio\15.0"; - + internal const string PortableVsTestManifestFilename = "Portable.VsTest.Manifest"; /// - /// Registry Subkey for VisualStudio Root under HKLM Registry node for 32 bit process. + /// The test settings file x path. /// - private const string VsRootRegKey32 = @"Software\Microsoft\VisualStudio\15.0"; - - /// - /// Registry Subkey for VisualStudio Root under HKLM Registry node for 64 bit process. - /// - private const string TaRootRegKey64 = @"Software\Wow6432Node\Microsoft\VisualStudio\15.0\Setup\VSTF\TestAgent"; - - /// - /// Registry key name for the TestAgent Install Directory. - /// - private const string ProductDirRegistryKeyName = "ProductDir"; - - /// - /// Environment variable which specifies the registry root - /// (This key will be primarily used in rascalPro) - /// - private const string RegistryRootEnvironmentVariableName = @"VisualStudio_RootRegistryKey"; + private const string TestSettingsFileXPath = "RunSettings/MSTest/SettingsFile"; /// - /// Registry key name for the visual studio Install Directory. + /// The results directory x path. /// - private const string InstallDirRegistryKeyName = "InstallDir"; + private const string ResultsDirectoryXPath = "RunSettings/RunConfiguration/ResultsDirectory"; /// /// Converts the relative paths in a runsetting file to absolute ones. /// - - /// - /// Converts the relative paths in a runsetting file to absolue ones. - /// /// Xml Document containing Runsettings xml /// Path of the .runsettings xml file [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")] @@ -95,15 +58,15 @@ public static void FixRelativePathsInRunSettings(XmlDocument xmlDocument, string { FixNodeFilePath(resultsDirectoryNode, root); } - } + } /// /// Check if Vstest.console is running in xcopyable mode /// /// true if vstest is running in xcopyable mode - public static bool CheckIfTestProcessIsRunningInXcopyableMode() + public static bool IsTestProcessRunningInXcopyableMode() { - return CheckIfTestProcessIsRunningInXcopyableMode(Process.GetCurrentProcess().MainModule.FileName); + return IsTestProcessRunningInXcopyableMode(Process.GetCurrentProcess().MainModule.FileName); } /// @@ -115,7 +78,7 @@ public static bool CheckIfTestProcessIsRunningInXcopyableMode() /// /// true if vstest is running in xcopyable mode /// - public static bool CheckIfTestProcessIsRunningInXcopyableMode(string exeName) + public static bool IsTestProcessRunningInXcopyableMode(string exeName) { // Get the directory of the exe var exeDir = Path.GetDirectoryName(exeName); @@ -123,161 +86,14 @@ public static bool CheckIfTestProcessIsRunningInXcopyableMode(string exeName) } /// - /// Gets the Visual studio install location. - /// - /// VS install path - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] - public static string GetVSInstallPath() - { - // Try custom Vs install path if available. This is done for rascal pro. - var vsInstallPathFromCustomRoot = GetVsInstallPathFromCustomRoot(); - if (!string.IsNullOrEmpty(vsInstallPathFromCustomRoot)) - { - return vsInstallPathFromCustomRoot; - } - - string path = null; - var subKey = VsRootRegKey32; - // Changing the key appropriately for 64 bit process. - if (Is64BitProcess()) - { - subKey = VsRootRegKey64; - } - using (var hklmKey = Registry.LocalMachine) - { - try - { - RegistryKey visualstudioSubKey = hklmKey.OpenSubKey(subKey); - var registryValue = visualstudioSubKey.GetValue(InstallDirRegistryKeyName).ToString(); - if (Directory.Exists(registryValue)) - { - path = registryValue; - } - } - catch (Exception) - { - //ignore the exception. - } - } - - return path; - } - - /// - /// Gets the TestAgent install location. - /// - /// TestAgent install path - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] - public static string GetTestAgentInstallPath() - { - string path = null; - var subKey = TaRootRegKey32; - - // Changing the key appropriately for 64 bit process. - if (Is64BitProcess()) - { - subKey = TaRootRegKey64; - } - using (var hklmKey = Registry.LocalMachine) - { - try - { - using (RegistryKey visualstudioSubKey = hklmKey.OpenSubKey(subKey)) - { - string registryValue = visualstudioSubKey.GetValue(ProductDirRegistryKeyName).ToString(); - var installLocation = Path.Combine(registryValue, @"Common7\IDE"); - if (Directory.Exists(installLocation)) - { - path = installLocation; - } - } - } - catch (Exception ex) - { - // ignore the exception. - EqtTrace.Verbose("Got following exception while searching for testAgent key {0}", ex.Message); - } - } - - return path; - } - - - - /// - /// Return true if the process executing is 64 bit process. - /// In 32 bit processes, IntPtr size is 4 and not 8 + /// The fix node file path. /// - /// The bool - private static bool Is64BitProcess() - { - return IntPtr.Size == 8; - } - - - /// - /// Get Vs install path from custom root - /// - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Need to ignore failures to read the registry settings")] - private static string GetVsInstallPathFromCustomRoot() - { - try - { - var registryKeyWhichContainsVsInstallPath = GetEnvironmentVariable(RegistryRootEnvironmentVariableName); - - if (string.IsNullOrEmpty(registryKeyWhichContainsVsInstallPath)) - { - return null; - } - - // Rascal always uses currentUser hive - // todo: OpenremoteBaseKey method doesn't exist in dotnet core. -#if NET46 - using (RegistryKey hiveKey = RegistryKey.OpenRemoteBaseKey(RegistryHive.CurrentUser, string.Empty)) - { - var visualstudioSubKey = hiveKey.OpenSubKey(registryKeyWhichContainsVsInstallPath); - var registryValue = visualstudioSubKey.GetValue("InstallDir").ToString(); - if (Directory.Exists(registryValue)) - { - return registryValue; - } - } -#endif - } - catch (Exception) - { - // ignore the exception. - } - - return null; - } - - /// - /// Returns the value of the environment variable - /// - /// - /// The key Name. + /// + /// The node. + /// + /// + /// The root. /// - /// - /// The . - /// - private static string GetEnvironmentVariable(string keyName) - { - var value = Environment.GetEnvironmentVariable(keyName); - if (!string.IsNullOrEmpty(value)) - { - return value; - } - - using (var key = Registry.CurrentUser.OpenSubKey("Environment", false)) - { - return key?.GetValue(keyName) as string; - } - } - private static void FixNodeFilePath(XmlNode node, string root) { var fileName = node.InnerXml; @@ -291,6 +107,6 @@ private static void FixNodeFilePath(XmlNode node, string root) node.InnerXml = fileName; } - } + } } } \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Utilities/project.json b/src/Microsoft.TestPlatform.Utilities/project.json index 3e67f8ae42..63a098f7db 100644 --- a/src/Microsoft.TestPlatform.Utilities/project.json +++ b/src/Microsoft.TestPlatform.Utilities/project.json @@ -9,14 +9,13 @@ "dependencies": { "Microsoft.TestPlatform.CoreUtilities": "15.0.0-*", - "Microsoft.TestPlatform.ObjectModel": "15.0.0-*", - "Microsoft.Win32.Registry": "4.0.0", - "System.Diagnostics.Process": "4.1.0" + "System.Diagnostics.Process": "4.1.0", + "Microsoft.TestPlatform.ObjectModel": "15-0-0-*" }, "runtimes": { - "win7-x64": { }, - "win7-x86": { } + "win7-x64": {}, + "win7-x86": {} }, "frameworks": { @@ -31,6 +30,6 @@ "NETStandard.Library": "1.6.0" } }, - "net46": { } + "net46": {} } } diff --git a/test/Microsoft.TestPlatform.Utilities.UnitTests/ClientUtilitiesTests.cs b/test/Microsoft.TestPlatform.Utilities.UnitTests/ClientUtilitiesTests.cs index 61ab121b6a..5870d25eb0 100644 --- a/test/Microsoft.TestPlatform.Utilities.UnitTests/ClientUtilitiesTests.cs +++ b/test/Microsoft.TestPlatform.Utilities.UnitTests/ClientUtilitiesTests.cs @@ -158,5 +158,33 @@ public void FixRelativePathsInRunSettingsShouldNotModifyEmptyResultsDirectory() Assert.AreEqual(runSettingsXML, finalSettingsXml); } + + [TestMethod] + public void IsTestProcessRunningInXcopyableModeShouldReturnFalseIfPortableManifestFileIsNotFound() + { + var cwd = Directory.GetCurrentDirectory(); + var dir = Path.Combine(cwd, "xcopyableTest"); + Directory.CreateDirectory("xcopyableTest"); + + Assert.IsFalse(ClientUtilities.IsTestProcessRunningInXcopyableMode(Path.Combine(dir, "dummy.exe"))); + + Directory.Delete(dir); + } + + [TestMethod] + public void IsTestProcessRunningInXcopyableModeShouldReturnTrueIfPortableManifestFileIsFound() + { + var cwd = Directory.GetCurrentDirectory(); + var dir = Path.Combine(cwd, "xcopyableTest"); + Directory.CreateDirectory("xcopyableTest"); + var filename = Path.Combine(dir, ClientUtilities.PortableVsTestManifestFilename); + + var fileStream = File.Create(Path.Combine(dir, ClientUtilities.PortableVsTestManifestFilename)); + Assert.IsTrue(ClientUtilities.IsTestProcessRunningInXcopyableMode(Path.Combine(dir, "dummy.exe"))); + + fileStream.Dispose(); + File.Delete(filename); + Directory.Delete(dir); + } } } From ff289608b7dcec7234f9811e03ea3e8debe80d45 Mon Sep 17 00:00:00 2001 From: Harsh Jain Date: Sun, 7 Aug 2016 16:15:52 +0530 Subject: [PATCH 4/5] Implemented Events in DataCollectionManager --- Nuget.config | 1 - .../Interfaces/IDataCollectionLog.cs | 18 + .../Interfaces/IDataCollectionManager.cs | 5 +- .../CopyRequestData.cs | 148 +++++ .../DataCollectionFileManager.cs | 515 +++++++++++++++++ .../DataCollectionManager.cs | 379 ++++++++++--- .../DataCollectionSessionConfiguration.cs | 53 ++ .../DataCollectorDiscoveryHelper.cs | 63 +-- .../Interfaces/IDataCollectionFileManager.cs | 59 ++ .../ObjectConversionHelper.cs | 125 +++++ .../Properties/AssemblyInfo.cs | 2 +- .../TestPlatformTestElement.cs | 117 ++++ .../Logging/Events/TestCaseStartEventArgs.cs | 24 + .../DataCollectionManagerTests.cs | 516 ++++++++++++++++++ .../DataCollectionPluginManagerTests.cs | 281 ---------- .../DummyDataCollectors.cs | 176 ++++++ .../MockDataCollectionFileManager.cs | 53 ++ .../Properties/AssemblyInfo.cs | 2 +- .../DataCollectionCoordinatorTests.cs | 1 + 19 files changed, 2109 insertions(+), 429 deletions(-) create mode 100644 src/Microsoft.TestPlatform.Common/DataCollection/Interfaces/IDataCollectionLog.cs create mode 100644 src/Microsoft.TestPlatform.DataCollection.v1/CopyRequestData.cs create mode 100644 src/Microsoft.TestPlatform.DataCollection.v1/DataCollectionFileManager.cs create mode 100644 src/Microsoft.TestPlatform.DataCollection.v1/DataCollectionSessionConfiguration.cs create mode 100644 src/Microsoft.TestPlatform.DataCollection.v1/Interfaces/IDataCollectionFileManager.cs create mode 100644 src/Microsoft.TestPlatform.DataCollection.v1/ObjectConversionHelper.cs create mode 100644 src/Microsoft.TestPlatform.DataCollection.v1/TestPlatformTestElement.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Logging/Events/TestCaseStartEventArgs.cs create mode 100644 test/Microsoft.TestPlatform.DataCollection.v1.UnitTests/DataCollectionManagerTests.cs delete mode 100644 test/Microsoft.TestPlatform.DataCollection.v1.UnitTests/DataCollectionPluginManagerTests.cs create mode 100644 test/Microsoft.TestPlatform.DataCollection.v1.UnitTests/DummyDataCollectors.cs create mode 100644 test/Microsoft.TestPlatform.DataCollection.v1.UnitTests/MockDataCollectionFileManager.cs diff --git a/Nuget.config b/Nuget.config index be7057cd5c..0463b5e693 100644 --- a/Nuget.config +++ b/Nuget.config @@ -7,7 +7,6 @@ - diff --git a/src/Microsoft.TestPlatform.Common/DataCollection/Interfaces/IDataCollectionLog.cs b/src/Microsoft.TestPlatform.Common/DataCollection/Interfaces/IDataCollectionLog.cs new file mode 100644 index 0000000000..9b772174a3 --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/DataCollection/Interfaces/IDataCollectionLog.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Common.DataCollection.Interfaces +{ + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + + /// + /// Interface for acting upon data collection messages. + /// + public interface IDataCollectionLog + { + /// + /// Log data collection messages + /// + /// DataCollectionMessage details + void SendDataCollectionMessage(DataCollectionMessageEventArgs args); + } +} diff --git a/src/Microsoft.TestPlatform.Common/DataCollection/Interfaces/IDataCollectionManager.cs b/src/Microsoft.TestPlatform.Common/DataCollection/Interfaces/IDataCollectionManager.cs index 935a40d6ae..e47e9c7d5b 100644 --- a/src/Microsoft.TestPlatform.Common/DataCollection/Interfaces/IDataCollectionManager.cs +++ b/src/Microsoft.TestPlatform.Common/DataCollection/Interfaces/IDataCollectionManager.cs @@ -5,12 +5,11 @@ namespace Microsoft.VisualStudio.TestPlatform.Common.DataCollection.Interfaces using System; using System.Collections.Generic; using System.Collections.ObjectModel; - using System.Linq; - using System.Threading.Tasks; using Microsoft.VisualStudio.TestPlatform.Common; using Microsoft.VisualStudio.TestPlatform.ObjectModel; - using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection; + + using TestCaseStartEventArgs = Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging.Events.TestCaseStartEventArgs; /// /// Defines the Data Collection Manager for Data Collectors. diff --git a/src/Microsoft.TestPlatform.DataCollection.v1/CopyRequestData.cs b/src/Microsoft.TestPlatform.DataCollection.v1/CopyRequestData.cs new file mode 100644 index 0000000000..2548fd4bb6 --- /dev/null +++ b/src/Microsoft.TestPlatform.DataCollection.v1/CopyRequestData.cs @@ -0,0 +1,148 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.DataCollection.V1 +{ + using System; + using System.IO; + using System.Threading; + + /// + /// Contains all information required for file transfer once scheduled from data collector. + /// + internal class CopyRequestData : IDisposable + { + #region Properties & Fields + /// + /// Specifies whether the object is disposed or not. + /// + private bool disposed; + + + /// + /// Set when this request is processed/completed. + /// + private ManualResetEvent requestCompleted; + + #endregion + + #region Constructor + + /// + /// Initializes a new instance of the class. + /// + /// + /// Base directory for creating collection log destination file path. + /// + /// + /// Header message containing all information for file transfer. + /// + internal CopyRequestData(string baseDirectory, FileDataHeaderMessage headerMessage) + { + this.requestCompleted = new ManualResetEvent(false); + this.FileDataHeaderMessage = headerMessage; + this.LocalFileName = Path.GetFileName(headerMessage.FileName); + + // Testcase specific collection should be created in testcase directorty. + var directoryPath = Path.Combine( + baseDirectory, + headerMessage.DataCollectionContext.HasTestCase ? this.FileDataHeaderMessage.DataCollectionContext.TestExecId.Id.ToString() : string.Empty); + + this.LocalFilePath = Path.Combine(directoryPath, this.LocalFileName); + } + + #endregion + + /// + /// Gets fileName with extension + /// + internal string LocalFileName + { + get; + private set; + } + + /// + /// Gets path to LocalFileName (including file name) + /// + internal string LocalFilePath + { + get; + private set; + } + + /// + /// Gets DataCollectionMessage sent by data collector for file transfer + /// + internal FileDataHeaderMessage FileDataHeaderMessage + { + get; + private set; + } + + /// + /// Gets error in processing the request. + /// + internal Exception Error + { + get; + private set; + } + + /// + /// Dispose event object + /// + public void Dispose() + { + this.Dispose(true); + + // Use SupressFinalize in case a subclass + // of this type implements a finalizer. + GC.SuppressFinalize(this); + } + + #region internal methods + /// + /// Wait for file transfer to complete. + /// + internal void WaitForCopyComplete() + { + this.requestCompleted.WaitOne(); + } + + /// + /// Mark this request as completed by setting the event. + /// Store any error information for further processing. + /// + /// + /// The error. + /// + internal void CompleteRequest(Exception error) + { + this.Error = error; + this.requestCompleted.Set(); + } + #endregion + + #region IDisposable + + /// + /// The dispose. + /// + /// + /// The disposing. + /// + protected virtual void Dispose(bool disposing) + { + if (!this.disposed) + { + if (disposing) + { + this.requestCompleted.Close(); + } + + this.disposed = true; + } + } + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.DataCollection.v1/DataCollectionFileManager.cs b/src/Microsoft.TestPlatform.DataCollection.v1/DataCollectionFileManager.cs new file mode 100644 index 0000000000..845a860b90 --- /dev/null +++ b/src/Microsoft.TestPlatform.DataCollection.v1/DataCollectionFileManager.cs @@ -0,0 +1,515 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.DataCollection.V1 +{ + using System; + using System.Collections.Generic; + using System.ComponentModel; + using System.Diagnostics; + using System.Globalization; + using System.IO; + using System.Linq; + + using Microsoft.VisualStudio.TestPlatform.Common.DataCollection.Interfaces; + using Microsoft.VisualStudio.TestPlatform.DataCollection.V1.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + + using CollectorDataEntry = Microsoft.VisualStudio.TestPlatform.ObjectModel.AttachmentSet; + using DataCollectionContext = Microsoft.VisualStudio.TestTools.Execution.DataCollectionContext; + using SessionId = Microsoft.VisualStudio.TestTools.Common.SessionId; + + /// + /// Manages file transfer from data collector to test runner service. + /// + internal class DataCollectionFileManager : IDisposable + { + #region Fields + /// + /// Max number of file transfer jobs. + /// + private const int MaxJobCount = 1024; + + + /// + /// Default results directory to be used when user didn't specifiy. + /// + private const string DefaultOutputDirectoryName = "TestPlatformResults"; + + + /// + /// Specifies whether the object is disposed or not. + /// + private bool disposed; + + /// + /// Configuration information about each active session. + /// (Currently there should be only one active session) + /// + private IDictionary sessionInfo; + + /// + /// Active file copy requests in the system. + /// + private IDictionary> copyfiles; + + + /// + /// Background job processor for moving file from source to destination. + /// + private TestTools.Common.BackgroundJobProcessor backgroundFileCopier; + + // todo : why is this required? + /// + /// Logger for data collection messages + /// + private IDataCollectionLog dataCollectionLog; + + #endregion + + #region Constructor + /// + /// Constructor + /// + internal DataCollectionFileManager(IDataCollectionLog dataCollectionLog) + { + Debug.Assert(dataCollectionLog != null, "dataCollectionLog cannot be null."); + this.dataCollectionLog = dataCollectionLog; + sessionInfo = new Dictionary(); + copyfiles = new Dictionary>(); + + //Background job processor for file copy/move operation(s). + backgroundFileCopier = new TestTools.Common.BackgroundJobProcessor( + "DataCollectionFileManager", + OnFileCopyRequest, + MaxJobCount); + } + #endregion + + #region Properties + internal IDictionary SessionInfo + { + get + { + return sessionInfo; + } + } + + internal IDictionary> Copyfiles + { + get + { + return copyfiles; + } + } + + #endregion + + #region internal methods + /// + /// Creates and stores session configuration for specified session id. + /// + /// session id + /// base output directory for session + internal void ConfigureSession(SessionId id, string outputDirectory) + { + ValidateArg.NotNull(id, "id"); + + string sessionOutputDirectory = string.Empty; + if (string.IsNullOrEmpty(outputDirectory)) + { + sessionOutputDirectory = Path.Combine(Path.GetTempPath(), DefaultOutputDirectoryName); + sessionOutputDirectory = Path.Combine(sessionOutputDirectory, id.Id.ToString()); + } + else + { + //Create a session specific directory under base output directory. + string expandedOutputDirectory = Environment.ExpandEnvironmentVariables(outputDirectory); + string absolutePath = Path.GetFullPath(expandedOutputDirectory); + sessionOutputDirectory = Path.Combine(absolutePath, id.Id.ToString()); + } + + //Create the output directory if it doesn't exist. + if (!Directory.Exists(sessionOutputDirectory)) + { + Directory.CreateDirectory(sessionOutputDirectory); + } + + DataCollectionSessionConfiguration sessionConfiguration = new DataCollectionSessionConfiguration(id, sessionOutputDirectory); + + lock (sessionInfo) + { + if (sessionInfo.ContainsKey(id)) + { + Debug.Fail(string.Format(CultureInfo.CurrentCulture, "Session with Id '{0}' is already configured", id.Id)); + sessionInfo[id] = sessionConfiguration; + } + else + { + sessionInfo.Add(id, sessionConfiguration); + } + } + } + + /// + /// Gets CollectorData associated with given collection context. + /// + /// + /// + internal List GetData(DataCollectionContext collectionContext) + { + List entries = new List(); + List copyRequests; + lock (copyfiles) + { + if (!copyfiles.TryGetValue(collectionContext, out copyRequests)) + { + if (EqtTrace.IsWarningEnabled) + { + EqtTrace.Warning("FileDataManager.GetData: Called for a context(SessionId:'{0}' TestCaseExecId:'{1}') which is not registered", + collectionContext.SessionId.Id.ToString(), collectionContext.HasTestCase ? collectionContext.TestExecId.Id.ToString() : "No TestCase"); + } + return entries; + } + copyfiles.Remove(collectionContext); + } + foreach (CopyRequestData copyRequest in copyRequests) + { + copyRequest.WaitForCopyComplete(); + if (null != copyRequest.Error) + { + //If there was error in processing the request, lets log it. + Guid testCaseId = collectionContext.HasTestCase ? collectionContext.TestExecId.Id : Guid.Empty; + LogError(copyRequest.Error.Message, copyRequest.FileDataHeaderMessage.Uri, copyRequest.FileDataHeaderMessage.FriendlyName, testCaseId); + } + else + { + //Create collectorDataEntry for each collected log. + CollectorDataEntry entry = entries.FirstOrDefault(e => Uri.Equals(e.Uri, copyRequest.FileDataHeaderMessage.Uri)); + if (null == entry) + { + entry = new CollectorDataEntry(copyRequest.FileDataHeaderMessage.Uri, copyRequest.FileDataHeaderMessage.FriendlyName); + entries.Add(entry); + } + entry.Attachments.Add(new UriDataAttachment(new Uri(copyRequest.LocalFilePath), copyRequest.FileDataHeaderMessage.Description)); + } + copyRequest.Dispose(); + } + return entries; + } + + + /// + /// Perform cleanup for the session. + /// All session related entries are deleted/disposed. + /// + /// + internal void CloseSession(SessionId id) + { + ValidateArg.NotNull(id, "id"); + + List requestsToDispose = new List(); + lock (copyfiles) + { + List contextToRemove = new List(); + foreach (KeyValuePair> kvp in copyfiles) + { + if (kvp.Key.SessionId.Equals(id)) + { + requestsToDispose.AddRange(kvp.Value); + contextToRemove.Add(kvp.Key); + } + } + foreach (DataCollectionContext context in contextToRemove) + { + copyfiles.Remove(context); + } + } + //Now dispose all requests. + requestsToDispose.ForEach(request => request.Dispose()); + } + + + /// + /// Process the message + /// + /// Data transfer message + internal void DispatchMessage(DataCollectorDataMessage collectorDataMessage) + { + FileDataHeaderMessage headerMessage = collectorDataMessage as FileDataHeaderMessage; + if (null != headerMessage) + { + AddNewFileTransfer(headerMessage); + } + else + { + if (EqtTrace.IsErrorEnabled) + { + EqtTrace.Error("DataCollectionFileManager.DispatchMessage: Got unexpected message of type '{0}'.", collectorDataMessage.GetType()); + } + } + } + + #endregion + + #region private methods + /// + /// Add a new file transfer (either copy/move) request. + /// + /// + private void AddNewFileTransfer(FileDataHeaderMessage headerMessage) + { + DataCollectionSessionConfiguration sessionConfiguration; + DataCollectionContext context = headerMessage.DataCollectionContext; + Debug.Assert(context != null, "DataCollectionManager.AddNewFileTransfer: FileDataHeaderMessage with null context."); + lock (sessionInfo) + { + + if (!sessionInfo.TryGetValue(context.SessionId, out sessionConfiguration)) + { + Debug.Fail(String.Format(CultureInfo.CurrentCulture, "DataCollectionManager.AddNewFileTransfer: File transfer requested for unknown session '{0}'", + context.SessionId)); + return; + } + } + string outputDirectory = sessionConfiguration.OutputDirectory; + + List requestedCopies; + lock (copyfiles) + { + if (!copyfiles.TryGetValue(context, out requestedCopies)) + { + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose("DataCollectionFileManager.AddNewFileTransfer: No copy request for collection context ({0}:{1}).", + context.SessionId.Id.ToString(), context.HasTestCase ? context.TestExecId.Id.ToString() : "NoTestCase"); + } + requestedCopies = new List(); + copyfiles.Add(context, requestedCopies); + } + else + { + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose("DataCollectionFileManager.AddNewFileTransfer: Found existing copy request(s) for collection context ({0}:{1}).", + context.SessionId.Id.ToString(), context.HasTestCase ? context.TestExecId.Id.ToString() : "NoTestCase"); + } + } + } + + CopyRequestData requestCopy = null; + lock (requestedCopies) + { + requestCopy = new CopyRequestData(outputDirectory, headerMessage); + //TODO::dhruvk::Detect duplicate file names + //TODO::dhruvk::File name needs to be reserved to avoid duplicate conflicts. + requestedCopies.Add(requestCopy); + } + + if (null != requestCopy) + { + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose("DataCollectionFileManager.AddNewFileTransfer: Enqueing for transfer."); + } + backgroundFileCopier.Enqueue(requestCopy); + } + + } + + + /// + /// Background job processor. + /// + /// + /// + private void OnFileCopyRequest(CopyRequestData fileCopyRequest, TestTools.Common.IQueuedJobs queuedJobs) + { + if (fileCopyRequest.FileDataHeaderMessage.PerformCleanup) + { + FileMove(fileCopyRequest); + } + else + { + FileCopy(fileCopyRequest); + } + } + + + /// + /// Do a file copy for the specified request + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Need to catch all exception type to send as data collection error to client.")] + private void FileCopy(CopyRequestData copyRequest) + { + Exception error = null; + try + { + Validate(copyRequest); + File.Copy(copyRequest.FileDataHeaderMessage.FileName, copyRequest.LocalFilePath); + } + catch (Exception ex) + { + error = ex; + } + finally + { + //Let collector know of transfer completed if it has registered transferCompletedHandler. + TriggerCallback(copyRequest.FileDataHeaderMessage.FileTransferCompletedHandler, copyRequest.FileDataHeaderMessage.UserToken, error, copyRequest.FileDataHeaderMessage.FileName); + CompleteFileTransfer(copyRequest, error); + } + } + + + /// + /// Sanity checks on CopyRequestData + /// + /// + private static void Validate(CopyRequestData fileCopyRequest) + { + if (!File.Exists(fileCopyRequest.FileDataHeaderMessage.FileName)) + { + throw new FileNotFoundException(string.Format(CultureInfo.CurrentCulture, + "Could not find source file '{0}'.", fileCopyRequest.FileDataHeaderMessage.FileName)); + } + string directoryName = Path.GetDirectoryName(fileCopyRequest.LocalFilePath); + if (!Directory.Exists(directoryName)) + { + Directory.CreateDirectory(directoryName); + } + else if (File.Exists(fileCopyRequest.LocalFilePath)) + { + File.Delete(fileCopyRequest.LocalFilePath); + } + } + + + /// + /// Make a callback indicating the file transfer is complete. + /// Needed when file copy is requested (as data collector might use requested file after transfer is complete). + /// + /// + /// + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Ignorable error which shouldn't crash the data collection framework.")] + private void TriggerCallback(AsyncCompletedEventHandler transferCompletedCallback, object userToken, Exception exception, string path) + { + Debug.Assert(!String.IsNullOrEmpty(path), "null or empty path"); + if (transferCompletedCallback != null) + { + try + { + transferCompletedCallback(this, new AsyncCompletedEventArgs(exception, false, userToken)); + } + catch (Exception e) + { + if (EqtTrace.IsErrorEnabled) + { + EqtTrace.Error("DataCollectionFileManager.TriggerCallBack: Error occurred while raising the file transfer completed callback for {0}. Error: {1}", path, e.ToString()); + } + } + } + } + + + /// + /// Move the file as specified in request. + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Need to catch all exception type to send as data collection error to client.")] + private void FileMove(CopyRequestData copyRequest) + { + Exception error = null; + try + { + Validate(copyRequest); + File.Move(copyRequest.FileDataHeaderMessage.FileName, copyRequest.LocalFilePath); + } + catch (Exception ex) + { + error = ex; + } + finally + { + //Let collector know of transfer completed if it has registered transferCompletedHandler. + TriggerCallback(copyRequest.FileDataHeaderMessage.FileTransferCompletedHandler, copyRequest.FileDataHeaderMessage.UserToken, error, copyRequest.FileDataHeaderMessage.FileName); + CompleteFileTransfer(copyRequest, error); + } + } + + + /// + /// Mark the request as processed/completed. + /// If any error occurred during processing, error information is also sent to copyrequest. + /// + /// + /// + private static void CompleteFileTransfer(CopyRequestData fileCopyRequest, Exception e) + { + fileCopyRequest.CompleteRequest(e); + } + + + /// + /// Logs an error message. + /// + /// + /// + /// + /// Id of testCase if available, null otherwise + private void LogError(string errorMessage, Uri collectorUri, string collectorFriendlyName, Guid testCaseId) + { + Debug.Assert(dataCollectionLog != null, "DataCollectionLog cannot be null"); + DataCollectionMessageEventArgs args = new DataCollectionMessageEventArgs(TestMessageLevel.Error, errorMessage); + args.Uri = collectorUri; + args.FriendlyName = collectorFriendlyName; + if (!Guid.Empty.Equals(testCaseId)) + { + args.TestCaseId = testCaseId; + } + dataCollectionLog.SendDataCollectionMessage(args); + } + + #endregion + + #region IDisposable + /// + /// Dispose event object + /// + public void Dispose() + { + Dispose(true); + + // Use SupressFinalize in case a subclass + // of this type implements a finalizer. + GC.SuppressFinalize(this); + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", MessageId = "backgroundFileCopier", Justification = "Bug in clr")] + protected virtual void Dispose(bool disposing) + { + if (!disposed) + { + if (disposing) + { + if (null != backgroundFileCopier) + { + backgroundFileCopier.Abort(); + + // Dont dispose the background job processor as this is leading to 99% cpu on some machines + // because of clr bug # 653263. Not disposing it is fine as the dispose is called only on + // process exit and on process exit all handles are disposed automatically. + // + // m_backgroundFileCopier.Dispose(); + backgroundFileCopier = null; + } + } + disposed = true; + } + } + #endregion + + } +} diff --git a/src/Microsoft.TestPlatform.DataCollection.v1/DataCollectionManager.cs b/src/Microsoft.TestPlatform.DataCollection.v1/DataCollectionManager.cs index 5396c66b5d..f242e30145 100644 --- a/src/Microsoft.TestPlatform.DataCollection.v1/DataCollectionManager.cs +++ b/src/Microsoft.TestPlatform.DataCollection.v1/DataCollectionManager.cs @@ -15,16 +15,19 @@ namespace Microsoft.VisualStudio.TestPlatform.DataCollection.V1 using Microsoft.VisualStudio.TestPlatform.Common.DataCollection.Interfaces; using Microsoft.VisualStudio.TestPlatform.DataCollection.V1.Interfaces; using Microsoft.VisualStudio.TestPlatform.ObjectModel; - using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; using Microsoft.VisualStudio.TestTools.Common; using CollectorDataEntry = Microsoft.VisualStudio.TestPlatform.ObjectModel.AttachmentSet; using DataCollectionContext = Microsoft.VisualStudio.TestTools.Execution.DataCollectionContext; using DataCollectionEnvironmentContext = Microsoft.VisualStudio.TestTools.Execution.DataCollectionEnvironmentContext; + using DataCollectionEventArgs = Microsoft.VisualStudio.TestTools.Execution.DataCollectionEventArgs; using DataCollector = Microsoft.VisualStudio.TestTools.Execution.DataCollector; using DataCollectorInformation = Microsoft.VisualStudio.TestTools.Execution.DataCollectorInformation; + using DataCollectorInvocationError = Microsoft.VisualStudio.TestTools.Execution.DataCollectorInvocationError; + using SessionEndEventArgs = Microsoft.VisualStudio.TestTools.Execution.SessionEndEventArgs; using SessionId = Microsoft.VisualStudio.TestTools.Common.SessionId; + using TestCaseStartEventArgs = Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging.Events.TestCaseStartEventArgs; /// /// The data collection plugin manager. @@ -33,16 +36,6 @@ internal class DataCollectionManager : IDataCollectionManager { #region Private Variables - /// - /// Cache of data collectors associated with the run. - /// - private Dictionary runDataCollectors; - - /// - /// Is data collection currently enabled. - /// - private bool isDataCollectionEnabled; - /// /// Data collection environment context. /// @@ -68,12 +61,22 @@ internal class DataCollectionManager : IDataCollectionManager /// private string collectionOutputDirectory; + /// + /// File manager for performing file transfer from data collector. + /// + private IDataCollectionFileManager dataCollectionFileManager; + + /// + /// Raised when data collection log message is received. + /// + //private IDataCollectionLog dataCollectionMessageLog; + #endregion /// /// Initializes a new instance of the class. /// - public DataCollectionManager() : this(default(IMessageSink)) + public DataCollectionManager() : this(default(IMessageSink), default(IDataCollectionFileManager)) { } @@ -83,23 +86,43 @@ public DataCollectionManager() : this(default(IMessageSink)) /// /// The message sink. /// - internal DataCollectionManager(IMessageSink messageSink) + /// + /// The data Collection File Manager. + /// + internal DataCollectionManager(IMessageSink messageSink, IDataCollectionFileManager dataCollectionFileManager) { - this.isDataCollectionEnabled = false; - this.runDataCollectors = new Dictionary(); + this.IsDataCollectionEnabled = false; + this.RunDataCollectors = new Dictionary(); this.messageSink = messageSink; this.userWorkItemFactory = new SafeAbortableUserWorkItemFactory(); - this.ConfigureNewSession(); + + // todo : revisit this. + this.dataCollectionFileManager = dataCollectionFileManager; } + /// + /// Gets cache of data collectors associated with the run. + /// + internal Dictionary RunDataCollectors { get; private set; } + + /// + /// Gets a value indicating whether data collection currently enabled. + /// + internal bool IsDataCollectionEnabled { get; private set; } + /// /// Raises TestCaseStart event to all data collectors configured for run. /// /// TestCaseStart event. public void TestCaseStarted(TestCaseStartEventArgs testCaseStartEventArgs) { - throw new NotImplementedException(); + if (!this.IsDataCollectionEnabled) + { + return; + } + + this.SendEvent(ObjectConversionHelper.ToTestCaseStartEventArgs(this.dataCollectionEnvironmentContext, testCaseStartEventArgs)); } /// @@ -110,7 +133,33 @@ public void TestCaseStarted(TestCaseStartEventArgs testCaseStartEventArgs) /// Collection of testCase attachmentSet. public Collection TestCaseEnded(TestCase testCase, ObjectModel.TestOutcome testOutcome) { - throw new NotImplementedException(); + if (!this.IsDataCollectionEnabled) + { + return null; + } + + var endEvent = ObjectConversionHelper.ToTestCaseEndEventArgs(this.dataCollectionEnvironmentContext, testCase, testOutcome); + this.SendEvent(endEvent); + + var result = this.GetCollectionEntries(endEvent.Context); + + foreach (var entry in result) + { + foreach (var file in entry.Attachments) + { + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose( + "Attachment Description: Collector:'{0}' Uri:'{1}' Description:'{2}' Uri:'{3}' ", + entry.DisplayName, + entry.Uri, + file.Description, + file.Uri); + } + } + } + + return result; } /// @@ -119,7 +168,15 @@ public Collection TestCaseEnded(TestCase testCase, ObjectMod /// Are test case level events required. public bool SessionStarted() { - throw new NotImplementedException(); + if (!this.IsDataCollectionEnabled) + { + // No TestCase level events are needed if data collection is disabled or no data collectors are loaded. + return false; + } + + this.SendEvent(ObjectConversionHelper.ToSessionStartEventArgs(this.dataCollectionEnvironmentContext)); + + return this.AreTestCaseLevelEventsRequired(); } /// @@ -129,7 +186,37 @@ public bool SessionStarted() /// Collection of session attachmentSet. public Collection SessionEnded(bool isCancelled) { - throw new NotImplementedException(); + if (!this.IsDataCollectionEnabled) + { + return null; + } + + SessionEndEventArgs endEvent = ObjectConversionHelper.ToSessionEndEventArgs(this.dataCollectionEnvironmentContext); + this.SendEvent(endEvent); + + // Datacollection needs to happen at this event also. This collection is assocaited with run. + var result = this.GetCollectionEntries(endEvent.Context); + + foreach (var entry in result) + { + foreach (var file in entry.Attachments) + { + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose( + "Run Attachment Description: Collector:'{0}' Uri:'{1}' Description:'{2}' Uri:'{3}' ", + entry.DisplayName, + entry.Uri, + file.Description, + file.Uri); + } + } + } + + // todo : make this call at the end of GetColelctionEntries or inside GetData itself. + this.dataCollectionFileManager.CloseSession(endEvent.Context.SessionId); + + return result; } /// @@ -170,9 +257,9 @@ public Collection SessionEnded(bool isCancelled) this.collectionOutputDirectory = runConfiguration.ResultsDirectory; } - this.isDataCollectionEnabled = runCollectionSettings.IsCollectionEnabled; + this.IsDataCollectionEnabled = runCollectionSettings.IsCollectionEnabled; - if (this.isDataCollectionEnabled) + if (this.IsDataCollectionEnabled) { executionEnvironmentVariables = this.LoadAndInitDataCollectors(runCollectionSettings); } @@ -213,6 +300,8 @@ protected virtual void Dispose(bool disposing) #region Private Methods + #region LoadAndInit DataCollectors + /// /// Creates an instance of collector plugin of given type. /// @@ -355,19 +444,6 @@ private static Type GetDataCollectorInformationFromAssembly(Assembly assembly, s return dctype; } - /// - /// Configures new session by - /// a. creating new session id - /// b. creating new data collection context. - /// c. creating new data collection environment context for local run. - /// - private void ConfigureNewSession() - { - // todo : add stuff here required to configure new session - var dataCollectionContext = new DataCollectionContext(new SessionId(Guid.NewGuid())); - this.dataCollectionEnvironmentContext = DataCollectionEnvironmentContext.CreateForLocalEnvironment(dataCollectionContext); - } - /// /// Loads the data collector plugins and initializes them. /// @@ -378,6 +454,16 @@ private void ConfigureNewSession() { IDictionary variables = new Dictionary(StringComparer.OrdinalIgnoreCase); + try + { + this.ConfigureNewSession(); + } + catch (SystemException ex) + { + this.LogWarning(string.Format(CultureInfo.CurrentCulture, Resource.ConfigureSessionError, ex.Message)); + return variables; + } + var enabledCollectors = this.GetDataCollectorsEnabledForRun(dataCollectionSettings); if (enabledCollectors.Count == 0) { @@ -478,9 +564,9 @@ private bool LoadAndInitDataCollector(DataCollectorSettings collectorSettings) return false; } - lock (this.runDataCollectors) + lock (this.RunDataCollectors) { - if (this.runDataCollectors.ContainsKey(collectorType)) + if (this.RunDataCollectors.ContainsKey(collectorType)) { // Collector is already loaded (may be configured twice). Ignore duplicates and return. return true; @@ -523,10 +609,10 @@ private bool LoadAndInitDataCollector(DataCollectorSettings collectorSettings) try { dataCollectorInfo.InitializeDataCollector(this.dataCollectionEnvironmentContext); - lock (this.runDataCollectors) + lock (this.RunDataCollectors) { // Add data collectors to run cache. - this.runDataCollectors[collectorType] = dataCollectorInfo; + this.RunDataCollectors[collectorType] = dataCollectorInfo; } } catch (Exception ex) @@ -543,19 +629,6 @@ private bool LoadAndInitDataCollector(DataCollectorSettings collectorSettings) return true; } - /// - /// Sends a warning message against the session which is not associated with a data collector. - /// - /// - /// This should only be used when we do not have the data collector info yet. After we have the data - /// collector info we can use the data collectors logger for errors. - /// - /// The message to be logged. - private void LogWarning(string warningMessage) - { - this.messageSink.SendMessage(new DataCollectionMessageEventArgs(TestMessageLevel.Warning, warningMessage)); - } - /// /// Given assemblyQualifiedName of type get the fully qualified name of assembly. /// @@ -584,49 +657,18 @@ private void RemoveDataCollectors(IReadOnlyCollection - /// The dispose data collector. - /// - /// - /// The data collector info. - /// - private void DisposeDataCollector(TestPlatformDataCollectorInfo dataCollectorInfo) - { - var dataCollectorType = dataCollectorInfo.DataCollector.GetType(); - try - { - if (EqtTrace.IsVerboseEnabled) - { - EqtTrace.Verbose("DataCollectionManager.DisposeDataCollector: calling Dispose() on {0}", dataCollectorType); + this.RunDataCollectors.Remove(dataCollectorToRemove.DataCollector.GetType()); } - dataCollectorInfo.DisposeDataCollector(); - } - catch (Exception ex) - { - if (EqtTrace.IsErrorEnabled) + if (this.RunDataCollectors.Count == 0) { - EqtTrace.Error("DataCollectionManager.DisposeDataCollector: exception while calling Dispose() on {0}: " + ex, dataCollectorType); + this.IsDataCollectionEnabled = false; } - - dataCollectorInfo.Logger.LogError( - this.dataCollectionEnvironmentContext.SessionDataCollectionContext, - string.Format(CultureInfo.CurrentCulture, Resource.DataCollectorDisposeError, dataCollectorType, ex.ToString())); } } @@ -681,9 +723,9 @@ private void DisposeDataCollector(TestPlatformDataCollectorInfo dataCollectorInf private List GetDataCollectorsSnapshot() { var datacollectorInfoList = new List(); - lock (this.runDataCollectors) + lock (this.RunDataCollectors) { - foreach (var dataCollectorInfo in this.runDataCollectors.Values) + foreach (var dataCollectorInfo in this.RunDataCollectors.Values) { if (dataCollectorInfo != null) { @@ -757,5 +799,164 @@ private List GetDataCollectorsSnapshot() } #endregion + + /// + /// Sends a warning message against the session which is not associated with a data collector. + /// + /// + /// This should only be used when we do not have the data collector info yet. After we have the data + /// collector info we can use the data collectors logger for errors. + /// + /// The message to be logged. + private void LogWarning(string warningMessage) + { + this.messageSink.SendMessage(new DataCollectionMessageEventArgs(TestMessageLevel.Warning, warningMessage)); + } + + /// + /// Configures new session by + /// a. creating new session id + /// b. creating new data collection context. + /// c. creating new data collection environment context for local run. + /// + private void ConfigureNewSession() + { + // todo : add stuff here required to configure new session + var dataCollectionContext = new DataCollectionContext(new SessionId(Guid.NewGuid())); + this.dataCollectionEnvironmentContext = DataCollectionEnvironmentContext.CreateForLocalEnvironment(dataCollectionContext); + } + + /// + /// The dispose data collector. + /// + /// + /// The data collector info. + /// + private void DisposeDataCollector(TestPlatformDataCollectorInfo dataCollectorInfo) + { + var dataCollectorType = dataCollectorInfo.DataCollector.GetType(); + try + { + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose("DataCollectionManager.DisposeDataCollector: calling Dispose() on {0}", dataCollectorType); + } + + dataCollectorInfo.DisposeDataCollector(); + } + catch (Exception ex) + { + if (EqtTrace.IsErrorEnabled) + { + EqtTrace.Error("DataCollectionManager.DisposeDataCollector: exception while calling Dispose() on {0}: " + ex, dataCollectorType); + } + + dataCollectorInfo.Logger.LogError( + this.dataCollectionEnvironmentContext.SessionDataCollectionContext, + string.Format(CultureInfo.CurrentCulture, Resource.DataCollectorDisposeError, dataCollectorType, ex.ToString())); + } + } + + /// + /// Sends the event to all data collectors and fires a callback on the sender, letting it + /// know when all plugins have completed processing the event + /// + /// The context information for the event + private void SendEvent(DataCollectionEventArgs args) + { + Debug.Assert(args != null, "'args' is null"); + + if (!this.IsDataCollectionEnabled) + { + if (EqtTrace.IsErrorEnabled) + { + EqtTrace.Error("RaiseEvent called when no collection is enabled."); + } + + Debug.Assert(false, "RaiseEvent called when no collection is enabled."); + return; + } + + var failedCollectors = new List(); + + foreach (var dataCollectorInfo in this.GetDataCollectorsSnapshot()) + { + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose("DataCollectionManger:SendEvent: Raising event {0} to collector {1}", args.GetType(), dataCollectorInfo.DataCollectorInformation.FriendlyName); + } + + // Send the event to all data collectors that registered for it + var invocationErrors = dataCollectorInfo.Events.RaiseEvent(args); + + if (invocationErrors.Count > 0) + { + // Iterate through the errors and log errors in the data collectors' loggers + foreach (DataCollectorInvocationError invocationError in invocationErrors) + { + // Log the error + dataCollectorInfo.Logger.LogError( + invocationError.EventArgs.Context, + string.Format(CultureInfo.CurrentCulture, Resource.DataCollectorRunError, invocationError.Exception.Message)); + } + + failedCollectors.Add(dataCollectorInfo); + } + } + + this.RemoveDataCollectors(failedCollectors); + } + + /// + /// The are test case level events required. + /// + /// + /// The . + /// + private bool AreTestCaseLevelEventsRequired() + { + var required = false; + foreach (var dataCollectorInfo in this.GetDataCollectorsSnapshot()) + { + required = required || dataCollectorInfo.Events.HasTestCaseStartOrEndOrFailedEventListener(true); + + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose( + "DataCollectionPluginManger:AreTestCaseLevelEventsRequired: Checked with Collector {0}, result = {1}", + dataCollectorInfo.DataCollectorInformation.FriendlyName, + required); + } + + if (required) + { + return true; + } + } + + return required; + } + + /// + /// The get collection entries. + /// + /// + /// The context. + /// + /// + /// The . + /// + private Collection GetCollectionEntries(DataCollectionContext context) + { + if (this.IsDataCollectionEnabled) + { + var entries = this.dataCollectionFileManager.GetData(context); + return new Collection(entries); + } + + return new Collection(); + } + + #endregion } } \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.DataCollection.v1/DataCollectionSessionConfiguration.cs b/src/Microsoft.TestPlatform.DataCollection.v1/DataCollectionSessionConfiguration.cs new file mode 100644 index 0000000000..b3bac189db --- /dev/null +++ b/src/Microsoft.TestPlatform.DataCollection.v1/DataCollectionSessionConfiguration.cs @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.DataCollection.V1 +{ + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using SessionId = Microsoft.VisualStudio.TestTools.Common.SessionId; + + /// + /// Session/Run configuration information for data collection. + /// + internal class DataCollectionSessionConfiguration + { + #region Constructor + /// + /// Initializes a new instance of the class. + /// + /// + /// Session id + /// + /// + /// Base output directory to store collection logs for this session. + /// + internal DataCollectionSessionConfiguration(SessionId id, string outputDirectory) + { + ValidateArg.NotNullOrEmpty(outputDirectory, "outputDirectory"); + this.SessionId = id; + this.OutputDirectory = outputDirectory; + } + + #endregion + + #region Properties + /// + /// Gets Id of associated session + /// + internal SessionId SessionId + { + get; + private set; + } + + /// + /// Gets base output directory to store collection logs. + /// + internal string OutputDirectory + { + get; + private set; + } + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.DataCollection.v1/DataCollectorDiscoveryHelper.cs b/src/Microsoft.TestPlatform.DataCollection.v1/DataCollectorDiscoveryHelper.cs index 004838c087..2f9c405a92 100644 --- a/src/Microsoft.TestPlatform.DataCollection.v1/DataCollectorDiscoveryHelper.cs +++ b/src/Microsoft.TestPlatform.DataCollection.v1/DataCollectorDiscoveryHelper.cs @@ -95,12 +95,12 @@ internal static XmlDocument GetConfigurationForAssembly(Assembly assembly) subFolders.Add(string.Empty); var configFilePath = string.Empty; - var iFolder = 0; - for (; iFolder < subFolders.Count; iFolder++) + var folderCounter = 0; + for (; folderCounter < subFolders.Count; folderCounter++) { configFilePath = Path.Combine( - Path.Combine(assemblyFolder, subFolders[iFolder]), + Path.Combine(assemblyFolder, subFolders[folderCounter]), configFileName); if (File.Exists(configFilePath)) @@ -110,7 +110,7 @@ internal static XmlDocument GetConfigurationForAssembly(Assembly assembly) } // We couldn't find a config file for this assembly anywhere. Return null - if (iFolder >= subFolders.Count) + if (folderCounter >= subFolders.Count) { EqtTrace.Warning("DataCollectorDiscovery: No configuration file found for data collector collector assembly {0} in all {1} locations", assembly.FullName, subFolders.Count); return null; @@ -165,7 +165,7 @@ private static string GetDataCollectorPluginDirectory() { if (EqtTrace.IsVerboseEnabled) { - EqtTrace.Verbose("TestPlatformDataCollectorDiscovery.GetDataCollectorPluginDirectory: Using config specified plugin directory '{0}'.", directoryFromConfig); + EqtTrace.Verbose("DataCollectorDiscoveryHelper.GetDataCollectorPluginDirectory: Using config specified plugin directory '{0}'.", directoryFromConfig); } return directoryFromConfig; @@ -174,7 +174,7 @@ private static string GetDataCollectorPluginDirectory() { if (EqtTrace.IsVerboseEnabled) { - EqtTrace.Verbose("TestPlatformDataCollectorDiscovery.GetDataCollectorPluginDirectory: Config specified plugin directory '{0}' not found. Value ignored.", directoryFromConfig); + EqtTrace.Verbose("DataCollectorDiscoveryHelper.GetDataCollectorPluginDirectory: Config specified plugin directory '{0}' not found. Value ignored.", directoryFromConfig); } } } @@ -188,15 +188,16 @@ private static string GetDataCollectorPluginDirectory() { if (EqtTrace.IsVerboseEnabled) { - EqtTrace.Verbose("TestPlatformDataCollectorDiscovery.GetDataCollectorPluginDirectory: Using plugin directory '{0}' relative to main module assembly location.", directoryFromProcess); + EqtTrace.Verbose("DataCollectorDiscoveryHelper.GetDataCollectorPluginDirectory: Using plugin directory '{0}' relative to main module assembly location.", directoryFromProcess); } + return directoryFromProcess; } else { if (EqtTrace.IsVerboseEnabled) { - EqtTrace.Verbose("TestPlatformDataCollectorDiscovery.GetDataCollectorPluginDirectory: Can not find plugin directory realtive to main module assembly location."); + EqtTrace.Verbose("DataCollectorDiscoveryHelper.GetDataCollectorPluginDirectory: Can not find plugin directory realtive to main module assembly location."); } } @@ -254,57 +255,13 @@ private static string GetAssemblyCultureLanguage(Assembly assembly) { if (EqtTrace.IsErrorEnabled) { - EqtTrace.Error("TestPlatformDataCollectorDiscovery: Error occurred while finding the fallback language for assembly {0}. Error: {1}", assembly.FullName, ex); + EqtTrace.Error("DataCollectorDiscoveryHelper: Error occurred while finding the fallback language for assembly {0}. Error: {1}", assembly.FullName, ex); } return null; } } - /// - /// The get install location from registry. - /// - /// - /// The sku install location. - /// - /// - /// The . - /// - private static string GetInstallLocationFromRegistry(string skuInstallLocation) - { - try - { - var directoryFromRegistry = Path.Combine(skuInstallLocation, DataCollectorsDirectoryName); - if (Directory.Exists(directoryFromRegistry)) - { - if (EqtTrace.IsVerboseEnabled) - { - EqtTrace.Verbose("TestPlatformDataCollectorDiscovery.GetDataCollectorPluginDirectory: Using plugin directory '{0}' relative to directory location in registry.", directoryFromRegistry); - } - - return directoryFromRegistry; - } - else - { - if (EqtTrace.IsVerboseEnabled) - { - EqtTrace.Verbose("TestPlatformDataCollectorDiscovery.GetDataCollectorPluginDirectory: Can not find plugin directory realtive to directory location in registry."); - } - } - } - catch (Exception ex) - { - if (EqtTrace.IsErrorEnabled) - { - EqtTrace.Error("TestPlatformDataCollectorDiscovery.GetDataCollectorPluginDirectory: Error finding plugin directory from registry. Error details:{0}.", ex.Message); - } - - // ignore the exception. - } - - return null; - } - #endregion } } \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.DataCollection.v1/Interfaces/IDataCollectionFileManager.cs b/src/Microsoft.TestPlatform.DataCollection.v1/Interfaces/IDataCollectionFileManager.cs new file mode 100644 index 0000000000..b589bd3266 --- /dev/null +++ b/src/Microsoft.TestPlatform.DataCollection.v1/Interfaces/IDataCollectionFileManager.cs @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.DataCollection.V1.Interfaces +{ + using System.Collections.Generic; + + using CollectorDataEntry = Microsoft.VisualStudio.TestPlatform.ObjectModel.AttachmentSet; + using DataCollectionContext = Microsoft.VisualStudio.TestTools.Execution.DataCollectionContext; + using SessionId = Microsoft.VisualStudio.TestTools.Common.SessionId; + + /// + /// The DataCollectionFileManager interface. + /// + internal interface IDataCollectionFileManager + { + /// + /// The configure session. + /// + /// + /// The id. + /// + /// + /// The output directory. + /// + void ConfigureSession(SessionId id, string outputDirectory); + + /// + /// The get data. + /// + /// + /// The collection context. + /// + /// + /// The . + /// + List GetData(DataCollectionContext collectionContext); + + /// + /// The close session. + /// + /// + /// The id. + /// + void CloseSession(SessionId id); + + /// + /// The dispatch message. + /// + /// + /// The collector data message. + /// + void DispatchMessage(DataCollectorDataMessage collectorDataMessage); + + /// + /// The dispose. + /// + void Dispose(); + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.DataCollection.v1/ObjectConversionHelper.cs b/src/Microsoft.TestPlatform.DataCollection.v1/ObjectConversionHelper.cs new file mode 100644 index 0000000000..259f281dcc --- /dev/null +++ b/src/Microsoft.TestPlatform.DataCollection.v1/ObjectConversionHelper.cs @@ -0,0 +1,125 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.DataCollection.V1 +{ + using System; + using System.Collections.Generic; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging.Events; + using Microsoft.VisualStudio.TestTools.Common; + + using DataCollectionContext = Microsoft.VisualStudio.TestTools.Execution.DataCollectionContext; + using DataCollectionEnvironmentContext = Microsoft.VisualStudio.TestTools.Execution.DataCollectionEnvironmentContext; + using SessionEndEventArgs = Microsoft.VisualStudio.TestTools.Execution.SessionEndEventArgs; + using SessionStartEventArgs = Microsoft.VisualStudio.TestTools.Execution.SessionStartEventArgs; + + /// + /// Data collector understands test tools object as they are written with MSTest interface. + /// Implements helper methods to converts test platform objects to test tools object so they + /// can be passed to data collectors. + /// + internal static class ObjectConversionHelper + { + /// + /// Cache for ITestElement. To ensure TestCase to TestElement mapping is unique + /// + private static Dictionary testElementCache = new Dictionary(); + + /// + /// Creates TestTools SessionStartEventArgs + /// + /// context in which data collection is being done. + /// TestTools SessionStartEventArgs + internal static SessionStartEventArgs ToSessionStartEventArgs(DataCollectionEnvironmentContext context) + { + var dataCollectionContext = new DataCollectionContext(context.SessionDataCollectionContext.SessionId); + return new SessionStartEventArgs(dataCollectionContext); + } + + /// + /// Creates TestTools SessionEndEventArgs + /// + /// context in which data collection is being done. + /// TestTools SessionEndEventArgs + internal static SessionEndEventArgs ToSessionEndEventArgs(DataCollectionEnvironmentContext context) + { + var dataCollectionContext = new DataCollectionContext(context.SessionDataCollectionContext.SessionId); + return new SessionEndEventArgs(dataCollectionContext); + } + + /// + /// Creates TestTools TestCaseStartEventArgs from TestPlatform TestCaseStartEventArgs + /// + /// context in which data collection is being done. + /// TestPlatform TestCaseStartEventArgs + /// TestTools TestCaseStartEventArgs + internal static TestTools.Execution.TestCaseStartEventArgs ToTestCaseStartEventArgs( + DataCollectionEnvironmentContext context, + TestCaseStartEventArgs e) + { + var testElement = ToTestElement(e.TestCase); + var dataCollectionContext = new DataCollectionContext(context.SessionDataCollectionContext.SessionId, testElement.ExecutionId); + var args = new TestTools.Execution.TestCaseStartEventArgs(dataCollectionContext, testElement, null); + return args; + } + + /// + /// Creates TestTools TestCaseEndEventArgs from TestPlatform TestResultEventArgs + /// + /// context in which data collection is being done. + /// Test case which is complete. + /// Outcome of the test case. + /// TestTools TestCaseEndEventArgs + internal static TestTools.Execution.TestCaseEndEventArgs ToTestCaseEndEventArgs(DataCollectionEnvironmentContext context, TestCase testCase, TestOutcome testOutCome) + { + var testElement = ToTestElement(testCase); + var dataCollectionContext = new DataCollectionContext(context.SessionDataCollectionContext.SessionId, testElement.ExecutionId); + var args = new TestTools.Execution.TestCaseEndEventArgs(dataCollectionContext, testElement, null, ToOutcome(testOutCome)); + return args; + } + + /// + /// Converts a TestPlatform TestCase to TestTools ITestElement + /// + /// TestPlatform TestCase + /// TestTools ITestElement + private static ITestElement ToTestElement(TestCase testCase) + { + ITestElement testElement; + if (!testElementCache.TryGetValue(testCase.Id, out testElement)) + { + testElement = new TestPlatformTestElement(testCase); + testElementCache.Add(testCase.Id, testElement); + } + + return testElement; + } + + /// + /// Converts TestPlatform TestOutcome to TestTools TestOutcome + /// + /// TestPlatform TestOutcome + /// TestTools TestOutcome + private static TestTools.Common.TestOutcome ToOutcome(ObjectModel.TestOutcome outcome) + { + switch (outcome) + { + case TestPlatform.ObjectModel.TestOutcome.Failed: + return TestTools.Common.TestOutcome.Failed; + + case TestPlatform.ObjectModel.TestOutcome.Passed: + return TestTools.Common.TestOutcome.Passed; + + case TestPlatform.ObjectModel.TestOutcome.Skipped: + return TestTools.Common.TestOutcome.NotExecuted; + + case TestPlatform.ObjectModel.TestOutcome.None: + case TestPlatform.ObjectModel.TestOutcome.NotFound: + default: + return TestTools.Common.TestOutcome.NotRunnable; + } + } + + } +} diff --git a/src/Microsoft.TestPlatform.DataCollection.v1/Properties/AssemblyInfo.cs b/src/Microsoft.TestPlatform.DataCollection.v1/Properties/AssemblyInfo.cs index 9a0a27a583..93a16ab3e5 100644 --- a/src/Microsoft.TestPlatform.DataCollection.v1/Properties/AssemblyInfo.cs +++ b/src/Microsoft.TestPlatform.DataCollection.v1/Properties/AssemblyInfo.cs @@ -7,7 +7,7 @@ // associated with an assembly. [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Microsoft.TestPlatform.DataCollection.Legacy")] +[assembly: AssemblyProduct("Microsoft.TestPlatform.DataCollection.v1")] [assembly: AssemblyTrademark("")] // Setting ComVisible to false makes the types in this assembly not visible diff --git a/src/Microsoft.TestPlatform.DataCollection.v1/TestPlatformTestElement.cs b/src/Microsoft.TestPlatform.DataCollection.v1/TestPlatformTestElement.cs new file mode 100644 index 0000000000..4e30d012fa --- /dev/null +++ b/src/Microsoft.TestPlatform.DataCollection.v1/TestPlatformTestElement.cs @@ -0,0 +1,117 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.DataCollection.V1 +{ + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestTools.Common; + using System; + using System.ComponentModel; + using System.Runtime.InteropServices; + using TestElement = Microsoft.VisualStudio.TestTools.Common.TestElement; + using TestType = Microsoft.VisualStudio.TestTools.Common.TestType; + + /// + /// The test platform test element. + /// + [Guid("2955A1D9-C1B3-4ADF-A183-B0098438C8F5")] +#if NET461 + [Serializable] +#endif + internal class TestPlatformTestElement : TestElement + { + #region Fields + + private static TestType testType = new TestType(typeof(TestPlatformTestElement).GUID); + + // The name of the adapter - this is used by Agent to figure out which Adapter to use. + private const string adapterName = null; + + #endregion + + #region Constructors + + public TestPlatformTestElement(TestCase testCase) + : base(new TestId(testCase.Id), testCase.FullyQualifiedName, testCase.DisplayName, testCase.Source) + { + this.m_executionId = new TestExecId(testCase.Id); + } + + + + /// + /// Copy constructor. + /// + /// + protected TestPlatformTestElement(TestPlatformTestElement copy) + : base(copy) + { + } + + #endregion + + #region TestElement Methods + + /// + /// Readonly. Exception will be thrown if trying to set it. + /// Obsolete. + /// + // Make it readonly so that we don't load this property + [ReadOnly(true)] + public override bool ReadOnly + { + get + { + return false; + } + set + { + // should not set this property + throw new NotSupportedException("Read only property"); + } + } + + + public override TestType TestType + { + get { return testType; } + } + + + public override object Clone() + { + return new TestPlatformTestElement(this); + } + + + /// + /// Adapter for test. + /// + public override string Adapter + { + get + { + return adapterName; + } + } + + /// + /// No plugin for test. + /// + public override string ControllerPlugin + { + get { return null; } + } + + public override bool CanBeAggregated + { + get { return true; } + } + + /// + /// TestPlatform tests are automated. + /// + public override bool IsAutomated { get { return true; } } + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Logging/Events/TestCaseStartEventArgs.cs b/src/Microsoft.TestPlatform.ObjectModel/Logging/Events/TestCaseStartEventArgs.cs new file mode 100644 index 0000000000..03da7883ae --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Logging/Events/TestCaseStartEventArgs.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging.Events +{ + /// + /// EventArg used for raising testcase start event. + /// + public class TestCaseStartEventArgs : EventArgs + { + public TestCaseStartEventArgs(TestCase testCase) + { + ValidateArg.NotNull(testCase, "testCase"); + TestCase = testCase; + } + + /// + /// Test case + /// + public TestCase TestCase { get; private set; } + } +} diff --git a/test/Microsoft.TestPlatform.DataCollection.v1.UnitTests/DataCollectionManagerTests.cs b/test/Microsoft.TestPlatform.DataCollection.v1.UnitTests/DataCollectionManagerTests.cs new file mode 100644 index 0000000000..4786d478ba --- /dev/null +++ b/test/Microsoft.TestPlatform.DataCollection.v1.UnitTests/DataCollectionManagerTests.cs @@ -0,0 +1,516 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.DataCollection.V1.UnitTests +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using System.Xml; + + using Microsoft.VisualStudio.TestPlatform.Common; + using Microsoft.VisualStudio.TestPlatform.Common.DataCollection; + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework; + using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.Utilities; + using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; + using Microsoft.VisualStudio.TestPlatform.DataCollection.V1; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + using TestCaseStartEventArgs = Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging.Events.TestCaseStartEventArgs; + + using Moq; + + using IMessageSink = Microsoft.VisualStudio.TestPlatform.DataCollection.V1.Interfaces.IMessageSink; + using System.Globalization; + using VisualStudio.TestPlatform.DataCollection.V1.Interfaces; + using VisualStudio.TestPlatform.ObjectModel; + + [TestClass] + public class DataCollectionManagerTests + { + private DataCollectionManager dataCollectionManager; + private DummyMessageSink mockMessageSink; + private RunSettings runSettings; + private MockDataCollectionFileManager mockDataCollectionFileManager; + + private string xmlSettings = + "\r\n\r\n \r\n 1\r\n .\\TestResults\r\n x86\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n"; + + [TestInitialize] + public void Init() + { + SetupMockExtensions(new string[] { typeof(DataCollectorsSettingsProvider).GetTypeInfo().Assembly.Location }, () => { }); + + this.mockMessageSink = new DummyMessageSink(); + this.mockDataCollectionFileManager = new MockDataCollectionFileManager(); + this.dataCollectionManager = new DataCollectionManager(this.mockMessageSink, this.mockDataCollectionFileManager); + this.runSettings = new RunSettings(); + this.runSettings.LoadSettingsXml(this.xmlSettings); + } + + [TestCleanup] + public void Cleanup() + { + this.mockDataCollectionFileManager.GetDataThrowException = false; + ResetExtensionsCache(); + MockDataCollector.Reset(); + MockDataCollector2.Reset(); + } + + #region LoadDataCollector + [TestMethod] + public void LoadDataCollectorShouldLoadDataCollectorAndReturnEnvironmentVariables() + { + var envVarList = new List> { new KeyValuePair("key", "value") }; + MockDataCollector.EnvVarList = envVarList; + this.runSettings.InitializeSettingsProviders(this.xmlSettings); + + var result = this.dataCollectionManager.LoadDataCollectors(this.runSettings); + + Assert.IsNotNull(result); + Assert.AreEqual(1, result.Count); + Assert.AreEqual("key", result.Keys.First()); + Assert.AreEqual("value", result.Values.First()); + } + + [TestMethod] + public void LoadDataCollectorShouldThrowExceptionIfRunSettingsIsNull() + { + Assert.ThrowsException(() => + { + this.dataCollectionManager.LoadDataCollectors(null); + }); + } + + [TestMethod] + public void LoadDataCollectorsShouldReturnEmptyDictionaryIfDataCollectionSettingsProviderIsNotRegistered() + { + var result = this.dataCollectionManager.LoadDataCollectors(this.runSettings); + + Assert.AreEqual(0, result.Count); + } + + [TestMethod] + public void LoadDataCollectorsShouldInitializeDataCollector() + { + this.runSettings.InitializeSettingsProviders(this.xmlSettings); + + var result = this.dataCollectionManager.LoadDataCollectors(this.runSettings); + + Assert.IsTrue(MockDataCollector.IsInitializeInvoked); + } + + [TestMethod] + public void LoadDataCollectorsShouldLogExceptionToMessageSinkIfInitializationFails() + { + this.runSettings.InitializeSettingsProviders(this.xmlSettings); + MockDataCollector.ThrowExceptionWhenInitialized = true; + + var result = this.dataCollectionManager.LoadDataCollectors(this.runSettings); + + Assert.IsTrue(this.mockMessageSink.IsSendMessageInvoked); + Assert.IsTrue(MockDataCollector.IsDisposeInvoked); + } + + [TestMethod] + public void LaodDataCollectorsShouldReturnEnviornmentVariables() + { + this.runSettings.InitializeSettingsProviders(this.xmlSettings); + var envVarList = new List>(); + var kvp = new KeyValuePair("key", "value"); + envVarList.Add(kvp); + MockDataCollector.EnvVarList = envVarList; + + var result = this.dataCollectionManager.LoadDataCollectors(this.runSettings); + + Assert.IsTrue(MockDataCollector.IsGetTestExecutionEnvironmentVariablesInvoked); + Assert.AreEqual(1, result.Count); + Assert.AreEqual("key", result.Keys.First()); + Assert.AreEqual("value", result.Values.First()); + } + + [TestMethod] + public void LoadDataCollectorsShouldReturnOnlyOneEnvirnmentVariableIfMoreThanOneVariablesWithSameKeyIsSpecified() + { + this.runSettings.InitializeSettingsProviders(this.xmlSettings); + var envVarList = new List>(); + envVarList.Add(new KeyValuePair("key", "value")); + envVarList.Add(new KeyValuePair("key", "value1")); + MockDataCollector.EnvVarList = envVarList; + + var result = this.dataCollectionManager.LoadDataCollectors(this.runSettings); + + Assert.IsTrue(MockDataCollector.IsGetTestExecutionEnvironmentVariablesInvoked); + Assert.AreEqual(1, result.Count); + Assert.AreEqual("key", result.Keys.First()); + Assert.AreEqual("value", result.Values.First()); + Assert.IsTrue(this.mockMessageSink.IsSendMessageInvoked); + } + + #endregion + + #region SessionStarted + + [TestMethod] + public void SessionStartedShouldSendSessionStartEventArgsToDataCollectors() + { + this.runSettings.InitializeSettingsProviders(this.xmlSettings); + this.dataCollectionManager.LoadDataCollectors(this.runSettings); + + var result = this.dataCollectionManager.SessionStarted(); + + Assert.IsFalse(result); + Assert.IsTrue(MockDataCollector.IsEvents_SessionStartInvoked); + } + + [TestMethod] + public void SessionStartedShouldReturnTrueIfTestCaseLevelEventsAreRegistered() + { + this.runSettings.InitializeSettingsProviders(this.xmlSettings); + MockDataCollector.ConfigureTestCaseLevelEvents = true; + this.dataCollectionManager.LoadDataCollectors(this.runSettings); + + var result = this.dataCollectionManager.SessionStarted(); + + Assert.IsTrue(result); + Assert.IsTrue(MockDataCollector.IsEvents_SessionStartInvoked); + } + + [TestMethod] + public void SessionStartedShouldReturnFalseIfDataCollectorsAreNotLoaded() + { + this.runSettings.InitializeSettingsProviders(this.xmlSettings); + + var result = this.dataCollectionManager.SessionStarted(); + + Assert.IsFalse(result); + } + + [TestMethod] + public void SessionStaretedShouldRemoveDataCollectorIfExceptionIsThrownWhileSendingSessionStartEventArgs() + { + this.runSettings.InitializeSettingsProviders(this.xmlSettings); + MockDataCollector.ConfigureTestCaseLevelEvents = true; + this.dataCollectionManager.LoadDataCollectors(this.runSettings); + MockDataCollector2.Events_SessionStartThrowException = true; + + this.dataCollectionManager.LoadDataCollectors(this.runSettings); + int count = this.dataCollectionManager.RunDataCollectors.Count; + + var result = this.dataCollectionManager.SessionStarted(); + + Assert.AreEqual(count - 1, this.dataCollectionManager.RunDataCollectors.Count); + Assert.AreEqual(string.Format(CultureInfo.CurrentCulture, Resource.DataCollectorRunError, MockDataCollector2.Events_SessionStartExceptionMessage), this.mockMessageSink.EventMessage); + Assert.IsTrue(result); + } + + #endregion + + #region SessionEnded + + [TestMethod] + public void SessionEndedShouldShouldSendSessionEndEventArgsToDataCollectors() + { + this.runSettings.InitializeSettingsProviders(this.xmlSettings); + MockDataCollector.ConfigureTestCaseLevelEvents = true; + this.dataCollectionManager.LoadDataCollectors(this.runSettings); + var attachment = new AttachmentSet(new Uri("DataCollector://Attachment"), "DataCollectorAttachment"); + this.mockDataCollectionFileManager.Attachments.Add(attachment); + + var result = this.dataCollectionManager.SessionEnded(isCancelled: false); + + Assert.IsTrue(MockDataCollector.IsEvents_SessionEndInvoked); + Assert.AreEqual(1, result.Count); + Assert.AreEqual(attachment.DisplayName, result.First().DisplayName); + Assert.AreEqual(attachment.Uri, result.First().Uri); + } + + [TestMethod] + public void SessionEndedShouldRemoveDataCollectorIfExceptionIsThrownWhileSendingSessionEndEventArgs() + { + this.runSettings.InitializeSettingsProviders(this.xmlSettings); + this.dataCollectionManager.LoadDataCollectors(this.runSettings); + MockDataCollector2.Events_SessionEndThrowException = true; + + int count = this.dataCollectionManager.RunDataCollectors.Count; + + var result = this.dataCollectionManager.SessionEnded(isCancelled: false); + + Assert.AreEqual(count - 1, this.dataCollectionManager.RunDataCollectors.Count); + Assert.AreEqual(string.Format(CultureInfo.CurrentCulture, Resource.DataCollectorRunError, MockDataCollector2.Events_SessionEndExceptionMessage), this.mockMessageSink.EventMessage); + + } + + [TestMethod] + public void SessionEndedShouldThrowExceptionIfExceptionIsThrownWhileGettingAttachments() + { + this.runSettings.InitializeSettingsProviders(this.xmlSettings); + this.dataCollectionManager.LoadDataCollectors(this.runSettings); + this.mockDataCollectionFileManager.GetDataThrowException = true; + + Assert.ThrowsException(() => + { + var result = this.dataCollectionManager.SessionEnded(isCancelled: false); + }); + } + + [TestMethod] + public void SessionEndedShouldReturnNullIfDataCollectorsAreNotLoaded() + { + this.runSettings.InitializeSettingsProviders(this.xmlSettings); + + var result = this.dataCollectionManager.SessionEnded(isCancelled: false); + + Assert.IsFalse(this.dataCollectionManager.IsDataCollectionEnabled); + Assert.IsNull(result); + } + + #endregion + + #region TestCaseStarted + + [TestMethod] + public void TestCaseStartedShouldSendTestCaseStartEventArgsToDataCollectors() + { + this.runSettings.InitializeSettingsProviders(this.xmlSettings); + MockDataCollector.ConfigureTestCaseLevelEvents = true; + this.dataCollectionManager.LoadDataCollectors(this.runSettings); + + var tc = new TestCase("Test", new Uri("Test://Case"), "Source"); + var testCaseStartEventArgs = new TestCaseStartEventArgs(tc); + + this.dataCollectionManager.TestCaseStarted(testCaseStartEventArgs); + + Assert.IsTrue(MockDataCollector.IsEvents_TestCaseStartInvoked); + } + + [TestMethod] + public void TestCaseStartedShouldNotSentEventToDataCollectorsIfDataCollectorsAreNotLoaded() + { + this.runSettings.InitializeSettingsProviders(this.xmlSettings); + MockDataCollector.ConfigureTestCaseLevelEvents = true; + + var tc = new TestCase("Test", new Uri("Test://Case"), "Source"); + var testCaseStartEventArgs = new TestCaseStartEventArgs(tc); + + this.dataCollectionManager.TestCaseStarted(testCaseStartEventArgs); + + Assert.IsFalse(this.dataCollectionManager.IsDataCollectionEnabled); + Assert.IsFalse(MockDataCollector.IsEvents_TestCaseStartInvoked); + } + + [TestMethod] + public void TestCaseStartedShouldNotSendTestCaseStartEventArgsIfTestCaseLevelEventsAreNotRequestByDataCollectors() + { + this.runSettings.InitializeSettingsProviders(this.xmlSettings); + MockDataCollector.ConfigureTestCaseLevelEvents = false; + this.dataCollectionManager.LoadDataCollectors(this.runSettings); + + var tc = new TestCase("Test", new Uri("Test://Case"), "Source"); + var testCaseStartEventArgs = new TestCaseStartEventArgs(tc); + + this.dataCollectionManager.TestCaseStarted(testCaseStartEventArgs); + + Assert.IsFalse(MockDataCollector.IsEvents_TestCaseStartInvoked); + } + + [TestMethod] + public void TestCaseStartedShouldRemoveDataCollectorIfExceptionIsThrownWhileSendingTestCaseStartEventArgs() + { + this.runSettings.InitializeSettingsProviders(this.xmlSettings); + MockDataCollector.ConfigureTestCaseLevelEvents = true; + MockDataCollector2.Events_TestCaseStartThrowException = true; + MockDataCollector2.ConfigureTestCaseLevelEvents = true; + this.dataCollectionManager.LoadDataCollectors(this.runSettings); + + this.dataCollectionManager.LoadDataCollectors(this.runSettings); + int count = this.dataCollectionManager.RunDataCollectors.Count; + + var tc = new TestCase("Test", new Uri("Test://Case"), "Source"); + var testCaseStartEventArgs = new TestCaseStartEventArgs(tc); + + this.dataCollectionManager.TestCaseStarted(testCaseStartEventArgs); + + Assert.AreEqual(count - 1, this.dataCollectionManager.RunDataCollectors.Count); + Assert.AreEqual(string.Format(CultureInfo.CurrentCulture, Resource.DataCollectorRunError, MockDataCollector2.Events_TestCaseStartExceptionMessage), this.mockMessageSink.EventMessage); + } + + #endregion + + #region TestCaseEnd + + [TestMethod] + public void TestCaseEndedShouldShouldSendSessionEndEventArgsToDataCollectors() + { + this.runSettings.InitializeSettingsProviders(this.xmlSettings); + MockDataCollector.ConfigureTestCaseLevelEvents = true; + this.dataCollectionManager.LoadDataCollectors(this.runSettings); + var attachment = new AttachmentSet(new Uri("DataCollector://Attachment"), "DataCollectorAttachment"); + this.mockDataCollectionFileManager.Attachments.Add(attachment); + + var tc = new TestCase("Test", new Uri("Test://Case"), "Source"); + + var result = this.dataCollectionManager.TestCaseEnded(tc, TestOutcome.Passed); + + Assert.IsTrue(MockDataCollector.IsEvents_TestCaseEndInvoked); + Assert.AreEqual(1, result.Count); + Assert.AreEqual(attachment.DisplayName, result.First().DisplayName); + Assert.AreEqual(attachment.Uri, result.First().Uri); + } + + [TestMethod] + public void TestCaseEndedShouldRemoveDataCollectorIfExceptionIsThrownWhileSendingTestCaseEndEventArgs() + { + this.runSettings.InitializeSettingsProviders(this.xmlSettings); + MockDataCollector2.Events_TestCaseEndThrowException = true; + MockDataCollector2.ConfigureTestCaseLevelEvents = true; + this.dataCollectionManager.LoadDataCollectors(this.runSettings); + + int count = this.dataCollectionManager.RunDataCollectors.Count; + + var tc = new TestCase("Test", new Uri("Test://Case"), "Source"); + + var result = this.dataCollectionManager.TestCaseEnded(tc, TestOutcome.Passed); + + Assert.AreEqual(count - 1, this.dataCollectionManager.RunDataCollectors.Count); + Assert.AreEqual(string.Format(CultureInfo.CurrentCulture, Resource.DataCollectorRunError, MockDataCollector2.Events_TestCaseEndExceptionMessage), this.mockMessageSink.EventMessage); + + } + + [TestMethod] + public void TestCaseEndedShouldThrowExceptionIfExceptionIsThrownWhileGettingAttachments() + { + this.runSettings.InitializeSettingsProviders(this.xmlSettings); + this.dataCollectionManager.LoadDataCollectors(this.runSettings); + this.mockDataCollectionFileManager.GetDataThrowException = true; + + var tc = new TestCase("Test", new Uri("Test://Case"), "Source"); + Assert.ThrowsException(() => + { + var result = this.dataCollectionManager.TestCaseEnded(tc, TestOutcome.Passed); + }); + } + + [TestMethod] + public void TestCaseEndedShouldReturnNullIfDataCollectorsAreNotLoaded() + { + this.runSettings.InitializeSettingsProviders(this.xmlSettings); + + var tc = new TestCase("Test", new Uri("Test://Case"), "Source"); + var result = this.dataCollectionManager.TestCaseEnded(tc, TestOutcome.Passed); + + Assert.IsFalse(this.dataCollectionManager.IsDataCollectionEnabled); + Assert.IsNull(result); + } + + #endregion + + public static void SetupMockExtensions(string[] extensions, Action callback) + { + // Setup mocks. + var testableTestPluginCache = new TestableTestPluginCache(new Mock().Object); + testableTestPluginCache.DoesDirectoryExistSetter = true; + + testableTestPluginCache.FilesInDirectory = (path, pattern) => + { + if (pattern.Equals("*.dll")) + { + callback.Invoke(); + return extensions; + } + return new string[] { }; + }; + + // Setup the testable instance. + TestPluginCache.Instance = testableTestPluginCache; + } + + + public static void ResetExtensionsCache() + { + TestPluginCache.Instance = null; + } + } + + public class TestableTestPluginCache : TestPluginCache + { + public TestableTestPluginCache(IPathUtilities pathUtilities) + : base(pathUtilities) + { + } + + internal Func FilesInDirectory + { + get; + set; + } + + public bool DoesDirectoryExistSetter + { + get; + set; + } + + public Func, TestExtensions> TestExtensionsSetter { get; set; } + + internal override bool DoesDirectoryExist(string path) + { + return this.DoesDirectoryExistSetter; + } + + internal override string[] GetFilesInDirectory(string path, string searchPattern) + { + return this.FilesInDirectory.Invoke(path, searchPattern); + } + + internal override TestExtensions GetTestExtensions(IEnumerable extensions) + { + if (this.TestExtensionsSetter == null) + { + return base.GetTestExtensions(extensions); + } + else + { + return this.TestExtensionsSetter.Invoke(extensions); + } + } + } + + internal class DummyMessageSink : IMessageSink + { + public bool IsSendMessageInvoked; + public string EventMessage; + + public void Reset() + { + this.IsSendMessageInvoked = false; + this.EventMessage = string.Empty; + } + + public EventHandler OnDataCollectionMessage + { + get + { + throw new NotImplementedException(); + } + + set + { + throw new NotImplementedException(); + } + } + + public void SendMessage(DataCollectorDataMessage collectorDataMessage) + { + throw new NotImplementedException(); + } + + public void SendMessage(DataCollectionMessageEventArgs args) + { + this.EventMessage = args.Message; + this.IsSendMessageInvoked = true; + } + } +} + diff --git a/test/Microsoft.TestPlatform.DataCollection.v1.UnitTests/DataCollectionPluginManagerTests.cs b/test/Microsoft.TestPlatform.DataCollection.v1.UnitTests/DataCollectionPluginManagerTests.cs deleted file mode 100644 index e2b3985f42..0000000000 --- a/test/Microsoft.TestPlatform.DataCollection.v1.UnitTests/DataCollectionPluginManagerTests.cs +++ /dev/null @@ -1,281 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -namespace Microsoft.TestPlatform.DataCollection.V1.UnitTests -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Reflection; - using System.Xml; - - using Microsoft.VisualStudio.TestPlatform.Common; - using Microsoft.VisualStudio.TestPlatform.Common.DataCollection; - using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework; - using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.Utilities; - using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; - using Microsoft.VisualStudio.TestPlatform.DataCollection.V1; - using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; - using Microsoft.VisualStudio.TestTools.Execution; - using Microsoft.VisualStudio.TestTools.UnitTesting; - - using Moq; - - using IMessageSink = Microsoft.VisualStudio.TestPlatform.DataCollection.V1.Interfaces.IMessageSink; - - [TestClass] - public class DataCollectionManagerTests - { - private DataCollectionManager dataCollectionManager; - private DummyMessageSink mockMessageSink; - private RunSettings runSettings; - private string xmlSettings = - "\r\n\r\n \r\n 1\r\n .\\TestResults\r\n x86\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n"; - - [TestInitialize] - public void Init() - { - SetupMockExtensions(new string[] { typeof(DataCollectorsSettingsProvider).GetTypeInfo().Assembly.Location }, () => { }); - - this.mockMessageSink = new DummyMessageSink(); - this.dataCollectionManager = new DataCollectionManager(this.mockMessageSink); - this.runSettings = new RunSettings(); - this.runSettings.LoadSettingsXml(this.xmlSettings); - } - - [TestCleanup] - public void Cleanup() - { - MockDataCollector.EnvVarList = null; - MockDataCollector.ThrowExceptionWhenInitialized = false; - ResetExtensionsCache(); - } - - [TestMethod] - public void LoadDataCollectorShouldLoadDataCollectorAndReturnEnvironmentVariables() - { - var envVarList = new List> { new KeyValuePair("key", "value") }; - MockDataCollector.EnvVarList = envVarList; - this.runSettings.InitializeSettingsProviders(this.xmlSettings); - - var result = this.dataCollectionManager.LoadDataCollectors(this.runSettings); - - Assert.IsNotNull(result); - Assert.AreEqual(1, result.Count); - Assert.AreEqual("key", result.Keys.First()); - Assert.AreEqual("value", result.Values.First()); - } - - [TestMethod] - public void LoadDataCollectorShouldThrowExceptionIfRunSettingsIsNull() - { - Assert.ThrowsException(() => - { - this.dataCollectionManager.LoadDataCollectors(null); - }); - } - - [TestMethod] - public void LoadDataCollectorsShouldReturnEmptyDictionaryIfDataCollectionSettingsProviderIsNotRegistered() - { - var result = this.dataCollectionManager.LoadDataCollectors(this.runSettings); - - Assert.AreEqual(0, result.Count); - } - - [TestMethod] - public void LoadDataCollectorsShouldInitializeDataCollector() - { - this.runSettings.InitializeSettingsProviders(this.xmlSettings); - - var result = this.dataCollectionManager.LoadDataCollectors(this.runSettings); - - Assert.IsTrue(MockDataCollector.IsInitializeInvoked); - } - - [TestMethod] - public void LoadDataCollectorsShouldLogExceptionToMessageSinkIfInitializationFails() - { - this.runSettings.InitializeSettingsProviders(this.xmlSettings); - MockDataCollector.ThrowExceptionWhenInitialized = true; - - var result = this.dataCollectionManager.LoadDataCollectors(this.runSettings); - - Assert.IsTrue(this.mockMessageSink.IsSendMessageInvoked); - Assert.IsTrue(MockDataCollector.IsDisposeInvoked); - } - - [TestMethod] - public void LaodDataCollectorsShouldReturnEnviornmentVariables() - { - this.runSettings.InitializeSettingsProviders(this.xmlSettings); - var envVarList = new List>(); - var kvp = new KeyValuePair("key", "value"); - envVarList.Add(kvp); - MockDataCollector.EnvVarList = envVarList; - - var result = this.dataCollectionManager.LoadDataCollectors(this.runSettings); - - Assert.IsTrue(MockDataCollector.IsGetTestExecutionEnvironmentVariablesInvoked); - Assert.AreEqual(1, result.Count); - Assert.AreEqual("key", result.Keys.First()); - Assert.AreEqual("value", result.Values.First()); - } - - [TestMethod] - public void LoadDataCollectorsShouldReturnOnlyOneEnvirnmentVariableIfMoreThanOneVariablesWithSameKeyIsSpecified() - { - this.runSettings.InitializeSettingsProviders(this.xmlSettings); - var envVarList = new List>(); - envVarList.Add(new KeyValuePair("key", "value")); - envVarList.Add(new KeyValuePair("key", "value1")); - MockDataCollector.EnvVarList = envVarList; - - var result = this.dataCollectionManager.LoadDataCollectors(this.runSettings); - - Assert.IsTrue(MockDataCollector.IsGetTestExecutionEnvironmentVariablesInvoked); - Assert.AreEqual(1, result.Count); - Assert.AreEqual("key", result.Keys.First()); - Assert.AreEqual("value", result.Values.First()); - Assert.IsTrue(this.mockMessageSink.IsSendMessageInvoked); - } - - public static void SetupMockExtensions(string[] extensions, Action callback) - { - // Setup mocks. - var testableTestPluginCache = new TestableTestPluginCache(new Mock().Object); - testableTestPluginCache.DoesDirectoryExistSetter = true; - - testableTestPluginCache.FilesInDirectory = (path, pattern) => - { - if (pattern.Equals("*.dll")) - { - callback.Invoke(); - return extensions; - } - return new string[] { }; - }; - - // Setup the testable instance. - TestPluginCache.Instance = testableTestPluginCache; - } - - public static void ResetExtensionsCache() - { - TestPluginCache.Instance = null; - } - } - - [DataCollectorTypeUri("datacollector://Company/Product/Version")] - [DataCollectorFriendlyName("Collect Log Files", false)] - public class MockDataCollector : DataCollector, ITestExecutionEnvironmentSpecifier - { - public static bool IsInitializeInvoked; - public static bool ThrowExceptionWhenInitialized; - public static bool IsDisposeInvoked; - public static bool IsGetTestExecutionEnvironmentVariablesInvoked; - public static bool GetTestExecutionEnvironmentVariablesThrowException; - public static bool DisposeShouldThrowException; - public static IEnumerable> EnvVarList; - - public override void Initialize(XmlElement configurationElement, DataCollectionEvents events, DataCollectionSink dataSink, DataCollectionLogger logger, DataCollectionEnvironmentContext environmentContext) - { - if (ThrowExceptionWhenInitialized) - { - throw new Exception("DataCollectorException"); - } - IsInitializeInvoked = true; - } - - protected override void Dispose(bool disposing) - { - IsDisposeInvoked = true; - if (DisposeShouldThrowException) - { - throw new Exception("DataCollectorException"); - } - } - - public IEnumerable> GetTestExecutionEnvironmentVariables() - { - if (GetTestExecutionEnvironmentVariablesThrowException) - { - throw new Exception("DataCollectorException"); - } - - IsGetTestExecutionEnvironmentVariablesInvoked = true; - return EnvVarList; - } - } - - public class TestableTestPluginCache : TestPluginCache - { - public TestableTestPluginCache(IPathUtilities pathUtilities) - : base(pathUtilities) - { - } - - internal Func FilesInDirectory - { - get; - set; - } - - public bool DoesDirectoryExistSetter - { - get; - set; - } - - public Func, TestExtensions> TestExtensionsSetter { get; set; } - - internal override bool DoesDirectoryExist(string path) - { - return this.DoesDirectoryExistSetter; - } - - internal override string[] GetFilesInDirectory(string path, string searchPattern) - { - return this.FilesInDirectory.Invoke(path, searchPattern); - } - - internal override TestExtensions GetTestExtensions(IEnumerable extensions) - { - if (this.TestExtensionsSetter == null) - { - return base.GetTestExtensions(extensions); - } - else - { - return this.TestExtensionsSetter.Invoke(extensions); - } - } - } - - internal class DummyMessageSink : IMessageSink - { - public bool IsSendMessageInvoked; - public EventHandler OnDataCollectionMessage - { - get - { - throw new NotImplementedException(); - } - - set - { - throw new NotImplementedException(); - } - } - - public void SendMessage(DataCollectorDataMessage collectorDataMessage) - { - throw new NotImplementedException(); - } - - public void SendMessage(DataCollectionMessageEventArgs args) - { - this.IsSendMessageInvoked = true; - } - } -} - diff --git a/test/Microsoft.TestPlatform.DataCollection.v1.UnitTests/DummyDataCollectors.cs b/test/Microsoft.TestPlatform.DataCollection.v1.UnitTests/DummyDataCollectors.cs new file mode 100644 index 0000000000..acf221588a --- /dev/null +++ b/test/Microsoft.TestPlatform.DataCollection.v1.UnitTests/DummyDataCollectors.cs @@ -0,0 +1,176 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.DataCollection.V1.UnitTests +{ + using Microsoft.VisualStudio.TestTools.Execution; + using System.Xml; + using System; + using System.Collections.Generic; + + + [DataCollectorTypeUri("datacollector://Company/Product/Version")] + [DataCollectorFriendlyName("Collect Log Files", false)] + public class MockDataCollector : DataCollector, ITestExecutionEnvironmentSpecifier + { + public static bool IsInitializeInvoked; + public static bool ThrowExceptionWhenInitialized; + public static bool IsDisposeInvoked; + public static bool IsGetTestExecutionEnvironmentVariablesInvoked; + public static bool GetTestExecutionEnvironmentVariablesThrowException; + public static bool DisposeShouldThrowException; + public static IEnumerable> EnvVarList; + public static bool IsEvents_SessionStartInvoked; + public static bool IsEvents_TestCaseStartInvoked; + public static bool ConfigureTestCaseLevelEvents; + public static bool IsEvents_SessionEndInvoked; + public static bool IsEvents_TestCaseEndInvoked; + + public static void Reset() + { + IsInitializeInvoked = false; + ThrowExceptionWhenInitialized = false; + IsDisposeInvoked = false; + IsGetTestExecutionEnvironmentVariablesInvoked = false; + GetTestExecutionEnvironmentVariablesThrowException = false; + DisposeShouldThrowException = false; + EnvVarList = null; + IsEvents_SessionStartInvoked = false; + IsEvents_TestCaseStartInvoked = false; + ConfigureTestCaseLevelEvents = false; + IsEvents_SessionEndInvoked = false; + IsEvents_TestCaseEndInvoked = false; + } + public override void Initialize(XmlElement configurationElement, DataCollectionEvents events, DataCollectionSink dataSink, DataCollectionLogger logger, DataCollectionEnvironmentContext environmentContext) + { + if (ThrowExceptionWhenInitialized) + { + throw new Exception("DataCollectorException"); + } + IsInitializeInvoked = true; + + events.SessionStart += Events_SessionStart; + events.SessionEnd += Events_SessionEnd; + if (ConfigureTestCaseLevelEvents) + { + events.TestCaseStart += Events_TestCaseStart; + events.TestCaseEnd += Events_TestCaseEnd; + } + } + + private void Events_TestCaseEnd(object sender, TestCaseEndEventArgs e) + { + IsEvents_TestCaseEndInvoked = true; + } + + private void Events_SessionEnd(object sender, SessionEndEventArgs e) + { + IsEvents_SessionEndInvoked = true; + } + + private void Events_TestCaseStart(object sender, TestCaseStartEventArgs e) + { + IsEvents_TestCaseStartInvoked = true; + } + + private void Events_SessionStart(object sender, SessionStartEventArgs e) + { + IsEvents_SessionStartInvoked = true; + } + + protected override void Dispose(bool disposing) + { + IsDisposeInvoked = true; + if (DisposeShouldThrowException) + { + throw new Exception("DataCollectorException"); + } + } + + public IEnumerable> GetTestExecutionEnvironmentVariables() + { + if (GetTestExecutionEnvironmentVariablesThrowException) + { + throw new Exception("DataCollectorException"); + } + + IsGetTestExecutionEnvironmentVariablesInvoked = true; + return EnvVarList; + } + } + + [DataCollectorTypeUri("datacollector://Company/Product/Version2")] + [DataCollectorFriendlyName("Collect Log Files2", false)] + public class MockDataCollector2 : DataCollector, ITestExecutionEnvironmentSpecifier + { + public static bool Events_SessionStartThrowException; + public static bool Events_TestCaseStartThrowException; + public static bool Events_SessionEndThrowException; + public static bool Events_TestCaseEndThrowException; + public static bool ConfigureTestCaseLevelEvents; + public static string Events_SessionStartExceptionMessage = "SessionStartException"; + public static string Events_TestCaseStartExceptionMessage = "TestCaseStartException"; + public static string Events_SessionEndExceptionMessage = "SessionEndException"; + public static string Events_TestCaseEndExceptionMessage = "TestCaseEndException"; + + + public static void Reset() + { + Events_SessionStartThrowException = false; + Events_TestCaseStartThrowException = false; + Events_SessionEndThrowException = false; + Events_TestCaseEndThrowException = false; + ConfigureTestCaseLevelEvents = false; + } + + public IEnumerable> GetTestExecutionEnvironmentVariables() + { + return new List>(); + } + + public override void Initialize(XmlElement configurationElement, DataCollectionEvents events, DataCollectionSink dataSink, DataCollectionLogger logger, DataCollectionEnvironmentContext environmentContext) + { + events.SessionStart += Events_SessionStart; + events.SessionEnd += Events_SessionEnd; + + if (ConfigureTestCaseLevelEvents) + { + events.TestCaseStart += Events_TestCaseStart; + events.TestCaseEnd += Events_TestCaseEnd; + } + } + + private void Events_TestCaseEnd(object sender, TestCaseEndEventArgs e) + { + if (Events_TestCaseEndThrowException) + { + throw new Exception(Events_TestCaseEndExceptionMessage); + } + } + + private void Events_TestCaseStart(object sender, TestCaseStartEventArgs e) + { + if (Events_TestCaseStartThrowException) + { + throw new Exception(Events_TestCaseStartExceptionMessage); + } + } + + private void Events_SessionEnd(object sender, SessionEndEventArgs e) + { + if (Events_SessionEndThrowException) + { + throw new Exception(Events_SessionEndExceptionMessage); + } + } + + private void Events_SessionStart(object sender, SessionStartEventArgs e) + { + if (Events_SessionStartThrowException) + { + throw new Exception(Events_SessionStartExceptionMessage); + } + } + } + + +} diff --git a/test/Microsoft.TestPlatform.DataCollection.v1.UnitTests/MockDataCollectionFileManager.cs b/test/Microsoft.TestPlatform.DataCollection.v1.UnitTests/MockDataCollectionFileManager.cs new file mode 100644 index 0000000000..dd2f7f13db --- /dev/null +++ b/test/Microsoft.TestPlatform.DataCollection.v1.UnitTests/MockDataCollectionFileManager.cs @@ -0,0 +1,53 @@ +using Microsoft.VisualStudio.TestPlatform.DataCollection.V1.Interfaces; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestPlatform.DataCollection.V1; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestTools.Common; +using Microsoft.VisualStudio.TestTools.Execution; + +namespace Microsoft.TestPlatform.DataCollection.V1.UnitTests +{ + internal class MockDataCollectionFileManager : IDataCollectionFileManager + { + public List Attachments; + public const string GetDataExceptionMessaage = "FileManagerExcpetion"; + public bool GetDataThrowException; + + public MockDataCollectionFileManager() + { + Attachments = new List(); + } + public void CloseSession(SessionId id) + { + //throw new NotImplementedException(); + } + + public void ConfigureSession(SessionId id, string outputDirectory) + { + throw new NotImplementedException(); + } + + public void DispatchMessage(DataCollectorDataMessage collectorDataMessage) + { + throw new NotImplementedException(); + } + + public void Dispose() + { + throw new NotImplementedException(); + } + + public List GetData(DataCollectionContext collectionContext) + { + if (GetDataThrowException) + { + throw new Exception(GetDataExceptionMessaage); + } + + return Attachments; + } + } +} diff --git a/test/Microsoft.TestPlatform.DataCollection.v1.UnitTests/Properties/AssemblyInfo.cs b/test/Microsoft.TestPlatform.DataCollection.v1.UnitTests/Properties/AssemblyInfo.cs index 075d3dae62..2de45b6137 100644 --- a/test/Microsoft.TestPlatform.DataCollection.v1.UnitTests/Properties/AssemblyInfo.cs +++ b/test/Microsoft.TestPlatform.DataCollection.v1.UnitTests/Properties/AssemblyInfo.cs @@ -7,7 +7,7 @@ // associated with an assembly. [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Microsoft.TestPlatform.DataCollection.UnitTests")] +[assembly: AssemblyProduct("Microsoft.TestPlatform.DataCollection.v1.UnitTests")] [assembly: AssemblyTrademark("")] // Setting ComVisible to false makes the types in this assembly not visible diff --git a/test/datacollector.UnitTests/DataCollectionCoordinatorTests.cs b/test/datacollector.UnitTests/DataCollectionCoordinatorTests.cs index 82bf15053c..edf372e7c1 100644 --- a/test/datacollector.UnitTests/DataCollectionCoordinatorTests.cs +++ b/test/datacollector.UnitTests/DataCollectionCoordinatorTests.cs @@ -14,6 +14,7 @@ namespace Microsoft.VisualStudio.TestPlatform.DataCollector.UnitTests using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection; using Microsoft.VisualStudio.TestTools.UnitTesting; + using TestCaseStartEventArgs = Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging.Events.TestCaseStartEventArgs; [TestClass] public class DataCollectionCoordinatorTests From 76941e97f72add550c0e030b3713eb41af468811 Mon Sep 17 00:00:00 2001 From: Harsh Jain Date: Mon, 8 Aug 2016 01:08:27 +0530 Subject: [PATCH 5/5] Implemented MessageSink, DataCollectionFileManager. --- .../DataCollection}/CopyRequestData.cs | 5 +- .../DataCollectionFileManager.cs | 589 ++++++++++++++++++ .../DataCollectionSessionConfiguration.cs | 4 +- .../DataCollectorDataMessage.cs | 5 +- .../Interfaces/IDataCollectionFileManager.cs | 21 +- .../Interfaces/IMessageSink.cs | 3 +- .../DataCollection/MessageSink.cs | 78 +++ .../Resources.Designer.cs | 11 +- .../Resources.resx | 3 + .../project.json | 6 +- .../DataCollectionFileManager.cs | 515 --------------- .../DataCollectionManager.cs | 11 +- .../ObjectConversionHelper.cs | 47 +- .../Resource.Designer.cs | 9 - .../Resource.resx | 3 - .../TestPlatformDataCollectionLogger.cs | 2 +- .../TestPlatformDataCollectionSink.cs | 5 +- .../TestPlatformDataCollectorInfo.cs | 2 +- .../DataCollector/DataCollectionContext.cs | 125 +++- .../Friends.cs | 7 + .../DataCollectionFileManagerTests.cs | 197 ++++++ .../DataCollection/MessageSinkTests.cs | 97 +++ .../MockDataCollectionFileManager.cs | 50 ++ .../RunConfigurationSettingsProviderTests.cs | 11 - .../project.json | 5 +- .../DataCollectionManagerTests.cs | 20 +- .../MockDataCollectionFileManager.cs | 28 +- .../TestPlatformDataCollectorInfoTests.cs | 3 +- 28 files changed, 1227 insertions(+), 635 deletions(-) rename src/{Microsoft.TestPlatform.DataCollection.v1 => Microsoft.TestPlatform.Common/DataCollection}/CopyRequestData.cs (97%) create mode 100644 src/Microsoft.TestPlatform.Common/DataCollection/DataCollectionFileManager.cs rename src/{Microsoft.TestPlatform.DataCollection.v1 => Microsoft.TestPlatform.Common/DataCollection}/DataCollectionSessionConfiguration.cs (90%) rename src/{Microsoft.TestPlatform.DataCollection.v1 => Microsoft.TestPlatform.Common/DataCollection}/DataCollectorDataMessage.cs (97%) rename src/{Microsoft.TestPlatform.DataCollection.v1 => Microsoft.TestPlatform.Common/DataCollection}/Interfaces/IDataCollectionFileManager.cs (63%) rename src/{Microsoft.TestPlatform.DataCollection.v1 => Microsoft.TestPlatform.Common/DataCollection}/Interfaces/IMessageSink.cs (87%) create mode 100644 src/Microsoft.TestPlatform.Common/DataCollection/MessageSink.cs delete mode 100644 src/Microsoft.TestPlatform.DataCollection.v1/DataCollectionFileManager.cs create mode 100644 test/Microsoft.TestPlatform.Common.UnitTests/DataCollection/DataCollectionFileManagerTests.cs create mode 100644 test/Microsoft.TestPlatform.Common.UnitTests/DataCollection/MessageSinkTests.cs create mode 100644 test/Microsoft.TestPlatform.Common.UnitTests/DataCollection/MockDataCollectionFileManager.cs delete mode 100644 test/Microsoft.TestPlatform.Common.UnitTests/DataCollection/RunConfigurationSettingsProviderTests.cs diff --git a/src/Microsoft.TestPlatform.DataCollection.v1/CopyRequestData.cs b/src/Microsoft.TestPlatform.Common/DataCollection/CopyRequestData.cs similarity index 97% rename from src/Microsoft.TestPlatform.DataCollection.v1/CopyRequestData.cs rename to src/Microsoft.TestPlatform.Common/DataCollection/CopyRequestData.cs index 2548fd4bb6..5b71134a26 100644 --- a/src/Microsoft.TestPlatform.DataCollection.v1/CopyRequestData.cs +++ b/src/Microsoft.TestPlatform.Common/DataCollection/CopyRequestData.cs @@ -1,6 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.VisualStudio.TestPlatform.DataCollection.V1 +namespace Microsoft.VisualStudio.TestPlatform.Common.DataCollection { using System; using System.IO; @@ -17,7 +17,6 @@ internal class CopyRequestData : IDisposable /// private bool disposed; - /// /// Set when this request is processed/completed. /// @@ -137,7 +136,7 @@ protected virtual void Dispose(bool disposing) { if (disposing) { - this.requestCompleted.Close(); + this.requestCompleted.Dispose(); } this.disposed = true; diff --git a/src/Microsoft.TestPlatform.Common/DataCollection/DataCollectionFileManager.cs b/src/Microsoft.TestPlatform.Common/DataCollection/DataCollectionFileManager.cs new file mode 100644 index 0000000000..98747ad098 --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/DataCollection/DataCollectionFileManager.cs @@ -0,0 +1,589 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Common.DataCollection +{ + using System; + using System.Collections.Generic; + using System.ComponentModel; + using System.Diagnostics; + using System.Globalization; + using System.IO; + using System.Linq; + + using Microsoft.VisualStudio.TestPlatform.Common.DataCollection.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + using Microsoft.VisualStudio.TestPlatform.Utilities; + + /// + /// Manages file transfer from data collector to test runner service. + /// + internal class DataCollectionFileManager : IDataCollectionFileManager + { + #region Fields + + /// + /// Max number of file transfer jobs. + /// + private const int MaxQueueLength = 500; + + /// + /// Max queue size + /// + private const int MaxQueueSize = 25000000; + + /// + /// Default results directory to be used when user didn't specify. + /// + private const string DefaultOutputDirectoryName = "TestPlatformResults"; + + /// + /// Specifies whether the object is disposed or not. + /// + private bool disposed; + + /// + /// Job queue for moving file from source to destination. + /// + private JobQueue fileCopierJobQueue; + + /// + /// Logger for data collection messages + /// + private IDataCollectionLog dataCollectionLog; + + #endregion + + #region Constructor + + /// + /// Initializes a new instance of the class. + /// + public DataCollectionFileManager() + : this(default(IDataCollectionLog)) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The data collection log. + /// + internal DataCollectionFileManager(IDataCollectionLog dataCollectionLog) + { + Debug.Assert(dataCollectionLog != null, "dataCollectionLog cannot be null."); + this.dataCollectionLog = dataCollectionLog; + this.SessionInfo = new Dictionary(); + this.CopyRequestDataDictionary = new Dictionary>(); + + this.fileCopierJobQueue = new JobQueue( + this.OnFileCopyRequest, + "DataCollectionFileManagerQueue", + MaxQueueLength, + MaxQueueSize, + true, + message => EqtTrace.Error(message)); + } + + #endregion + + #region Properties + + /// + /// Gets the session info. + /// + internal IDictionary SessionInfo { get; } + + /// + /// Gets the copy request data dictionary. + /// + internal IDictionary> CopyRequestDataDictionary { get; } + + #endregion + + #region public methods + + /// + /// Creates and stores session configuration for specified session id. + /// + /// session id + /// base output directory for session + public void ConfigureSession(SessionId id, string outputDirectory) + { + ValidateArg.NotNull(id, nameof(id)); + + string sessionOutputDirectory; + + if (string.IsNullOrEmpty(outputDirectory)) + { + sessionOutputDirectory = Path.Combine(Path.GetTempPath(), DefaultOutputDirectoryName); + sessionOutputDirectory = Path.Combine(sessionOutputDirectory, id.Id.ToString()); + } + else + { + // Create a session specific directory under base output directory. + var expandedOutputDirectory = Environment.ExpandEnvironmentVariables(outputDirectory); + var absolutePath = Path.GetFullPath(expandedOutputDirectory); + sessionOutputDirectory = Path.Combine(absolutePath, id.Id.ToString()); + } + + // Create the output directory if it doesn't exist. + if (!Directory.Exists(sessionOutputDirectory)) + { + Directory.CreateDirectory(sessionOutputDirectory); + } + + var sessionConfiguration = new DataCollectionSessionConfiguration(id, sessionOutputDirectory); + + lock (this.SessionInfo) + { + if (this.SessionInfo.ContainsKey(id)) + { + Debug.Fail( + string.Format(CultureInfo.CurrentCulture, "Session with Id '{0}' is already configured", id.Id)); + this.SessionInfo[id] = sessionConfiguration; + } + else + { + this.SessionInfo.Add(id, sessionConfiguration); + } + } + } + + /// + /// Gets CollectorData associated with given collection context. + /// + /// + /// The data collection context. + /// + /// + /// The . + /// + public List GetData(DataCollectionContext dataCollectionContext) + { + var entries = new List(); + List copyRequests; + lock (this.CopyRequestDataDictionary) + { + if (!this.CopyRequestDataDictionary.TryGetValue(dataCollectionContext, out copyRequests)) + { + if (EqtTrace.IsWarningEnabled) + { + EqtTrace.Warning( + "FileDataManager.GetData: Called for a context(SessionId:'{0}' TestCaseExecId:'{1}') which is not registered", + dataCollectionContext.SessionId.Id.ToString(), + dataCollectionContext.HasTestCase + ? dataCollectionContext.TestExecId.Id.ToString() + : "No TestCase"); + } + + return entries; + } + + this.CopyRequestDataDictionary.Remove(dataCollectionContext); + } + + foreach (var copyRequest in copyRequests) + { + copyRequest.WaitForCopyComplete(); + if (null != copyRequest.Error) + { + // If there was error in processing the request, lets log it. + var testCaseId = dataCollectionContext.HasTestCase + ? dataCollectionContext.TestExecId.Id + : Guid.Empty; + this.LogError( + copyRequest.Error.Message, + copyRequest.FileDataHeaderMessage.Uri, + copyRequest.FileDataHeaderMessage.FriendlyName, + testCaseId); + } + else + { + // Create collectorDataEntry for each collected log. + var entry = + entries.FirstOrDefault( + e => Uri.Equals(e.Uri, copyRequest.FileDataHeaderMessage.Uri)); + if (null == entry) + { + entry = new AttachmentSet( + copyRequest.FileDataHeaderMessage.Uri, + copyRequest.FileDataHeaderMessage.FriendlyName); + entries.Add(entry); + } + + entry.Attachments.Add( + new UriDataAttachment( + new Uri(copyRequest.LocalFilePath), + copyRequest.FileDataHeaderMessage.Description)); + } + + copyRequest.Dispose(); + } + + return entries; + } + + /// + /// Perform cleanup for the session. + /// All session related entries are deleted/disposed. + /// + /// Session Id. + public void CloseSession(SessionId sessionId) + { + ValidateArg.NotNull(sessionId, nameof(sessionId)); + + var requestsToDispose = new List(); + lock (this.CopyRequestDataDictionary) + { + var contextToRemove = new List(); + foreach (var kvp in this.CopyRequestDataDictionary) + { + if (kvp.Key.SessionId.Equals(sessionId)) + { + requestsToDispose.AddRange(kvp.Value); + contextToRemove.Add(kvp.Key); + } + } + + foreach (var context in contextToRemove) + { + this.CopyRequestDataDictionary.Remove(context); + } + } + + // Dispose all requests. + requestsToDispose.ForEach(request => request.Dispose()); + } + + /// + /// Process the message + /// + /// Data transfer message + public void DispatchMessage(DataCollectorDataMessage collectorDataMessage) + { + var headerMessage = collectorDataMessage as FileDataHeaderMessage; + if (null != headerMessage) + { + this.AddNewFileTransfer(headerMessage); + } + else + { + if (EqtTrace.IsErrorEnabled) + { + EqtTrace.Error( + "DataCollectionFileManager.DispatchMessage: Got unexpected message of type '{0}'.", + collectorDataMessage.GetType()); + } + } + } + + /// + /// Dispose event object + /// + public void Dispose() + { + this.Dispose(true); + + // Use SupressFinalize in case a subclass + // of this type implements a finalizer. + GC.SuppressFinalize(this); + } + + #endregion + + /// + /// The dispose. + /// + /// + /// The disposing. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", MessageId = "backgroundFileCopier", Justification = "Bug in clr")] + protected virtual void Dispose(bool disposing) + { + if (!this.disposed) + { + if (disposing) + { + if (null != this.fileCopierJobQueue) + { + this.fileCopierJobQueue.Dispose(); + } + } + + this.disposed = true; + } + } + + #region private methods + + /// + /// Mark the request as processed/completed. + /// If any error occurred during processing, error information is also sent to copy request. + /// + /// + /// The file copy request. + /// + /// + /// The e. + /// + private static void CompleteFileTransfer(CopyRequestData fileCopyRequest, Exception e) + { + fileCopyRequest.CompleteRequest(e); + } + + /// + /// Sanity checks on CopyRequestData + /// + /// + /// The copy Request. + /// + private static void Validate(CopyRequestData copyRequest) + { + if (!File.Exists(copyRequest.FileDataHeaderMessage.FileName)) + { + throw new FileNotFoundException( + string.Format( + CultureInfo.CurrentCulture, + "Could not find source file '{0}'.", + copyRequest.FileDataHeaderMessage.FileName)); + } + + var directoryName = Path.GetDirectoryName(copyRequest.LocalFilePath); + if (!Directory.Exists(directoryName)) + { + Directory.CreateDirectory(directoryName); + } + else if (File.Exists(copyRequest.LocalFilePath)) + { + File.Delete(copyRequest.LocalFilePath); + } + } + + /// + /// Add a new file transfer (either copy/move) request. + /// + /// File data header message. + private void AddNewFileTransfer(FileDataHeaderMessage headerMessage) + { + DataCollectionSessionConfiguration sessionConfiguration; + var context = headerMessage.DataCollectionContext; + Debug.Assert( + context != null, + "DataCollectionManager.AddNewFileTransfer: FileDataHeaderMessage with null context."); + lock (this.SessionInfo) + { + if (!this.SessionInfo.TryGetValue(context.SessionId, out sessionConfiguration)) + { + Debug.Fail( + string.Format( + CultureInfo.CurrentCulture, + "DataCollectionManager.AddNewFileTransfer: File transfer requested for unknown session '{0}'", + context.SessionId)); + return; + } + } + + var outputDirectory = sessionConfiguration.OutputDirectory; + + List requestedCopies; + lock (this.CopyRequestDataDictionary) + { + if (!this.CopyRequestDataDictionary.TryGetValue(context, out requestedCopies)) + { + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose( + "DataCollectionFileManager.AddNewFileTransfer: No copy request for collection context ({0}:{1}).", + context.SessionId.Id.ToString(), + context.HasTestCase ? context.TestExecId.Id.ToString() : "NoTestCase"); + } + + requestedCopies = new List(); + this.CopyRequestDataDictionary.Add(context, requestedCopies); + } + else + { + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose( + "DataCollectionFileManager.AddNewFileTransfer: Found existing copy request(s) for collection context ({0}:{1}).", + context.SessionId.Id.ToString(), + context.HasTestCase ? context.TestExecId.Id.ToString() : "NoTestCase"); + } + } + } + + CopyRequestData requestCopy; + lock (requestedCopies) + { + requestCopy = new CopyRequestData(outputDirectory, headerMessage); + requestedCopies.Add(requestCopy); + } + + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose("DataCollectionFileManager.AddNewFileTransfer: Enqueing for transfer."); + } + + this.fileCopierJobQueue.QueueJob(requestCopy, 0); + } + + /// + /// Background job processor. + /// + /// Copy request data. + /// + private void OnFileCopyRequest(CopyRequestData fileCopyRequest) + { + if (fileCopyRequest.FileDataHeaderMessage.PerformCleanup) + { + this.FileMove(fileCopyRequest); + } + else + { + this.FileCopy(fileCopyRequest); + } + } + + /// + /// Do a file copy for the specified request + /// + /// Copy request data. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", + Justification = "Need to catch all exception type to send as data collection error to client.")] + private void FileCopy(CopyRequestData copyRequest) + { + Exception error = null; + try + { + Validate(copyRequest); + File.Copy(copyRequest.FileDataHeaderMessage.FileName, copyRequest.LocalFilePath); + } + catch (Exception ex) + { + error = ex; + } + finally + { + // Let collector know of transfer completed if it has registered transferCompletedHandler. + this.TriggerCallback( + copyRequest.FileDataHeaderMessage.FileTransferCompletedHandler, + copyRequest.FileDataHeaderMessage.UserToken, + error, + copyRequest.FileDataHeaderMessage.FileName); + CompleteFileTransfer(copyRequest, error); + } + } + + /// + /// Make a callback indicating the file transfer is complete. + /// Needed when file copy is requested (as data collector might use requested file after transfer is complete). + /// + /// + /// The transfer completed callback. + /// + /// + /// The user token. + /// + /// + /// The exception. + /// + /// + /// The path. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", + Justification = "Ignorable error which shouldn't crash the data collection framework.")] + private void TriggerCallback( + AsyncCompletedEventHandler transferCompletedCallback, + object userToken, + Exception exception, + string path) + { + Debug.Assert(!string.IsNullOrEmpty(path), "null or empty path"); + if (transferCompletedCallback != null) + { + try + { + transferCompletedCallback(this, new AsyncCompletedEventArgs(exception, false, userToken)); + } + catch (Exception e) + { + if (EqtTrace.IsErrorEnabled) + { + EqtTrace.Error( + "DataCollectionFileManager.TriggerCallBack: Error occurred while raising the file transfer completed callback for {0}. Error: {1}", + path, + e.ToString()); + } + } + } + } + + /// + /// Move the file as specified in request. + /// + /// Copy request data + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", + Justification = "Need to catch all exception type to send as data collection error to client.")] + private void FileMove(CopyRequestData copyRequest) + { + Exception error = null; + try + { + Validate(copyRequest); + File.Move(copyRequest.FileDataHeaderMessage.FileName, copyRequest.LocalFilePath); + } + catch (Exception ex) + { + error = ex; + } + finally + { + // Let collector know of transfer completed if it has registered transferCompletedHandler. + this.TriggerCallback( + copyRequest.FileDataHeaderMessage.FileTransferCompletedHandler, + copyRequest.FileDataHeaderMessage.UserToken, + error, + copyRequest.FileDataHeaderMessage.FileName); + CompleteFileTransfer(copyRequest, error); + } + } + + /// + /// Logs an error message. + /// + /// + /// The error message. + /// + /// + /// The collector uri. + /// + /// + /// The collector friendly name. + /// + /// + /// Id of testCase if available, null otherwise. + /// + private void LogError(string errorMessage, Uri collectorUri, string collectorFriendlyName, Guid testCaseId) + { + Debug.Assert(this.dataCollectionLog != null, "DataCollectionLog cannot be null"); + var args = new DataCollectionMessageEventArgs(TestMessageLevel.Error, errorMessage) + { + Uri = collectorUri, + FriendlyName = collectorFriendlyName + }; + + if (!testCaseId.Equals(Guid.Empty)) + { + args.TestCaseId = testCaseId; + } + + this.dataCollectionLog.SendDataCollectionMessage(args); + } + + #endregion + } +} diff --git a/src/Microsoft.TestPlatform.DataCollection.v1/DataCollectionSessionConfiguration.cs b/src/Microsoft.TestPlatform.Common/DataCollection/DataCollectionSessionConfiguration.cs similarity index 90% rename from src/Microsoft.TestPlatform.DataCollection.v1/DataCollectionSessionConfiguration.cs rename to src/Microsoft.TestPlatform.Common/DataCollection/DataCollectionSessionConfiguration.cs index b3bac189db..b885ef977a 100644 --- a/src/Microsoft.TestPlatform.DataCollection.v1/DataCollectionSessionConfiguration.cs +++ b/src/Microsoft.TestPlatform.Common/DataCollection/DataCollectionSessionConfiguration.cs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.VisualStudio.TestPlatform.DataCollection.V1 +namespace Microsoft.VisualStudio.TestPlatform.Common.DataCollection { using Microsoft.VisualStudio.TestPlatform.ObjectModel; - using SessionId = Microsoft.VisualStudio.TestTools.Common.SessionId; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection; /// /// Session/Run configuration information for data collection. diff --git a/src/Microsoft.TestPlatform.DataCollection.v1/DataCollectorDataMessage.cs b/src/Microsoft.TestPlatform.Common/DataCollection/DataCollectorDataMessage.cs similarity index 97% rename from src/Microsoft.TestPlatform.DataCollection.v1/DataCollectorDataMessage.cs rename to src/Microsoft.TestPlatform.Common/DataCollection/DataCollectorDataMessage.cs index 854bbb27d1..4b90dca9cb 100644 --- a/src/Microsoft.TestPlatform.DataCollection.v1/DataCollectorDataMessage.cs +++ b/src/Microsoft.TestPlatform.Common/DataCollection/DataCollectorDataMessage.cs @@ -1,11 +1,12 @@ // Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.VisualStudio.TestPlatform.DataCollection.V1 + +namespace Microsoft.VisualStudio.TestPlatform.Common.DataCollection { using System; using System.ComponentModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel; - using Microsoft.VisualStudio.TestTools.Execution; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection; /// /// Base class for all message used in file transfer. diff --git a/src/Microsoft.TestPlatform.DataCollection.v1/Interfaces/IDataCollectionFileManager.cs b/src/Microsoft.TestPlatform.Common/DataCollection/Interfaces/IDataCollectionFileManager.cs similarity index 63% rename from src/Microsoft.TestPlatform.DataCollection.v1/Interfaces/IDataCollectionFileManager.cs rename to src/Microsoft.TestPlatform.Common/DataCollection/Interfaces/IDataCollectionFileManager.cs index b589bd3266..c932529e9a 100644 --- a/src/Microsoft.TestPlatform.DataCollection.v1/Interfaces/IDataCollectionFileManager.cs +++ b/src/Microsoft.TestPlatform.Common/DataCollection/Interfaces/IDataCollectionFileManager.cs @@ -1,17 +1,17 @@ // Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.VisualStudio.TestPlatform.DataCollection.V1.Interfaces +namespace Microsoft.VisualStudio.TestPlatform.Common.DataCollection.Interfaces { + using System; using System.Collections.Generic; - using CollectorDataEntry = Microsoft.VisualStudio.TestPlatform.ObjectModel.AttachmentSet; - using DataCollectionContext = Microsoft.VisualStudio.TestTools.Execution.DataCollectionContext; - using SessionId = Microsoft.VisualStudio.TestTools.Common.SessionId; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection; /// /// The DataCollectionFileManager interface. /// - internal interface IDataCollectionFileManager + internal interface IDataCollectionFileManager : IDisposable { /// /// The configure session. @@ -27,13 +27,13 @@ internal interface IDataCollectionFileManager /// /// The get data. /// - /// - /// The collection context. + /// + /// The session Id. /// /// /// The . /// - List GetData(DataCollectionContext collectionContext); + List GetData(DataCollectionContext sessionId); /// /// The close session. @@ -50,10 +50,5 @@ internal interface IDataCollectionFileManager /// The collector data message. /// void DispatchMessage(DataCollectorDataMessage collectorDataMessage); - - /// - /// The dispose. - /// - void Dispose(); } } \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.DataCollection.v1/Interfaces/IMessageSink.cs b/src/Microsoft.TestPlatform.Common/DataCollection/Interfaces/IMessageSink.cs similarity index 87% rename from src/Microsoft.TestPlatform.DataCollection.v1/Interfaces/IMessageSink.cs rename to src/Microsoft.TestPlatform.Common/DataCollection/Interfaces/IMessageSink.cs index 4166005a5c..6bcb5d9f42 100644 --- a/src/Microsoft.TestPlatform.DataCollection.v1/Interfaces/IMessageSink.cs +++ b/src/Microsoft.TestPlatform.Common/DataCollection/Interfaces/IMessageSink.cs @@ -1,9 +1,10 @@ // Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.VisualStudio.TestPlatform.DataCollection.V1.Interfaces +namespace Microsoft.VisualStudio.TestPlatform.Common.DataCollection.Interfaces { using System; + using Microsoft.VisualStudio.TestPlatform.Common.DataCollection; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; /// diff --git a/src/Microsoft.TestPlatform.Common/DataCollection/MessageSink.cs b/src/Microsoft.TestPlatform.Common/DataCollection/MessageSink.cs new file mode 100644 index 0000000000..1db4aafbde --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/DataCollection/MessageSink.cs @@ -0,0 +1,78 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.TestPlatform.Common.DataCollection +{ + using System; + using System.Globalization; + + using Microsoft.VisualStudio.TestPlatform.Common.DataCollection.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + using Microsoft.VisualStudio.TestPlatform.Utilities; + + using Resources = Microsoft.VisualStudio.TestPlatform.Common.Resources; + + /// + /// The message sink. + /// + internal class MessageSink : IMessageSink + { + /// + /// The file manager. + /// + private IDataCollectionFileManager fileManager; + + /// + /// Initializes a new instance of the class. + /// + public MessageSink() : this(new DataCollectionFileManager()) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The file manager. + /// + internal MessageSink(IDataCollectionFileManager fileManager) + { + ValidateArg.NotNull(fileManager, nameof(fileManager)); + this.fileManager = fileManager; + } + + /// + /// Gets or sets the on data collection message. + /// + public EventHandler OnDataCollectionMessage { get; set; } + + /// + /// The send message. + /// + /// + /// The args. + /// + public void SendMessage(DataCollectionMessageEventArgs args) + { + this.OnDataCollectionMessage.SafeInvoke(this, args, "DataCollectionManager.SendMessage"); + } + + /// + /// Data collection message as sent by DataCollectionSink. + /// + /// DataCollection data message + public void SendMessage(DataCollectorDataMessage collectorDataMessage) + { + ValidateArg.NotNull(collectorDataMessage, nameof(collectorDataMessage)); + + if (collectorDataMessage is FileDataHeaderMessage) + { + // Dispatch message to file manager. + this.fileManager.DispatchMessage(collectorDataMessage); + return; + } + + throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Resources.DataCollectorUnsupportedMessageType, collectorDataMessage.GetType())); + } + } +} diff --git a/src/Microsoft.TestPlatform.Common/Resources.Designer.cs b/src/Microsoft.TestPlatform.Common/Resources.Designer.cs index 120051e1c6..05b11e6390 100644 --- a/src/Microsoft.TestPlatform.Common/Resources.Designer.cs +++ b/src/Microsoft.TestPlatform.Common/Resources.Designer.cs @@ -1,4 +1,4 @@ -//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 @@ -77,6 +77,15 @@ public class Resources { } } + /// + /// Looks up a localized string similar to Sending message of Message Type '{0}' is not supported.. + /// + public static string DataCollectorUnsupportedMessageType { + get { + return ResourceManager.GetString("DataCollectorUnsupportedMessageType", resourceCulture); + } + } + /// /// Looks up a localized string similar to Duplicate test extension URI '{0}'. Ignoring the duplicate extension.. /// diff --git a/src/Microsoft.TestPlatform.Common/Resources.resx b/src/Microsoft.TestPlatform.Common/Resources.resx index fff7c2610d..a0689d26fb 100644 --- a/src/Microsoft.TestPlatform.Common/Resources.resx +++ b/src/Microsoft.TestPlatform.Common/Resources.resx @@ -123,6 +123,9 @@ Diagnostic data adapter ('{0}') message: {1}. + + Sending message of Message Type '{0}' is not supported. + Duplicate test extension URI '{0}'. Ignoring the duplicate extension. diff --git a/src/Microsoft.TestPlatform.Common/project.json b/src/Microsoft.TestPlatform.Common/project.json index be88cd7f09..c45bbe2c07 100644 --- a/src/Microsoft.TestPlatform.Common/project.json +++ b/src/Microsoft.TestPlatform.Common/project.json @@ -8,7 +8,8 @@ }, "dependencies": { - "Microsoft.TestPlatform.ObjectModel": "15.0.0-*" + "Microsoft.TestPlatform.ObjectModel": "15.0.0-*", + "Microsoft.TestPlatform.CoreUtilities": "15.0.0-*" }, "frameworks": { @@ -19,7 +20,8 @@ ], "dependencies": { "NETStandard.Library": "1.6.0", - "System.Runtime.Loader": "4.0.0-rc2-24027" + "System.Runtime.Loader": "4.0.0-rc2-24027", + "System.Threading": "4.0.11" } }, "net46": { diff --git a/src/Microsoft.TestPlatform.DataCollection.v1/DataCollectionFileManager.cs b/src/Microsoft.TestPlatform.DataCollection.v1/DataCollectionFileManager.cs deleted file mode 100644 index 845a860b90..0000000000 --- a/src/Microsoft.TestPlatform.DataCollection.v1/DataCollectionFileManager.cs +++ /dev/null @@ -1,515 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -namespace Microsoft.VisualStudio.TestPlatform.DataCollection.V1 -{ - using System; - using System.Collections.Generic; - using System.ComponentModel; - using System.Diagnostics; - using System.Globalization; - using System.IO; - using System.Linq; - - using Microsoft.VisualStudio.TestPlatform.Common.DataCollection.Interfaces; - using Microsoft.VisualStudio.TestPlatform.DataCollection.V1.Interfaces; - using Microsoft.VisualStudio.TestPlatform.ObjectModel; - using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; - - using CollectorDataEntry = Microsoft.VisualStudio.TestPlatform.ObjectModel.AttachmentSet; - using DataCollectionContext = Microsoft.VisualStudio.TestTools.Execution.DataCollectionContext; - using SessionId = Microsoft.VisualStudio.TestTools.Common.SessionId; - - /// - /// Manages file transfer from data collector to test runner service. - /// - internal class DataCollectionFileManager : IDisposable - { - #region Fields - /// - /// Max number of file transfer jobs. - /// - private const int MaxJobCount = 1024; - - - /// - /// Default results directory to be used when user didn't specifiy. - /// - private const string DefaultOutputDirectoryName = "TestPlatformResults"; - - - /// - /// Specifies whether the object is disposed or not. - /// - private bool disposed; - - /// - /// Configuration information about each active session. - /// (Currently there should be only one active session) - /// - private IDictionary sessionInfo; - - /// - /// Active file copy requests in the system. - /// - private IDictionary> copyfiles; - - - /// - /// Background job processor for moving file from source to destination. - /// - private TestTools.Common.BackgroundJobProcessor backgroundFileCopier; - - // todo : why is this required? - /// - /// Logger for data collection messages - /// - private IDataCollectionLog dataCollectionLog; - - #endregion - - #region Constructor - /// - /// Constructor - /// - internal DataCollectionFileManager(IDataCollectionLog dataCollectionLog) - { - Debug.Assert(dataCollectionLog != null, "dataCollectionLog cannot be null."); - this.dataCollectionLog = dataCollectionLog; - sessionInfo = new Dictionary(); - copyfiles = new Dictionary>(); - - //Background job processor for file copy/move operation(s). - backgroundFileCopier = new TestTools.Common.BackgroundJobProcessor( - "DataCollectionFileManager", - OnFileCopyRequest, - MaxJobCount); - } - #endregion - - #region Properties - internal IDictionary SessionInfo - { - get - { - return sessionInfo; - } - } - - internal IDictionary> Copyfiles - { - get - { - return copyfiles; - } - } - - #endregion - - #region internal methods - /// - /// Creates and stores session configuration for specified session id. - /// - /// session id - /// base output directory for session - internal void ConfigureSession(SessionId id, string outputDirectory) - { - ValidateArg.NotNull(id, "id"); - - string sessionOutputDirectory = string.Empty; - if (string.IsNullOrEmpty(outputDirectory)) - { - sessionOutputDirectory = Path.Combine(Path.GetTempPath(), DefaultOutputDirectoryName); - sessionOutputDirectory = Path.Combine(sessionOutputDirectory, id.Id.ToString()); - } - else - { - //Create a session specific directory under base output directory. - string expandedOutputDirectory = Environment.ExpandEnvironmentVariables(outputDirectory); - string absolutePath = Path.GetFullPath(expandedOutputDirectory); - sessionOutputDirectory = Path.Combine(absolutePath, id.Id.ToString()); - } - - //Create the output directory if it doesn't exist. - if (!Directory.Exists(sessionOutputDirectory)) - { - Directory.CreateDirectory(sessionOutputDirectory); - } - - DataCollectionSessionConfiguration sessionConfiguration = new DataCollectionSessionConfiguration(id, sessionOutputDirectory); - - lock (sessionInfo) - { - if (sessionInfo.ContainsKey(id)) - { - Debug.Fail(string.Format(CultureInfo.CurrentCulture, "Session with Id '{0}' is already configured", id.Id)); - sessionInfo[id] = sessionConfiguration; - } - else - { - sessionInfo.Add(id, sessionConfiguration); - } - } - } - - /// - /// Gets CollectorData associated with given collection context. - /// - /// - /// - internal List GetData(DataCollectionContext collectionContext) - { - List entries = new List(); - List copyRequests; - lock (copyfiles) - { - if (!copyfiles.TryGetValue(collectionContext, out copyRequests)) - { - if (EqtTrace.IsWarningEnabled) - { - EqtTrace.Warning("FileDataManager.GetData: Called for a context(SessionId:'{0}' TestCaseExecId:'{1}') which is not registered", - collectionContext.SessionId.Id.ToString(), collectionContext.HasTestCase ? collectionContext.TestExecId.Id.ToString() : "No TestCase"); - } - return entries; - } - copyfiles.Remove(collectionContext); - } - foreach (CopyRequestData copyRequest in copyRequests) - { - copyRequest.WaitForCopyComplete(); - if (null != copyRequest.Error) - { - //If there was error in processing the request, lets log it. - Guid testCaseId = collectionContext.HasTestCase ? collectionContext.TestExecId.Id : Guid.Empty; - LogError(copyRequest.Error.Message, copyRequest.FileDataHeaderMessage.Uri, copyRequest.FileDataHeaderMessage.FriendlyName, testCaseId); - } - else - { - //Create collectorDataEntry for each collected log. - CollectorDataEntry entry = entries.FirstOrDefault(e => Uri.Equals(e.Uri, copyRequest.FileDataHeaderMessage.Uri)); - if (null == entry) - { - entry = new CollectorDataEntry(copyRequest.FileDataHeaderMessage.Uri, copyRequest.FileDataHeaderMessage.FriendlyName); - entries.Add(entry); - } - entry.Attachments.Add(new UriDataAttachment(new Uri(copyRequest.LocalFilePath), copyRequest.FileDataHeaderMessage.Description)); - } - copyRequest.Dispose(); - } - return entries; - } - - - /// - /// Perform cleanup for the session. - /// All session related entries are deleted/disposed. - /// - /// - internal void CloseSession(SessionId id) - { - ValidateArg.NotNull(id, "id"); - - List requestsToDispose = new List(); - lock (copyfiles) - { - List contextToRemove = new List(); - foreach (KeyValuePair> kvp in copyfiles) - { - if (kvp.Key.SessionId.Equals(id)) - { - requestsToDispose.AddRange(kvp.Value); - contextToRemove.Add(kvp.Key); - } - } - foreach (DataCollectionContext context in contextToRemove) - { - copyfiles.Remove(context); - } - } - //Now dispose all requests. - requestsToDispose.ForEach(request => request.Dispose()); - } - - - /// - /// Process the message - /// - /// Data transfer message - internal void DispatchMessage(DataCollectorDataMessage collectorDataMessage) - { - FileDataHeaderMessage headerMessage = collectorDataMessage as FileDataHeaderMessage; - if (null != headerMessage) - { - AddNewFileTransfer(headerMessage); - } - else - { - if (EqtTrace.IsErrorEnabled) - { - EqtTrace.Error("DataCollectionFileManager.DispatchMessage: Got unexpected message of type '{0}'.", collectorDataMessage.GetType()); - } - } - } - - #endregion - - #region private methods - /// - /// Add a new file transfer (either copy/move) request. - /// - /// - private void AddNewFileTransfer(FileDataHeaderMessage headerMessage) - { - DataCollectionSessionConfiguration sessionConfiguration; - DataCollectionContext context = headerMessage.DataCollectionContext; - Debug.Assert(context != null, "DataCollectionManager.AddNewFileTransfer: FileDataHeaderMessage with null context."); - lock (sessionInfo) - { - - if (!sessionInfo.TryGetValue(context.SessionId, out sessionConfiguration)) - { - Debug.Fail(String.Format(CultureInfo.CurrentCulture, "DataCollectionManager.AddNewFileTransfer: File transfer requested for unknown session '{0}'", - context.SessionId)); - return; - } - } - string outputDirectory = sessionConfiguration.OutputDirectory; - - List requestedCopies; - lock (copyfiles) - { - if (!copyfiles.TryGetValue(context, out requestedCopies)) - { - if (EqtTrace.IsVerboseEnabled) - { - EqtTrace.Verbose("DataCollectionFileManager.AddNewFileTransfer: No copy request for collection context ({0}:{1}).", - context.SessionId.Id.ToString(), context.HasTestCase ? context.TestExecId.Id.ToString() : "NoTestCase"); - } - requestedCopies = new List(); - copyfiles.Add(context, requestedCopies); - } - else - { - if (EqtTrace.IsVerboseEnabled) - { - EqtTrace.Verbose("DataCollectionFileManager.AddNewFileTransfer: Found existing copy request(s) for collection context ({0}:{1}).", - context.SessionId.Id.ToString(), context.HasTestCase ? context.TestExecId.Id.ToString() : "NoTestCase"); - } - } - } - - CopyRequestData requestCopy = null; - lock (requestedCopies) - { - requestCopy = new CopyRequestData(outputDirectory, headerMessage); - //TODO::dhruvk::Detect duplicate file names - //TODO::dhruvk::File name needs to be reserved to avoid duplicate conflicts. - requestedCopies.Add(requestCopy); - } - - if (null != requestCopy) - { - if (EqtTrace.IsVerboseEnabled) - { - EqtTrace.Verbose("DataCollectionFileManager.AddNewFileTransfer: Enqueing for transfer."); - } - backgroundFileCopier.Enqueue(requestCopy); - } - - } - - - /// - /// Background job processor. - /// - /// - /// - private void OnFileCopyRequest(CopyRequestData fileCopyRequest, TestTools.Common.IQueuedJobs queuedJobs) - { - if (fileCopyRequest.FileDataHeaderMessage.PerformCleanup) - { - FileMove(fileCopyRequest); - } - else - { - FileCopy(fileCopyRequest); - } - } - - - /// - /// Do a file copy for the specified request - /// - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Need to catch all exception type to send as data collection error to client.")] - private void FileCopy(CopyRequestData copyRequest) - { - Exception error = null; - try - { - Validate(copyRequest); - File.Copy(copyRequest.FileDataHeaderMessage.FileName, copyRequest.LocalFilePath); - } - catch (Exception ex) - { - error = ex; - } - finally - { - //Let collector know of transfer completed if it has registered transferCompletedHandler. - TriggerCallback(copyRequest.FileDataHeaderMessage.FileTransferCompletedHandler, copyRequest.FileDataHeaderMessage.UserToken, error, copyRequest.FileDataHeaderMessage.FileName); - CompleteFileTransfer(copyRequest, error); - } - } - - - /// - /// Sanity checks on CopyRequestData - /// - /// - private static void Validate(CopyRequestData fileCopyRequest) - { - if (!File.Exists(fileCopyRequest.FileDataHeaderMessage.FileName)) - { - throw new FileNotFoundException(string.Format(CultureInfo.CurrentCulture, - "Could not find source file '{0}'.", fileCopyRequest.FileDataHeaderMessage.FileName)); - } - string directoryName = Path.GetDirectoryName(fileCopyRequest.LocalFilePath); - if (!Directory.Exists(directoryName)) - { - Directory.CreateDirectory(directoryName); - } - else if (File.Exists(fileCopyRequest.LocalFilePath)) - { - File.Delete(fileCopyRequest.LocalFilePath); - } - } - - - /// - /// Make a callback indicating the file transfer is complete. - /// Needed when file copy is requested (as data collector might use requested file after transfer is complete). - /// - /// - /// - /// - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Ignorable error which shouldn't crash the data collection framework.")] - private void TriggerCallback(AsyncCompletedEventHandler transferCompletedCallback, object userToken, Exception exception, string path) - { - Debug.Assert(!String.IsNullOrEmpty(path), "null or empty path"); - if (transferCompletedCallback != null) - { - try - { - transferCompletedCallback(this, new AsyncCompletedEventArgs(exception, false, userToken)); - } - catch (Exception e) - { - if (EqtTrace.IsErrorEnabled) - { - EqtTrace.Error("DataCollectionFileManager.TriggerCallBack: Error occurred while raising the file transfer completed callback for {0}. Error: {1}", path, e.ToString()); - } - } - } - } - - - /// - /// Move the file as specified in request. - /// - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Need to catch all exception type to send as data collection error to client.")] - private void FileMove(CopyRequestData copyRequest) - { - Exception error = null; - try - { - Validate(copyRequest); - File.Move(copyRequest.FileDataHeaderMessage.FileName, copyRequest.LocalFilePath); - } - catch (Exception ex) - { - error = ex; - } - finally - { - //Let collector know of transfer completed if it has registered transferCompletedHandler. - TriggerCallback(copyRequest.FileDataHeaderMessage.FileTransferCompletedHandler, copyRequest.FileDataHeaderMessage.UserToken, error, copyRequest.FileDataHeaderMessage.FileName); - CompleteFileTransfer(copyRequest, error); - } - } - - - /// - /// Mark the request as processed/completed. - /// If any error occurred during processing, error information is also sent to copyrequest. - /// - /// - /// - private static void CompleteFileTransfer(CopyRequestData fileCopyRequest, Exception e) - { - fileCopyRequest.CompleteRequest(e); - } - - - /// - /// Logs an error message. - /// - /// - /// - /// - /// Id of testCase if available, null otherwise - private void LogError(string errorMessage, Uri collectorUri, string collectorFriendlyName, Guid testCaseId) - { - Debug.Assert(dataCollectionLog != null, "DataCollectionLog cannot be null"); - DataCollectionMessageEventArgs args = new DataCollectionMessageEventArgs(TestMessageLevel.Error, errorMessage); - args.Uri = collectorUri; - args.FriendlyName = collectorFriendlyName; - if (!Guid.Empty.Equals(testCaseId)) - { - args.TestCaseId = testCaseId; - } - dataCollectionLog.SendDataCollectionMessage(args); - } - - #endregion - - #region IDisposable - /// - /// Dispose event object - /// - public void Dispose() - { - Dispose(true); - - // Use SupressFinalize in case a subclass - // of this type implements a finalizer. - GC.SuppressFinalize(this); - } - - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", MessageId = "backgroundFileCopier", Justification = "Bug in clr")] - protected virtual void Dispose(bool disposing) - { - if (!disposed) - { - if (disposing) - { - if (null != backgroundFileCopier) - { - backgroundFileCopier.Abort(); - - // Dont dispose the background job processor as this is leading to 99% cpu on some machines - // because of clr bug # 653263. Not disposing it is fine as the dispose is called only on - // process exit and on process exit all handles are disposed automatically. - // - // m_backgroundFileCopier.Dispose(); - backgroundFileCopier = null; - } - } - disposed = true; - } - } - #endregion - - } -} diff --git a/src/Microsoft.TestPlatform.DataCollection.v1/DataCollectionManager.cs b/src/Microsoft.TestPlatform.DataCollection.v1/DataCollectionManager.cs index f242e30145..029f37a5ca 100644 --- a/src/Microsoft.TestPlatform.DataCollection.v1/DataCollectionManager.cs +++ b/src/Microsoft.TestPlatform.DataCollection.v1/DataCollectionManager.cs @@ -11,9 +11,10 @@ namespace Microsoft.VisualStudio.TestPlatform.DataCollection.V1 using System.Linq; using System.Reflection; + using Common.DataCollection; + using Microsoft.VisualStudio.TestPlatform.Common; using Microsoft.VisualStudio.TestPlatform.Common.DataCollection.Interfaces; - using Microsoft.VisualStudio.TestPlatform.DataCollection.V1.Interfaces; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; using Microsoft.VisualStudio.TestTools.Common; @@ -76,7 +77,7 @@ internal class DataCollectionManager : IDataCollectionManager /// /// Initializes a new instance of the class. /// - public DataCollectionManager() : this(default(IMessageSink), default(IDataCollectionFileManager)) + public DataCollectionManager() : this(new MessageSink(), new DataCollectionFileManager()) { } @@ -214,7 +215,7 @@ public Collection SessionEnded(bool isCancelled) } // todo : make this call at the end of GetColelctionEntries or inside GetData itself. - this.dataCollectionFileManager.CloseSession(endEvent.Context.SessionId); + this.dataCollectionFileManager.CloseSession(ObjectConversionHelper.ToSessionId(endEvent.Context.SessionId)); return result; } @@ -934,7 +935,7 @@ private bool AreTestCaseLevelEventsRequired() } } - return required; + return false; } /// @@ -950,7 +951,7 @@ private Collection GetCollectionEntries(DataCollectionContex { if (this.IsDataCollectionEnabled) { - var entries = this.dataCollectionFileManager.GetData(context); + var entries = this.dataCollectionFileManager.GetData(ObjectConversionHelper.ToDataCollectionConetxt(context)); return new Collection(entries); } diff --git a/src/Microsoft.TestPlatform.DataCollection.v1/ObjectConversionHelper.cs b/src/Microsoft.TestPlatform.DataCollection.v1/ObjectConversionHelper.cs index 259f281dcc..04bc00677b 100644 --- a/src/Microsoft.TestPlatform.DataCollection.v1/ObjectConversionHelper.cs +++ b/src/Microsoft.TestPlatform.DataCollection.v1/ObjectConversionHelper.cs @@ -71,7 +71,7 @@ internal static SessionEndEventArgs ToSessionEndEventArgs(DataCollectionEnvironm /// Test case which is complete. /// Outcome of the test case. /// TestTools TestCaseEndEventArgs - internal static TestTools.Execution.TestCaseEndEventArgs ToTestCaseEndEventArgs(DataCollectionEnvironmentContext context, TestCase testCase, TestOutcome testOutCome) + internal static TestTools.Execution.TestCaseEndEventArgs ToTestCaseEndEventArgs(DataCollectionEnvironmentContext context, TestCase testCase, ObjectModel.TestOutcome testOutCome) { var testElement = ToTestElement(testCase); var dataCollectionContext = new DataCollectionContext(context.SessionDataCollectionContext.SessionId, testElement.ExecutionId); @@ -79,6 +79,50 @@ internal static TestTools.Execution.TestCaseEndEventArgs ToTestCaseEndEventArgs( return args; } + /// + /// The to session id. + /// + /// + /// The session id. + /// + /// + /// The . + /// + internal static ObjectModel.DataCollection.SessionId ToSessionId(TestTools.Common.SessionId sessionId) + { + return new ObjectModel.DataCollection.SessionId(sessionId.Id); + } + + /// + /// The to test exec id. + /// + /// + /// The test exec id. + /// + /// + /// The . + /// + internal static ObjectModel.DataCollection.TestExecId ToTestExecId(TestTools.Common.TestExecId testExecId) + { + if (testExecId == null) + return null; + return new ObjectModel.DataCollection.TestExecId(testExecId.Id); + } + + /// + /// The to data collection context. + /// + /// + /// The data collection context. + /// + /// + /// The . + /// + internal static ObjectModel.DataCollection.DataCollectionContext ToDataCollectionConetxt(TestTools.Execution.DataCollectionContext dataCollectionContext) + { + return new ObjectModel.DataCollection.DataCollectionContext(ToSessionId(dataCollectionContext.SessionId), ToTestExecId(dataCollectionContext.TestExecId)); + } + /// /// Converts a TestPlatform TestCase to TestTools ITestElement /// @@ -120,6 +164,5 @@ private static TestTools.Common.TestOutcome ToOutcome(ObjectModel.TestOutcome ou return TestTools.Common.TestOutcome.NotRunnable; } } - } } diff --git a/src/Microsoft.TestPlatform.DataCollection.v1/Resource.Designer.cs b/src/Microsoft.TestPlatform.DataCollection.v1/Resource.Designer.cs index c3be4bca9c..e9c150da54 100644 --- a/src/Microsoft.TestPlatform.DataCollection.v1/Resource.Designer.cs +++ b/src/Microsoft.TestPlatform.DataCollection.v1/Resource.Designer.cs @@ -140,15 +140,6 @@ public class Resource { } } - /// - /// Looks up a localized string similar to Sending message of Message Type '{0}' is not supported.. - /// - public static string DataCollectorUnsupportedMessageType { - get { - return ResourceManager.GetString("DataCollectorUnsupportedMessageType", resourceCulture); - } - } - /// /// Looks up a localized string similar to Timed out invoking diagnostic data adapter event handler '{0}'. /// diff --git a/src/Microsoft.TestPlatform.DataCollection.v1/Resource.resx b/src/Microsoft.TestPlatform.DataCollection.v1/Resource.resx index 312f87cb15..9e97baf3a1 100644 --- a/src/Microsoft.TestPlatform.DataCollection.v1/Resource.resx +++ b/src/Microsoft.TestPlatform.DataCollection.v1/Resource.resx @@ -144,9 +144,6 @@ Failed to get type for diagnostic data adapter '{0}'. Error: {1}. - - Sending message of Message Type '{0}' is not supported. - Timed out invoking diagnostic data adapter event handler '{0}'. diff --git a/src/Microsoft.TestPlatform.DataCollection.v1/TestPlatformDataCollectionLogger.cs b/src/Microsoft.TestPlatform.DataCollection.v1/TestPlatformDataCollectionLogger.cs index 512acdbeb1..baf7eaf2ee 100644 --- a/src/Microsoft.TestPlatform.DataCollection.v1/TestPlatformDataCollectionLogger.cs +++ b/src/Microsoft.TestPlatform.DataCollection.v1/TestPlatformDataCollectionLogger.cs @@ -6,7 +6,7 @@ namespace Microsoft.VisualStudio.TestPlatform.DataCollection.V1 using System.Diagnostics; using System.Globalization; - using Microsoft.VisualStudio.TestPlatform.DataCollection.V1.Interfaces; + using Microsoft.VisualStudio.TestPlatform.Common.DataCollection.Interfaces; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; diff --git a/src/Microsoft.TestPlatform.DataCollection.v1/TestPlatformDataCollectionSink.cs b/src/Microsoft.TestPlatform.DataCollection.v1/TestPlatformDataCollectionSink.cs index 64efde01e3..577560b6d5 100644 --- a/src/Microsoft.TestPlatform.DataCollection.v1/TestPlatformDataCollectionSink.cs +++ b/src/Microsoft.TestPlatform.DataCollection.v1/TestPlatformDataCollectionSink.cs @@ -6,7 +6,8 @@ namespace Microsoft.VisualStudio.TestPlatform.DataCollection.V1 using System.ComponentModel; using System.Diagnostics; - using Microsoft.VisualStudio.TestPlatform.DataCollection.V1.Interfaces; + using Microsoft.VisualStudio.TestPlatform.Common.DataCollection; + using Microsoft.VisualStudio.TestPlatform.Common.DataCollection.Interfaces; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using DataCollectionSink = Microsoft.VisualStudio.TestTools.Execution.DataCollectionSink; @@ -76,7 +77,7 @@ public override void SendFileAsync(FileTransferInformation fileTransferInformati Debug.Assert(System.IO.File.Exists(fileTransferInformation.Path), "DataCollector file '" + fileTransferInformation.Path + "' does not exist!"); var headerMsg = new FileDataHeaderMessage( - fileTransferInformation.Context, + ObjectConversionHelper.ToDataCollectionConetxt(fileTransferInformation.Context), fileTransferInformation.ClientFileName, fileTransferInformation.Description, fileTransferInformation.DeleteFile, diff --git a/src/Microsoft.TestPlatform.DataCollection.v1/TestPlatformDataCollectorInfo.cs b/src/Microsoft.TestPlatform.DataCollection.v1/TestPlatformDataCollectorInfo.cs index de90ebaee0..17b1d0bd56 100644 --- a/src/Microsoft.TestPlatform.DataCollection.v1/TestPlatformDataCollectorInfo.cs +++ b/src/Microsoft.TestPlatform.DataCollection.v1/TestPlatformDataCollectorInfo.cs @@ -6,7 +6,7 @@ namespace Microsoft.VisualStudio.TestPlatform.DataCollection.V1 using System.Collections.Generic; using System.Xml; - using Microsoft.VisualStudio.TestPlatform.DataCollection.V1.Interfaces; + using Microsoft.VisualStudio.TestPlatform.Common.DataCollection.Interfaces; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestTools.Common; diff --git a/src/Microsoft.TestPlatform.ObjectModel/DataCollector/DataCollectionContext.cs b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/DataCollectionContext.cs index c66cb0e6af..6f8f4b7f6f 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/DataCollector/DataCollectionContext.cs +++ b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/DataCollectionContext.cs @@ -2,14 +2,35 @@ namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection { + using System; + /// /// Class representing the context in which data collection occurs. /// -#if NET451 - [Serializable] +#if NET46 + [Serializable] #endif public class DataCollectionContext { + #region Private Fields + + /// + /// The session id. + /// + private readonly SessionId sessionId; + + /// + /// The test exec id. + /// + private readonly TestExecId testExecId; + + /// + /// The hash code. + /// + private readonly int hashCode; + + #endregion + #region Constructors // NOTE: These constructors are protected internal to allow 3rd parties to @@ -30,30 +51,35 @@ public class DataCollectionContext // class and pass to us for creating data collection events. /// - /// Constructs a DataCollectionContext indicating that there is a session, + /// Initializes a new instance of the class indicating that there is a session, /// but no executing test, in context. /// - /// The session under which the data collection occurs. Cannot be null. + /// + /// The session under which the data collection occurs. Cannot be null. + /// protected internal DataCollectionContext(SessionId sessionId) : this(sessionId, null) { } /// - /// Constructs a DataCollectionContext indicating that there is a session and an executing test, + /// Initializes a new instance of the class indicating that there is a session and an executing test, /// but no test step, in context. /// - /// The session under which the data collection occurs. Cannot be null. - /// The test execution under which the data collection occurs, - /// or null if no executing test case is in context + /// + /// The session under which the data collection occurs. Cannot be null. + /// + /// + /// The test execution under which the data collection occurs, + /// or null if no executing test case is in context + /// protected internal DataCollectionContext(SessionId sessionId, TestExecId testExecId) { - //todo - //EqtAssert.ParameterNotNull(sessionId, "sessionId"); + ValidateArg.NotNull(sessionId, "sessionId"); this.sessionId = sessionId; this.testExecId = testExecId; - this.hashCode = ComputeHashCode(); + this.hashCode = this.ComputeHashCode(); } #endregion @@ -61,94 +87,129 @@ protected internal DataCollectionContext(SessionId sessionId, TestExecId testExe #region Properties /// - /// Identifies the session under which the data collection occurs. Will not be null. + /// Gets the session under which the data collection occurs. /// public SessionId SessionId { get { - return sessionId; + return this.sessionId; } } /// - /// Identifies the test execution under which the data collection occurs, + /// Gets the test execution under which the data collection occurs, /// or null if no such test exists. /// public TestExecId TestExecId { get { - return testExecId; + return this.testExecId; } } /// - /// Returns true if there is an executing test case associated with this context. + /// Gets a value indicating whether there is an executing test case associated with this context. /// public bool HasTestCase { - get { return testExecId != null; } + get { return this.testExecId != null; } } #endregion #region Equals and Hashcode + /// + /// The ==. + /// + /// + /// The context 1. + /// + /// + /// The context 2. + /// + /// Value indicating whether the data collection contexts are equal. + /// public static bool operator ==(DataCollectionContext context1, DataCollectionContext context2) { return object.Equals(context1, context2); } + /// + /// The !=. + /// + /// + /// The context 1. + /// + /// + /// The context 2. + /// + /// Value indicating whether the data collection contexts are equal. + /// public static bool operator !=(DataCollectionContext context1, DataCollectionContext context2) { return !(context1 == context2); } + /// + /// The equals. + /// + /// + /// The object. + /// + /// + /// The . + /// public override bool Equals(object obj) { - DataCollectionContext other = obj as DataCollectionContext; + var other = obj as DataCollectionContext; if (other == null) { return false; } - return sessionId.Equals(other.sessionId) - && (testExecId == null ? other.testExecId == null : testExecId.Equals(other.testExecId)); + return this.sessionId.Equals(other.sessionId) + && (this.testExecId == null ? other.testExecId == null : this.testExecId.Equals(other.testExecId)); } + /// + /// The get hash code. + /// + /// + /// The . + /// public override int GetHashCode() { - return hashCode; + return this.hashCode; } #endregion #region Private Methods + /// + /// The compute hash code. + /// + /// + /// The . + /// private int ComputeHashCode() { - int hashCode = 17; + var hashCode = 17; hashCode = 31 * hashCode + sessionId.GetHashCode(); - if (testExecId != null) + if (this.testExecId != null) { - hashCode = 31 * hashCode + testExecId.GetHashCode(); + hashCode = 31 * hashCode + this.testExecId.GetHashCode(); } return hashCode; } #endregion - - #region Private Fields - - private readonly SessionId sessionId; - private readonly TestExecId testExecId; - private readonly int hashCode; - - #endregion } } diff --git a/src/Microsoft.TestPlatform.ObjectModel/Friends.cs b/src/Microsoft.TestPlatform.ObjectModel/Friends.cs index 9bb9221087..ef7124dd2d 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/Friends.cs +++ b/src/Microsoft.TestPlatform.ObjectModel/Friends.cs @@ -1,5 +1,12 @@ using System.Runtime.CompilerServices; +#region Product Assemblies [assembly: InternalsVisibleTo("Microsoft.VisualStudio.TestPlatform.Extensions.VSTestIntegration, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("Microsoft.VisualStudio.TestPlatform.Extensions.MSAppContainerAdapter, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("Microsoft.VisualStudio.TestPlatform.Extensions.MSPhoneAdapter, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] +[assembly: InternalsVisibleTo("Microsoft.TestPlatform.DataCollection.v1, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] +#endregion + +# region Test Assemblies +[assembly: InternalsVisibleTo("Microsoft.TestPlatform.Common.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] +#endregion diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/DataCollection/DataCollectionFileManagerTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/DataCollection/DataCollectionFileManagerTests.cs new file mode 100644 index 0000000000..0a9a0b60a6 --- /dev/null +++ b/test/Microsoft.TestPlatform.Common.UnitTests/DataCollection/DataCollectionFileManagerTests.cs @@ -0,0 +1,197 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.Common.UnitTests.DataCollection +{ + using System; + using System.ComponentModel; + using System.IO; + using System.Linq; + using System.Threading; + + using Microsoft.VisualStudio.TestPlatform.Common.DataCollection; + using Microsoft.VisualStudio.TestPlatform.Common.DataCollection.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + using Moq; + + [TestClass] + public class DataCollectionFileManagerTests + { + private Mock mockDataCollectionLog; + private DataCollectionFileManager dataCollectionFileManager; + private SessionId sessionId; + private Guid guid; + private DataCollectionContext dataCollectionContext; + private string fileName; + private FileStream fileStream; + private DataCollectorDataMessage dataCollectorDataMessage; + private string friendlyName = "TestDataCollector"; + private Uri uri = new Uri("datacollector://Company/Product/Version"); + private EventWaitHandle waitHandle; + + [TestInitialize] + public void Init() + { + this.mockDataCollectionLog = new Mock(); + this.dataCollectionFileManager = new DataCollectionFileManager(this.mockDataCollectionLog.Object); + this.guid = Guid.NewGuid(); + this.sessionId = new SessionId(guid); + this.dataCollectionContext = new DataCollectionContext(this.sessionId); + this.fileName = "filename1.txt"; + + this.waitHandle = new AutoResetEvent(false); + var handler = new AsyncCompletedEventHandler((a, e) => { waitHandle.Set(); }); + this.dataCollectorDataMessage = new FileDataHeaderMessage(this.dataCollectionContext, this.fileName, "description", false, new object(), handler, this.uri, this.friendlyName); + this.fileStream = File.Create(this.fileName); + this.fileStream.Dispose(); + } + + [TestCleanup] + public void Cleanup() + { + File.Delete(this.fileStream.Name); + this.waitHandle.Reset(); + } + + [TestMethod] + public void ConfigureSessionShouldThrowExceptionIfNullIsPassed() + { + Assert.ThrowsException(() => + { + this.dataCollectionFileManager.ConfigureSession(null, string.Empty); + }); + } + + [TestMethod] + public void ConfigureSessionShouldSetDefaultPathIfOutputDirectoryPathIsNull() + { + this.dataCollectionFileManager.ConfigureSession(this.sessionId, string.Empty); + + Assert.AreEqual(this.dataCollectionFileManager.SessionInfo.First().Value.OutputDirectory, Path.Combine(Path.GetTempPath(), "TestPlatformResults", this.guid.ToString())); + } + + [TestMethod] + public void ConfigureSessionShouldSetCorrectGuidAndOutputPath() + { + this.dataCollectionFileManager.ConfigureSession(this.sessionId, Directory.GetCurrentDirectory()); + + Assert.IsNotNull(this.dataCollectionFileManager.SessionInfo); + Assert.AreEqual(1, this.dataCollectionFileManager.SessionInfo.Count); + Assert.AreEqual(this.sessionId, this.dataCollectionFileManager.SessionInfo.Keys.First()); + Assert.AreEqual(this.dataCollectionFileManager.SessionInfo.First().Value.SessionId, this.sessionId); + Assert.AreEqual(Path.Combine(Directory.GetCurrentDirectory(), this.guid.ToString()), this.dataCollectionFileManager.SessionInfo.First().Value.OutputDirectory); + } + + [TestMethod] + public void DispatchMessageShouldNotAddNewFileTransferIfSessionIsNotConfigured() + { + this.dataCollectionFileManager.DispatchMessage(this.dataCollectorDataMessage); + + Assert.AreEqual(this.dataCollectionFileManager.CopyRequestDataDictionary.Count(), 0); + } + + [TestMethod] + public void DispatchMessageShouldAddNewFileTransferAndCopyFileToOutputDirectoryIfDeleteFileIsFalse() + { + this.dataCollectionFileManager.ConfigureSession(this.sessionId, Directory.GetCurrentDirectory()); + + this.dataCollectionFileManager.DispatchMessage(this.dataCollectorDataMessage); + + // Wait for file operations to complete + this.waitHandle.WaitOne(); + + Assert.IsTrue(File.Exists(Path.Combine(Directory.GetCurrentDirectory(), this.sessionId.Id.ToString(), this.fileName))); + Assert.IsTrue(File.Exists(this.fileName)); + Assert.AreEqual(this.dataCollectionFileManager.CopyRequestDataDictionary.Count, 1); + } + + [TestMethod] + public void DispatchMessageShouldAddNewFileTransferAndMoveFileToOutputDirectoryIfDeleteFileIsTrue() + { + this.dataCollectionFileManager.ConfigureSession(this.sessionId, Directory.GetCurrentDirectory()); + AsyncCompletedEventHandler handler = new AsyncCompletedEventHandler((a, e) => { this.waitHandle.Set(); }); + this.dataCollectorDataMessage = new FileDataHeaderMessage(this.dataCollectionContext, this.fileName, "description", true, new object(), handler, uri, friendlyName); + + this.dataCollectionFileManager.DispatchMessage(dataCollectorDataMessage); + + // Wait for file operations to complete + waitHandle.WaitOne(); + + Assert.AreEqual(this.dataCollectionFileManager.CopyRequestDataDictionary.Count, 1); + Assert.IsTrue(File.Exists(Path.Combine(Directory.GetCurrentDirectory(), guid.ToString(), this.fileName))); + Assert.IsFalse(File.Exists(this.fileName)); + + } + + [TestMethod] + public void DispatchMessageShouldNotAddNewFileTransferIfNullIsPassed() + { + this.dataCollectionFileManager.DispatchMessage(null); + + Assert.AreEqual(this.dataCollectionFileManager.CopyRequestDataDictionary.Count, 0); + } + + [TestMethod] + public void GetDataShouldReturnAllActiveFileTransferData() + { + this.dataCollectionFileManager.ConfigureSession(sessionId, Directory.GetCurrentDirectory()); + + this.dataCollectionFileManager.DispatchMessage(this.dataCollectorDataMessage); + + Assert.AreEqual(1, this.dataCollectionFileManager.CopyRequestDataDictionary.Count); + var result = this.dataCollectionFileManager.GetData(this.dataCollectionContext); + + Assert.AreEqual(0, this.dataCollectionFileManager.CopyRequestDataDictionary.Count); + Assert.AreEqual(1, result.Count); + Assert.AreEqual(friendlyName, result[0].DisplayName); + Assert.AreEqual(uri, result[0].Uri); + Assert.AreEqual(1, result[0].Attachments.Count); + } + + [TestMethod] + public void GetDataShouldNotReutrnAnyDataWhenActiveFileTransferAreNotPresent() + { + this.dataCollectionFileManager.ConfigureSession(sessionId, Directory.GetCurrentDirectory()); + + var result = this.dataCollectionFileManager.GetData(this.dataCollectionContext); + Assert.AreEqual(0, result.Count); + } + + [TestMethod] + public void CloseSessionShouldThrowExcetionIfSessionIdIsNull() + { + Assert.ThrowsException(() => + { + var dataCollectionLog = new Mock(); + DataCollectionFileManager dcFileManager = new DataCollectionFileManager(dataCollectionLog.Object); + dcFileManager.CloseSession(null); + }); + } + + [TestMethod] + public void CloseSessionShouldCloseSessionForGivenSessionId() + { + this.dataCollectionFileManager.ConfigureSession(sessionId, Directory.GetCurrentDirectory()); + + this.dataCollectionFileManager.DispatchMessage(this.dataCollectorDataMessage); + + var count = this.dataCollectionFileManager.CopyRequestDataDictionary.Count; + + this.dataCollectionFileManager.CloseSession(this.sessionId); + Assert.AreEqual(count - 1, this.dataCollectionFileManager.CopyRequestDataDictionary.Count); + } + + [TestMethod] + public void CloseSessionShouldNotCloseOtherSessions() + { + this.dataCollectionFileManager.ConfigureSession(sessionId, Directory.GetCurrentDirectory()); + + this.dataCollectionFileManager.DispatchMessage(this.dataCollectorDataMessage); + + SessionId sessionId1 = new SessionId(Guid.NewGuid()); + this.dataCollectionFileManager.CloseSession(sessionId1); + Assert.AreEqual(1, this.dataCollectionFileManager.CopyRequestDataDictionary.Count); + } + } +} diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/DataCollection/MessageSinkTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/DataCollection/MessageSinkTests.cs new file mode 100644 index 0000000000..0ad33dbb79 --- /dev/null +++ b/test/Microsoft.TestPlatform.Common.UnitTests/DataCollection/MessageSinkTests.cs @@ -0,0 +1,97 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.Common.UnitTests.DataCollection +{ + using System; + + using Microsoft.VisualStudio.TestPlatform.Common.DataCollection; + using Microsoft.VisualStudio.TestPlatform.Common.DataCollection.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class MessageSinkTests + { + private MessageSink messageSink; + private MockDataCollectionFileManager mockDataCollectionFileManager; + private DataCollectionMessageEventArgs args; + bool IsMessageSink_OnDataCollectionMessageInvoked; + private DataCollectorDataMessage dataCollectorDataMessage; + private FileDataHeaderMessage fileDataHeaderMessage; + + [TestInitialize] + public void Init() + { + this.args = new DataCollectionMessageEventArgs(TestMessageLevel.Informational, "Message"); + this.mockDataCollectionFileManager = new MockDataCollectionFileManager(); + this.messageSink = new MessageSink(this.mockDataCollectionFileManager); + + var dataCollectionContext = new DataCollectionContext(new SessionId(Guid.NewGuid())); + this.dataCollectorDataMessage = new DataCollectorDataMessage(dataCollectionContext, new Uri("File://Message"), "FileMessage"); + + this.fileDataHeaderMessage = new FileDataHeaderMessage(dataCollectionContext, "filename", "description", false, new object(), null, new Uri("File://Message"), "FileMessage"); + } + + [TestMethod] + public void SendMessageShouldInvokeOnDataCollectionMessageEventHandlerIfRegistered() + { + this.messageSink.SendMessage(this.args); + Assert.IsFalse(this.IsMessageSink_OnDataCollectionMessageInvoked); + + // Register event handler. + this.messageSink.OnDataCollectionMessage += new EventHandler(MessageSink_OnDataCollectionMessage); + + this.messageSink.SendMessage(this.args); + Assert.IsTrue(this.IsMessageSink_OnDataCollectionMessageInvoked); + } + + [TestMethod] + public void SendMessageShouldThrowExceptionIfDataCollectorDataMessageIsNull() + { + Assert.ThrowsException(() => + { + this.messageSink.SendMessage(default(DataCollectorDataMessage)); + }); + } + + [TestMethod] + public void SendMessageShouldInvokeFileManagerDispatchMessage() + { + this.messageSink.SendMessage(this.fileDataHeaderMessage); + + Assert.IsTrue(this.mockDataCollectionFileManager.IsDispatchMessageInvoked); + Assert.AreEqual(this.fileDataHeaderMessage, this.mockDataCollectionFileManager.DataCollectorDataMessage); + } + + [TestMethod] + public void SendMessageShouldInvokeInvalidOperationExceptionIfArgumentIfNotOfTypeFileDataHeaderMessage() + { + Assert.ThrowsException(() => + { + this.messageSink.SendMessage(this.dataCollectorDataMessage); + }); + } + + [TestMethod] + public void SendMessageShouldThrowExceptionIfExcectpionIsThrownByDataCollectionFileManager() + { + this.mockDataCollectionFileManager.DispatchMessageThrowException = true; + + Assert.ThrowsException(() => + { + this.messageSink.SendMessage(this.fileDataHeaderMessage); + }); + + Assert.IsTrue(this.mockDataCollectionFileManager.IsDispatchMessageInvoked); + Assert.AreEqual(this.fileDataHeaderMessage, this.mockDataCollectionFileManager.DataCollectorDataMessage); + } + + + private void MessageSink_OnDataCollectionMessage(object sender, DataCollectionMessageEventArgs args) + { + this.IsMessageSink_OnDataCollectionMessageInvoked = true; + Assert.AreEqual(args, this.args); + } + } +} diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/DataCollection/MockDataCollectionFileManager.cs b/test/Microsoft.TestPlatform.Common.UnitTests/DataCollection/MockDataCollectionFileManager.cs new file mode 100644 index 0000000000..0c3737092c --- /dev/null +++ b/test/Microsoft.TestPlatform.Common.UnitTests/DataCollection/MockDataCollectionFileManager.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace TestPlatform.Common.UnitTests.DataCollection +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + + using Microsoft.VisualStudio.TestPlatform.Common.DataCollection; + using Microsoft.VisualStudio.TestPlatform.Common.DataCollection.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection; + + internal class MockDataCollectionFileManager : IDataCollectionFileManager + { + public bool DispatchMessageThrowException = false; + public bool IsDispatchMessageInvoked; + public DataCollectorDataMessage DataCollectorDataMessage; + + public void CloseSession(SessionId id) + { + } + + public void ConfigureSession(SessionId id, string outputDirectory) + { + throw new NotImplementedException(); + } + + public void DispatchMessage(DataCollectorDataMessage collectorDataMessage) + { + this.IsDispatchMessageInvoked = true; + this.DataCollectorDataMessage = collectorDataMessage; + if (this.DispatchMessageThrowException) + { + throw new Exception(); + } + } + + public void Dispose() + { + throw new NotImplementedException(); + } + + public List GetData(DataCollectionContext dataCollectionContext) + { + throw new NotImplementedException(); + } + } +} diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/DataCollection/RunConfigurationSettingsProviderTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/DataCollection/RunConfigurationSettingsProviderTests.cs deleted file mode 100644 index 3b1d7861f7..0000000000 --- a/test/Microsoft.TestPlatform.Common.UnitTests/DataCollection/RunConfigurationSettingsProviderTests.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace TestPlatform.Common.UnitTests.DataCollection -{ - public class RunConfigurationSettingsProviderTests - { - } -} diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/project.json b/test/Microsoft.TestPlatform.Common.UnitTests/project.json index b12ef7b3dc..697894e22b 100644 --- a/test/Microsoft.TestPlatform.Common.UnitTests/project.json +++ b/test/Microsoft.TestPlatform.Common.UnitTests/project.json @@ -1,4 +1,4 @@ -{ +{ "version": "15.0.0-*", "buildOptions": { @@ -19,7 +19,8 @@ "MSTest.TestFramework": "1.0.0-preview", "moq.netcore": "4.4.0-beta8", "System.Diagnostics.TraceSource": "4.0.0-rc2-24015", - "Microsoft.TestPlatform.Common": "15.0.0-*" + "Microsoft.TestPlatform.Common": "15.0.0-*", + "System.Runtime": "4.1.0" }, "frameworks": { diff --git a/test/Microsoft.TestPlatform.DataCollection.v1.UnitTests/DataCollectionManagerTests.cs b/test/Microsoft.TestPlatform.DataCollection.v1.UnitTests/DataCollectionManagerTests.cs index 4786d478ba..21e780e3fc 100644 --- a/test/Microsoft.TestPlatform.DataCollection.v1.UnitTests/DataCollectionManagerTests.cs +++ b/test/Microsoft.TestPlatform.DataCollection.v1.UnitTests/DataCollectionManagerTests.cs @@ -4,9 +4,9 @@ namespace Microsoft.TestPlatform.DataCollection.V1.UnitTests { using System; using System.Collections.Generic; + using System.Globalization; using System.Linq; using System.Reflection; - using System.Xml; using Microsoft.VisualStudio.TestPlatform.Common; using Microsoft.VisualStudio.TestPlatform.Common.DataCollection; @@ -17,15 +17,14 @@ namespace Microsoft.TestPlatform.DataCollection.V1.UnitTests using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; using Microsoft.VisualStudio.TestTools.UnitTesting; - using TestCaseStartEventArgs = Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging.Events.TestCaseStartEventArgs; - using Moq; - using IMessageSink = Microsoft.VisualStudio.TestPlatform.DataCollection.V1.Interfaces.IMessageSink; - using System.Globalization; - using VisualStudio.TestPlatform.DataCollection.V1.Interfaces; + using VisualStudio.TestPlatform.Common.DataCollection.Interfaces; using VisualStudio.TestPlatform.ObjectModel; + using IMessageSink = Microsoft.VisualStudio.TestPlatform.Common.DataCollection.Interfaces.IMessageSink; + using TestCaseStartEventArgs = Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging.Events.TestCaseStartEventArgs; + [TestClass] public class DataCollectionManagerTests { @@ -196,7 +195,7 @@ public void SessionStaretedShouldRemoveDataCollectorIfExceptionIsThrownWhileSend MockDataCollector2.Events_SessionStartThrowException = true; this.dataCollectionManager.LoadDataCollectors(this.runSettings); - int count = this.dataCollectionManager.RunDataCollectors.Count; + var count = this.dataCollectionManager.RunDataCollectors.Count; var result = this.dataCollectionManager.SessionStarted(); @@ -233,13 +232,12 @@ public void SessionEndedShouldRemoveDataCollectorIfExceptionIsThrownWhileSending this.dataCollectionManager.LoadDataCollectors(this.runSettings); MockDataCollector2.Events_SessionEndThrowException = true; - int count = this.dataCollectionManager.RunDataCollectors.Count; + var count = this.dataCollectionManager.RunDataCollectors.Count; var result = this.dataCollectionManager.SessionEnded(isCancelled: false); Assert.AreEqual(count - 1, this.dataCollectionManager.RunDataCollectors.Count); Assert.AreEqual(string.Format(CultureInfo.CurrentCulture, Resource.DataCollectorRunError, MockDataCollector2.Events_SessionEndExceptionMessage), this.mockMessageSink.EventMessage); - } [TestMethod] @@ -375,7 +373,6 @@ public void TestCaseEndedShouldRemoveDataCollectorIfExceptionIsThrownWhileSendin Assert.AreEqual(count - 1, this.dataCollectionManager.RunDataCollectors.Count); Assert.AreEqual(string.Format(CultureInfo.CurrentCulture, Resource.DataCollectorRunError, MockDataCollector2.Events_TestCaseEndExceptionMessage), this.mockMessageSink.EventMessage); - } [TestMethod] @@ -405,7 +402,6 @@ public void TestCaseEndedShouldReturnNullIfDataCollectorsAreNotLoaded() } #endregion - public static void SetupMockExtensions(string[] extensions, Action callback) { // Setup mocks. @@ -419,6 +415,7 @@ public static void SetupMockExtensions(string[] extensions, Action callback) callback.Invoke(); return extensions; } + return new string[] { }; }; @@ -426,7 +423,6 @@ public static void SetupMockExtensions(string[] extensions, Action callback) TestPluginCache.Instance = testableTestPluginCache; } - public static void ResetExtensionsCache() { TestPluginCache.Instance = null; diff --git a/test/Microsoft.TestPlatform.DataCollection.v1.UnitTests/MockDataCollectionFileManager.cs b/test/Microsoft.TestPlatform.DataCollection.v1.UnitTests/MockDataCollectionFileManager.cs index dd2f7f13db..aee0b2b368 100644 --- a/test/Microsoft.TestPlatform.DataCollection.v1.UnitTests/MockDataCollectionFileManager.cs +++ b/test/Microsoft.TestPlatform.DataCollection.v1.UnitTests/MockDataCollectionFileManager.cs @@ -1,15 +1,15 @@ -using Microsoft.VisualStudio.TestPlatform.DataCollection.V1.Interfaces; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.VisualStudio.TestPlatform.DataCollection.V1; -using Microsoft.VisualStudio.TestPlatform.ObjectModel; -using Microsoft.VisualStudio.TestTools.Common; -using Microsoft.VisualStudio.TestTools.Execution; +// Copyright (c) Microsoft. All rights reserved. namespace Microsoft.TestPlatform.DataCollection.V1.UnitTests { + using System; + using System.Collections.Generic; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection; + using VisualStudio.TestPlatform.Common.DataCollection; + using VisualStudio.TestPlatform.Common.DataCollection.Interfaces; + internal class MockDataCollectionFileManager : IDataCollectionFileManager { public List Attachments; @@ -18,11 +18,11 @@ internal class MockDataCollectionFileManager : IDataCollectionFileManager public MockDataCollectionFileManager() { - Attachments = new List(); + this.Attachments = new List(); } + public void CloseSession(SessionId id) { - //throw new NotImplementedException(); } public void ConfigureSession(SessionId id, string outputDirectory) @@ -40,14 +40,14 @@ public void Dispose() throw new NotImplementedException(); } - public List GetData(DataCollectionContext collectionContext) + public List GetData(DataCollectionContext dataCollectionContext) { - if (GetDataThrowException) + if (this.GetDataThrowException) { throw new Exception(GetDataExceptionMessaage); } - return Attachments; + return this.Attachments; } } } diff --git a/test/Microsoft.TestPlatform.DataCollection.v1.UnitTests/TestPlatformDataCollectorInfoTests.cs b/test/Microsoft.TestPlatform.DataCollection.v1.UnitTests/TestPlatformDataCollectorInfoTests.cs index 2466575cab..33f2799acc 100644 --- a/test/Microsoft.TestPlatform.DataCollection.v1.UnitTests/TestPlatformDataCollectorInfoTests.cs +++ b/test/Microsoft.TestPlatform.DataCollection.v1.UnitTests/TestPlatformDataCollectorInfoTests.cs @@ -8,9 +8,8 @@ namespace Microsoft.TestPlatform.DataCollection.V1.UnitTests using System.Threading.Tasks; using System.Xml; + using Microsoft.VisualStudio.TestPlatform.Common.DataCollection.Interfaces; using Microsoft.VisualStudio.TestPlatform.DataCollection.V1; - using Microsoft.VisualStudio.TestPlatform.DataCollection.V1.Interfaces; - using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection; using Microsoft.VisualStudio.TestTools.UnitTesting; using DataCollectionContext = Microsoft.VisualStudio.TestTools.Execution.DataCollectionContext;