forked from microsoft/vstest
/
FakesUtilities.cs
301 lines (262 loc) · 12.4 KB
/
FakesUtilities.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.VisualStudio.TestPlatform.Common.Utilities
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Xml;
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities;
/// <summary>
/// Provides helper to configure run settings for Fakes. Works even when Fakes are not installed on the machine.
/// </summary>
public static class FakesUtilities
{
private const string ConfiguratorAssemblyQualifiedName = "Microsoft.VisualStudio.TestPlatform.Fakes.FakesDataCollectorConfiguration";
private const string NetFrameworkConfiguratorMethodName = "GetDataCollectorSettingsOrDefault";
private const string CrossPlatformConfiguratorMethodName = "GetCrossPlatformDataCollectorSettings";
private const string FakesConfiguratorAssembly = "Microsoft.VisualStudio.TestPlatform.Fakes, Version=16.0.0.0, Culture=neutral";
/// <summary>
/// Dynamically compute the Fakes data collector settings, given a set of test assemblies
/// </summary>
/// <param name="sources">test sources</param>
/// <param name="runSettingsXml">runsettings</param>
/// <returns>updated runsettings for fakes</returns>
public static string GenerateFakesSettingsForRunConfiguration(string[] sources, string runSettingsXml)
{
if (sources == null)
{
throw new ArgumentNullException(nameof(sources));
}
if (runSettingsXml == null)
{
throw new ArgumentNullException(nameof(runSettingsXml));
}
var doc = new XmlDocument();
using (var xmlReader = XmlReader.Create(
new StringReader(runSettingsXml),
new XmlReaderSettings() { CloseInput = true }))
{
doc.Load(xmlReader);
}
var frameworkVersion = GetFramework(runSettingsXml);
if (frameworkVersion == null)
{
return runSettingsXml;
}
return TryAddFakesDataCollectorSettings(doc, sources, (FrameworkVersion)frameworkVersion)
? doc.OuterXml
: runSettingsXml;
}
/// <summary>
/// returns FrameworkVersion contained in the runsettingsXML
/// </summary>
/// <param name="runSettingsXml"></param>
/// <returns></returns>
private static FrameworkVersion? GetFramework(string runSettingsXml)
{
// We assume that only .NET Core, .NET Standard, or .NET Framework projects can have fakes.
var targetFramework = XmlRunSettingsUtilities.GetRunConfigurationNode(runSettingsXml)?.TargetFramework;
if (targetFramework == null)
{
return null;
}
// Since there are no FrameworkVersion values for .Net Core 2.0 +, we check TargetFramework instead
// and default to FrameworkCore10 for .Net Core
if (targetFramework.Name.IndexOf("netstandard", StringComparison.OrdinalIgnoreCase) >= 0 ||
targetFramework.Name.IndexOf("netcoreapp", StringComparison.OrdinalIgnoreCase) >= 0 ||
targetFramework.Name.IndexOf("net5", StringComparison.OrdinalIgnoreCase) >= 0)
{
return FrameworkVersion.FrameworkCore10;
}
// Since the Datacollector is separated on the NetFramework/NetCore line, any value of NETFramework
// can be passed along to the fakes data collector configuration creator.
// We default to Framework40 to preserve back compat
return FrameworkVersion.Framework40;
}
/// <summary>
/// Tries to embed the Fakes data collector settings for the given run settings.
/// </summary>
/// <param name="runSettings">runsettings</param>
/// <param name="sources">test sources</param>
/// <returns>true if runSettings was modified; false otherwise.</returns>
private static bool TryAddFakesDataCollectorSettings(
XmlDocument runSettings,
IEnumerable<string> sources,
FrameworkVersion framework)
{
// If user provided fakes settings don't do anything
if (XmlRunSettingsUtilities.ContainsDataCollector(runSettings.CreateNavigator(), FakesMetadata.DataCollectorUri))
{
return false;
}
// A new Fakes Congigurator API makes the decision to add the right datacollector uri to the configuration
// There now exist two data collector URIs to support two different scenarios. The new scenario involves
// using the CLRIE profiler, and the old involves using the Intellitrace profiler (which isn't supported in
// .NET Core scenarios). The old API still exists for fallback measures.
var crossPlatformConfigurator = TryGetFakesCrossPlatformDataCollectorConfigurator();
if (crossPlatformConfigurator != null)
{
var sourceTFMMap = CreateDictionary(sources, framework);
var fakesSettings = crossPlatformConfigurator(sourceTFMMap);
// if no fakes, return settings unchanged
if (fakesSettings == null)
{
return false;
}
XmlRunSettingsUtilities.InsertDataCollectorsNode(runSettings.CreateNavigator(), fakesSettings);
return true;
}
return AddFallbackFakesSettings(runSettings, sources, framework);
}
private static IDictionary<string, FrameworkVersion> CreateDictionary(IEnumerable<string> sources, FrameworkVersion framework)
{
var dict = new Dictionary<string, FrameworkVersion>();
foreach(var source in sources)
{
if (!dict.ContainsKey(source))
{
dict.Add(source, framework);
}
}
return dict;
}
private static bool AddFallbackFakesSettings(
XmlDocument runSettings,
IEnumerable<string> sources,
FrameworkVersion framework)
{
// The fallback settings is for the old implementation of fakes
// that only supports .Net Framework versions
if (framework != FrameworkVersion.Framework35 &&
framework != FrameworkVersion.Framework40 &&
framework != FrameworkVersion.Framework45)
{
return false;
}
Func<IEnumerable<string>, string> netFrameworkConfigurator = TryGetNetFrameworkFakesDataCollectorConfigurator();
if (netFrameworkConfigurator == null)
{
return false;
}
// if no fakes, return settings unchanged
var fakesConfiguration = netFrameworkConfigurator(sources);
if (fakesConfiguration == null)
{
return false;
}
// integrate fakes settings in configuration
// if the settings don't have any data collector settings, populate with empty settings
EnsureSettingsNode(runSettings, new DataCollectionRunSettings());
// embed fakes settings
var fakesSettings = CreateFakesDataCollectorSettings();
var doc = new XmlDocument();
using (var xmlReader = XmlReader.Create(
new StringReader(fakesConfiguration),
new XmlReaderSettings() { CloseInput = true }))
{
doc.Load(xmlReader);
}
fakesSettings.Configuration = doc.DocumentElement;
XmlRunSettingsUtilities.InsertDataCollectorsNode(runSettings.CreateNavigator(), fakesSettings);
return true;
}
/// <summary>
/// Ensures that an xml element corresponding to the test run settings exists in the setting document.
/// </summary>
/// <param name="settings">settings</param>
/// <param name="settingsNode">settingsNode</param>
private static void EnsureSettingsNode(XmlDocument settings, TestRunSettings settingsNode)
{
Debug.Assert(settingsNode != null, "Invalid Settings Node");
Debug.Assert(settings != null, "Invalid Settings");
var root = settings.DocumentElement;
if (root[settingsNode.Name] == null)
{
var newElement = settingsNode.ToXml();
XmlNode newNode = settings.ImportNode(newElement, true);
root.AppendChild(newNode);
}
}
private static Func<IEnumerable<string>, string> TryGetNetFrameworkFakesDataCollectorConfigurator()
{
#if NETFRAMEWORK
try
{
Assembly assembly = Assembly.Load(FakesConfiguratorAssembly);
var type = assembly?.GetType(ConfiguratorAssemblyQualifiedName, false);
var method = type?.GetMethod(NetFrameworkConfiguratorMethodName, new Type[] { typeof(IEnumerable<string>) });
if (method != null)
{
return (Func<IEnumerable<string>, string>)method.CreateDelegate(typeof(Func<IEnumerable<string>, string>));
}
}
catch (Exception ex)
{
if (EqtTrace.IsInfoEnabled)
{
EqtTrace.Info("Failed to create Fakes Configurator. Reason:{0} ", ex);
}
}
#endif
return null;
}
private static Func<IDictionary<string, FrameworkVersion>, DataCollectorSettings> TryGetFakesCrossPlatformDataCollectorConfigurator()
{
try
{
Assembly assembly = Assembly.Load(new AssemblyName(FakesConfiguratorAssembly));
var type = assembly?.GetType(ConfiguratorAssemblyQualifiedName, false, false);
var method = type?.GetMethod(CrossPlatformConfiguratorMethodName, new Type[] { typeof(IDictionary<string, FrameworkVersion>) });
if (method != null)
{
return (Func<IDictionary<string, FrameworkVersion>, DataCollectorSettings>)method.CreateDelegate(typeof(Func<IDictionary<string, FrameworkVersion>, DataCollectorSettings>));
}
}
catch (Exception ex)
{
if (EqtTrace.IsInfoEnabled)
{
EqtTrace.Info("Failed to create newly implemented Fakes Configurator. Reason:{0} ", ex);
}
}
return null;
}
/// <summary>
/// Adds the Fakes data collector settings in the run settings document.
/// </summary>
/// <returns>
/// The <see cref="DataCollectorSettings"/>.
/// </returns>
private static DataCollectorSettings CreateFakesDataCollectorSettings()
{
// embed the fakes run settings
var settings = new DataCollectorSettings
{
AssemblyQualifiedName = FakesMetadata.DataCollectorAssemblyQualifiedName,
FriendlyName = FakesMetadata.FriendlyName,
IsEnabled = true,
Uri = new Uri(FakesMetadata.DataCollectorUri)
};
return settings;
}
internal static class FakesMetadata
{
/// <summary>
/// Friendly name of the data collector
/// </summary>
public const string FriendlyName = "UnitTestIsolation";
/// <summary>
/// Gets the URI of the data collector
/// </summary>
public const string DataCollectorUri = "datacollector://microsoft/unittestisolation/1.0";
/// <summary>
/// Gets the assembly qualified name of the data collector type
/// </summary>
public const string DataCollectorAssemblyQualifiedName = "Microsoft.VisualStudio.TraceCollector.UnitTestIsolationDataCollector, Microsoft.VisualStudio.TraceCollector, Version=16.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a";
}
}
}