--- a +++ b/Implementation/APUnitTestFramework/DependencyResolver.cs @@ -0,0 +1,437 @@ +using _3S.CoDeSys.Core; +using _3S.CoDeSys.Core.ComponentModel; +using _3S.CoDeSys.Core.Components; +using _3S.CoDeSys.Core.Views; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Xml; + +namespace _3S.APUnitTestFramework +{ + class DependencyResolver + { + public DependencyResolver(Testbed testbed, string profileName, Profile profile) + { + if (testbed == null) + throw new ArgumentNullException(nameof(testbed)); + + _testbed = testbed; + + var byTypeGuid = new Dictionary<Guid, IValueProvider>(); + var byItfType = new Dictionary<Type, List<IValueProvider>>(); + foreach (var kvp in testbed.Mocks) + { + var prototype = kvp.Value.CreatePrototypeFunc(); + var valueProvider = new MockValueProvider(kvp.Key, prototype, kvp.Value); + byTypeGuid[kvp.Key] = valueProvider; + + foreach (var itfType in prototype.GetType().GetInterfaces()) + { + List<IValueProvider> valueProviderList; + byItfType.TryGetValue(itfType, out valueProviderList); + if (valueProviderList == null) + { + valueProviderList = new List<IValueProvider>(); + byItfType[itfType] = valueProviderList; + } + valueProviderList.Add(valueProvider); + } + } + + _mockFactoriesByTypeGuid = byTypeGuid; + _mockFactoriesByItfType = byItfType.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.ToArray()); + _profileName = profileName ?? ComponentManager.Singleton.ActiveProfileName; + _profile = profile ?? ComponentManager.Singleton.ActiveProfile; + } + + public IValueProvider ResolveSingle(Type itfType, bool optional, MemberInfo member) + { + IValueProvider[] results; + _mockFactoriesByItfType.TryGetValue(itfType, out results); + + if (results == null) + { + // We did not find an implementation among the mocks. + // Try the additional plug-ins, if any. This discovery is similar to the original + // component model implementation. In the case of any doubts, look there. + + if (_systemTypeInfos == null) + _systemTypeInfos = ComponentManager.Singleton.GetSystemTypes(); + + var itfTypeFullName = itfType.FullName; + Guid systemTypeGuid; + try + { + var enum1 = Enumerable.Where(_systemTypeInfos, typeInfo => typeInfo.TypeName == itfTypeFullName || typeInfo.ItfTypesFast.Contains(itfTypeFullName)); + var enum2 = Enumerable.Select(enum1, typeInfo => typeInfo.TypeGuid); + systemTypeGuid = Enumerable.SingleOrDefault(enum2); + } + catch + { + var exception = new MultipleInstancesForInjectionException(member); + _testbed.RaiseDependencyError(exception, itfType, null); + return new ExceptionValueProvider(exception); + } + + if (systemTypeGuid != Guid.Empty) + return new SystemInstanceValueProvider(systemTypeGuid); + + if (itfType.IsAssignableFrom(SystemInstances.MessageService.GetType())) + return new MessageServiceValueProvider(); + + IValueProvider vp = null; + foreach (var t in ComponentManager.Singleton.PlugInCache.GetTypesFast(itfTypeFullName, null)) + { + if (vp == null) + { + vp = new TypeGuidValueProvider(t); + } + else + { + var exception = new MultipleInstancesForInjectionException(member); + _testbed.RaiseDependencyError(exception, itfType, null); + return new ExceptionValueProvider(exception); + } + } + + if (vp != null) + results = new[] { vp }; + } + + if (results == null && optional) + { + return new NullValueProvider(); + } + else if (results == null) + { + var exception = new NoInstanceForInjectionException(member); + _testbed.RaiseDependencyError(exception, itfType, null); + return new ExceptionValueProvider(exception); + } + else if (results.Length == 1) + { + return results[0]; + } + else + { + var exception = new MultipleInstancesForInjectionException(member); + _testbed.RaiseDependencyError(exception, itfType, null); + return new ExceptionValueProvider(exception); + } + } + + public IValueProvider ResolveSpecific(Guid typeGuid, Type itfType, bool optional, MemberInfo member) + { + IValueProvider result; + _mockFactoriesByTypeGuid.TryGetValue(typeGuid, out result); + + if (result == null) + { + // We did not find an implementation among the mocks. + // Try the additional plug-ins, if any. This discovery is similar to the original + // component model implementation. In the case of any doubts, look there. + + var exists = false; + try + { + exists = ComponentManager.Singleton.TryGetPlugInType(typeGuid, true) != null; + } + catch { } + + if (exists) + return new TypeGuidValueProvider(typeGuid); + } + + if (result == null && optional) + { + return new NullValueProvider(); + } + else if (result == null) + { + var exception = new NoInstanceForInjectionException(member); + _testbed.RaiseDependencyError(exception, itfType, typeGuid); + return new ExceptionValueProvider(exception); + } + else + { + return result; + } + } + + public IEnumerable<IValueProvider> ResolveMultiple(Type itfType, bool optional, MemberInfo member) + { + IValueProvider[] results; + _mockFactoriesByItfType.TryGetValue(itfType, out results); + + if (results == null) + { + // We did not find an implementation among the mocks. + // Try the additional plug-ins, if any. This discovery is similar to the original + // component model implementation. In the case of any doubts, look there. + + var typeGuids = ComponentManager.Singleton.PlugInCache.GetTypesFast(itfType.FullName, null); + if (Enumerable.Any(typeGuids)) + results = typeGuids.Select(typeGuid => new TypeGuidValueProvider(typeGuid)).ToArray(); + } + + if (results == null && optional) + { + return new IValueProvider[0]; + } + else if (results == null) + { + var exception = new NoInstanceForInjectionException(member); + _testbed.RaiseDependencyError(exception, itfType, null); + return new ExceptionValueProvider(exception); + } + else + { + return results; + } + } + + public void ResolveProfile(MemberInfo member, out string profileName, out Profile profile) + { + profileName = _profileName; + profile = _profile; + } + + public IWinFormWrapper ResolveFrameForm(MemberInfo member) + { + if (_frameForm == null) + _frameForm = ResolveSingle(typeof(IWinFormWrapper), true, member).CreateInstance<IWinFormWrapper>(); + + return _frameForm; + } + + private Testbed _testbed; + private IDictionary<Guid, IValueProvider> _mockFactoriesByTypeGuid; + private IDictionary<Type, IValueProvider[]> _mockFactoriesByItfType; + private string _profileName; + private Profile _profile; + private IWinFormWrapper _frameForm; + private TypeInformation[] _systemTypeInfos; + } + + interface IValueProvider + { + Guid TypeGuid { get; } + Type Type { get; } + TypeInformation TypeInformation { get; } + T CreateInstance<T>() where T : class; + } + + class MockValueProvider : IValueProvider + { + public MockValueProvider(Guid typeGuid, object prototype, MockConstructor mockConstructor) + { + if (mockConstructor == null) + throw new ArgumentNullException(nameof(mockConstructor)); + + TypeGuid = typeGuid; + Type = prototype.GetType(); + _mockConstructor = mockConstructor; + + // As the TypeInformation type does not have any suitable public constructors for us, + // we must (partially) construct the instance using reflection and fake XML, + // unfortunately. + + XmlDocument plugInInfoXmlDocument = new XmlDocument(); + plugInInfoXmlDocument.AppendChild(plugInInfoXmlDocument.CreateElement("ROOT")); + plugInInfoXmlDocument.DocumentElement.SetAttribute("AssemblyName", Assembly.GetExecutingAssembly().FullName); + plugInInfoXmlDocument.DocumentElement.SetAttribute("CodeBase", Assembly.GetExecutingAssembly().CodeBase); + plugInInfoXmlDocument.DocumentElement.SetAttribute("GUID", XmlConvert.ToString(Guid.NewGuid())); + plugInInfoXmlDocument.DocumentElement.SetAttribute("Version", "1.0.0.0"); + plugInInfoXmlDocument.DocumentElement.SetAttribute("System", XmlConvert.ToString(false)); + plugInInfoXmlDocument.DocumentElement.SetAttribute("Company", "The Unit Test Company"); + plugInInfoXmlDocument.DocumentElement.SetAttribute("Copyright", "Copyright © 2016 by The Unit Test Company. All rights reserved."); + plugInInfoXmlDocument.DocumentElement.SetAttribute("Description", "Generated mock plug-in for the Unit Test Framework."); + plugInInfoXmlDocument.DocumentElement.SetAttribute("Product", "CODESYS Automation Platform Unit Test Framework"); + plugInInfoXmlDocument.DocumentElement.SetAttribute("Title", "CODESYS Automation Platform Unit Test Framework"); + XmlElement typeElement = plugInInfoXmlDocument.CreateElement("Type"); + plugInInfoXmlDocument.DocumentElement.AppendChild(typeElement); + typeElement.SetAttribute("GUID", XmlConvert.ToString(TypeGuid)); + typeElement.SetAttribute("Name", Type.FullName); + typeElement.SetAttribute("ItfTypes", string.Join(";", prototype.GetType().GetInterfaces().Select(itf => itf.FullName))); + + var plugInInfoCtor = typeof(PlugInInformation).GetConstructor( + BindingFlags.Instance | BindingFlags.NonPublic, + null, + new[] { typeof(XmlElement) }, + null); + var plugInInfo = (PlugInInformation)plugInInfoCtor.Invoke(new object[] { plugInInfoXmlDocument.DocumentElement }); + + TypeInformation = plugInInfo.TypeInfosFast.First(); // We added exactly one type above, so we should able to get it back now. + } + + public Guid TypeGuid { get; private set; } + public Type Type { get; private set; } + public TypeInformation TypeInformation { get; private set; } + + public T CreateInstance<T>() where T : class + { + if (_mockConstructor.SystemInstance) + { + if (_systemInstance == null) + _systemInstance = _mockConstructor.CreateFunc(); + return (T)_systemInstance; + } + else + { + return (T)_mockConstructor.CreateFunc(); + } + } + + private MockConstructor _mockConstructor; + private object _systemInstance; + } + + class NullValueProvider : IValueProvider + { + public Guid TypeGuid + { + get { return Guid.Empty; } + } + + public Type Type + { + get { return null; } + } + + public TypeInformation TypeInformation + { + get { return null; } + } + + public T CreateInstance<T>() where T : class + { + return null; + } + } + + class SystemInstanceValueProvider : IValueProvider + { + public SystemInstanceValueProvider(Guid typeGuid) + { + TypeGuid = typeGuid; + } + + public Type Type + { + get { return ComponentManager.Singleton.TryGetPlugInType(TypeGuid, true); } + } + + public Guid TypeGuid { get; private set; } + + public TypeInformation TypeInformation + { + get { return ComponentManager.Singleton.PlugInCache.GetTypeInformation(TypeGuid, null); } + } + + public T CreateInstance<T>() where T : class + { + return ComponentManager.Singleton.TryGetSystemInstance<T>(); + } + } + + class MessageServiceValueProvider : IValueProvider + { + public Type Type + { + get { return SystemInstances.MessageService.GetType(); } + } + + public Guid TypeGuid + { + get + { + if (s_typeGuid == null) + { + var attr = TypeGuidAttribute.FromObject(SystemInstances.MessageService); + s_typeGuid = attr != null ? attr.Guid : Guid.Empty; + } + return s_typeGuid.Value; + } + } + + private static Guid? s_typeGuid; + + public TypeInformation TypeInformation + { + get { return ComponentManager.Singleton.PlugInCache.GetTypeInformation(TypeGuid, null); } + } + + public T CreateInstance<T>() where T : class + { + return (T)SystemInstances.MessageService; + } + } + + class TypeGuidValueProvider : IValueProvider + { + public TypeGuidValueProvider(Guid typeGuid) + { + TypeGuid = typeGuid; + } + + public Type Type + { + get { return ComponentManager.Singleton.TryGetPlugInType(TypeGuid, true); } + } + + public Guid TypeGuid { get; private set; } + + public TypeInformation TypeInformation + { + get { return ComponentManager.Singleton.PlugInCache.GetTypeInformation(TypeGuid, null); } + } + + public T CreateInstance<T>() where T : class + { + return ComponentManager.Singleton.TryCreateInstance<T>(TypeGuid); + } + } + + class ExceptionValueProvider : IValueProvider, IEnumerable<IValueProvider> + { + public ExceptionValueProvider(Exception exception) + { + _exception = exception; + } + + public Type Type + { + get { throw _exception; } + } + + public Guid TypeGuid + { + get { throw _exception; } + } + + public TypeInformation TypeInformation + { + get { throw _exception; } + } + + public T CreateInstance<T>() where T : class + { + throw _exception; + } + + public IEnumerator<IValueProvider> GetEnumerator() + { + throw _exception; + } + + IEnumerator IEnumerable.GetEnumerator() + { + throw _exception; + } + + private Exception _exception; + } +}