a | b/Interfaces/+APUnitTestFramework/Testbed.cs | ||
---|---|---|---|
1 | using _3S.CoDeSys.Core.ComponentModel; |
||
2 | using _3S.CoDeSys.Core.Components; |
||
3 | using _3S.CoDeSys.Core.Licensing; |
||
4 | using System; |
||
5 | using System.Collections.Generic; |
||
6 | using System.Collections.ObjectModel; |
||
7 | using System.ComponentModel; |
||
8 | using System.Diagnostics; |
||
9 | using System.IO; |
||
10 | using System.Linq; |
||
11 | using System.Reflection; |
||
12 | using System.Runtime.Serialization.Json; |
||
13 | using System.Threading; |
||
14 | using System.Xml; |
||
15 | using System.Xml.Linq; |
||
16 | using System.Xml.XPath; |
||
17 | |||
18 | namespace _3S.APUnitTestFramework |
||
19 | { |
||
20 | /// <summary> |
||
21 | /// Use this class to create a single unit test environment. It can be considered being a |
||
22 | /// lightweight Automation Platform process containing the testee plug-in and mocks for all |
||
23 | /// dependencies. |
||
24 | /// </summary> |
||
25 | /// <remarks> |
||
26 | /// Note that the testbed will only work properly if all involved dependencies are handled |
||
27 | /// using our Dependency Injection mechanisms, and if the (FxCop-enforced) |
||
28 | /// APEnvironment-DependencyBag implementation pattern is used. |
||
29 | /// </remarks> |
||
30 | public class Testbed : IDisposable |
||
31 | { |
||
32 | /// <summary> |
||
33 | /// Creates a new testbed. |
||
34 | /// </summary> |
||
35 | public Testbed() |
||
36 | { |
||
37 | } |
||
38 | |||
39 | /// <summary> |
||
40 | /// Gets or sets the root folder of the Automation Platform installation. If this property |
||
41 | /// is not explicitly set, or explicitly set to <c>null</c>, then the root folder will be |
||
42 | /// automatically determined as described in the Remarks section. |
||
43 | /// </summary> |
||
44 | /// <exception cref="InvalidOperationException"> |
||
45 | /// The value of this property is set after <see cref="Initialize"/> has been called. |
||
46 | /// </exception> |
||
47 | /// <remarks> |
||
48 | /// There are two heuristics how to find out the root folder of the Automation Platform |
||
49 | /// installation, applied in that order given: |
||
50 | /// <list type="bullet"> |
||
51 | /// <item> |
||
52 | /// If the unit test assembly is within an Apaddon directory structure, then the default |
||
53 | /// target in the corresponding <c>AddOn.json</c> file will be used as root folder of the |
||
54 | /// test environment. |
||
55 | /// </item> |
||
56 | /// <item> |
||
57 | /// If the unit test assembly is not within an Apaddon directory structure, or the |
||
58 | /// <c>AddOn.json</c> file could not be found or evaluated, then the root folder as |
||
59 | /// specified in the <c>%CODESYS_OUTPUTDIR%</c> or <c>%CODESYS_64_OUTPUTDIR%</c> environment variable will be used |
||
60 | /// depending on <c>%CODESYS_PLATFORM%</c>. This |
||
61 | /// scenario should work in all scenarios where unit tests are developed for standard |
||
62 | /// CODESYS plug-ins. |
||
63 | /// </item> |
||
64 | /// </list> |
||
65 | /// </remarks> |
||
66 | public string RootFolder |
||
67 | { |
||
68 | get |
||
69 | { |
||
70 | if (_rootFolder == null) |
||
71 | { |
||
72 | _rootFolder = DeriveRootFolderFromApaddon(); |
||
73 | if (string.IsNullOrEmpty(_rootFolder) || !Directory.Exists(_rootFolder)) |
||
74 | _rootFolder = DeriveRootFolderFromStandard(); |
||
75 | if (string.IsNullOrEmpty(_rootFolder) || !Directory.Exists(_rootFolder)) |
||
76 | _rootFolder = string.Empty; |
||
77 | } |
||
78 | return _rootFolder; |
||
79 | } |
||
80 | |||
81 | set |
||
82 | { |
||
83 | if (_initializeCalled) |
||
84 | throw new InvalidOperationException("Attempt to set the RootFolder property after calling Initialize()"); |
||
85 | |||
86 | _rootFolder = value; |
||
87 | } |
||
88 | } |
||
89 | |||
90 | private static string DeriveRootFolderFromApaddon() |
||
91 | { |
||
92 | try |
||
93 | { |
||
94 | var thisAssemblyDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); |
||
95 | var targetsDirectory = Path.Combine(thisAssemblyDirectory, @"..\..\..\..\..\Targets"); |
||
96 | var targetsFile = Path.Combine(targetsDirectory, @".addon"); |
||
97 | |||
98 | // NewtonSoft.Json would be a nice alternative, but unfortunately we are not |
||
99 | // allowed to reference it from an interface assembly. |
||
100 | // However, .NET has got built-in support. |
||
101 | var jsonData = File.ReadAllBytes(targetsFile); |
||
102 | var jsonReader = JsonReaderWriterFactory.CreateJsonReader(jsonData, new XmlDictionaryReaderQuotas()); |
||
103 | var jsonRoot = XElement.Load(jsonReader); |
||
104 | var nameElement = jsonRoot.XPathSelectElement("//active/name"); |
||
105 | var name = nameElement.Value; |
||
106 | |||
107 | var targetDirectory = Path.Combine(targetsDirectory, name); |
||
108 | return targetDirectory; |
||
109 | } |
||
110 | catch |
||
111 | { |
||
112 | return null; |
||
113 | } |
||
114 | } |
||
115 | |||
116 | private static string DeriveRootFolderFromStandard() |
||
117 | { |
||
118 | if (Environment.GetEnvironmentVariable("CODESYS_PLATFORM") != null && |
||
119 | (Environment.GetEnvironmentVariable("CODESYS_PLATFORM").Equals("x64", StringComparison.CurrentCultureIgnoreCase) || |
||
120 | Environment.GetEnvironmentVariable("CODESYS_PLATFORM").Equals("BOTH", StringComparison.CurrentCultureIgnoreCase))) |
||
121 | { |
||
122 | return Environment.ExpandEnvironmentVariables(Environment.GetEnvironmentVariable("CODESYS_64_OUTPUTDIR")); |
||
123 | } |
||
124 | else |
||
125 | { |
||
126 | return Environment.ExpandEnvironmentVariables(Environment.GetEnvironmentVariable("CODESYS_OUTPUTDIR")); |
||
127 | } |
||
128 | |||
129 | } |
||
130 | |||
131 | /// <summary> |
||
132 | /// Call this method in order to add a plug-in to the test environment. |
||
133 | /// </summary> |
||
134 | /// <param name="plugInGuid"> |
||
135 | /// The GUID of the plug-in to be added to the test environment. The newest version will be |
||
136 | /// used. |
||
137 | /// </param> |
||
138 | /// <exception cref="InvalidOperationException"> |
||
139 | /// This method is called after <see cref="Initialize"/> has been called. |
||
140 | /// </exception> |
||
141 | /// <remarks> |
||
142 | /// One should use this possibility with care. When adding "real" plug-ins to the test |
||
143 | /// environment, the idea of a "pure" unit test will be compromised, where every dependency |
||
144 | /// should be a mock with an exactly specified behavior. |
||
145 | /// Furthermore, please do not add the <c>Engine.plugin</c> to the environment. The reason |
||
146 | /// is that two Component Model implementations would then be part of the environment, |
||
147 | /// causing the Component Manager to throw an exception during initialization. |
||
148 | /// </remarks> |
||
149 | public void IncludeAdditionalPlugInGuid(Guid plugInGuid) |
||
150 | { |
||
151 | if (_initializeCalled) |
||
152 | throw new InvalidOperationException("Attempt to include additional plug-ins after calling Initialize()"); |
||
153 | |||
154 | if (_additionalPlugInGuids == null) |
||
155 | _additionalPlugInGuids = new HashSet<Guid>(); |
||
156 | _additionalPlugInGuids.Add(plugInGuid); |
||
157 | } |
||
158 | |||
159 | /// <summary> |
||
160 | /// Gets the plug-in GUIDs of the additional plug-ins that have been added via <see cref= |
||
161 | /// "IncludeAdditionalPlugInGuid(Guid)"/>. |
||
162 | /// </summary> |
||
163 | public IEnumerable<Guid> AdditionalPlugInGuids |
||
164 | { |
||
165 | get |
||
166 | { |
||
167 | if (_additionalPlugInGuids != null) |
||
168 | return _additionalPlugInGuids; |
||
169 | else |
||
170 | return new Guid[0]; |
||
171 | } |
||
172 | } |
||
173 | |||
174 | /// <summary> |
||
175 | /// Adds a mock to the test environment. This is the preferred way to fulfill any |
||
176 | /// dependency of the testee. |
||
177 | /// <seealso cref="AddMock(Guid, Func{object}, bool)"/> |
||
178 | /// <seealso cref="AddMock(Guid, Func{object}, Func{object}, bool)"/> |
||
179 | /// </summary> |
||
180 | /// <param name="typeGuid"> |
||
181 | /// The type GUID of the mock type. If the testee's dependency is described by an <see |
||
182 | /// cref="InjectSpecificInstanceAttribute"/> or an <see cref= |
||
183 | /// "InjectSpecificTypeInformationAttribute"/>, then the specified GUID must match the |
||
184 | /// requested one, otherwise a new GUID can be created. |
||
185 | /// </param> |
||
186 | /// <param name="createFunc"> |
||
187 | /// A delegate which creates the mock instance on request. |
||
188 | /// </param> |
||
189 | /// <exception cref="InvalidOperationException"> |
||
190 | /// This method is called after <see cref="Initialize"/> has been called. |
||
191 | /// </exception> |
||
192 | /// <exception cref="ArgumentNullException"> |
||
193 | /// <paramref name="createFunc"/> is <c>null</c>. |
||
194 | /// </exception> |
||
195 | /// <exception cref="ArgumentException"> |
||
196 | /// This method is called multiple times with the same <paramref name="typeGuid"/> value. |
||
197 | /// </exception> |
||
198 | public void AddMock(Guid typeGuid, Func<object> createFunc) |
||
199 | { |
||
200 | AddMock(typeGuid, createFunc, createFunc, false); |
||
201 | } |
||
202 | |||
203 | /// <summary> |
||
204 | /// Adds a mock to the test environment. This is the preferred way to fulfill any |
||
205 | /// dependency of the testee. |
||
206 | /// <seealso cref="AddMock(Guid, Func{object})"/> |
||
207 | /// <seealso cref="AddMock(Guid, Func{object}, Func{object}, bool)"/> |
||
208 | /// </summary> |
||
209 | /// <param name="typeGuid"> |
||
210 | /// The type GUID of the mock type. If the testee's dependency is described by an <see |
||
211 | /// cref="InjectSpecificInstanceAttribute"/> or an <see cref= |
||
212 | /// "InjectSpecificTypeInformationAttribute"/>, then the specified GUID must match the |
||
213 | /// requested one, otherwise a new GUID can be created. |
||
214 | /// </param> |
||
215 | /// <param name="createFunc"> |
||
216 | /// A delegate which creates the mock instance on request. |
||
217 | /// </param> |
||
218 | /// <param name="systemInstance"> |
||
219 | /// This mock is a system instance, i.e. it will be a global singleton. |
||
220 | /// </param> |
||
221 | /// <exception cref="InvalidOperationException"> |
||
222 | /// This method is called after <see cref="Initialize"/> has been called. |
||
223 | /// </exception> |
||
224 | /// <exception cref="ArgumentNullException"> |
||
225 | /// <paramref name="createFunc"/> is <c>null</c>. |
||
226 | /// </exception> |
||
227 | /// <exception cref="ArgumentException"> |
||
228 | /// This method is called multiple times with the same <paramref name="typeGuid"/> value. |
||
229 | /// </exception> |
||
230 | public void AddMock(Guid typeGuid, Func<object> createFunc, bool systemInstance) |
||
231 | { |
||
232 | AddMock(typeGuid, createFunc, createFunc, systemInstance); |
||
233 | } |
||
234 | |||
235 | /// <summary> |
||
236 | /// Adds a mock to the test environment. This is the preferred way to fulfill any |
||
237 | /// dependency of the testee. |
||
238 | /// <seealso cref="AddMock(Guid, Func{object})"/> |
||
239 | /// <seealso cref="AddMock(Guid, Func{object}, bool)"/> |
||
240 | /// </summary> |
||
241 | /// <param name="typeGuid"> |
||
242 | /// The type GUID of the mock type. If the testee's dependency is described by an <see |
||
243 | /// cref="InjectSpecificInstanceAttribute"/> or an <see cref= |
||
244 | /// "InjectSpecificTypeInformationAttribute"/>, then the specified GUID must match the |
||
245 | /// requested one, otherwise a new GUID can be created. |
||
246 | /// </param> |
||
247 | /// <param name="createFunc"> |
||
248 | /// A delegate which creates the mock instance on request. |
||
249 | /// </param> |
||
250 | /// <param name="createPrototypeFunc"> |
||
251 | /// The testbed will create prototypes for the mock instance during initialization. If |
||
252 | /// there is a different logic for prototype instances compared to "real" instances, then |
||
253 | /// a dedicated creation function can be specified using this parameter. (For example, a |
||
254 | /// prototype instance should not attach itself to events, whereas a "real" instance might |
||
255 | /// do it.) The other overloads of this method use the same creation function for both |
||
256 | /// construction methods. |
||
257 | /// </param> |
||
258 | /// <param name="systemInstance"> |
||
259 | /// This mock is a system instance, i.e. it will be a global singleton. |
||
260 | /// </param> |
||
261 | /// <exception cref="InvalidOperationException"> |
||
262 | /// This method is called after <see cref="Initialize"/> has been called. |
||
263 | /// </exception> |
||
264 | /// <exception cref="ArgumentNullException"> |
||
265 | /// <paramref name="createFunc"/> is <c>null</c>. |
||
266 | /// </exception> |
||
267 | /// <exception cref="ArgumentException"> |
||
268 | /// This method is called multiple times with the same <paramref name="typeGuid"/> value. |
||
269 | /// </exception> |
||
270 | public void AddMock(Guid typeGuid, Func<object> createFunc, Func<object> createPrototypeFunc, bool systemInstance) |
||
271 | { |
||
272 | if (_initializeCalled) |
||
273 | throw new InvalidOperationException("Attempt to add mock after calling Initialize()"); |
||
274 | |||
275 | if (_mocks == null) |
||
276 | _mocks = new Dictionary<Guid, MockConstructor>(); |
||
277 | |||
278 | var mockConstructor = new MockConstructor(createFunc, createPrototypeFunc, systemInstance); |
||
279 | _mocks.Add(typeGuid, mockConstructor); |
||
280 | } |
||
281 | |||
282 | /// <summary> |
||
283 | /// Gets a read-only dictionary of all mock constructors that have been added via <see |
||
284 | /// cref="AddMock(Guid, MockConstructor)"/>. |
||
285 | /// </summary> |
||
286 | public IDictionary<Guid, MockConstructor> Mocks |
||
287 | { |
||
288 | get |
||
289 | { |
||
290 | if (_mocks != null) |
||
291 | return new ReadOnlyDictionary<Guid, MockConstructor>(_mocks); |
||
292 | else |
||
293 | return new ReadOnlyDictionary<Guid, MockConstructor>(new Dictionary<Guid, MockConstructor>()); |
||
294 | } |
||
295 | } |
||
296 | |||
297 | /// <summary> |
||
298 | /// Initializes the test environment, after the caller has performed all necessary |
||
299 | /// preparations using <see cref="RootFolder"/>, <see cref="IncludeAdditionalPlugInGuid |
||
300 | /// (Guid)"/>, and <see cref="AddMock(Guid, MockConstructor)"/>. |
||
301 | /// </summary> |
||
302 | /// <exception cref="InvalidOperationException"> |
||
303 | /// The <see cref="RootFolder"/> property returns an invalid or non-existing directory. |
||
304 | /// </exception> |
||
305 | /// <exception cref="PlugInDoesNotExistException"> |
||
306 | /// Either the <c>APUnitTestFramework.plugin</c> or one of the plug-ins denoted by <see |
||
307 | /// cref="AdditionalPlugInGuids"/> is not installed. |
||
308 | /// </exception> |
||
309 | /// <exception cref="Exception"> |
||
310 | /// This method rethrows all exceptions that are thrown by the standard <see cref= |
||
311 | /// "ComponentManager"/> implementation. |
||
312 | /// </exception> |
||
313 | /// <remarks> |
||
314 | /// This initialization routine comprises the following steps: |
||
315 | /// <list type="bullet"> |
||
316 | /// <item> |
||
317 | /// Resetting the <c>ComponentManager</c> singleton. |
||
318 | /// </item> |
||
319 | /// <item> |
||
320 | /// Resetting the <c>ComponentModel</c> singleton. |
||
321 | /// </item> |
||
322 | /// <item> |
||
323 | /// Scanning all loaded assemblies for <c>APEnvironment</c> classes and resetting their |
||
324 | /// lazily initialized bag field (type <c>Lazy<DependencyBag></c>). This is the |
||
325 | /// reason why the testbed only works reliably for plug-ins which conform to the |
||
326 | /// APEnvironment-DependencyBag implementation pattern, as enforced by our internal FxCop |
||
327 | /// coding rules. |
||
328 | /// </item> |
||
329 | /// <item> |
||
330 | /// Setting the root folder as specified by the <see cref="RootFolder"/> property. |
||
331 | /// </item> |
||
332 | /// <item> |
||
333 | /// Creating a temporary profile which only contains the <c>APUnitTestFramework.plugin</c> |
||
334 | /// and the plug-ins as specified via the <see cref="IncludeAdditionalPlugInGuid(Guid)"/> |
||
335 | /// method. |
||
336 | /// </item> |
||
337 | /// <item> |
||
338 | /// Creating the system instances. |
||
339 | /// </item> |
||
340 | /// </list> |
||
341 | /// </remarks> |
||
342 | public void Initialize() |
||
343 | { |
||
344 | _initializeCalled = true; |
||
345 | |||
346 | // Reset all static things. |
||
347 | |||
348 | // Unregister component Manager from AppDomain events |
||
349 | var fiAssemblyLoad = AppDomain.CurrentDomain.GetType().GetField("AssemblyLoad", BindingFlags.Instance | BindingFlags.NonPublic); |
||
350 | if (fiAssemblyLoad != null) |
||
351 | { |
||
352 | AssemblyLoadEventHandler eventHandler = fiAssemblyLoad.GetValue(AppDomain.CurrentDomain) as AssemblyLoadEventHandler; |
||
353 | if (eventHandler != null) |
||
354 | { |
||
355 | AppDomain.CurrentDomain.AssemblyLoad -= eventHandler; |
||
356 | } |
||
357 | } |
||
358 | var fiAssemblyResolve = AppDomain.CurrentDomain.GetType().GetField("_AssemblyResolve", BindingFlags.Instance | BindingFlags.NonPublic); |
||
359 | if (fiAssemblyResolve != null) |
||
360 | { |
||
361 | ResolveEventHandler eventHandler = fiAssemblyResolve.GetValue(AppDomain.CurrentDomain) as ResolveEventHandler; |
||
362 | if (eventHandler != null) |
||
363 | { |
||
364 | AppDomain.CurrentDomain.AssemblyResolve -= eventHandler; |
||
365 | } |
||
366 | } |
||
367 | |||
368 | // The private s_singleton field is obfuscated! As a hack, discover all fields for |
||
369 | // that one which is of type ComponentManager. Ugh! |
||
370 | var fieldInfo = typeof(ComponentManager).GetFields(BindingFlags.Static | BindingFlags.NonPublic) |
||
371 | .Single(fi => typeof(ComponentManager).IsAssignableFrom(fi.FieldType)); |
||
372 | fieldInfo.SetValue(null, null); |
||
373 | |||
374 | // The private s_singleton field is obfuscated! As a hack, discover all fields for |
||
375 | // that one which is of type ComponentModel. Doubled ugh! |
||
376 | fieldInfo = typeof(ComponentModel).GetFields(BindingFlags.Static | BindingFlags.NonPublic) |
||
377 | .Single(fi => typeof(ComponentModel).IsAssignableFrom(fi.FieldType)); |
||
378 | fieldInfo.SetValue(null, null); |
||
379 | |||
380 | // Finally, we must reset all the dependency bags that are around. Obviously, this is |
||
381 | // only possible by reflection. |
||
382 | // Note: We are heavily tweaking the Lazy<T> object's internal members. A different |
||
383 | // idea would be to create new Lazy<T> objects with the original value factories. |
||
384 | // However, it turns out to be impossible because the value factory is internally |
||
385 | // replaced by something else in the Lazy<T> implementation |
||
386 | // ("ALREADY_INVOKED_SENTINEL") so that it becomes impossible for us to get hold of the |
||
387 | // original value factory as specified in the constructor. |
||
388 | var alreadyInitializedLazyDependencyBags = AppDomain.CurrentDomain.GetAssemblies() |
||
389 | .Where(asm => PlugInGuidAttribute.FromAssembly(asm) != null) |
||
390 | .SelectMany(asm => asm.GetTypes()) |
||
391 | .Where(type => type.Name == "APEnvironment") |
||
392 | .SelectMany(type => type.GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)) |
||
393 | .Where(field => field.FieldType.IsGenericType && typeof(Lazy<>).IsAssignableFrom(field.FieldType.GetGenericTypeDefinition())) |
||
394 | .Select(field => field.GetValue(null)); |
||
395 | foreach (var dependencyBag in alreadyInitializedLazyDependencyBags) |
||
396 | { |
||
397 | var boxed = dependencyBag.GetType().GetField("m_boxed", BindingFlags.Instance | BindingFlags.NonPublic); |
||
398 | boxed.SetValue(dependencyBag, null); |
||
399 | |||
400 | var valueFactory = dependencyBag.GetType().GetField("m_valueFactory", BindingFlags.Instance | BindingFlags.NonPublic); |
||
401 | valueFactory.SetValue(dependencyBag, null); |
||
402 | |||
403 | var isValueCreated = (bool)dependencyBag.GetType().GetProperty("IsValueCreated").GetValue(dependencyBag); |
||
404 | Debug.Assert(!isValueCreated); |
||
405 | } |
||
406 | |||
407 | // Initialize the component manager at the specified or derived root folder. |
||
408 | |||
409 | var rootFolder = RootFolder; |
||
410 | if (string.IsNullOrEmpty(rootFolder) || !Directory.Exists(rootFolder)) |
||
411 | throw new InvalidOperationException("The RootFolder property has not been set correctly and could not be derived from your development environment."); |
||
412 | |||
413 | ComponentManager.SetRootFolder(RootFolder); |
||
414 | new ComponentManager(); |
||
415 | bool foo; |
||
416 | ComponentManager.Singleton.InitializePlugInCache(null, new LicensingInitializationReporter(), out foo); |
||
417 | |||
418 | // Create a temporary profile which contains |
||
419 | // - the APUnitTestFramework plug-in |
||
420 | // - the list of plugins as specified in the AdditionalPlugInGuids property. |
||
421 | // - (nothing else). |
||
422 | // Use a unique profile name so that concurrent independent unit test runs are possible |
||
423 | // without interference. |
||
424 | |||
425 | var profileName = "APUnitTestFramework_" + DateTime.Now.Ticks; |
||
426 | var profile = ComponentManager.Singleton.ProfileList.CreateProfile(profileName); |
||
427 | profile.SetVersionConstraint(PLUGINGUID_APUNITTESTFRAMEWORK, new NewestVersionConstraint()); |
||
428 | foreach (var plugInGuid in AdditionalPlugInGuids) |
||
429 | profile.SetVersionConstraint(plugInGuid, new NewestVersionConstraint()); |
||
430 | ComponentManager.Singleton.SetActiveProfile(profileName, profile); |
||
431 | |||
432 | // Check whether all plug-ins are really existing. Otherwise throw an exception. |
||
433 | foreach (var plugInGuid in profile.GetEntries()) |
||
434 | { |
||
435 | var versionConstraint = profile.GetVersionConstraint(plugInGuid); |
||
436 | var allVersions = ComponentManager.Singleton.PlugInCache.GetPlugInVersionsFast(plugInGuid); |
||
437 | var version = versionConstraint.FindVersionFast(allVersions); |
||
438 | if (version == null) |
||
439 | throw new PlugInDoesNotExistException(new PlugInName(plugInGuid, new Version("0.0.0.0"))); |
||
440 | } |
||
441 | |||
442 | var implementationProperty = typeof(ComponentModel).GetProperty("Implementation", BindingFlags.Instance | BindingFlags.NonPublic); |
||
443 | var componentModelImpl = (IComponentModelImplementationForUnitTest)implementationProperty.GetValue(ComponentModel.Singleton); |
||
444 | componentModelImpl.SetupForUnitTest(this, profileName, profile); |
||
445 | ComponentManager.Singleton.CreateSystemInstances(null); |
||
446 | } |
||
447 | |||
448 | public void Dispose() |
||
449 | { |
||
450 | if (!_initializeCalled) |
||
451 | return; // No temporary profile has been set yet, so there is nothing to clean up. |
||
452 | |||
453 | for (var i = 0; i < 10; i++) |
||
454 | { |
||
455 | try |
||
456 | { |
||
457 | var profile = ComponentManager.Singleton.ActiveProfileName; |
||
458 | ComponentManager.Singleton.ProfileList.DeleteProfile(profile); |
||
459 | break; |
||
460 | } |
||
461 | catch |
||
462 | { |
||
463 | Thread.Sleep(500); |
||
464 | } |
||
465 | } |
||
466 | } |
||
467 | |||
468 | /// <summary> |
||
469 | /// This event is triggered for all dependencies that could not be resolved. While |
||
470 | /// developing a unit test, subscribing this event can be useful to recognize all |
||
471 | /// dependencies that need a mock. |
||
472 | /// </summary> |
||
473 | public event EventHandler<DependencyErrorEventArgs> DependencyError; |
||
474 | |||
475 | /// <summary> |
||
476 | /// For internal use. |
||
477 | /// </summary> |
||
478 | /// <param name="exception"></param> |
||
479 | /// <param name="requiredInterfaceType"></param> |
||
480 | /// <param name="specificTypeGuid"></param> |
||
481 | public void RaiseDependencyError(ComponentModelException exception, Type requiredInterfaceType, Guid? specificTypeGuid) |
||
482 | { |
||
483 | DependencyError?.Invoke(this, new DependencyErrorEventArgs(exception, requiredInterfaceType, specificTypeGuid)); |
||
484 | } |
||
485 | |||
486 | private string _rootFolder; |
||
487 | private HashSet<Guid> _additionalPlugInGuids; |
||
488 | private Dictionary<Guid, MockConstructor> _mocks; |
||
489 | private bool _initializeCalled; |
||
490 | private static readonly Guid PLUGINGUID_APUNITTESTFRAMEWORK = new Guid("{DFAA33F9-68D4-4BFB-B9AF-2EAD069DD7CA}"); |
||
491 | |||
492 | class LicensingInitializationReporter : ILicensingInitializationReporter2 |
||
493 | { |
||
494 | public ReporterUsage Usage { get; set; } = ReporterUsage.StartUp; |
||
495 | |||
496 | public bool CheckLicense(PlugInInformation plugInInfo) |
||
497 | { |
||
498 | // Licensed plug-ins with valid license -> OK |
||
499 | // Licensed plug-ins without valid license -> No check. Probably the test will |
||
500 | // crash afterwards due to the missing decryption. |
||
501 | return true; |
||
502 | } |
||
503 | |||
504 | public bool ReportMissingLicenses(IEnumerable<PlugInInformation> plugInInfos) |
||
505 | { |
||
506 | return false; |
||
507 | } |
||
508 | } |
||
509 | } |
||
510 | } |