From 3fa9ea3cf11c397bf72aa0219f42fbd5642088fd Mon Sep 17 00:00:00 2001 From: Alexander <2088777+ajeckmans@users.noreply.github.com> Date: Wed, 10 Apr 2024 10:39:21 +0200 Subject: [PATCH 01/21] Update Reqnroll.Verify.targets (#97) --- .../build/Reqnroll.Verify.targets | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Plugins/Reqnroll.Verify/Reqnroll.Verify.ReqnrollPlugin/build/Reqnroll.Verify.targets b/Plugins/Reqnroll.Verify/Reqnroll.Verify.ReqnrollPlugin/build/Reqnroll.Verify.targets index ebf38b7c2..dfa8e8105 100644 --- a/Plugins/Reqnroll.Verify/Reqnroll.Verify.ReqnrollPlugin/build/Reqnroll.Verify.targets +++ b/Plugins/Reqnroll.Verify/Reqnroll.Verify.ReqnrollPlugin/build/Reqnroll.Verify.targets @@ -1,9 +1,9 @@ - <_VerifyGeneratorPluginFramework Condition=" '$(MSBuildRuntimeType)' == 'Core'">netcoreapp2.1 + <_VerifyGeneratorPluginFramework Condition=" '$(MSBuildRuntimeType)' == 'Core'">netcoreapp3.1 <_VerifyGeneratorPluginFramework Condition=" '$(MSBuildRuntimeType)' != 'Core'">net462 <_VerifyGeneratorPluginPath>$(MSBuildThisFileDirectory)$(_VerifyGeneratorPluginFramework)\Reqnroll.Verify.ReqnrollPlugin.dll - \ No newline at end of file + From 4f620a0a7ebf9e1a65cf536c22200c842fc3ff06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1sp=C3=A1r=20Nagy?= Date: Wed, 10 Apr 2024 13:37:18 +0200 Subject: [PATCH 02/21] Include BoDi to Reqnroll package (#91) (#95) * Include BoDi to Reqnroll package (#91) * Move BoDi code to Reqnroll project * split & cleanup BoDi.cs * Remove Reqnroll.BoDi project --- Reqnroll.BoDi/BoDi.cs | 920 ------------------ Reqnroll.BoDi/Reqnroll.BoDi.csproj | 22 - Reqnroll.sln | 16 +- Reqnroll/BoDi/IContainedInstance.cs | 6 + Reqnroll/BoDi/IObjectContainer.cs | 117 +++ Reqnroll/BoDi/IStrategyRegistration.cs | 15 + Reqnroll/BoDi/ObjectContainer.cs | 730 ++++++++++++++ Reqnroll/BoDi/ObjectContainerException.cs | 27 + Reqnroll/Reqnroll.csproj | 22 +- Reqnroll/Reqnroll.nuspec | 5 +- .../Reqnroll.SystemTests.csproj | 3 +- Tests/Reqnroll.SystemTests/SystemTestBase.cs | 46 +- 12 files changed, 938 insertions(+), 991 deletions(-) delete mode 100644 Reqnroll.BoDi/BoDi.cs delete mode 100644 Reqnroll.BoDi/Reqnroll.BoDi.csproj create mode 100644 Reqnroll/BoDi/IContainedInstance.cs create mode 100644 Reqnroll/BoDi/IObjectContainer.cs create mode 100644 Reqnroll/BoDi/IStrategyRegistration.cs create mode 100644 Reqnroll/BoDi/ObjectContainer.cs create mode 100644 Reqnroll/BoDi/ObjectContainerException.cs diff --git a/Reqnroll.BoDi/BoDi.cs b/Reqnroll.BoDi/BoDi.cs deleted file mode 100644 index ba657a233..000000000 --- a/Reqnroll.BoDi/BoDi.cs +++ /dev/null @@ -1,920 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Reflection; -using System.Runtime.Serialization; -using System.Threading; - -namespace Reqnroll.BoDi -{ - [Serializable] - public class ObjectContainerException : Exception - { - public ObjectContainerException(string message, Type[] resolutionPath) : base(GetMessage(message, resolutionPath)) - { - } - - protected ObjectContainerException( - SerializationInfo info, - StreamingContext context) : base(info, context) - { - } - - static private string GetMessage(string message, Type[] resolutionPath) - { - if (resolutionPath == null || resolutionPath.Length == 0) - return message; - - return string.Format("{0} (resolution path: {1})", message, string.Join("->", resolutionPath.Select(t => t.FullName).ToArray())); - } - } - - public interface IObjectContainer : IDisposable - { - /// - /// Fired when a new object is created directly by the container. It is not invoked for resolving instance and factory registrations. - /// - event Action ObjectCreated; - - /// - /// Registers a type as the desired implementation type of an interface. - /// - /// Implementation type - /// Interface will be resolved - /// An object which allows to change resolving strategy. - /// A name to register named instance, otherwise null. - /// If there was already a resolve for the . - /// - /// Previous registrations can be overridden before the first resolution for the . - /// - IStrategyRegistration RegisterTypeAs(string name = null) where TType : class, TInterface; - - /// - /// Registers an instance - /// - /// Interface will be resolved - /// The instance implements the interface. - /// A name to register named instance, otherwise null. - /// Whether the instance should be disposed on container dispose, otherwise false. - /// If is null. - /// If there was already a resolve for the . - /// - /// Previous registrations can be overridden before the first resolution for the . - /// The instance will be registered in the object pool, so if a (for another interface) would require an instance of the dynamic type of the , the will be returned. - /// - void RegisterInstanceAs(TInterface instance, string name = null, bool dispose = false) where TInterface : class; - - /// - /// Registers an instance - /// - /// The instance implements the interface. - /// Interface will be resolved - /// A name to register named instance, otherwise null. - /// Whether the instance should be disposed on container dispose, otherwise false. - /// If is null. - /// If there was already a resolve for the . - /// - /// Previous registrations can be overridden before the first resolution for the . - /// The instance will be registered in the object pool, so if a (for another interface) would require an instance of the dynamic type of the , the will be returned. - /// - void RegisterInstanceAs(object instance, Type interfaceType, string name = null, bool dispose = false); - - /// - /// Registers an instance produced by . The delegate will be called only once and the instance it returned will be returned in each resolution. - /// - /// Interface to register as. - /// The function to run to obtain the instance. - /// A name to resolve named instance, otherwise null. - IStrategyRegistration RegisterFactoryAs(Func factoryDelegate, string name = null); - - /// - /// Resolves an implementation object for an interface or type. - /// - /// The interface or type. - /// An object implementing . - /// - /// The container pools the objects, so if the interface is resolved twice or the same type is registered for multiple interfaces, a single instance is created and returned. - /// - T Resolve(); - - /// - /// Resolves an implementation object for an interface or type. - /// - /// A name to resolve named instance, otherwise null. - /// The interface or type. - /// An object implementing . - /// - /// The container pools the objects, so if the interface is resolved twice or the same type is registered for multiple interfaces, a single instance is created and returned. - /// - T Resolve(string name); - - /// - /// Resolves an implementation object for an interface or type. - /// - /// The interface or type. - /// A name to resolve named instance, otherwise null. - /// An object implementing . - /// - /// The container pools the objects, so if the interface is resolved twice or the same type is registered for multiple interfaces, a single instance is created and returned. - /// - object Resolve(Type typeToResolve, string name = null); - - /// - /// Resolves all implementations of an interface or type. - /// - /// The interface or type. - /// An object implementing . - IEnumerable ResolveAll() where T : class; - - /// - /// Determines whether the interface or type is registered. - /// - /// The interface or type. - /// true if the interface or type is registered; otherwise false. - bool IsRegistered(); - - /// - /// Determines whether the interface or type is registered with the specified name. - /// - /// The interface or type. - /// The name. - /// true if the interface or type is registered; otherwise false. - bool IsRegistered(string name); - } - public interface IContainedInstance - { - IObjectContainer Container { get; } - } - public interface IStrategyRegistration - { - /// - /// Changes resolving strategy to a new instance per each dependency. - /// - /// - IStrategyRegistration InstancePerDependency(); - /// - /// Changes resolving strategy to a single instance per object container. This strategy is a default behaviour. - /// - /// - IStrategyRegistration InstancePerContext(); - } - - public class ObjectContainer : IObjectContainer - { - private const string REGISTERED_NAME_PARAMETER_NAME = "registeredName"; - - /// - /// A very simple immutable linked list of . - /// - private class ResolutionList - { - private readonly RegistrationKey _currentRegistrationKey; - private readonly Type _currentResolvedType; - private readonly ResolutionList _nextNode; - private bool IsLast => _nextNode == null; - - public ResolutionList() - { - Debug.Assert(IsLast); - } - - private ResolutionList(RegistrationKey currentRegistrationKey, Type currentResolvedType, ResolutionList nextNode) - { - if (nextNode == null) throw new ArgumentNullException("nextNode"); - - _currentRegistrationKey = currentRegistrationKey; - _currentResolvedType = currentResolvedType; - _nextNode = nextNode; - } - - public ResolutionList AddToEnd(RegistrationKey registrationKey, Type resolvedType) - { - return new ResolutionList(registrationKey, resolvedType, this); - } - - // ReSharper disable once UnusedMember.Local - public bool Contains(Type resolvedType) - { - if (resolvedType == null) throw new ArgumentNullException("resolvedType"); - return GetReverseEnumerable().Any(i => i.Value == resolvedType); - } - - public bool Contains(RegistrationKey registrationKey) - { - return GetReverseEnumerable().Any(i => i.Key.Equals(registrationKey)); - } - - private IEnumerable> GetReverseEnumerable() - { - var node = this; - while (!node.IsLast) - { - yield return new KeyValuePair(node._currentRegistrationKey, node._currentResolvedType); - node = node._nextNode; - } - } - - public Type[] ToTypeList() - { - return GetReverseEnumerable().Select(i => i.Value ?? i.Key.Type).Reverse().ToArray(); - } - - public override string ToString() - { - return string.Join(",", GetReverseEnumerable().Select(n => string.Format("{0}:{1}", n.Key, n.Value))); - } - } - - private struct RegistrationKey - { - public readonly Type Type; - public readonly string Name; - - public RegistrationKey(Type type, string name) - { - if (type == null) throw new ArgumentNullException("type"); - - Type = type; - Name = name; - } - - private Type TypeGroup - { - get - { - if (Type.IsGenericType && !Type.IsGenericTypeDefinition) - return Type.GetGenericTypeDefinition(); - return Type; - } - } - - public override string ToString() - { - Debug.Assert(Type.FullName != null); - if (Name == null) - return Type.FullName; - - return string.Format("{0}('{1}')", Type.FullName, Name); - } - - bool Equals(RegistrationKey other) - { - var isInvertable = other.TypeGroup == Type || other.Type == TypeGroup || other.Type == Type; - return isInvertable && String.Equals(other.Name, Name, StringComparison.CurrentCultureIgnoreCase); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (obj.GetType() != typeof(RegistrationKey)) return false; - return Equals((RegistrationKey)obj); - } - - public override int GetHashCode() - { - return TypeGroup.GetHashCode(); - } - } - - #region Registration types - - private enum SolvingStrategy - { - PerContext, - PerDependency - } - - private interface IRegistration - { - object Resolve(ObjectContainer container, RegistrationKey keyToResolve, ResolutionList resolutionPath); - } - - private class TypeRegistration : RegistrationWithStrategy, IRegistration - { - private readonly Type _implementationType; - private readonly object _syncRoot = new object(); - - public TypeRegistration(Type implementationType) - { - _implementationType = implementationType; - } - - protected override object ResolvePerContext(ObjectContainer container, RegistrationKey keyToResolve, ResolutionList resolutionPath) - { - var typeToConstruct = GetTypeToConstruct(keyToResolve); - - var pooledObjectKey = new RegistrationKey(typeToConstruct, keyToResolve.Name); - - var result = ExecuteWithLock(_syncRoot, () => container.GetPooledObject(pooledObjectKey), () => - { - if (typeToConstruct.IsInterface) - throw new ObjectContainerException("Interface cannot be resolved: " + keyToResolve, - resolutionPath.ToTypeList()); - - var obj = container.CreateObject(typeToConstruct, resolutionPath, keyToResolve); - container._objectPool.Add(pooledObjectKey, obj); - return obj; - }, resolutionPath, container.ConcurrentObjectResolutionTimeout); - - return result; - } - - - - protected override object ResolvePerDependency(ObjectContainer container, RegistrationKey keyToResolve, ResolutionList resolutionPath) - { - var typeToConstruct = GetTypeToConstruct(keyToResolve); - if (typeToConstruct.IsInterface) - throw new ObjectContainerException("Interface cannot be resolved: " + keyToResolve, resolutionPath.ToTypeList()); - return container.CreateObject(typeToConstruct, resolutionPath, keyToResolve); - } - - private Type GetTypeToConstruct(RegistrationKey keyToResolve) - { - var targetType = _implementationType; - if (targetType.IsGenericTypeDefinition) - { - var typeArgs = keyToResolve.Type.GetGenericArguments(); - targetType = targetType.MakeGenericType(typeArgs); - } - return targetType; - } - - public override string ToString() - { - return "Type: " + _implementationType.FullName; - } - } - - private class InstanceRegistration : IRegistration - { - private readonly object _instance; - - public InstanceRegistration(object instance) - { - _instance = instance; - } - - public object Resolve(ObjectContainer container, RegistrationKey keyToResolve, ResolutionList resolutionPath) - { - return _instance; - } - - public override string ToString() - { - string instanceText; - try - { - instanceText = _instance.ToString(); - } - catch (Exception ex) - { - instanceText = ex.Message; - } - - return "Instance: " + instanceText; - } - } - - private abstract class RegistrationWithStrategy : IStrategyRegistration - { - protected SolvingStrategy SolvingStrategy = SolvingStrategy.PerContext; - public virtual object Resolve(ObjectContainer container, RegistrationKey keyToResolve, ResolutionList resolutionPath) - { - if (SolvingStrategy == SolvingStrategy.PerDependency) - { - return ResolvePerDependency(container, keyToResolve, resolutionPath); - } - return ResolvePerContext(container, keyToResolve, resolutionPath); - } - - protected abstract object ResolvePerContext(ObjectContainer container, RegistrationKey keyToResolve, ResolutionList resolutionPath); - protected abstract object ResolvePerDependency(ObjectContainer container, RegistrationKey keyToResolve, ResolutionList resolutionPath); - - public IStrategyRegistration InstancePerDependency() - { - SolvingStrategy = SolvingStrategy.PerDependency; - return this; - } - - public IStrategyRegistration InstancePerContext() - { - SolvingStrategy = SolvingStrategy.PerContext; - return this; - } - - protected static object ExecuteWithLock(object lockObject, Func getter, Func factory, ResolutionList resolutionPath, TimeSpan timeout) - { - var obj = getter(); - - if (obj != null) - return obj; - - if (timeout == TimeSpan.Zero) - return factory(); - - if (Monitor.TryEnter(lockObject, timeout)) - { - try - { - obj = getter(); - - if (obj != null) - return obj; - - obj = factory(); - return obj; - } - finally - { - Monitor.Exit(lockObject); - } - } - - throw new ObjectContainerException("Concurrent object resolution timeout (potential circular dependency).", resolutionPath.ToTypeList()); - } - } - - private class FactoryRegistration : RegistrationWithStrategy, IRegistration - { - private readonly Delegate _factoryDelegate; - private readonly object _syncRoot = new object(); - public FactoryRegistration(Delegate factoryDelegate) - { - _factoryDelegate = factoryDelegate; - } - - protected override object ResolvePerContext(ObjectContainer container, RegistrationKey keyToResolve, ResolutionList resolutionPath) - { - var result = ExecuteWithLock(_syncRoot, () => container.GetPooledObject(keyToResolve), () => - { - var obj = container.InvokeFactoryDelegate(_factoryDelegate, resolutionPath, keyToResolve); - container._objectPool.Add(keyToResolve, obj); - return obj; - }, resolutionPath, container.ConcurrentObjectResolutionTimeout); - - return result; - } - protected override object ResolvePerDependency(ObjectContainer container, RegistrationKey keyToResolve, ResolutionList resolutionPath) - { - return container.InvokeFactoryDelegate(_factoryDelegate, resolutionPath, keyToResolve); - } - } - - private class NonDisposableWrapper - { - public object Object { get; private set; } - - public NonDisposableWrapper(object obj) - { - Object = obj; - } - } - - private class NamedInstanceDictionaryRegistration : IRegistration - { - public object Resolve(ObjectContainer container, RegistrationKey keyToResolve, ResolutionList resolutionPath) - { - var typeToResolve = keyToResolve.Type; - Debug.Assert(typeToResolve.IsGenericType && typeToResolve.GetGenericTypeDefinition() == typeof(IDictionary<,>)); - - var genericArguments = typeToResolve.GetGenericArguments(); - var keyType = genericArguments[0]; - var targetType = genericArguments[1]; - var result = (IDictionary)Activator.CreateInstance(typeof(Dictionary<,>).MakeGenericType(genericArguments)); - - foreach (var namedRegistration in container._registrations.Where(r => r.Key.Name != null && r.Key.Type == targetType).Select(r => r.Key).ToList()) - { - var convertedKey = ChangeType(namedRegistration.Name, keyType); - Debug.Assert(convertedKey != null); - result.Add(convertedKey, container.Resolve(namedRegistration.Type, namedRegistration.Name)); - } - - return result; - } - - private object ChangeType(string name, Type keyType) - { - if (keyType.IsEnum) - return Enum.Parse(keyType, name, true); - - Debug.Assert(keyType == typeof(string)); - return name; - } - } - - #endregion - - public static TimeSpan DefaultConcurrentObjectResolutionTimeout { get; set; } = TimeSpan.FromSeconds(1); - private bool _isDisposed = false; - private readonly ObjectContainer _baseContainer; - private readonly ConcurrentDictionary _registrations = new(); - private readonly List _resolvedKeys = new(); - private readonly Dictionary _objectPool = new(); - - public event Action ObjectCreated; - public IObjectContainer BaseContainer => _baseContainer; - - /// - /// Sets the timeout for thread-safe object resolution. By default, it uses the value of that is initialized to 1 second. Setting it to disables thread-safe resolution. - /// - public TimeSpan ConcurrentObjectResolutionTimeout { get; set; } = DefaultConcurrentObjectResolutionTimeout; - - public ObjectContainer(IObjectContainer baseContainer = null) - { - if (baseContainer != null && !(baseContainer is ObjectContainer)) - throw new ArgumentException("Base container must be an ObjectContainer", "baseContainer"); - - _baseContainer = (ObjectContainer)baseContainer; - RegisterInstanceAs(this); - } - - #region Registration - - public IStrategyRegistration RegisterTypeAs(Type implementationType, string name = null) where TInterface : class - { - Type interfaceType = typeof(TInterface); - return RegisterTypeAsInternal(implementationType, interfaceType, name); - } - - public IStrategyRegistration RegisterTypeAs(string name = null) where TType : class, TInterface - { - Type interfaceType = typeof(TInterface); - Type implementationType = typeof(TType); - return RegisterTypeAsInternal(implementationType, interfaceType, name); - } - - public IStrategyRegistration RegisterTypeAs(Type implementationType, Type interfaceType, string name = null) - { - if (!IsValidTypeMapping(implementationType, interfaceType)) - throw new InvalidOperationException("type mapping is not valid"); - return RegisterTypeAsInternal(implementationType, interfaceType, name); - } - - private bool IsValidTypeMapping(Type implementationType, Type interfaceType) - { - if (interfaceType.IsAssignableFrom(implementationType)) - return true; - - if (interfaceType.IsGenericTypeDefinition && implementationType.IsGenericTypeDefinition) - { - var baseTypes = GetBaseTypes(implementationType).ToArray(); - return baseTypes.Any(t => t.IsGenericType && t.GetGenericTypeDefinition() == interfaceType); - } - - return false; - } - - private static IEnumerable GetBaseTypes(Type type) - { - if (type.BaseType == null) return type.GetInterfaces(); - - return Enumerable.Repeat(type.BaseType, 1) - .Concat(type.GetInterfaces()) - .Concat(type.GetInterfaces().SelectMany(GetBaseTypes)) - .Concat(GetBaseTypes(type.BaseType)); - } - - - private RegistrationKey CreateNamedInstanceDictionaryKey(Type targetType) - { - return new RegistrationKey(typeof(IDictionary<,>).MakeGenericType(typeof(string), targetType), null); - } - - private void AddRegistration(RegistrationKey key, IRegistration registration) - { - _registrations[key] = registration; - - AddNamedDictionaryRegistration(key); - } - - private IRegistration EnsureImplicitRegistration(RegistrationKey key) - { - var registration = _registrations.GetOrAdd(key, (registrationKey => new TypeRegistration(registrationKey.Type))); - - AddNamedDictionaryRegistration(key); - - return registration; - } - - private void AddNamedDictionaryRegistration(RegistrationKey key) - { - if (key.Name != null) - { - var dictKey = CreateNamedInstanceDictionaryKey(key.Type); - _registrations.TryAdd(dictKey, new NamedInstanceDictionaryRegistration()); - } - } - - private IStrategyRegistration RegisterTypeAsInternal(Type implementationType, Type interfaceType, string name) - { - var registrationKey = new RegistrationKey(interfaceType, name); - AssertNotResolved(registrationKey); - - ClearRegistrations(registrationKey); - var typeRegistration = new TypeRegistration(implementationType); - AddRegistration(registrationKey, typeRegistration); - - return typeRegistration; - } - - public void RegisterInstanceAs(object instance, Type interfaceType, string name = null, bool dispose = false) - { - if (instance == null) - throw new ArgumentNullException("instance"); - var registrationKey = new RegistrationKey(interfaceType, name); - AssertNotResolved(registrationKey); - - ClearRegistrations(registrationKey); - AddRegistration(registrationKey, new InstanceRegistration(instance)); - _objectPool[new RegistrationKey(instance.GetType(), name)] = GetPoolableInstance(instance, dispose); - } - - private static object GetPoolableInstance(object instance, bool dispose) - { - return (instance is IDisposable) && !dispose ? new NonDisposableWrapper(instance) : instance; - } - - public void RegisterInstanceAs(TInterface instance, string name = null, bool dispose = false) where TInterface : class - { - RegisterInstanceAs(instance, typeof(TInterface), name, dispose); - } - - public IStrategyRegistration RegisterFactoryAs(Func factoryDelegate, string name = null) - { - return RegisterFactoryAs(factoryDelegate, typeof(TInterface), name); - } - - public IStrategyRegistration RegisterFactoryAs(Func factoryDelegate, string name = null) - { - return RegisterFactoryAs(factoryDelegate, typeof(TInterface), name); - } - - public void RegisterFactoryAs(Delegate factoryDelegate, string name = null) - { - RegisterFactoryAs(factoryDelegate, typeof(TInterface), name); - } - - public IStrategyRegistration RegisterFactoryAs(Delegate factoryDelegate, Type interfaceType, string name = null) - { - if (factoryDelegate == null) throw new ArgumentNullException("factoryDelegate"); - if (interfaceType == null) throw new ArgumentNullException("interfaceType"); - - var registrationKey = new RegistrationKey(interfaceType, name); - AssertNotResolved(registrationKey); - - ClearRegistrations(registrationKey); - var factoryRegistration = new FactoryRegistration(factoryDelegate); - AddRegistration(registrationKey, factoryRegistration); - - return factoryRegistration; - } - - public bool IsRegistered() - { - return IsRegistered(null); - } - - public bool IsRegistered(string name) - { - Type typeToResolve = typeof(T); - - var keyToResolve = new RegistrationKey(typeToResolve, name); - - return _registrations.ContainsKey(keyToResolve); - } - - // ReSharper disable once UnusedParameter.Local - private void AssertNotResolved(RegistrationKey interfaceType) - { - if (_resolvedKeys.Contains(interfaceType)) - throw new ObjectContainerException("An object has been resolved for this interface already.", null); - } - - private void ClearRegistrations(RegistrationKey registrationKey) - { - _registrations.TryRemove(registrationKey, out _); - } - - - #endregion - - #region Resolve - - public T Resolve() - { - return Resolve(null); - } - - public T Resolve(string name) - { - Type typeToResolve = typeof(T); - - object resolvedObject = Resolve(typeToResolve, name); - - return (T)resolvedObject; - } - - public object Resolve(Type typeToResolve, string name = null) - { - return Resolve(typeToResolve, new ResolutionList(), name); - } - - public IEnumerable ResolveAll() where T : class - { - return _registrations - .Where(x => x.Key.Type == typeof(T)) - .Select(x => Resolve(x.Key.Type, x.Key.Name) as T); - } - - private object Resolve(Type typeToResolve, ResolutionList resolutionPath, string name) - { - AssertNotDisposed(); - - var keyToResolve = new RegistrationKey(typeToResolve, name); - object resolvedObject = ResolveObject(keyToResolve, resolutionPath); - if (!_resolvedKeys.Contains(keyToResolve)) - { - _resolvedKeys.Add(keyToResolve); - } - Debug.Assert(typeToResolve.IsInstanceOfType(resolvedObject)); - return resolvedObject; - } - - private KeyValuePair? GetRegistrationResult(RegistrationKey keyToResolve) - { - IRegistration registration; - if (_registrations.TryGetValue(keyToResolve, out registration)) - { - return new KeyValuePair(this, registration); - } - - if (_baseContainer != null) - return _baseContainer.GetRegistrationResult(keyToResolve); - - if (IsSpecialNamedInstanceDictionaryKey(keyToResolve)) - { - var targetType = keyToResolve.Type.GetGenericArguments()[1]; - return GetRegistrationResult(CreateNamedInstanceDictionaryKey(targetType)); - } - - // if there was no named registration, we still return an empty dictionary - if (IsDefaultNamedInstanceDictionaryKey(keyToResolve)) - { - return new KeyValuePair(this, new NamedInstanceDictionaryRegistration()); - } - - return null; - } - - private bool IsDefaultNamedInstanceDictionaryKey(RegistrationKey keyToResolve) - { - return IsNamedInstanceDictionaryKey(keyToResolve) && - keyToResolve.Type.GetGenericArguments()[0] == typeof(string); - } - - private bool IsSpecialNamedInstanceDictionaryKey(RegistrationKey keyToResolve) - { - return IsNamedInstanceDictionaryKey(keyToResolve) && - keyToResolve.Type.GetGenericArguments()[0].IsEnum; - } - - private bool IsNamedInstanceDictionaryKey(RegistrationKey keyToResolve) - { - return keyToResolve.Name == null && keyToResolve.Type.IsGenericType && keyToResolve.Type.GetGenericTypeDefinition() == typeof(IDictionary<,>); - } - - private object GetPooledObject(RegistrationKey pooledObjectKey) - { - object obj; - if (GetObjectFromPool(pooledObjectKey, out obj)) - return obj; - - return null; - } - - private bool GetObjectFromPool(RegistrationKey pooledObjectKey, out object obj) - { - if (!_objectPool.TryGetValue(pooledObjectKey, out obj)) - return false; - - var nonDisposableWrapper = obj as NonDisposableWrapper; - if (nonDisposableWrapper != null) - obj = nonDisposableWrapper.Object; - - return true; - } - - private object ResolveObject(RegistrationKey keyToResolve, ResolutionList resolutionPath) - { - if (keyToResolve.Type.IsPrimitive || keyToResolve.Type == typeof(string) || keyToResolve.Type.IsValueType) - throw new ObjectContainerException("Primitive types or structs cannot be resolved: " + keyToResolve.Type.FullName, resolutionPath.ToTypeList()); - - var registrationResult = GetRegistrationResult(keyToResolve); - - var registrationToUse = registrationResult ?? - new KeyValuePair(this, EnsureImplicitRegistration(keyToResolve)); - - var resolutionPathForResolve = registrationToUse.Key == this ? - resolutionPath : new ResolutionList(); - var result = registrationToUse.Value.Resolve(registrationToUse.Key, keyToResolve, resolutionPathForResolve); - - return result; - } - - - private object CreateObject(Type type, ResolutionList resolutionPath, RegistrationKey keyToResolve) - { - var ctors = type.GetConstructors(); - if (ctors.Length == 0) - ctors = type.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance); - - Debug.Assert(ctors.Length > 0, "Class must have a constructor!"); - - int maxParamCount = ctors.Max(ctor => ctor.GetParameters().Length); - var maxParamCountCtors = ctors.Where(ctor => ctor.GetParameters().Length == maxParamCount).ToArray(); - - object obj; - if (maxParamCountCtors.Length == 1) - { - ConstructorInfo ctor = maxParamCountCtors[0]; - if (resolutionPath.Contains(keyToResolve)) - throw new ObjectContainerException("Circular dependency found! " + type.FullName, resolutionPath.ToTypeList()); - - var args = ResolveArguments(ctor.GetParameters(), keyToResolve, resolutionPath.AddToEnd(keyToResolve, type)); - obj = ctor.Invoke(args); - } - else - { - throw new ObjectContainerException("Multiple public constructors with same maximum parameter count are not supported! " + type.FullName, resolutionPath.ToTypeList()); - } - - OnObjectCreated(obj); - - return obj; - } - - protected virtual void OnObjectCreated(object obj) - { - var eventHandler = ObjectCreated; - if (eventHandler != null) - eventHandler(obj); - } - - private object InvokeFactoryDelegate(Delegate factoryDelegate, ResolutionList resolutionPath, RegistrationKey keyToResolve) - { - if (resolutionPath.Contains(keyToResolve)) - throw new ObjectContainerException("Circular dependency found! " + factoryDelegate, resolutionPath.ToTypeList()); - - var args = ResolveArguments(factoryDelegate.Method.GetParameters(), keyToResolve, resolutionPath.AddToEnd(keyToResolve, null)); - return factoryDelegate.DynamicInvoke(args); - } - - private object[] ResolveArguments(IEnumerable parameters, RegistrationKey keyToResolve, ResolutionList resolutionPath) - { - return parameters.Select(p => IsRegisteredNameParameter(p) ? ResolveRegisteredName(keyToResolve) : Resolve(p.ParameterType, resolutionPath, null)).ToArray(); - } - - private object ResolveRegisteredName(RegistrationKey keyToResolve) - { - return keyToResolve.Name; - } - - private bool IsRegisteredNameParameter(ParameterInfo parameterInfo) - { - return parameterInfo.ParameterType == typeof(string) && - parameterInfo.Name.Equals(REGISTERED_NAME_PARAMETER_NAME); - } - - #endregion - - public override string ToString() - { - return string.Join(Environment.NewLine, - _registrations - .Where(r => !(r.Value is NamedInstanceDictionaryRegistration)) - .Select(r => string.Format("{0} -> {1}", r.Key, (r.Key.Type == typeof(IObjectContainer) && r.Key.Name == null) ? "" : r.Value.ToString()))); - } - - private void AssertNotDisposed() - { - if (_isDisposed) - throw new ObjectContainerException("Object container disposed", null); - } - - public void Dispose() - { - _isDisposed = true; - - foreach (var obj in _objectPool.Values.OfType().Where(o => !ReferenceEquals(o, this))) - obj.Dispose(); - - _objectPool.Clear(); - _registrations.Clear(); - _resolvedKeys.Clear(); - } - } -} diff --git a/Reqnroll.BoDi/Reqnroll.BoDi.csproj b/Reqnroll.BoDi/Reqnroll.BoDi.csproj deleted file mode 100644 index d0c0a5657..000000000 --- a/Reqnroll.BoDi/Reqnroll.BoDi.csproj +++ /dev/null @@ -1,22 +0,0 @@ - - - netstandard2.0 - Reqnroll.BoDi - Reqnroll.BoDi - $(Reqnroll_KeyFile) - $(Reqnroll_EnableStrongNameSigning) - $(Reqnroll_PublicSign) - - true - $(NoWarn);1591 - true - - true - true - $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb - - - - - - diff --git a/Reqnroll.sln b/Reqnroll.sln index 8cccaac52..36de45414 100644 --- a/Reqnroll.sln +++ b/Reqnroll.sln @@ -129,9 +129,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GitHubWorkflows", "GitHubWo .github\workflows\lock.yml = .github\workflows\lock.yml EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Reqnroll.BoDi", "Reqnroll.BoDi\Reqnroll.BoDi.csproj", "{8E1AB4D0-98E8-46C5-97EC-7EF47B5DFD27}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Reqnroll.SystemTests", "Tests\Reqnroll.SystemTests\Reqnroll.SystemTests.csproj", "{C658B37D-FD36-4868-9070-4EB452FAE526}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Reqnroll.SystemTests", "Tests\Reqnroll.SystemTests\Reqnroll.SystemTests.csproj", "{C658B37D-FD36-4868-9070-4EB452FAE526}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -515,18 +513,6 @@ Global {C073A609-8A6A-4707-86B0-7BB32EF8ACEE}.Release|Any CPU.Build.0 = Release|Any CPU {C073A609-8A6A-4707-86B0-7BB32EF8ACEE}.VS2010IntegrationTest|Any CPU.ActiveCfg = Debug|Any CPU {C073A609-8A6A-4707-86B0-7BB32EF8ACEE}.VS2010IntegrationTest|Any CPU.Build.0 = Debug|Any CPU - {8E1AB4D0-98E8-46C5-97EC-7EF47B5DFD27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8E1AB4D0-98E8-46C5-97EC-7EF47B5DFD27}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8E1AB4D0-98E8-46C5-97EC-7EF47B5DFD27}.Debug-MSTest|Any CPU.ActiveCfg = Debug|Any CPU - {8E1AB4D0-98E8-46C5-97EC-7EF47B5DFD27}.Debug-MSTest|Any CPU.Build.0 = Debug|Any CPU - {8E1AB4D0-98E8-46C5-97EC-7EF47B5DFD27}.Debug-NUnit|Any CPU.ActiveCfg = Debug|Any CPU - {8E1AB4D0-98E8-46C5-97EC-7EF47B5DFD27}.Debug-NUnit|Any CPU.Build.0 = Debug|Any CPU - {8E1AB4D0-98E8-46C5-97EC-7EF47B5DFD27}.Debug-XUnit|Any CPU.ActiveCfg = Debug|Any CPU - {8E1AB4D0-98E8-46C5-97EC-7EF47B5DFD27}.Debug-XUnit|Any CPU.Build.0 = Debug|Any CPU - {8E1AB4D0-98E8-46C5-97EC-7EF47B5DFD27}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8E1AB4D0-98E8-46C5-97EC-7EF47B5DFD27}.Release|Any CPU.Build.0 = Release|Any CPU - {8E1AB4D0-98E8-46C5-97EC-7EF47B5DFD27}.VS2010IntegrationTest|Any CPU.ActiveCfg = Debug|Any CPU - {8E1AB4D0-98E8-46C5-97EC-7EF47B5DFD27}.VS2010IntegrationTest|Any CPU.Build.0 = Debug|Any CPU {C658B37D-FD36-4868-9070-4EB452FAE526}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C658B37D-FD36-4868-9070-4EB452FAE526}.Debug|Any CPU.Build.0 = Debug|Any CPU {C658B37D-FD36-4868-9070-4EB452FAE526}.Debug-MSTest|Any CPU.ActiveCfg = Debug|Any CPU diff --git a/Reqnroll/BoDi/IContainedInstance.cs b/Reqnroll/BoDi/IContainedInstance.cs new file mode 100644 index 000000000..87f0e9667 --- /dev/null +++ b/Reqnroll/BoDi/IContainedInstance.cs @@ -0,0 +1,6 @@ +namespace Reqnroll.BoDi; + +public interface IContainedInstance +{ + IObjectContainer Container { get; } +} diff --git a/Reqnroll/BoDi/IObjectContainer.cs b/Reqnroll/BoDi/IObjectContainer.cs new file mode 100644 index 000000000..29e61c1c8 --- /dev/null +++ b/Reqnroll/BoDi/IObjectContainer.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; + +namespace Reqnroll.BoDi; + +public interface IObjectContainer : IDisposable +{ + /// + /// Fired when a new object is created directly by the container. It is not invoked for resolving instance and factory registrations. + /// + event Action ObjectCreated; + + /// + /// Registers a type as the desired implementation type of an interface. + /// + /// Implementation type + /// Interface will be resolved + /// An object which allows to change resolving strategy. + /// A name to register named instance, otherwise null. + /// If there was already a resolve for the . + /// + /// Previous registrations can be overridden before the first resolution for the . + /// + IStrategyRegistration RegisterTypeAs(string name = null) where TType : class, TInterface; + + /// + /// Registers an instance + /// + /// Interface will be resolved + /// The instance implements the interface. + /// A name to register named instance, otherwise null. + /// Whether the instance should be disposed on container dispose, otherwise false. + /// If is null. + /// If there was already a resolve for the . + /// + /// Previous registrations can be overridden before the first resolution for the . + /// The instance will be registered in the object pool, so if a (for another interface) would require an instance of the dynamic type of the , the will be returned. + /// + void RegisterInstanceAs(TInterface instance, string name = null, bool dispose = false) where TInterface : class; + + /// + /// Registers an instance + /// + /// The instance implements the interface. + /// Interface will be resolved + /// A name to register named instance, otherwise null. + /// Whether the instance should be disposed on container dispose, otherwise false. + /// If is null. + /// If there was already a resolve for the . + /// + /// Previous registrations can be overridden before the first resolution for the . + /// The instance will be registered in the object pool, so if a (for another interface) would require an instance of the dynamic type of the , the will be returned. + /// + void RegisterInstanceAs(object instance, Type interfaceType, string name = null, bool dispose = false); + + /// + /// Registers an instance produced by . The delegate will be called only once and the instance it returned will be returned in each resolution. + /// + /// Interface to register as. + /// The function to run to obtain the instance. + /// A name to resolve named instance, otherwise null. + IStrategyRegistration RegisterFactoryAs(Func factoryDelegate, string name = null); + + /// + /// Resolves an implementation object for an interface or type. + /// + /// The interface or type. + /// An object implementing . + /// + /// The container pools the objects, so if the interface is resolved twice or the same type is registered for multiple interfaces, a single instance is created and returned. + /// + T Resolve(); + + /// + /// Resolves an implementation object for an interface or type. + /// + /// A name to resolve named instance, otherwise null. + /// The interface or type. + /// An object implementing . + /// + /// The container pools the objects, so if the interface is resolved twice or the same type is registered for multiple interfaces, a single instance is created and returned. + /// + T Resolve(string name); + + /// + /// Resolves an implementation object for an interface or type. + /// + /// The interface or type. + /// A name to resolve named instance, otherwise null. + /// An object implementing . + /// + /// The container pools the objects, so if the interface is resolved twice or the same type is registered for multiple interfaces, a single instance is created and returned. + /// + object Resolve(Type typeToResolve, string name = null); + + /// + /// Resolves all implementations of an interface or type. + /// + /// The interface or type. + /// An object implementing . + IEnumerable ResolveAll() where T : class; + + /// + /// Determines whether the interface or type is registered. + /// + /// The interface or type. + /// true if the interface or type is registered; otherwise false. + bool IsRegistered(); + + /// + /// Determines whether the interface or type is registered with the specified name. + /// + /// The interface or type. + /// The name. + /// true if the interface or type is registered; otherwise false. + bool IsRegistered(string name); +} diff --git a/Reqnroll/BoDi/IStrategyRegistration.cs b/Reqnroll/BoDi/IStrategyRegistration.cs new file mode 100644 index 000000000..b69fb20ee --- /dev/null +++ b/Reqnroll/BoDi/IStrategyRegistration.cs @@ -0,0 +1,15 @@ +namespace Reqnroll.BoDi; + +public interface IStrategyRegistration +{ + /// + /// Changes resolving strategy to a new instance per each dependency. + /// + /// + IStrategyRegistration InstancePerDependency(); + /// + /// Changes resolving strategy to a single instance per object container. This strategy is a default behaviour. + /// + /// + IStrategyRegistration InstancePerContext(); +} diff --git a/Reqnroll/BoDi/ObjectContainer.cs b/Reqnroll/BoDi/ObjectContainer.cs new file mode 100644 index 000000000..4e7ab8603 --- /dev/null +++ b/Reqnroll/BoDi/ObjectContainer.cs @@ -0,0 +1,730 @@ +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Threading; + +namespace Reqnroll.BoDi; + +public class ObjectContainer : IObjectContainer +{ + private const string REGISTERED_NAME_PARAMETER_NAME = "registeredName"; + + /// + /// A very simple immutable linked list of . + /// + private class ResolutionList + { + private readonly RegistrationKey _currentRegistrationKey; + private readonly Type _currentResolvedType; + private readonly ResolutionList _nextNode; + private bool IsLast => _nextNode == null; + + public ResolutionList() + { + Debug.Assert(IsLast); + } + + private ResolutionList(RegistrationKey currentRegistrationKey, Type currentResolvedType, ResolutionList nextNode) + { + _currentRegistrationKey = currentRegistrationKey; + _currentResolvedType = currentResolvedType; + _nextNode = nextNode ?? throw new ArgumentNullException(nameof(nextNode)); + } + + public ResolutionList AddToEnd(RegistrationKey registrationKey, Type resolvedType) + { + return new ResolutionList(registrationKey, resolvedType, this); + } + + // ReSharper disable once UnusedMember.Local + public bool Contains(Type resolvedType) + { + if (resolvedType == null) throw new ArgumentNullException(nameof(resolvedType)); + return GetReverseEnumerable().Any(i => i.Value == resolvedType); + } + + public bool Contains(RegistrationKey registrationKey) + { + return GetReverseEnumerable().Any(i => i.Key.Equals(registrationKey)); + } + + private IEnumerable> GetReverseEnumerable() + { + var node = this; + while (!node.IsLast) + { + yield return new KeyValuePair(node._currentRegistrationKey, node._currentResolvedType); + node = node._nextNode; + } + } + + public Type[] ToTypeList() + { + return GetReverseEnumerable().Select(i => i.Value ?? i.Key.Type).Reverse().ToArray(); + } + + public override string ToString() + { + return string.Join(",", GetReverseEnumerable().Select(n => $"{n.Key}:{n.Value}")); + } + } + + private readonly struct RegistrationKey(Type type, string name) + { + public readonly Type Type = type ?? throw new ArgumentNullException(nameof(type)); + public readonly string Name = name; + + private Type TypeGroup + { + get + { + if (Type.IsGenericType && !Type.IsGenericTypeDefinition) + return Type.GetGenericTypeDefinition(); + return Type; + } + } + + public override string ToString() + { + Debug.Assert(Type.FullName != null); + if (Name == null) + return Type.FullName; + + return $"{Type.FullName}('{Name}')"; + } + + bool Equals(RegistrationKey other) + { + var isInvertible = other.TypeGroup == Type || other.Type == TypeGroup || other.Type == Type; + return isInvertible && String.Equals(other.Name, Name, StringComparison.CurrentCultureIgnoreCase); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (obj.GetType() != typeof(RegistrationKey)) return false; + return Equals((RegistrationKey)obj); + } + + public override int GetHashCode() + { + return TypeGroup.GetHashCode(); + } + } + + #region Registration types + + private enum SolvingStrategy + { + PerContext, + PerDependency + } + + private interface IRegistration + { + object Resolve(ObjectContainer container, RegistrationKey keyToResolve, ResolutionList resolutionPath); + } + + private class TypeRegistration(Type _implementationType) : RegistrationWithStrategy, IRegistration + { + private readonly object _syncRoot = new(); + + protected override object ResolvePerContext(ObjectContainer container, RegistrationKey keyToResolve, ResolutionList resolutionPath) + { + var typeToConstruct = GetTypeToConstruct(keyToResolve); + + var pooledObjectKey = new RegistrationKey(typeToConstruct, keyToResolve.Name); + + var result = ExecuteWithLock(_syncRoot, () => container.GetPooledObject(pooledObjectKey), () => + { + if (typeToConstruct.IsInterface) + throw new ObjectContainerException("Interface cannot be resolved: " + keyToResolve, + resolutionPath.ToTypeList()); + + var obj = container.CreateObject(typeToConstruct, resolutionPath, keyToResolve); + container._objectPool.Add(pooledObjectKey, obj); + return obj; + }, resolutionPath, container.ConcurrentObjectResolutionTimeout); + + return result; + } + + + + protected override object ResolvePerDependency(ObjectContainer container, RegistrationKey keyToResolve, ResolutionList resolutionPath) + { + var typeToConstruct = GetTypeToConstruct(keyToResolve); + if (typeToConstruct.IsInterface) + throw new ObjectContainerException("Interface cannot be resolved: " + keyToResolve, resolutionPath.ToTypeList()); + return container.CreateObject(typeToConstruct, resolutionPath, keyToResolve); + } + + private Type GetTypeToConstruct(RegistrationKey keyToResolve) + { + var targetType = _implementationType; + if (targetType.IsGenericTypeDefinition) + { + var typeArgs = keyToResolve.Type.GetGenericArguments(); + targetType = targetType.MakeGenericType(typeArgs); + } + return targetType; + } + + public override string ToString() + { + return "Type: " + _implementationType.FullName; + } + } + + private class InstanceRegistration(object _instance) : IRegistration + { + public object Resolve(ObjectContainer container, RegistrationKey keyToResolve, ResolutionList resolutionPath) + { + return _instance; + } + + public override string ToString() + { + string instanceText; + try + { + instanceText = _instance.ToString(); + } + catch (Exception ex) + { + instanceText = ex.Message; + } + + return "Instance: " + instanceText; + } + } + + private abstract class RegistrationWithStrategy : IStrategyRegistration + { + protected SolvingStrategy SolvingStrategy = SolvingStrategy.PerContext; + public virtual object Resolve(ObjectContainer container, RegistrationKey keyToResolve, ResolutionList resolutionPath) + { + if (SolvingStrategy == SolvingStrategy.PerDependency) + { + return ResolvePerDependency(container, keyToResolve, resolutionPath); + } + return ResolvePerContext(container, keyToResolve, resolutionPath); + } + + protected abstract object ResolvePerContext(ObjectContainer container, RegistrationKey keyToResolve, ResolutionList resolutionPath); + protected abstract object ResolvePerDependency(ObjectContainer container, RegistrationKey keyToResolve, ResolutionList resolutionPath); + + public IStrategyRegistration InstancePerDependency() + { + SolvingStrategy = SolvingStrategy.PerDependency; + return this; + } + + public IStrategyRegistration InstancePerContext() + { + SolvingStrategy = SolvingStrategy.PerContext; + return this; + } + + protected static object ExecuteWithLock(object lockObject, Func getter, Func factory, ResolutionList resolutionPath, TimeSpan timeout) + { + var obj = getter(); + + if (obj != null) + return obj; + + if (timeout == TimeSpan.Zero) + return factory(); + + if (Monitor.TryEnter(lockObject, timeout)) + { + try + { + obj = getter(); + + if (obj != null) + return obj; + + obj = factory(); + return obj; + } + finally + { + Monitor.Exit(lockObject); + } + } + + throw new ObjectContainerException("Concurrent object resolution timeout (potential circular dependency).", resolutionPath.ToTypeList()); + } + } + + private class FactoryRegistration(Delegate _factoryDelegate) : RegistrationWithStrategy, IRegistration + { + private readonly object _syncRoot = new(); + + protected override object ResolvePerContext(ObjectContainer container, RegistrationKey keyToResolve, ResolutionList resolutionPath) + { + var result = ExecuteWithLock(_syncRoot, () => container.GetPooledObject(keyToResolve), () => + { + var obj = container.InvokeFactoryDelegate(_factoryDelegate, resolutionPath, keyToResolve); + container._objectPool.Add(keyToResolve, obj); + return obj; + }, resolutionPath, container.ConcurrentObjectResolutionTimeout); + + return result; + } + protected override object ResolvePerDependency(ObjectContainer container, RegistrationKey keyToResolve, ResolutionList resolutionPath) + { + return container.InvokeFactoryDelegate(_factoryDelegate, resolutionPath, keyToResolve); + } + } + + private class NonDisposableWrapper(object obj) + { + public object Object { get; } = obj; + } + + private class NamedInstanceDictionaryRegistration : IRegistration + { + public object Resolve(ObjectContainer container, RegistrationKey keyToResolve, ResolutionList resolutionPath) + { + var typeToResolve = keyToResolve.Type; + Debug.Assert(typeToResolve.IsGenericType && typeToResolve.GetGenericTypeDefinition() == typeof(IDictionary<,>)); + + var genericArguments = typeToResolve.GetGenericArguments(); + var keyType = genericArguments[0]; + var targetType = genericArguments[1]; + var result = (IDictionary)Activator.CreateInstance(typeof(Dictionary<,>).MakeGenericType(genericArguments))!; + + foreach (var namedRegistration in container._registrations.Where(r => r.Key.Name != null && r.Key.Type == targetType).Select(r => r.Key).ToList()) + { + var convertedKey = ChangeType(namedRegistration.Name, keyType); + Debug.Assert(convertedKey != null); + result.Add(convertedKey, container.Resolve(namedRegistration.Type, namedRegistration.Name)); + } + + return result; + } + + private object ChangeType(string name, Type keyType) + { + if (keyType.IsEnum) + return Enum.Parse(keyType, name, true); + + Debug.Assert(keyType == typeof(string)); + return name; + } + } + + #endregion + + public static TimeSpan DefaultConcurrentObjectResolutionTimeout { get; set; } = TimeSpan.FromSeconds(1); + private bool _isDisposed = false; + private readonly ObjectContainer _baseContainer; + private readonly ConcurrentDictionary _registrations = new(); + private readonly List _resolvedKeys = new(); + private readonly Dictionary _objectPool = new(); + + public event Action ObjectCreated; + public IObjectContainer BaseContainer => _baseContainer; + + /// + /// Sets the timeout for thread-safe object resolution. By default, it uses the value of that is initialized to 1 second. Setting it to disables thread-safe resolution. + /// + public TimeSpan ConcurrentObjectResolutionTimeout { get; set; } = DefaultConcurrentObjectResolutionTimeout; + + public ObjectContainer(IObjectContainer baseContainer = null) + { + if (baseContainer != null && !(baseContainer is ObjectContainer)) + throw new ArgumentException("Base container must be an ObjectContainer", nameof(baseContainer)); + + _baseContainer = (ObjectContainer)baseContainer; + RegisterInstanceAs(this); + } + + #region Registration + + public IStrategyRegistration RegisterTypeAs(Type implementationType, string name = null) where TInterface : class + { + Type interfaceType = typeof(TInterface); + return RegisterTypeAsInternal(implementationType, interfaceType, name); + } + + public IStrategyRegistration RegisterTypeAs(string name = null) where TType : class, TInterface + { + Type interfaceType = typeof(TInterface); + Type implementationType = typeof(TType); + return RegisterTypeAsInternal(implementationType, interfaceType, name); + } + + public IStrategyRegistration RegisterTypeAs(Type implementationType, Type interfaceType, string name = null) + { + if (!IsValidTypeMapping(implementationType, interfaceType)) + throw new InvalidOperationException("type mapping is not valid"); + return RegisterTypeAsInternal(implementationType, interfaceType, name); + } + + private bool IsValidTypeMapping(Type implementationType, Type interfaceType) + { + if (interfaceType.IsAssignableFrom(implementationType)) + return true; + + if (interfaceType.IsGenericTypeDefinition && implementationType!.IsGenericTypeDefinition) + { + var baseTypes = GetBaseTypes(implementationType).ToArray(); + return baseTypes.Any(t => t.IsGenericType && t.GetGenericTypeDefinition() == interfaceType); + } + + return false; + } + + private static IEnumerable GetBaseTypes(Type type) + { + if (type.BaseType == null) return type.GetInterfaces(); + + return Enumerable.Repeat(type.BaseType, 1) + .Concat(type.GetInterfaces()) + .Concat(type.GetInterfaces().SelectMany(GetBaseTypes)) + .Concat(GetBaseTypes(type.BaseType)); + } + + + private RegistrationKey CreateNamedInstanceDictionaryKey(Type targetType) + { + return new RegistrationKey(typeof(IDictionary<,>).MakeGenericType(typeof(string), targetType), null); + } + + private void AddRegistration(RegistrationKey key, IRegistration registration) + { + _registrations[key] = registration; + + AddNamedDictionaryRegistration(key); + } + + private IRegistration EnsureImplicitRegistration(RegistrationKey key) + { + var registration = _registrations.GetOrAdd(key, (registrationKey => new TypeRegistration(registrationKey.Type))); + + AddNamedDictionaryRegistration(key); + + return registration; + } + + private void AddNamedDictionaryRegistration(RegistrationKey key) + { + if (key.Name != null) + { + var dictKey = CreateNamedInstanceDictionaryKey(key.Type); + _registrations.TryAdd(dictKey, new NamedInstanceDictionaryRegistration()); + } + } + + private IStrategyRegistration RegisterTypeAsInternal(Type implementationType, Type interfaceType, string name) + { + var registrationKey = new RegistrationKey(interfaceType, name); + AssertNotResolved(registrationKey); + + ClearRegistrations(registrationKey); + var typeRegistration = new TypeRegistration(implementationType); + AddRegistration(registrationKey, typeRegistration); + + return typeRegistration; + } + + public void RegisterInstanceAs(object instance, Type interfaceType, string name = null, bool dispose = false) + { + if (instance == null) + throw new ArgumentNullException(nameof(instance)); + var registrationKey = new RegistrationKey(interfaceType, name); + AssertNotResolved(registrationKey); + + ClearRegistrations(registrationKey); + AddRegistration(registrationKey, new InstanceRegistration(instance)); + _objectPool[new RegistrationKey(instance.GetType(), name)] = GetPoolableInstance(instance, dispose); + } + + private static object GetPoolableInstance(object instance, bool dispose) + { + return (instance is IDisposable) && !dispose ? new NonDisposableWrapper(instance) : instance; + } + + public void RegisterInstanceAs(TInterface instance, string name = null, bool dispose = false) where TInterface : class + { + RegisterInstanceAs(instance, typeof(TInterface), name, dispose); + } + + public IStrategyRegistration RegisterFactoryAs(Func factoryDelegate, string name = null) + { + return RegisterFactoryAs(factoryDelegate, typeof(TInterface), name); + } + + public IStrategyRegistration RegisterFactoryAs(Func factoryDelegate, string name = null) + { + return RegisterFactoryAs(factoryDelegate, typeof(TInterface), name); + } + + public void RegisterFactoryAs(Delegate factoryDelegate, string name = null) + { + RegisterFactoryAs(factoryDelegate, typeof(TInterface), name); + } + + public IStrategyRegistration RegisterFactoryAs(Delegate factoryDelegate, Type interfaceType, string name = null) + { + if (factoryDelegate == null) throw new ArgumentNullException(nameof(factoryDelegate)); + if (interfaceType == null) throw new ArgumentNullException(nameof(interfaceType)); + + var registrationKey = new RegistrationKey(interfaceType, name); + AssertNotResolved(registrationKey); + + ClearRegistrations(registrationKey); + var factoryRegistration = new FactoryRegistration(factoryDelegate); + AddRegistration(registrationKey, factoryRegistration); + + return factoryRegistration; + } + + public bool IsRegistered() + { + return IsRegistered(null); + } + + public bool IsRegistered(string name) + { + Type typeToResolve = typeof(T); + + var keyToResolve = new RegistrationKey(typeToResolve, name); + + return _registrations.ContainsKey(keyToResolve); + } + + // ReSharper disable once UnusedParameter.Local + private void AssertNotResolved(RegistrationKey interfaceType) + { + if (_resolvedKeys.Contains(interfaceType)) + throw new ObjectContainerException("An object has been resolved for this interface already.", null); + } + + private void ClearRegistrations(RegistrationKey registrationKey) + { + _registrations.TryRemove(registrationKey, out _); + } + + + #endregion + + #region Resolve + + public T Resolve() + { + return Resolve(null); + } + + public T Resolve(string name) + { + Type typeToResolve = typeof(T); + + object resolvedObject = Resolve(typeToResolve, name); + + return (T)resolvedObject; + } + + public object Resolve(Type typeToResolve, string name = null) + { + return Resolve(typeToResolve, new ResolutionList(), name); + } + + public IEnumerable ResolveAll() where T : class + { + return _registrations + .Where(x => x.Key.Type == typeof(T)) + .Select(x => Resolve(x.Key.Type, x.Key.Name) as T); + } + + private object Resolve(Type typeToResolve, ResolutionList resolutionPath, string name) + { + AssertNotDisposed(); + + var keyToResolve = new RegistrationKey(typeToResolve, name); + object resolvedObject = ResolveObject(keyToResolve, resolutionPath); + if (!_resolvedKeys.Contains(keyToResolve)) + { + _resolvedKeys.Add(keyToResolve); + } + Debug.Assert(typeToResolve.IsInstanceOfType(resolvedObject)); + return resolvedObject; + } + + private KeyValuePair? GetRegistrationResult(RegistrationKey keyToResolve) + { + if (_registrations.TryGetValue(keyToResolve, out var registration)) + { + return new KeyValuePair(this, registration); + } + + if (_baseContainer != null) + return _baseContainer.GetRegistrationResult(keyToResolve); + + if (IsSpecialNamedInstanceDictionaryKey(keyToResolve)) + { + var targetType = keyToResolve.Type.GetGenericArguments()[1]; + return GetRegistrationResult(CreateNamedInstanceDictionaryKey(targetType)); + } + + // if there was no named registration, we still return an empty dictionary + if (IsDefaultNamedInstanceDictionaryKey(keyToResolve)) + { + return new KeyValuePair(this, new NamedInstanceDictionaryRegistration()); + } + + return null; + } + + private bool IsDefaultNamedInstanceDictionaryKey(RegistrationKey keyToResolve) + { + return IsNamedInstanceDictionaryKey(keyToResolve) && + keyToResolve.Type.GetGenericArguments()[0] == typeof(string); + } + + private bool IsSpecialNamedInstanceDictionaryKey(RegistrationKey keyToResolve) + { + return IsNamedInstanceDictionaryKey(keyToResolve) && + keyToResolve.Type.GetGenericArguments()[0].IsEnum; + } + + private bool IsNamedInstanceDictionaryKey(RegistrationKey keyToResolve) + { + return keyToResolve.Name == null && keyToResolve.Type.IsGenericType && keyToResolve.Type.GetGenericTypeDefinition() == typeof(IDictionary<,>); + } + + private object GetPooledObject(RegistrationKey pooledObjectKey) + { + if (GetObjectFromPool(pooledObjectKey, out object obj)) + return obj; + + return null; + } + + private bool GetObjectFromPool(RegistrationKey pooledObjectKey, out object obj) + { + if (!_objectPool.TryGetValue(pooledObjectKey, out obj)) + return false; + + if (obj is NonDisposableWrapper nonDisposableWrapper) + obj = nonDisposableWrapper.Object; + + return true; + } + + private object ResolveObject(RegistrationKey keyToResolve, ResolutionList resolutionPath) + { + if (keyToResolve.Type.IsPrimitive || keyToResolve.Type == typeof(string) || keyToResolve.Type.IsValueType) + throw new ObjectContainerException("Primitive types or structs cannot be resolved: " + keyToResolve.Type.FullName, resolutionPath.ToTypeList()); + + var registrationResult = GetRegistrationResult(keyToResolve); + + var registrationToUse = registrationResult ?? + new KeyValuePair(this, EnsureImplicitRegistration(keyToResolve)); + + var resolutionPathForResolve = registrationToUse.Key == this ? + resolutionPath : new ResolutionList(); + var result = registrationToUse.Value.Resolve(registrationToUse.Key, keyToResolve, resolutionPathForResolve); + + return result; + } + + + private object CreateObject(Type type, ResolutionList resolutionPath, RegistrationKey keyToResolve) + { + var constructors = type.GetConstructors(); + if (constructors.Length == 0) + constructors = type.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance); + + Debug.Assert(constructors.Length > 0, "Class must have a constructor!"); + + int maxParamCount = constructors.Max(ctor => ctor.GetParameters().Length); + var maxParamCountConstructors = constructors.Where(ctor => ctor.GetParameters().Length == maxParamCount).ToArray(); + + object obj; + if (maxParamCountConstructors.Length == 1) + { + ConstructorInfo ctor = maxParamCountConstructors[0]; + if (resolutionPath.Contains(keyToResolve)) + throw new ObjectContainerException("Circular dependency found! " + type.FullName, resolutionPath.ToTypeList()); + + var args = ResolveArguments(ctor.GetParameters(), keyToResolve, resolutionPath.AddToEnd(keyToResolve, type)); + obj = ctor.Invoke(args); + } + else + { + throw new ObjectContainerException("Multiple public constructors with same maximum parameter count are not supported! " + type.FullName, resolutionPath.ToTypeList()); + } + + OnObjectCreated(obj); + + return obj; + } + + protected virtual void OnObjectCreated(object obj) + { + var eventHandler = ObjectCreated; + if (eventHandler != null) + eventHandler(obj); + } + + private object InvokeFactoryDelegate(Delegate factoryDelegate, ResolutionList resolutionPath, RegistrationKey keyToResolve) + { + if (resolutionPath.Contains(keyToResolve)) + throw new ObjectContainerException("Circular dependency found! " + factoryDelegate, resolutionPath.ToTypeList()); + + var args = ResolveArguments(factoryDelegate.Method.GetParameters(), keyToResolve, resolutionPath.AddToEnd(keyToResolve, null)); + return factoryDelegate.DynamicInvoke(args); + } + + private object[] ResolveArguments(IEnumerable parameters, RegistrationKey keyToResolve, ResolutionList resolutionPath) + { + return parameters.Select(p => IsRegisteredNameParameter(p) ? ResolveRegisteredName(keyToResolve) : Resolve(p.ParameterType, resolutionPath, null)).ToArray(); + } + + private object ResolveRegisteredName(RegistrationKey keyToResolve) + { + return keyToResolve.Name; + } + + private bool IsRegisteredNameParameter(ParameterInfo parameterInfo) + { + return parameterInfo.ParameterType == typeof(string) && + parameterInfo!.Name!.Equals(REGISTERED_NAME_PARAMETER_NAME); + } + + #endregion + + public override string ToString() + { + return string.Join(Environment.NewLine, + _registrations + .Where(r => !(r.Value is NamedInstanceDictionaryRegistration)) + .Select(r => $"{r.Key} -> {((r.Key.Type == typeof(IObjectContainer) && r.Key.Name == null) ? "" : r.Value.ToString())}")); + } + + private void AssertNotDisposed() + { + if (_isDisposed) + throw new ObjectContainerException("Object container disposed", null); + } + + public void Dispose() + { + _isDisposed = true; + + foreach (var obj in _objectPool.Values.OfType().Where(o => !ReferenceEquals(o, this))) + obj.Dispose(); + + _objectPool.Clear(); + _registrations.Clear(); + _resolvedKeys.Clear(); + } +} diff --git a/Reqnroll/BoDi/ObjectContainerException.cs b/Reqnroll/BoDi/ObjectContainerException.cs new file mode 100644 index 000000000..a0f7b20d3 --- /dev/null +++ b/Reqnroll/BoDi/ObjectContainerException.cs @@ -0,0 +1,27 @@ +using System; +using System.Linq; +using System.Runtime.Serialization; + +namespace Reqnroll.BoDi; + +[Serializable] +public class ObjectContainerException : Exception +{ + public ObjectContainerException(string message, Type[] resolutionPath) : base(GetMessage(message, resolutionPath)) + { + } + + protected ObjectContainerException( + SerializationInfo info, + StreamingContext context) : base(info, context) + { + } + + private static string GetMessage(string message, Type[] resolutionPath) + { + if (resolutionPath == null || resolutionPath.Length == 0) + return message; + + return $"{message} (resolution path: {string.Join("->", resolutionPath.Select(t => t.FullName).ToArray())})"; + } +} diff --git a/Reqnroll/Reqnroll.csproj b/Reqnroll/Reqnroll.csproj index 53fec9a3f..473f67ce8 100644 --- a/Reqnroll/Reqnroll.csproj +++ b/Reqnroll/Reqnroll.csproj @@ -64,29 +64,13 @@ - - - - - - + + - + diff --git a/Reqnroll/Reqnroll.nuspec b/Reqnroll/Reqnroll.nuspec index c7ff03075..75c6b93ff 100644 --- a/Reqnroll/Reqnroll.nuspec +++ b/Reqnroll/Reqnroll.nuspec @@ -17,7 +17,6 @@ $copyright$ - @@ -27,7 +26,6 @@ - @@ -39,12 +37,13 @@ - + + diff --git a/Tests/Reqnroll.SystemTests/Reqnroll.SystemTests.csproj b/Tests/Reqnroll.SystemTests/Reqnroll.SystemTests.csproj index 4ef30b6fe..00b6cf412 100644 --- a/Tests/Reqnroll.SystemTests/Reqnroll.SystemTests.csproj +++ b/Tests/Reqnroll.SystemTests/Reqnroll.SystemTests.csproj @@ -18,11 +18,12 @@ + + - false diff --git a/Tests/Reqnroll.SystemTests/SystemTestBase.cs b/Tests/Reqnroll.SystemTests/SystemTestBase.cs index 61dd12c7b..e785c36fa 100644 --- a/Tests/Reqnroll.SystemTests/SystemTestBase.cs +++ b/Tests/Reqnroll.SystemTests/SystemTestBase.cs @@ -1,13 +1,14 @@ using System; using System.Text.RegularExpressions; using FluentAssertions; +using Microsoft.Extensions.DependencyInjection; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Reqnroll.BoDi; using Reqnroll.SystemTests.Drivers; using Reqnroll.TestProjectGenerator; using Reqnroll.TestProjectGenerator.Data; using Reqnroll.TestProjectGenerator.Driver; using Reqnroll.TestProjectGenerator.Helpers; +using Scrutor; namespace Reqnroll.SystemTests; public abstract class SystemTestBase @@ -17,7 +18,7 @@ public abstract class SystemTestBase protected VSTestExecutionDriver _vsTestExecutionDriver = null!; protected TestFileManager _testFileManager = new(); protected FolderCleaner _folderCleaner = null!; - protected ObjectContainer _testContainer = null!; + protected IServiceProvider _testContainer = null!; protected TestRunConfiguration _testRunConfiguration = null!; protected CurrentVersionDriver _currentVersionDriver = null!; protected CompilationDriver _compilationDriver = null!; @@ -32,29 +33,52 @@ public void TestInitializeMethod() TestInitialize(); } + protected virtual IServiceCollection ConfigureServices() + { + var services = new ServiceCollection(); + + services.AddSingleton(); + + services.Scan(scan => scan + .FromAssemblyOf() + .AddClasses() + .UsingRegistrationStrategy(RegistrationStrategy.Skip) + .AsSelf() + .WithScopedLifetime()); + + services.Scan(scan => scan + .FromAssemblyOf() + .AddClasses(c => c.InNamespaceOf()) + .UsingRegistrationStrategy(RegistrationStrategy.Skip) + .AsSelf() + .WithScopedLifetime()); + + return services; + } + protected virtual void TestInitialize() { - _testContainer = new ObjectContainer(); - _testContainer.RegisterTypeAs(); + var services = ConfigureServices(); + _testContainer = services.BuildServiceProvider(); - _testRunConfiguration = _testContainer.Resolve(); + _testRunConfiguration = _testContainer.GetService(); _testRunConfiguration.ProgrammingLanguage = ProgrammingLanguage.CSharp; _testRunConfiguration.ProjectFormat = ProjectFormat.New; _testRunConfiguration.ConfigurationFormat = ConfigurationFormat.Json; _testRunConfiguration.TargetFramework = TargetFramework.Net80; _testRunConfiguration.UnitTestProvider = UnitTestProvider.MSTest; - _currentVersionDriver = _testContainer.Resolve(); + _currentVersionDriver = _testContainer.GetService(); _currentVersionDriver.NuGetVersion = NuGetPackageVersion.Version; _currentVersionDriver.ReqnrollNuGetVersion = NuGetPackageVersion.Version; - _folderCleaner = _testContainer.Resolve(); + _folderCleaner = _testContainer.GetService(); _folderCleaner.EnsureOldRunFoldersCleaned(); - _projectsDriver = _testContainer.Resolve(); - _executionDriver = _testContainer.Resolve(); - _vsTestExecutionDriver = _testContainer.Resolve(); - _compilationDriver = _testContainer.Resolve(); + _projectsDriver = _testContainer.GetService(); + _executionDriver = _testContainer.GetService(); + _vsTestExecutionDriver = _testContainer.GetService(); + _compilationDriver = _testContainer.GetService(); } protected void AddFeatureFileFromResource(string fileName, int? preparedTests = null) From 791b0c532e17521673c2184035dd5ee2be62c16f Mon Sep 17 00:00:00 2001 From: Chris Rudolphi <1702962+clrudolphi@users.noreply.github.com> Date: Tue, 16 Apr 2024 07:40:32 -0500 Subject: [PATCH 03/21] Fix for 81 - Cucumber Expression using Enums errors when two enums exist with the same short name (#100) * Fix for 81 - Cucumber Expression using Enums errors when two enums with the same short name exist as parameters to cucumber expressions. This fix causes the CucumberExpressionParameterTypeRegistry to use the FQN of the enum Types used as Cucumber Expression parameters. * Change Log * Modified CucumberExpressionParameterTypeRegistry to lookup enums by type FullName. * Code clean-up per PR code review https://github.com/reqnroll/Reqnroll/pull/100#pullrequestreview-2000984101 * Clean-up per PR code review. * Added to test to confirm creation of InValid StepDefinitionBindings and BindingRegistry in InValid state when ambiguous enum cucumber parameter expressions are present. Modified Error message for formatting. --- CHANGELOG.md | 1 + ...CucumberExpressionParameterTypeRegistry.cs | 11 +- ...berExpressionParameterTypeRegistryTests.cs | 164 +++++++++++++++--- 3 files changed, 153 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 56adc0b8b..11ae4b901 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ test run (global) context instead of the test thread context. * Support for PriorityAttribute in MsTest adapter * Support for Scenario Outline / DataRowAttribute in MsTest adapter +* Fix for #81 in which Cucumber Expressions fail when two enums with the same short name (differing namespaces) are used as parameters # v1.0.1 - 2024-02-16 diff --git a/Reqnroll/Bindings/CucumberExpressions/CucumberExpressionParameterTypeRegistry.cs b/Reqnroll/Bindings/CucumberExpressions/CucumberExpressionParameterTypeRegistry.cs index b46663a7a..56699730f 100644 --- a/Reqnroll/Bindings/CucumberExpressions/CucumberExpressionParameterTypeRegistry.cs +++ b/Reqnroll/Bindings/CucumberExpressions/CucumberExpressionParameterTypeRegistry.cs @@ -120,7 +120,8 @@ private IEnumerable GetEnumTypes { yield return new BuiltInCucumberExpressionParameterTypeTransformation( CucumberExpressionParameterType.MatchAllRegex, - enumParameterType); + enumParameterType, + enumParameterType.Type.FullName); } } @@ -128,6 +129,14 @@ public IParameterType LookupByTypeName(string name) { if (_parameterTypesByName.Value.TryGetValue(name, out var parameterType)) return parameterType; + //enum keys contain the Fullname of the type, try matching on the short name: + var matchingEnums = _parameterTypesByName.Value.Where(kvp => kvp.Value.ParameterType.IsEnum && (kvp.Key.EndsWith("." + name) || kvp.Key.EndsWith("+" + name))).ToArray(); + if (matchingEnums.Length == 0) { return null; } + if (matchingEnums.Length == 1) { return matchingEnums[0].Value; } + if (matchingEnums.Length > 1) + { + throw new ReqnrollException($"Ambiguous enum in cucumber expression. Multiple enums share the same short name '{name}'. Use the enum's full name in the cucumber expression or define a [StepArgumentTransformation] with the chosen type and the short name."); + } return null; } diff --git a/Tests/Reqnroll.RuntimeTests/Bindings/CucumberExpressions/CucumberExpressionParameterTypeRegistryTests.cs b/Tests/Reqnroll.RuntimeTests/Bindings/CucumberExpressions/CucumberExpressionParameterTypeRegistryTests.cs index ccc3e6e67..44e66dcff 100644 --- a/Tests/Reqnroll.RuntimeTests/Bindings/CucumberExpressions/CucumberExpressionParameterTypeRegistryTests.cs +++ b/Tests/Reqnroll.RuntimeTests/Bindings/CucumberExpressions/CucumberExpressionParameterTypeRegistryTests.cs @@ -1,34 +1,154 @@ using System; +using System.Linq; +using System.Xml.Linq; using FluentAssertions; using Reqnroll.Bindings; using Reqnroll.Bindings.CucumberExpressions; +using Reqnroll.Bindings.Discovery; +using Reqnroll.Bindings.Reflection; +using Reqnroll.Infrastructure; using Xunit; -namespace Reqnroll.RuntimeTests.Bindings.CucumberExpressions; +namespace Reqnroll.RuntimeTests.Bindings.CucumberExpressions { -public class CucumberExpressionParameterTypeRegistryTests -{ - // Most of the logic in CucumberExpressionParameterTypeRegistry can only be tested in integration of a cucumber expression match, - // so those are tested in the CucumberExpressionIntegrationTests class. - - private BindingRegistry _bindingRegistry; - private CucumberExpressionParameterTypeRegistry CreateSut() + public class CucumberExpressionParameterTypeRegistryTests { - _bindingRegistry = new BindingRegistry(); - return new CucumberExpressionParameterTypeRegistry(_bindingRegistry); - } + // Most of the logic in CucumberExpressionParameterTypeRegistry can only be tested in integration of a cucumber expression match, + // so those are tested in the CucumberExpressionIntegrationTests class. + + private BindingRegistry _bindingRegistry; + private CucumberExpressionParameterTypeRegistry CreateSut() + { + _bindingRegistry = new BindingRegistry(); + return new CucumberExpressionParameterTypeRegistry(_bindingRegistry); + } + + [Fact] + public void Should_provide_string_type() + { + var sut = CreateSut(); + var paramType = sut.LookupByTypeName("string"); + + // The regex '.*' provided by the CucumberExpressionParameterTypeRegistry is fake and + // will be ignored because of the special string type handling implemented in ReqnrollCucumberExpression. + // See ReqnrollCucumberExpression.HandleStringType for detailed explanation. + paramType.Should().NotBeNull(); + paramType.RegexStrings.Should().HaveCount(1); + paramType.RegexStrings[0].Should().Be(".*"); + } + + public class SampleEnumUsingClass + { + public void MethodUsingSampleColorEnum1(SampleColorEnum color) { } + } + [Fact] + public void Should_not_error_on_multiple_enums_of_the_same_name() + { + var sut = CreateSut(); + IBindingMethod enumUsingBindingMethod1 = new RuntimeBindingMethod(typeof(SampleEnumUsingClass).GetMethod(nameof(SampleEnumUsingClass.MethodUsingSampleColorEnum1))); + sut.OnBindingMethodProcessed(enumUsingBindingMethod1); + IBindingMethod enumUsingBindingMethod2 = new RuntimeBindingMethod(typeof(CucumberAddtionalExpressions.EnumCucumberExpressions).GetMethod(nameof(CucumberAddtionalExpressions.EnumCucumberExpressions.MethodUsingSampleColorEnum2))); + sut.OnBindingMethodProcessed(enumUsingBindingMethod2); + var paramTypes = sut.GetParameterTypes().Where(pt => pt.ParameterType.IsEnum).ToList(); + + paramTypes.Should().HaveCount(2); + } + + [Fact] + public void ParameterTypeRegistry_should_identify_error_when_given_multiple_bindings_with_an_ambiguous_enum_as_a_parameter() + { + var expression = "I have {SampleColorEnum} cucumbers in my belly"; + var containerBuilder = new ContainerBuilder(new CucumberExpressionIntegrationTests.TestDependencyProvider()); + var globalContainer = containerBuilder.CreateGlobalContainer(GetType().Assembly); - [Fact] - public void Should_provide_string_type() + var bindingSourceProcessor = globalContainer.Resolve(); + + var bindingRegistry = globalContainer.Resolve(); + + // set up first method binding that uses an ambiguous enum parameter + var bindingSourceMethod = new BindingSourceMethod + { + BindingMethod = new RuntimeBindingMethod(typeof(CucumberExpressionIntegrationTests.SampleBindings).GetMethod(nameof(CucumberExpressionIntegrationTests.SampleBindings.StepDefWithEnumParam))), + IsPublic = true, + Attributes = new[] + { + new BindingSourceAttribute + { + AttributeType = new RuntimeBindingType(typeof(GivenAttribute)), + AttributeValues = new IBindingSourceAttributeValueProvider[] + { + new BindingSourceAttributeValueProvider(expression) + } + } + } + }; + bindingSourceProcessor.ProcessType( + new BindingSourceType + { + BindingType = new RuntimeBindingType(typeof(CucumberExpressionIntegrationTests.SampleBindings)), + Attributes = new[] + { + new BindingSourceAttribute + { + AttributeType = new RuntimeBindingType(typeof(BindingAttribute)) + } + }, + IsPublic = true, + IsClass = true + }); + bindingSourceProcessor.ProcessMethod(bindingSourceMethod); + + // set up second method binding that uses an ambiguous enum parameter + var second_bindingSourceMethod = new BindingSourceMethod + { + BindingMethod = new RuntimeBindingMethod(typeof(CucumberAddtionalExpressions.EnumCucumberExpressions).GetMethod(nameof(CucumberAddtionalExpressions.EnumCucumberExpressions.MethodUsingSampleColorEnum2))), + IsPublic = true, + Attributes = new[] + { + new BindingSourceAttribute + { + AttributeType = new RuntimeBindingType(typeof(GivenAttribute)), + AttributeValues = new IBindingSourceAttributeValueProvider[] + { + new BindingSourceAttributeValueProvider(expression) + } + } + } + }; + bindingSourceProcessor.ProcessType( + new BindingSourceType + { + BindingType = new RuntimeBindingType(typeof(CucumberAddtionalExpressions.EnumCucumberExpressions)), + Attributes = new[] + { + new BindingSourceAttribute + { + AttributeType = new RuntimeBindingType(typeof(BindingAttribute)) + } + }, + IsPublic = true, + IsClass = true + }); + bindingSourceProcessor.ProcessMethod(second_bindingSourceMethod); + + + bindingSourceProcessor.BuildingCompleted(); + + bindingRegistry.IsValid.Should().BeFalse(); + var stepDefs = bindingRegistry.GetStepDefinitions().ToArray(); + stepDefs.Count().Should().Be(2); + stepDefs.All(sd => sd.SourceExpression == expression).Should().BeTrue(); + stepDefs.All(sd => sd.IsValid == false).Should().BeTrue(); + stepDefs.All(sd => sd.ErrorMessage.StartsWith("Ambiguous enum")).Should().BeTrue(); + } + } +} +namespace Reqnroll.RuntimeTests.Bindings.CucumberAddtionalExpressions +{ + public class EnumCucumberExpressions { - var sut = CreateSut(); - var paramType = sut.LookupByTypeName("string"); - - // The regex '.*' provided by the CucumberExpressionParameterTypeRegistry is fake and - // will be ignored because of the special string type handling implemented in ReqnrollCucumberExpression. - // See ReqnrollCucumberExpression.HandleStringType for detailed explanation. - paramType.Should().NotBeNull(); - paramType.RegexStrings.Should().HaveCount(1); - paramType.RegexStrings[0].Should().Be(".*"); + public enum SampleColorEnum { Yellow, Brown }; + + public void MethodUsingSampleColorEnum2(SampleColorEnum color) { } } } \ No newline at end of file From 77bea548459bb87b3b7c32082b41022f5dee5a0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1sp=C3=A1r=20Nagy?= Date: Wed, 17 Apr 2024 07:33:15 +0200 Subject: [PATCH 04/21] Fleshing out Generation System Tests (2) (#99) * Commit to flesh out Generation System Test. * Added test to verify that async step bindings are executed in the order specified in the Scenario. * Tests that verify that Before/After Hooks are run. * cleanup; promoted some code from GenerationTestBAse to SystemTestBase. * Adjusted the expectations of the Undefined Step test to account for different handling by MSTest. Adjusted Ignored test to disable row tests; this provides for consistent handling by all three test frameworks. * fix build * code cleanup * refactor common scenarios * Improve hook / step assertions * Merge outcome related tests * Fixes Adding @ignore to an Examples block generates invalid code for NUnit v3+ issue (#103) * Add fix to CHANGELOG * cleanup * Test scenario outlines (nr of examples, params are available in ScenarioContext, examples tags) * Add portability tests for before/after test run hooks (.NET Framework version of Reqnroll is subscribed to assembly unload) --------- Co-authored-by: Chris Rudolphi <1702962+clrudolphi@users.noreply.github.com> --- CHANGELOG.md | 1 + .../NUnit3TestGeneratorProvider.cs | 2 +- .../AppConfigGeneratorTests.cs | 1 - .../FeatureFileGeneratorTests.cs | 1 - .../JsonConfigGeneratorTests.cs | 1 - .../ConfigurationModel/Configuration.cs | 1 - .../Data/AppConfigSection.cs | 2 +- .../Data/ProjectFile.cs | 3 +- .../Data/SolutionFile.cs | 28 +- .../{HooksDriver.cs => BindingsDriver.cs} | 78 ++++-- ...onDriver.cs => ConfigurationFileDriver.cs} | 4 +- .../Driver/JsonConfigurationLoaderDriver.cs | 8 +- .../Driver/ProjectsDriver.cs | 32 ++- .../Driver/TestSuiteSetupDriver.cs | 8 +- .../BaseBindingsGenerator.cs | 2 +- .../CSharp10BindingsGenerator.cs | 5 + .../CSharpBindingsGenerator.cs | 5 + .../AppConfigGenerator.cs | 1 - .../Factories/ProjectBuilderFactory.cs | 1 - .../FeatureFileGenerator.cs | 18 +- .../FilesystemWriter/FileWriter.cs | 2 + .../ProjectBuilder.cs | 14 +- .../TRXParser.cs | 2 + .../TestExecutionResult.cs | 2 +- .../TestProjectFolders.cs | 1 + .../VSTestExecutionDriver.cs | 5 +- ...8b0de49-90e4-4313-9c7d-e1fe55c4c33d.csproj | 125 +++++++++ .../Drivers/ConfigurationLoaderDriver.cs | 12 +- .../StepDefinitions/ConfigurationSteps.cs | 8 +- .../StepDefinitions/ExecutionResultSteps.cs | 10 +- .../ReqnrollConfigurationSteps.cs | 19 +- .../Generation/GenerationTestBase.cs | 262 +++++++++++++++++- .../Generation/NUnitGenerationTest.cs | 8 + .../Generation/XUnitGenerationTest.cs | 3 + .../Portability/PortabilityTestBase.cs | 21 +- Tests/Reqnroll.SystemTests/Smoke/SmokeTest.cs | 6 +- Tests/Reqnroll.SystemTests/SystemTestBase.cs | 79 +++++- 37 files changed, 644 insertions(+), 137 deletions(-) rename Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Driver/{HooksDriver.cs => BindingsDriver.cs} (57%) rename Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Driver/{ConfigurationDriver.cs => ConfigurationFileDriver.cs} (97%) create mode 100644 Tests/Reqnroll.GeneratorTests/nCrunchTemp_38b0de49-90e4-4313-9c7d-e1fe55c4c33d.csproj diff --git a/CHANGELOG.md b/CHANGELOG.md index 11ae4b901..0ada8ba44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * Support for PriorityAttribute in MsTest adapter * Support for Scenario Outline / DataRowAttribute in MsTest adapter * Fix for #81 in which Cucumber Expressions fail when two enums with the same short name (differing namespaces) are used as parameters +* Fix: Adding @ignore to an Examples block generates invalid code for NUnit v3+ (#103) # v1.0.1 - 2024-02-16 diff --git a/Reqnroll.Generator/UnitTestProvider/NUnit3TestGeneratorProvider.cs b/Reqnroll.Generator/UnitTestProvider/NUnit3TestGeneratorProvider.cs index 898bb8035..66fc9568f 100644 --- a/Reqnroll.Generator/UnitTestProvider/NUnit3TestGeneratorProvider.cs +++ b/Reqnroll.Generator/UnitTestProvider/NUnit3TestGeneratorProvider.cs @@ -140,7 +140,7 @@ public void SetRow(TestClassGenerationContext generationContext, CodeMemberMetho } if (isIgnored) - args.Add(new CodeAttributeArgument("Ignored", new CodePrimitiveExpression(true))); + args.Add(new CodeAttributeArgument("IgnoreReason", new CodePrimitiveExpression("Ignored by @ignore tag"))); CodeDomHelper.AddAttribute(testMethod, ROW_ATTR, args.ToArray()); } diff --git a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator.Tests/AppConfigGeneratorTests.cs b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator.Tests/AppConfigGeneratorTests.cs index 8d7afcc27..730e35230 100644 --- a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator.Tests/AppConfigGeneratorTests.cs +++ b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator.Tests/AppConfigGeneratorTests.cs @@ -2,7 +2,6 @@ using Reqnroll.TestProjectGenerator.ConfigurationModel; using Reqnroll.TestProjectGenerator.Data; using Reqnroll.TestProjectGenerator.Factories.ConfigurationGenerator; -using Reqnroll.TestProjectGenerator.NewApi._1_Memory; using Xunit; namespace Reqnroll.TestProjectGenerator.Tests diff --git a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator.Tests/FeatureFileGeneratorTests.cs b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator.Tests/FeatureFileGeneratorTests.cs index 4692c1fac..f4a23adf1 100644 --- a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator.Tests/FeatureFileGeneratorTests.cs +++ b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator.Tests/FeatureFileGeneratorTests.cs @@ -1,5 +1,4 @@ using FluentAssertions; -using Reqnroll.TestProjectGenerator.NewApi._1_Memory; using Xunit; namespace Reqnroll.TestProjectGenerator.Tests diff --git a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator.Tests/JsonConfigGeneratorTests.cs b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator.Tests/JsonConfigGeneratorTests.cs index 5137bb094..57d2d6239 100644 --- a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator.Tests/JsonConfigGeneratorTests.cs +++ b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator.Tests/JsonConfigGeneratorTests.cs @@ -2,7 +2,6 @@ using Reqnroll.TestProjectGenerator.ConfigurationModel; using Reqnroll.TestProjectGenerator.Data; using Reqnroll.TestProjectGenerator.Factories.ConfigurationGenerator; -using Reqnroll.TestProjectGenerator.NewApi._1_Memory; using Xunit; namespace Reqnroll.TestProjectGenerator.Tests diff --git a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/ConfigurationModel/Configuration.cs b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/ConfigurationModel/Configuration.cs index 9e3414abd..ff316838f 100644 --- a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/ConfigurationModel/Configuration.cs +++ b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/ConfigurationModel/Configuration.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Globalization; using Reqnroll.TestProjectGenerator.Data; -using Reqnroll.TestProjectGenerator.NewApi._1_Memory; namespace Reqnroll.TestProjectGenerator.ConfigurationModel { diff --git a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Data/AppConfigSection.cs b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Data/AppConfigSection.cs index b1f01a9ff..0c53ec045 100644 --- a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Data/AppConfigSection.cs +++ b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Data/AppConfigSection.cs @@ -1,4 +1,4 @@ -namespace Reqnroll.TestProjectGenerator.NewApi._1_Memory +namespace Reqnroll.TestProjectGenerator.Data { public class AppConfigSection { diff --git a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Data/ProjectFile.cs b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Data/ProjectFile.cs index 312a06e7b..a8d37693a 100644 --- a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Data/ProjectFile.cs +++ b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Data/ProjectFile.cs @@ -4,12 +4,11 @@ namespace Reqnroll.TestProjectGenerator.Data { [DebuggerDisplay("{" + nameof(Path) + ("} [{" + nameof(BuildAction) + "}]"))] - public class ProjectFile :SolutionFile //FeatureFiles, Code, App.Config, NuGet.Config, packages.config, + public class ProjectFile: SolutionFile //FeatureFiles, Code, App.Config, NuGet.Config, packages.config, { public ProjectFile(string path, string buildAction, string content, CopyToOutputDirectory copyToOutputDirectory = CopyToOutputDirectory.DoNotCopy) : this(path, buildAction, content, copyToOutputDirectory, new Dictionary()) { - } public ProjectFile(string path, string buildAction, string content, CopyToOutputDirectory copyToOutputDirectory, IReadOnlyDictionary additionalMsBuildProperties) : base(path, content) diff --git a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Data/SolutionFile.cs b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Data/SolutionFile.cs index 3be064212..e5bf1615a 100644 --- a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Data/SolutionFile.cs +++ b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Data/SolutionFile.cs @@ -1,14 +1,30 @@ +using System; + namespace Reqnroll.TestProjectGenerator.Data { - public class SolutionFile + public class SolutionFile(string path, string content) { - public SolutionFile(string path, string content) + private bool _isFrozen = false; + + public string Path { get; } = path; //relative from project + public string Content { get; private set; } = content; + + internal void Freeze() { - Path = path; - Content = content; + _isFrozen = true; } - public string Path { get; } //relative from project - public string Content { get; } + public void Append(string addedContent) + { + if (_isFrozen) + { + throw new InvalidOperationException("Cannot append to frozen file"); + } + + if (!Content.EndsWith(Environment.NewLine)) + Content += Environment.NewLine; + + Content += addedContent; + } } } \ No newline at end of file diff --git a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Driver/HooksDriver.cs b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Driver/BindingsDriver.cs similarity index 57% rename from Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Driver/HooksDriver.cs rename to Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Driver/BindingsDriver.cs index 749b3df18..fb9bb5ad0 100644 --- a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Driver/HooksDriver.cs +++ b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Driver/BindingsDriver.cs @@ -8,15 +8,8 @@ namespace Reqnroll.TestProjectGenerator.Driver { - public class HooksDriver + public class BindingsDriver(TestProjectFolders _testProjectFolders) { - private readonly TestProjectFolders _testProjectFolders; - - public HooksDriver(TestProjectFolders testProjectFolders) - { - _testProjectFolders = testProjectFolders; - } - public void CheckIsHookExecuted(string methodName, int expectedTimesExecuted) { int hookExecutionCount = GetHookExecutionCount(methodName); @@ -27,13 +20,12 @@ public int GetHookExecutionCount(string methodName) { _testProjectFolders.PathToSolutionDirectory.Should().NotBeNullOrWhiteSpace(); - string pathToHookLogFile = Path.Combine(_testProjectFolders.PathToSolutionDirectory, "steps.log"); - if (!File.Exists(pathToHookLogFile)) + if (!File.Exists(_testProjectFolders.LogFilePath)) { return 0; } - string content = File.ReadAllText(pathToHookLogFile); + string content = File.ReadAllText(_testProjectFolders.LogFilePath); content.Should().NotBeNull(); var regex = new Regex($@"-> hook: {methodName}"); @@ -41,13 +33,15 @@ public int GetHookExecutionCount(string methodName) return regex.Matches(content).Count; } + private string GetLogFileLockPath() => _testProjectFolders.LogFilePath + ".lock"; + public void AcquireHookLock() { _testProjectFolders.PathToSolutionDirectory.Should().NotBeNullOrWhiteSpace(); - var pathToHookLogFile = Path.Combine(_testProjectFolders.PathToSolutionDirectory, "steps.log.lock"); + var pathToHookLogFile = GetLogFileLockPath(); - Directory.CreateDirectory(Path.GetDirectoryName(pathToHookLogFile)); + Directory.CreateDirectory(Path.GetDirectoryName(pathToHookLogFile)!); using (File.Open(pathToHookLogFile, FileMode.CreateNew)) { } @@ -56,10 +50,7 @@ public void AcquireHookLock() public void ReleaseHookLock() { _testProjectFolders.PathToSolutionDirectory.Should().NotBeNullOrWhiteSpace(); - - var pathToHookLogFile = Path.Combine(_testProjectFolders.PathToSolutionDirectory, "steps.log.lock"); - - File.Delete(pathToHookLogFile); + File.Delete(GetLogFileLockPath()); } public async Task WaitForIsWaitingForHookLockAsync(string methodName) @@ -81,14 +72,12 @@ private bool CheckIsWaitingForHookLock(string methodName) { _testProjectFolders.PathToSolutionDirectory.Should().NotBeNullOrWhiteSpace(); - var pathToHookLogFile = Path.Combine(_testProjectFolders.PathToSolutionDirectory, "steps.log"); - - if (!File.Exists(pathToHookLogFile)) + if (!File.Exists(_testProjectFolders.LogFilePath)) { return false; } - var content = File.ReadAllText(pathToHookLogFile); + var content = File.ReadAllText(_testProjectFolders.LogFilePath); content.Should().NotBeNull(); var regex = new Regex($@"-> waiting for hook lock: {methodName}"); @@ -96,12 +85,19 @@ private bool CheckIsWaitingForHookLock(string methodName) return regex.Matches(content).Count == 1; } + public IEnumerable GetActualLogLines(string category) + { + _testProjectFolders.PathToSolutionDirectory.Should().NotBeNullOrWhiteSpace(); + + var lines = File.ReadAllLines(_testProjectFolders.LogFilePath); + return lines.Where(l => l.StartsWith($"-> {category}:")); + } + public void CheckIsNotHookExecuted(string methodName, int timesExecuted) { _testProjectFolders.PathToSolutionDirectory.Should().NotBeNullOrWhiteSpace(); - var pathToHookLogFile = Path.Combine(_testProjectFolders.PathToSolutionDirectory, "steps.log"); - var content = File.ReadAllText(pathToHookLogFile); + var content = File.ReadAllText(_testProjectFolders.LogFilePath); content.Should().NotBeNull(); var regex = new Regex($@"-> hook: {methodName}"); @@ -109,14 +105,40 @@ public void CheckIsNotHookExecuted(string methodName, int timesExecuted) regex.Matches(content).Count.Should().NotBe(timesExecuted); } - public void CheckIsHookExecutedInOrder(IEnumerable methodNames) + private IEnumerable GetActualHookLines() => GetActualLogLines("hook"); + + public void AssertHooksExecutedInOrder(params string[] methodNames) { - _testProjectFolders.PathToSolutionDirectory.Should().NotBeNullOrWhiteSpace(); + var hookLines = GetActualHookLines(); - var pathToHookLogFile = Path.Combine(_testProjectFolders.PathToSolutionDirectory, "steps.log"); - var lines = File.ReadAllLines(pathToHookLogFile); var methodNameLines = methodNames.Select(m => $"-> hook: {m}"); - lines.Should().ContainInOrder(methodNameLines); + hookLines.Should().ContainInOrder(methodNameLines); + } + + public void AssertExecutedHooksEqual(params string[] methodNames) + { + var hookLines = GetActualHookLines(); + + var methodNameLines = methodNames.Select(m => $"-> hook: {m}"); + hookLines.Should().Equal(methodNameLines); + } + + private IEnumerable GetActualStepLines() => GetActualLogLines("step"); + + public void AssertStepsExecutedInOrder(params string[] methodNames) + { + var stepLines = GetActualStepLines(); + + var methodNameLines = methodNames.Select(m => $"-> step: {m}"); + stepLines.Should().ContainInOrder(methodNameLines); + } + + public void AssertExecutedStepsEqual(params string[] methodNames) + { + var stepLines = GetActualStepLines(); + + var methodNameLines = methodNames.Select(m => $"-> step: {m}"); + stepLines.Should().Equal(methodNameLines); } } } diff --git a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Driver/ConfigurationDriver.cs b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Driver/ConfigurationFileDriver.cs similarity index 97% rename from Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Driver/ConfigurationDriver.cs rename to Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Driver/ConfigurationFileDriver.cs index 74115448d..9272f1ccc 100644 --- a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Driver/ConfigurationDriver.cs +++ b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Driver/ConfigurationFileDriver.cs @@ -4,11 +4,11 @@ namespace Reqnroll.TestProjectGenerator.Driver { - public class ConfigurationDriver + public class ConfigurationFileDriver { private readonly SolutionDriver _solutionDriver; - public ConfigurationDriver(SolutionDriver solutionDriver) + public ConfigurationFileDriver(SolutionDriver solutionDriver) { _solutionDriver = solutionDriver; } diff --git a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Driver/JsonConfigurationLoaderDriver.cs b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Driver/JsonConfigurationLoaderDriver.cs index 5b97b7b85..f8b6799c6 100644 --- a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Driver/JsonConfigurationLoaderDriver.cs +++ b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Driver/JsonConfigurationLoaderDriver.cs @@ -5,17 +5,17 @@ namespace Reqnroll.TestProjectGenerator.Driver public class JsonConfigurationLoaderDriver { private readonly ProjectsDriver _projectsDriver; - private readonly ConfigurationDriver _configurationDriver; + private readonly ConfigurationFileDriver _configurationFileDriver; - public JsonConfigurationLoaderDriver(ProjectsDriver projectsDriver, ConfigurationDriver configurationDriver) + public JsonConfigurationLoaderDriver(ProjectsDriver projectsDriver, ConfigurationFileDriver configurationFileDriver) { _projectsDriver = projectsDriver; - _configurationDriver = configurationDriver; + _configurationFileDriver = configurationFileDriver; } public void AddReqnrollJson(string reqnrollJson) { - _configurationDriver.SetConfigurationFormat(ConfigurationFormat.None); + _configurationFileDriver.SetConfigurationFormat(ConfigurationFormat.None); _projectsDriver.AddFile("reqnroll.json", reqnrollJson); } } diff --git a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Driver/ProjectsDriver.cs b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Driver/ProjectsDriver.cs index 07f1680eb..9eee89bd3 100644 --- a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Driver/ProjectsDriver.cs +++ b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Driver/ProjectsDriver.cs @@ -12,6 +12,7 @@ public class ProjectsDriver private readonly SolutionDriver _solutionDriver; private readonly ProjectBuilderFactory _projectBuilderFactory; private readonly TestProjectFolders _testProjectFolders; + public ProjectFile LastFeatureFile { get; private set; } public ProjectsDriver(SolutionDriver solutionDriver, ProjectBuilderFactory projectBuilderFactory, TestProjectFolders testProjectFolders) { @@ -59,9 +60,9 @@ public void AddHookBinding(string eventType, string name, string hookTypeAttribu AddHookBinding(_solutionDriver.DefaultProject, eventType, name, code, order, hookTypeAttributeTags, methodScopeAttributeTags, classScopeAttributeTags); } - public void AddHookBinding(string eventType, string name, string code = "", int? order = null, IList hookTypeAttributeTags = null, IList methodScopeAttributeTags = null, IList classScopeAttributeTags = null) + public void AddHookBinding(string eventType, string name = null, string code = "", int? order = null, IList hookTypeAttributeTags = null, IList methodScopeAttributeTags = null, IList classScopeAttributeTags = null) { - AddHookBinding(_solutionDriver.DefaultProject, eventType, name, code, order, hookTypeAttributeTags, methodScopeAttributeTags, classScopeAttributeTags); + AddHookBinding(_solutionDriver.DefaultProject, eventType, name ?? eventType, code, order, hookTypeAttributeTags, methodScopeAttributeTags, classScopeAttributeTags); } private void AddHookBinding(ProjectBuilder project, string eventType, string name, string code = "", int? order = null, IList hookTypeAttributeTags = null, IList methodScopeAttributeTags = null, IList classScopeAttributeTags = null) @@ -81,27 +82,34 @@ private void AddAsyncHookBindingIncludingLocking(ProjectBuilder project, string public void AddFeatureFile(string featureFileContent) { - _solutionDriver.DefaultProject.AddFeatureFile(featureFileContent); + LastFeatureFile = _solutionDriver.DefaultProject.AddFeatureFile(featureFileContent); } public void AddScenario(string scenarioContent) { - AddFeatureFile( - $$""" - Feature: Sample Feature - - {{scenarioContent}} - """); + if (LastFeatureFile != null) + { + LastFeatureFile.Append(scenarioContent); + } + else + { + AddFeatureFile( + $$""" + Feature: Sample Feature + + {{scenarioContent}} + """); + } } - public void AddStepBinding(string attributeName, string regex, string csharpcode, string vbnetcode) + public void AddStepBinding(string attributeName, string regex, string csharpcode, string vbnetcode = null) { _solutionDriver.DefaultProject.AddStepBinding(attributeName, regex, csharpcode, vbnetcode); } public void AddLoggingStepBinding(string attributeName, string methodName, string regex) { - _solutionDriver.DefaultProject.AddLoggingStepBinding(attributeName, methodName, Path.Combine(_testProjectFolders.PathToSolutionDirectory, "steps.log"), regex); + _solutionDriver.DefaultProject.AddLoggingStepBinding(attributeName, methodName, regex); } public void AddStepBinding(string projectName, string bindingCode) => AddStepBinding(_solutionDriver.Projects[projectName], bindingCode); @@ -157,7 +165,7 @@ public void AddNuGetPackage(string nugetPackage, string nugetVersion) _solutionDriver.DefaultProject.AddNuGetPackage(nugetPackage, nugetVersion); } - public void AddFailingStepBinding(string scenarioBlock, string stepRegex) + public void AddFailingStepBinding(string scenarioBlock = "StepDefinition", string stepRegex = ".*") { AddStepBinding(scenarioBlock, stepRegex, @"throw new System.Exception(""simulated failure"");", @"Throw New System.Exception(""simulated failure"")"); } diff --git a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Driver/TestSuiteSetupDriver.cs b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Driver/TestSuiteSetupDriver.cs index 63f60f2f8..6ea0185af 100644 --- a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Driver/TestSuiteSetupDriver.cs +++ b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Driver/TestSuiteSetupDriver.cs @@ -9,16 +9,16 @@ public class TestSuiteSetupDriver private readonly ProjectsDriver _projectsDriver; private readonly TestSuiteInitializationDriver _testSuiteInitializationDriver; private readonly JsonConfigurationLoaderDriver _jsonConfigurationLoaderDriver; - private readonly ConfigurationDriver _configurationDriver; + private readonly ConfigurationFileDriver _configurationFileDriver; private bool _isProjectCreated; public TestSuiteSetupDriver(ProjectsDriver projectsDriver, TestSuiteInitializationDriver testSuiteInitializationDriver, JsonConfigurationLoaderDriver jsonConfigurationLoaderDriver, - ConfigurationDriver configurationDriver) + ConfigurationFileDriver configurationFileDriver) { _projectsDriver = projectsDriver; _testSuiteInitializationDriver = testSuiteInitializationDriver; _jsonConfigurationLoaderDriver = jsonConfigurationLoaderDriver; - _configurationDriver = configurationDriver; + _configurationFileDriver = configurationFileDriver; } public void AddGenericWhenStepBinding() @@ -136,7 +136,7 @@ public void AddScenarioWithGivenStep(string step, string tags = "") public void AddAppConfigFromString(string appConfigContent) { - _configurationDriver.SetConfigurationFormat(ConfigurationFormat.None); + _configurationFileDriver.SetConfigurationFormat(ConfigurationFormat.None); _projectsDriver.AddFile("app.config", appConfigContent); } } diff --git a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Factories/BindingsGenerator/BaseBindingsGenerator.cs b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Factories/BindingsGenerator/BaseBindingsGenerator.cs index 646145720..d799d3426 100644 --- a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Factories/BindingsGenerator/BaseBindingsGenerator.cs +++ b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Factories/BindingsGenerator/BaseBindingsGenerator.cs @@ -16,7 +16,7 @@ public ProjectFile GenerateStepDefinition(string methodName, string methodImplem return GenerateStepDefinition(method); } - public ProjectFile GenerateLoggingStepDefinition(string methodName, string pathToLogFile, string attributeName, string regex, ParameterType parameterType = ParameterType.Normal, string argumentName = null) + public ProjectFile GenerateLoggingStepDefinition(string methodName, string attributeName, string regex, ParameterType parameterType = ParameterType.Normal, string argumentName = null) { string method = GetLoggingStepDefinitionCode(methodName, attributeName, regex, parameterType, argumentName); return GenerateStepDefinition(method); diff --git a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Factories/BindingsGenerator/CSharp10BindingsGenerator.cs b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Factories/BindingsGenerator/CSharp10BindingsGenerator.cs index f8cfd23dd..2f7809437 100644 --- a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Factories/BindingsGenerator/CSharp10BindingsGenerator.cs +++ b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Factories/BindingsGenerator/CSharp10BindingsGenerator.cs @@ -44,6 +44,11 @@ internal static void LogHook([CallerMemberName] string stepName = null!) Retry(5, () => WriteToFile($@"-> hook: {stepName}{Environment.NewLine}")); } + internal static void LogCustom(string category, string value, [CallerMemberName] string memberName = null) + { + Retry(5, () => WriteToFile($@"-> {category}: {value}:{memberName}{Environment.NewLine}")); + } + static void WriteToFile(string line) { using (FileStream fs = File.Open(LogFileLocation, FileMode.Append, FileAccess.Write, FileShare.None)) diff --git a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Factories/BindingsGenerator/CSharpBindingsGenerator.cs b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Factories/BindingsGenerator/CSharpBindingsGenerator.cs index fd0a17861..40c0229a6 100644 --- a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Factories/BindingsGenerator/CSharpBindingsGenerator.cs +++ b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Factories/BindingsGenerator/CSharpBindingsGenerator.cs @@ -109,6 +109,11 @@ internal static async Task LogHookIncludingLockingAsync([CallerMemberName] strin WriteToFile($@"-> hook: {stepName}{Environment.NewLine}"); } + internal static void LogCustom(string category, string value, [CallerMemberName] string memberName = null) + { + Retry(5, () => WriteToFile($@"-> {category}: {value}:{memberName}{Environment.NewLine}")); + } + static void WriteToFile(string line) { using (FileStream fs = File.Open(LogFileLocation, FileMode.Append, FileAccess.Write, FileShare.None)) diff --git a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Factories/ConfigurationGenerator/AppConfigGenerator.cs b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Factories/ConfigurationGenerator/AppConfigGenerator.cs index f7a0329eb..1e790efba 100644 --- a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Factories/ConfigurationGenerator/AppConfigGenerator.cs +++ b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Factories/ConfigurationGenerator/AppConfigGenerator.cs @@ -9,7 +9,6 @@ using Reqnroll.TestProjectGenerator.Data; using Reqnroll.TestProjectGenerator.Extensions; using Reqnroll.TestProjectGenerator.Helpers; -using Reqnroll.TestProjectGenerator.NewApi._1_Memory; namespace Reqnroll.TestProjectGenerator.Factories.ConfigurationGenerator { diff --git a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Factories/ProjectBuilderFactory.cs b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Factories/ProjectBuilderFactory.cs index 54437d9ec..500ac8098 100644 --- a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Factories/ProjectBuilderFactory.cs +++ b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Factories/ProjectBuilderFactory.cs @@ -3,7 +3,6 @@ using Reqnroll.TestProjectGenerator.Factories.BindingsGenerator; using Reqnroll.TestProjectGenerator.Factories.ConfigurationGenerator; using Reqnroll.TestProjectGenerator.Helpers; -using Reqnroll.TestProjectGenerator.NewApi._1_Memory; namespace Reqnroll.TestProjectGenerator.Factories { diff --git a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/FeatureFileGenerator.cs b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/FeatureFileGenerator.cs index 8e17c551f..984f99e52 100644 --- a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/FeatureFileGenerator.cs +++ b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/FeatureFileGenerator.cs @@ -1,17 +1,15 @@ using System; using Reqnroll.TestProjectGenerator.Data; -namespace Reqnroll.TestProjectGenerator.NewApi._1_Memory +namespace Reqnroll.TestProjectGenerator; + +public class FeatureFileGenerator { - public class FeatureFileGenerator + public ProjectFile Generate(string featureFileContent, string featureFileName = null) { - public ProjectFile Generate(string featureFileContent, string featureFileName = null) - { - featureFileName = featureFileName ?? $"FeatureFile{Guid.NewGuid():N}.feature"; - + featureFileName = featureFileName ?? $"FeatureFile{Guid.NewGuid():N}.feature"; - string fileContent = featureFileContent.Replace("'''", "\"\"\""); - return new ProjectFile(featureFileName, "None", fileContent); - } + string fileContent = featureFileContent.Replace("'''", "\"\"\""); + return new ProjectFile(featureFileName, "None", fileContent); } -} +} \ No newline at end of file diff --git a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/FilesystemWriter/FileWriter.cs b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/FilesystemWriter/FileWriter.cs index b2ea4c261..bcdab71ec 100644 --- a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/FilesystemWriter/FileWriter.cs +++ b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/FilesystemWriter/FileWriter.cs @@ -26,6 +26,8 @@ public void Write(SolutionFile projectFile, string projectRootPath) { File.WriteAllText(absolutePath, projectFile.Content); } + + projectFile.Freeze(); } } } diff --git a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/ProjectBuilder.cs b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/ProjectBuilder.cs index 48c1e3ea7..ef8329902 100644 --- a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/ProjectBuilder.cs +++ b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/ProjectBuilder.cs @@ -6,7 +6,6 @@ using Reqnroll.TestProjectGenerator.Driver; using Reqnroll.TestProjectGenerator.Factories.BindingsGenerator; using Reqnroll.TestProjectGenerator.Factories.ConfigurationGenerator; -using Reqnroll.TestProjectGenerator.NewApi._1_Memory; namespace Reqnroll.TestProjectGenerator { @@ -79,13 +78,14 @@ public void AddFile(ProjectFile projectFile) _project.AddFile(projectFile ?? throw new ArgumentNullException(nameof(projectFile))); } - public void AddFeatureFile(string featureFileContent) + public ProjectFile AddFeatureFile(string featureFileContent) { EnsureProjectExists(); var featureFile = _featureFileGenerator.Generate(featureFileContent); _project.AddFile(featureFile); + return featureFile; } public void AddStepBinding(string attributeName, string regex, string csharpcode, string vbnetcode) @@ -100,15 +100,15 @@ public void AddStepBinding(string attributeName, string regex, string csharpcode _project.AddFile(bindingsGenerator.GenerateStepDefinition("StepBinding", methodImplementation, attributeName, regex, ParameterType.DocString, "docStringArg")); } - public void AddLoggingStepBinding(string attributeName, string methodName, string pathToLogFile, string regex) + public void AddLoggingStepBinding(string attributeName, string methodName, string regex) { EnsureProjectExists(); var bindingsGenerator = _bindingsGeneratorFactory.FromLanguage(_project.ProgrammingLanguage); - _project.AddFile(bindingsGenerator.GenerateLoggingStepDefinition(methodName, pathToLogFile, attributeName, regex)); - _project.AddFile(bindingsGenerator.GenerateLoggingStepDefinition(methodName, pathToLogFile, attributeName, regex, ParameterType.Table, "tableArg")); - _project.AddFile(bindingsGenerator.GenerateLoggingStepDefinition(methodName, pathToLogFile, attributeName, regex, ParameterType.DocString, "docStringArg")); + _project.AddFile(bindingsGenerator.GenerateLoggingStepDefinition(methodName, attributeName, regex)); + _project.AddFile(bindingsGenerator.GenerateLoggingStepDefinition(methodName, attributeName, regex, ParameterType.Table, "tableArg")); + _project.AddFile(bindingsGenerator.GenerateLoggingStepDefinition(methodName, attributeName, regex, ParameterType.DocString, "docStringArg")); } public void AddHookBinding(string eventType, string name, string code = "", int? order = null, IList hookTypeAttributeTags = null, IList methodScopeAttributeTags = null, @@ -245,7 +245,7 @@ private void EnsureProjectExists() _project.AddNuGetPackage("Microsoft.Bcl.AsyncInterfaces", "6.0.0", new NuGetPackageAssembly("Microsoft.Bcl.AsyncInterfaces", "netstandard2.0\\Microsoft.Bcl.AsyncInterfaces.dll")); var generator = _bindingsGeneratorFactory.FromLanguage(_project.ProgrammingLanguage); - _project.AddFile(generator.GenerateLoggerClass(Path.Combine(_testProjectFolders.PathToSolutionDirectory, "steps.log"))); + _project.AddFile(generator.GenerateLoggerClass(_testProjectFolders.LogFilePath)); switch (_project.ProgrammingLanguage) { diff --git a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/TRXParser.cs b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/TRXParser.cs index d4f19e435..44a7648f1 100644 --- a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/TRXParser.cs +++ b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/TRXParser.cs @@ -155,6 +155,7 @@ private List GetTestResultsInternal(XElement testRunResultsElement, var testResults = from unitTestResultElement in testRunResultsElement?.Elements(_unitTestResultElementName) ?? Enumerable.Empty() let outputElement = unitTestResultElement.Element(_unitTestResultOutputElementName) let idAttribute = unitTestResultElement.Attribute("executionId") + let testNameAttribute = unitTestResultElement.Attribute("testName") let outcomeAttribute = unitTestResultElement.Attribute("outcome") let stdOutElement = outputElement?.Element(_unitTestResultStdOutElementName) let errorInfoElement = outputElement?.Element(xmlns + "ErrorInfo") @@ -165,6 +166,7 @@ private List GetTestResultsInternal(XElement testRunResultsElement, select new TestResult { Id = idAttribute.Value, + TestName = testNameAttribute.Value, Outcome = outcomeAttribute.Value, StdOut = stdOutElement?.Value, ErrorMessage = errorMessage?.Value, diff --git a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/TestExecutionResult.cs b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/TestExecutionResult.cs index 4385aa41c..e8e8522f0 100644 --- a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/TestExecutionResult.cs +++ b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/TestExecutionResult.cs @@ -32,7 +32,7 @@ public class TestResult public string Id { get; set; } public string Outcome { get; set; } public string StdOut { get; set; } - public string Title { get; set; } + public string TestName { get; set; } public string Feature { get; set; } public int ScheduleOrder { get; set; } public List Steps { get; set; } diff --git a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/TestProjectFolders.cs b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/TestProjectFolders.cs index 61f07fae6..addc8d75f 100644 --- a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/TestProjectFolders.cs +++ b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/TestProjectFolders.cs @@ -12,6 +12,7 @@ public class TestProjectFolders public string CompiledAssemblyPath { get; set; } public string PathToSolutionDirectory => Path.GetDirectoryName(PathToSolutionFile); public string TestAssemblyFileName { get; set; } + public string LogFilePath => Path.Combine(PathToSolutionDirectory, "steps.log"); public string PathToSolutionFile { diff --git a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/VSTestExecutionDriver.cs b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/VSTestExecutionDriver.cs index f53a95208..c1edcc62b 100644 --- a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/VSTestExecutionDriver.cs +++ b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/VSTestExecutionDriver.cs @@ -53,10 +53,9 @@ public VSTestExecutionDriver( public void CheckIsBindingMethodExecuted(string methodName, int timesExecuted) { - string pathToLogFile = Path.Combine(_testProjectFolders.PathToSolutionDirectory, "steps.log"); - string logFileContent = File.ReadAllText(pathToLogFile, Encoding.UTF8); + string logFileContent = File.ReadAllText(_testProjectFolders.LogFilePath, Encoding.UTF8); - var regex = new Regex($@"-> step: {methodName}"); + var regex = new Regex($"-> step: {methodName}"); regex.Match(logFileContent).Success.Should().BeTrue($"method {methodName} was not executed."); regex.Matches(logFileContent).Count.Should().Be(timesExecuted); diff --git a/Tests/Reqnroll.GeneratorTests/nCrunchTemp_38b0de49-90e4-4313-9c7d-e1fe55c4c33d.csproj b/Tests/Reqnroll.GeneratorTests/nCrunchTemp_38b0de49-90e4-4313-9c7d-e1fe55c4c33d.csproj new file mode 100644 index 000000000..5018cc575 --- /dev/null +++ b/Tests/Reqnroll.GeneratorTests/nCrunchTemp_38b0de49-90e4-4313-9c7d-e1fe55c4c33d.csproj @@ -0,0 +1,125 @@ + + + $(Reqnroll_Test_TFM) + Reqnroll.GeneratorTests + $(Reqnroll_KeyFile) + $(Reqnroll_EnableStrongNameSigning) + $(Reqnroll_PublicSign) + Reqnroll.GeneratorTests + true + false + + + + + PreserveNewest + + + + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + all + runtime; build; native; contentfiles; analyzers + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Reqnroll.Specs/Drivers/ConfigurationLoaderDriver.cs b/Tests/Reqnroll.Specs/Drivers/ConfigurationLoaderDriver.cs index 94824f804..9b6033f5a 100644 --- a/Tests/Reqnroll.Specs/Drivers/ConfigurationLoaderDriver.cs +++ b/Tests/Reqnroll.Specs/Drivers/ConfigurationLoaderDriver.cs @@ -6,12 +6,12 @@ namespace Reqnroll.Specs.Drivers { public class ConfigurationLoaderDriver { - private readonly ConfigurationDriver _configurationDriver; + private readonly ConfigurationFileDriver _configurationFileDriver; private readonly SolutionDriver _solutionDriver; - public ConfigurationLoaderDriver(ConfigurationDriver configurationDriver, SolutionDriver solutionDriver) + public ConfigurationLoaderDriver(ConfigurationFileDriver configurationFileDriver, SolutionDriver solutionDriver) { - _configurationDriver = configurationDriver; + _configurationFileDriver = configurationFileDriver; _solutionDriver = solutionDriver; } @@ -21,11 +21,11 @@ public void SetFromReqnrollConfiguration(ReqnrollConfiguration reqnrollConfigura foreach (string stepAssemblyName in reqnrollConfiguration.AdditionalStepAssemblies) { - _configurationDriver.AddStepAssembly(project, new BindingAssembly(stepAssemblyName)); + _configurationFileDriver.AddStepAssembly(project, new BindingAssembly(stepAssemblyName)); } - _configurationDriver.SetBindingCulture(project, reqnrollConfiguration.BindingCulture); - _configurationDriver.SetFeatureLanguage(project, reqnrollConfiguration.FeatureLanguage); + _configurationFileDriver.SetBindingCulture(project, reqnrollConfiguration.BindingCulture); + _configurationFileDriver.SetFeatureLanguage(project, reqnrollConfiguration.FeatureLanguage); } } } diff --git a/Tests/Reqnroll.Specs/StepDefinitions/ConfigurationSteps.cs b/Tests/Reqnroll.Specs/StepDefinitions/ConfigurationSteps.cs index 8c133a16c..921b41ee8 100644 --- a/Tests/Reqnroll.Specs/StepDefinitions/ConfigurationSteps.cs +++ b/Tests/Reqnroll.Specs/StepDefinitions/ConfigurationSteps.cs @@ -5,12 +5,12 @@ namespace Reqnroll.Specs.StepDefinitions [Binding] public class ConfigurationSteps { - private readonly ConfigurationDriver _configurationDriver; + private readonly ConfigurationFileDriver _configurationFileDriver; private readonly CompilationResultDriver _compilationResultDriver; - public ConfigurationSteps(ConfigurationDriver configurationDriver, CompilationResultDriver compilationResultDriver) + public ConfigurationSteps(ConfigurationFileDriver configurationFileDriver, CompilationResultDriver compilationResultDriver) { - _configurationDriver = configurationDriver; + _configurationFileDriver = configurationFileDriver; _compilationResultDriver = compilationResultDriver; } @@ -29,7 +29,7 @@ public void ThenTheReqnroll_JsonIsUsedForConfiguration() [Given(@"the feature language is '(.*)'")] public void GivenTheFeatureLanguageIs(string featureLanguage) { - _configurationDriver.SetFeatureLanguage(featureLanguage); + _configurationFileDriver.SetFeatureLanguage(featureLanguage); } } } diff --git a/Tests/Reqnroll.Specs/StepDefinitions/ExecutionResultSteps.cs b/Tests/Reqnroll.Specs/StepDefinitions/ExecutionResultSteps.cs index b55537bda..9c0ee1b43 100644 --- a/Tests/Reqnroll.Specs/StepDefinitions/ExecutionResultSteps.cs +++ b/Tests/Reqnroll.Specs/StepDefinitions/ExecutionResultSteps.cs @@ -13,14 +13,14 @@ namespace Reqnroll.Specs.StepDefinitions [Binding] public class ExecutionResultSteps { - private readonly HooksDriver _hooksDriver; + private readonly BindingsDriver _bindingsDriver; private readonly VSTestExecutionDriver _vsTestExecutionDriver; private readonly TestProjectFolders _testProjectFolders; private readonly TestRunLogDriver _testRunLogDriver; - public ExecutionResultSteps(HooksDriver hooksDriver, VSTestExecutionDriver vsTestExecutionDriver, TestProjectFolders testProjectFolders, TestRunLogDriver testRunLogDriver) + public ExecutionResultSteps(BindingsDriver bindingsDriver, VSTestExecutionDriver vsTestExecutionDriver, TestProjectFolders testProjectFolders, TestRunLogDriver testRunLogDriver) { - _hooksDriver = hooksDriver; + _bindingsDriver = bindingsDriver; _vsTestExecutionDriver = vsTestExecutionDriver; _testProjectFolders = testProjectFolders; _testRunLogDriver = testRunLogDriver; @@ -66,13 +66,13 @@ public void ThenTheBindingMethodIsExecuted(string methodName, int times) [Then(@"the hook '(.*)' is executed (\d+) times")] public void ThenTheHookIsExecuted(string methodName, int times) { - _hooksDriver.CheckIsHookExecuted(methodName, times); + _bindingsDriver.CheckIsHookExecuted(methodName, times); } [Then(@"the hooks are executed in the order")] public void ThenTheHooksAreExecutedInTheOrder(Table table) { - _hooksDriver.CheckIsHookExecutedInOrder(table.Rows.Select(r => r[0])); + _bindingsDriver.AssertHooksExecutedInOrder(table.Rows.Select(r => r[0]).ToArray()); } [Then(@"the execution log should contain text '(.*)'")] diff --git a/Tests/Reqnroll.Specs/StepDefinitions/ReqnrollConfigurationSteps.cs b/Tests/Reqnroll.Specs/StepDefinitions/ReqnrollConfigurationSteps.cs index 1ecab808b..f40a732ee 100644 --- a/Tests/Reqnroll.Specs/StepDefinitions/ReqnrollConfigurationSteps.cs +++ b/Tests/Reqnroll.Specs/StepDefinitions/ReqnrollConfigurationSteps.cs @@ -1,25 +1,24 @@ using Reqnroll.Specs.Drivers; using Reqnroll.TestProjectGenerator; using Reqnroll.TestProjectGenerator.Driver; -using ConfigurationDriver = Reqnroll.TestProjectGenerator.Driver.ConfigurationDriver; namespace Reqnroll.Specs.StepDefinitions { [Binding] public class ReqnrollConfigurationSteps { - private readonly ConfigurationDriver _configurationDriver; + private readonly ConfigurationFileDriver _configurationFileDriver; private readonly JsonConfigurationParserDriver _jsonConfigurationParserDriver; private readonly ConfigurationLoaderDriver _configurationLoaderDriver; private readonly TestSuiteSetupDriver _testSuiteSetupDriver; public ReqnrollConfigurationSteps( - ConfigurationDriver configurationDriver, + ConfigurationFileDriver configurationFileDriver, JsonConfigurationParserDriver jsonConfigurationParserDriver, ConfigurationLoaderDriver configurationLoaderDriver, TestSuiteSetupDriver testSuiteSetupDriver) { - _configurationDriver = configurationDriver; + _configurationFileDriver = configurationFileDriver; _jsonConfigurationParserDriver = jsonConfigurationParserDriver; _configurationLoaderDriver = configurationLoaderDriver; _testSuiteSetupDriver = testSuiteSetupDriver; @@ -28,7 +27,7 @@ public ReqnrollConfigurationSteps( [Given(@"the project has no reqnroll\.json configuration")] public void GivenTheProjectHasNoReqnroll_JsonConfiguration() { - _configurationDriver.SetConfigurationFormat(ConfigurationFormat.None); + _configurationFileDriver.SetConfigurationFormat(ConfigurationFormat.None); } [Given(@"there is a project with this reqnroll\.json configuration")] @@ -47,31 +46,31 @@ public void GivenTheReqnrollConfigurationIs(string reqnrollJsonConfig) [Given(@"the project is configured to use the (.+) provider")] public void GivenTheProjectIsConfiguredToUseTheUnitTestProvider(string providerName) { - _configurationDriver.SetUnitTestProvider(providerName); + _configurationFileDriver.SetUnitTestProvider(providerName); } [Given(@"Reqnroll is configured in the reqnroll\.json")] public void GivenReqnrollIsConfiguredInTheReqnrollJson() { - _configurationDriver.SetConfigurationFormat(ConfigurationFormat.Json); + _configurationFileDriver.SetConfigurationFormat(ConfigurationFormat.Json); } [Given(@"obsoleteBehavior configuration value is set to (.*)")] public void GivenObsoleteBehaviorConfigurationValueIsSetTo(string obsoleteBehaviorValue) { - _configurationDriver.SetRuntimeObsoleteBehavior(obsoleteBehaviorValue); + _configurationFileDriver.SetRuntimeObsoleteBehavior(obsoleteBehaviorValue); } [Given(@"row testing is (.+)")] public void GivenRowTestingIsRowTest(bool enabled) { - _configurationDriver.SetIsRowTestsAllowed(enabled); + _configurationFileDriver.SetIsRowTestsAllowed(enabled); } [Given(@"the type '(.*)' is registered as '(.*)' in Reqnroll runtime configuration")] public void GivenTheTypeIsRegisteredAsInReqnrollRuntimeConfiguration(string typeName, string interfaceName) { - _configurationDriver.AddRuntimeRegisterDependency(typeName, interfaceName); + _configurationFileDriver.AddRuntimeRegisterDependency(typeName, interfaceName); } [Given(@"there is a scenario")] diff --git a/Tests/Reqnroll.SystemTests/Generation/GenerationTestBase.cs b/Tests/Reqnroll.SystemTests/Generation/GenerationTestBase.cs index f76a3a41a..561f42714 100644 --- a/Tests/Reqnroll.SystemTests/Generation/GenerationTestBase.cs +++ b/Tests/Reqnroll.SystemTests/Generation/GenerationTestBase.cs @@ -1,9 +1,11 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Linq; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using FluentAssertions; namespace Reqnroll.SystemTests.Generation; [TestCategory("Generation")] -public class GenerationTestBase : SystemTestBase +public abstract class GenerationTestBase : SystemTestBase { [TestMethod] public void GeneratorAllIn_sample_can_be_handled() @@ -19,29 +21,265 @@ public void GeneratorAllIn_sample_can_be_handled() public void Handles_simple_scenarios_without_namespace_collisions() { _projectsDriver.CreateProject("CollidingNamespace.Reqnroll", "C#"); - + AddSimpleScenarioAndOutline(); AddScenario( """ - Scenario: Sample Scenario - When something happens - Scenario: Scenario with DataTable When something happens with | who | when | | me | today | | someone else | tomorrow | """); - _projectsDriver.AddPassingStepBinding(); + AddPassingStepBinding(); + + ExecuteTests(); + + ShouldAllScenariosPass(); + } + + #region Test different outcomes: success, failure, pending, undefined, ignored (scenario & scenario outline) + protected virtual string GetExpectedPendingOutcome() => "NotExecuted"; + protected virtual string GetExpectedUndefinedOutcome() => "NotExecuted"; + protected virtual string GetExpectedIgnoredOutcome() => "NotExecuted"; + + [TestMethod] + public void Handles_different_scenario_and_scenario_outline_outcomes() + { + AddFeatureFile( + """ + Feature: Sample Feature + + Scenario: Passing scenario + When the step passes + + Scenario: Failing scenario + When the step fails + + Scenario: Pending scenario + When the step is pending + + Scenario: Undefined scenario + When the step is undefined + + @ignore + Scenario: Ignored scenario + When the step fails + + Scenario Outline: SO + When the step + Examples: + | result | + | passes | + | fails | + | is pending | + | is undefined | + @ignore + Examples: + | result | + | ignored | + + @ignore + Scenario Outline: ExampleIgnored + When the step + Examples: + | result | + | fails | + """); + _projectsDriver.AddPassingStepBinding(stepRegex: "the step passes"); + _projectsDriver.AddFailingStepBinding(stepRegex: "the step fails"); + _projectsDriver.AddStepBinding("StepDefinition", regex: "the step is pending", "throw new PendingStepException();", "Throw New PendingStepException()"); + ExecuteTests(); + + // handles PASSED + var expectedPassedOutcome = "Passed"; + _vsTestExecutionDriver.LastTestExecutionResult.LeafTestResults + .Should().ContainSingle(tr => tr.TestName.StartsWith("Passing")) + .Which.Outcome.Should().Be(expectedPassedOutcome); + _vsTestExecutionDriver.LastTestExecutionResult.LeafTestResults + .Should().ContainSingle(tr => tr.TestName.StartsWith("SO") && tr.TestName.Contains("passes")) + .Which.Outcome.Should().Be(expectedPassedOutcome); + + // handles FAILED + var expectedFailedOutcome = "Failed"; + _vsTestExecutionDriver.LastTestExecutionResult.LeafTestResults + .Should().ContainSingle(tr => tr.TestName.StartsWith("Failing")) + .Which.Outcome.Should().Be(expectedFailedOutcome); + _vsTestExecutionDriver.LastTestExecutionResult.LeafTestResults + .Should().ContainSingle(tr => tr.TestName.StartsWith("SO") && tr.TestName.Contains("fails")) + .Which.Outcome.Should().Be(expectedFailedOutcome); + + // handles PENDING + var expectedPendingOutcome = GetExpectedPendingOutcome(); + _vsTestExecutionDriver.LastTestExecutionResult.LeafTestResults + .Should().ContainSingle(tr => tr.TestName.StartsWith("Pending")) + .Which.Outcome.Should().Be(expectedPendingOutcome); + _vsTestExecutionDriver.LastTestExecutionResult.LeafTestResults + .Should().ContainSingle(tr => tr.TestName.StartsWith("SO") && tr.TestName.Contains("pending")) + .Which.Outcome.Should().Be(expectedPendingOutcome); + + // handles UNDEFINED + var expectedUndefinedOutcome = GetExpectedUndefinedOutcome(); + _vsTestExecutionDriver.LastTestExecutionResult.LeafTestResults + .Should().ContainSingle(tr => tr.TestName.StartsWith("Undefined")) + .Which.Outcome.Should().Be(expectedUndefinedOutcome); + _vsTestExecutionDriver.LastTestExecutionResult.LeafTestResults + .Should().ContainSingle(tr => tr.TestName.StartsWith("SO") && tr.TestName.Contains("undefined")) + .Which.Outcome.Should().Be(expectedUndefinedOutcome); + + // handles IGNORED + var expectedIgnoredOutcome = GetExpectedIgnoredOutcome(); + _vsTestExecutionDriver.LastTestExecutionResult.LeafTestResults + .Should().ContainSingle(tr => tr.TestName.StartsWith("Ignored")) + .Which.Outcome.Should().Be(expectedIgnoredOutcome); + _vsTestExecutionDriver.LastTestExecutionResult.LeafTestResults + .Should().ContainSingle(tr => tr.TestName.StartsWith("ExampleIgnored")) + .Which.Outcome.Should().Be(expectedIgnoredOutcome); + AssertIgnoredScenarioOutlineExampleHandled(); + } + + protected virtual void AssertIgnoredScenarioOutlineExampleHandled() + { + // the scenario outline examples ignored by a tag on the examples block are not generated + _vsTestExecutionDriver.LastTestExecutionResult.LeafTestResults + .Should().NotContain(tr => tr.TestName.StartsWith("SO") && tr.TestName.Contains("ignored")) + .And.Subject.Where(tr => tr.TestName.StartsWith("SO")).Should().HaveCount(4); + + } + #endregion + + #region Test async steps (async steps are executed in order) + [TestMethod] + public void Async_steps_are_executed_in_order() + { + AddScenario( + """ + Scenario: Async Scenario Steps + When Async step 1 is called + When Async step 2 is called + When Async step 3 is called + """); + + AddBindingClass( + """ + namespace AsyncSequence.StepDefinitions + { + [Binding] + public class AsyncSequenceStepDefinitions + { + [When("Async step 1 is called")] + public async Task WhenStep1IsTaken() + { + await Task.Run(() => global::Log.LogStep() ); + } + [When("Async step 2 is called")] + public async Task WhenStep2IsTaken() + { + await Task.Run(() => global::Log.LogStep() ); + } + [When("Async step 3 is called")] + public async Task WhenStep3IsTaken() + { + await Task.Run(() => global::Log.LogStep() ); + } + } + } + """); + + ExecuteTests(); + _bindingDriver.AssertExecutedStepsEqual("WhenStep1IsTaken", "WhenStep2IsTaken", "WhenStep3IsTaken"); + + ShouldAllScenariosPass(); + } + #endregion + + #region Test hooks: before/after run, feature & scenario hook (require special handling by test frameworks) + [TestMethod] + public void TestRun_Feature_and_Scenario_hooks_are_executed_in_right_order() + { + var testsInFeatureFile = 3; + AddSimpleScenario(); + AddSimpleScenarioOutline(testsInFeatureFile - 1); + AddPassingStepBinding(); + AddHookBinding("BeforeTestRun"); + AddHookBinding("AfterTestRun"); + AddHookBinding("BeforeFeature"); + AddHookBinding("AfterFeature"); + AddHookBinding("BeforeScenario"); + AddHookBinding("AfterScenario"); ExecuteTests(); + _bindingDriver.AssertExecutedHooksEqual( + "BeforeTestRun", + "BeforeFeature", + "BeforeScenario", + "AfterScenario", + "BeforeScenario", + "AfterScenario", + "BeforeScenario", + "AfterScenario", + "AfterFeature", + "AfterTestRun"); ShouldAllScenariosPass(); } + #endregion + + #region Test scenario outlines (nr of examples, params are available in ScenarioContext, allowRowTests=false, examples tags) + + [TestMethod] + public void Scenario_outline_examples_gather_tags_and_parameters() + { + AddFeatureFile( + """ + @feature_tag + Feature: Sample Feature + + @rule_tag + Rule: Sample Rule + + @so1_tag + Scenario Outline: SO1 + When happens to + Examples: + | what1 | person | + | something | me | + | nothing | you | + + @so2_tag + Scenario Outline: SO2 + When happens to + Examples: + | what2 | person | + | something | me | + @e3_tag + Examples: E3 + | what2 | person | + | nothing | you | + """); + _projectsDriver.AddStepBinding("StepDefinition", regex: ".*", + """ + global::Log.LogCustom("tags", string.Join(",", _scenarioContext.ScenarioInfo.CombinedTags)); + global::Log.LogCustom("parameters", string.Join(",", _scenarioContext.ScenarioInfo.Arguments.OfType().Select(kv => $"{kv.Key}={kv.Value}"))); + """); + + ExecuteTests(); + + ShouldAllScenariosPass(4); + + _bindingDriver.GetActualLogLines("tags").Should().BeEquivalentTo( + "-> tags: so1_tag,feature_tag,rule_tag:StepBinding", + "-> tags: so1_tag,feature_tag,rule_tag:StepBinding", + "-> tags: so2_tag,feature_tag,rule_tag:StepBinding", + "-> tags: so2_tag,e3_tag,feature_tag,rule_tag:StepBinding"); + + _bindingDriver.GetActualLogLines("parameters").Should().BeEquivalentTo( + "-> parameters: what1=something,person=me:StepBinding", + "-> parameters: what1=nothing,person=you:StepBinding", + "-> parameters: what2=something,person=me:StepBinding", + "-> parameters: what2=nothing,person=you:StepBinding"); + } + + #endregion - //TODO: test different outcomes: success, failure, pending, undefined, ignored (scenario & scenario outline) - //TODO: test async steps (async steps are executed in order) - //TODO: test hooks: before/after test run (require special handling by test frameworks) - //TODO: test hooks: before/after test feature & scenario (require special handling by test frameworks) - //TODO: test scenario outlines (nr of examples, params are available in ScenarioContext, allowRowTests=false, examples tags) //TODO: test parallel execution (details TBD) - maybe this should be in a separate test class } diff --git a/Tests/Reqnroll.SystemTests/Generation/NUnitGenerationTest.cs b/Tests/Reqnroll.SystemTests/Generation/NUnitGenerationTest.cs index 9d4b15b4a..f5f8238ef 100644 --- a/Tests/Reqnroll.SystemTests/Generation/NUnitGenerationTest.cs +++ b/Tests/Reqnroll.SystemTests/Generation/NUnitGenerationTest.cs @@ -1,3 +1,4 @@ +using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Reqnroll.TestProjectGenerator; @@ -16,4 +17,11 @@ protected override void TestInitialize() base.TestInitialize(); _testRunConfiguration.UnitTestProvider = UnitTestProvider.NUnit3; } + + protected override void AssertIgnoredScenarioOutlineExampleHandled() + { + _vsTestExecutionDriver.LastTestExecutionResult.LeafTestResults + .Should().ContainSingle(tr => tr.TestName.StartsWith("SO") && tr.TestName.Contains("ignored")) + .Which.Outcome.Should().Be(GetExpectedIgnoredOutcome()); + } } \ No newline at end of file diff --git a/Tests/Reqnroll.SystemTests/Generation/XUnitGenerationTest.cs b/Tests/Reqnroll.SystemTests/Generation/XUnitGenerationTest.cs index adf2a569f..086f2ab37 100644 --- a/Tests/Reqnroll.SystemTests/Generation/XUnitGenerationTest.cs +++ b/Tests/Reqnroll.SystemTests/Generation/XUnitGenerationTest.cs @@ -16,4 +16,7 @@ protected override void TestInitialize() base.TestInitialize(); _testRunConfiguration.UnitTestProvider = UnitTestProvider.xUnit; } + + protected override string GetExpectedPendingOutcome() => "Failed"; + protected override string GetExpectedUndefinedOutcome() => "Failed"; } \ No newline at end of file diff --git a/Tests/Reqnroll.SystemTests/Portability/PortabilityTestBase.cs b/Tests/Reqnroll.SystemTests/Portability/PortabilityTestBase.cs index 7ff9c6efd..cfb178601 100644 --- a/Tests/Reqnroll.SystemTests/Portability/PortabilityTestBase.cs +++ b/Tests/Reqnroll.SystemTests/Portability/PortabilityTestBase.cs @@ -1,6 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Reqnroll.TestProjectGenerator.Driver; -using System.Runtime.InteropServices; namespace Reqnroll.SystemTests.Portability; @@ -40,7 +39,21 @@ public void GeneratorAllIn_sample_can_be_compiled_with_DotnetMSBuild() _compilationDriver.CompileSolution(); } - //TODO: test different outcomes: success, failure, pending, undefined, ignored (scenario & scenario outline) - //TODO: test async steps (async steps are executed in order) - //TODO: test before/after test run hooks (.NET Framework version of Reqnroll is subscribed to assembly unload) + #region Test before/after test run hooks (.NET Framework version of Reqnroll is subscribed to assembly unload) + [TestMethod] + public void TestRun_hooks_are_executed() + { + AddSimpleScenario(); + AddPassingStepBinding(); + AddHookBinding("BeforeTestRun"); + AddHookBinding("AfterTestRun"); + + ExecuteTests(); + + _bindingDriver.AssertExecutedHooksEqual( + "BeforeTestRun", + "AfterTestRun"); + ShouldAllScenariosPass(); + } + #endregion } diff --git a/Tests/Reqnroll.SystemTests/Smoke/SmokeTest.cs b/Tests/Reqnroll.SystemTests/Smoke/SmokeTest.cs index d64f4d6ab..5f098744f 100644 --- a/Tests/Reqnroll.SystemTests/Smoke/SmokeTest.cs +++ b/Tests/Reqnroll.SystemTests/Smoke/SmokeTest.cs @@ -9,11 +9,7 @@ public class SmokeTest : SystemTestBase [TestMethod] public void Handles_the_simplest_scenario() { - AddScenario( - """ - Scenario: Sample Scenario - When something happens - """); + AddSimpleScenario(); _projectsDriver.AddPassingStepBinding(); ExecuteTests(); diff --git a/Tests/Reqnroll.SystemTests/SystemTestBase.cs b/Tests/Reqnroll.SystemTests/SystemTestBase.cs index e785c36fa..e0b8d9013 100644 --- a/Tests/Reqnroll.SystemTests/SystemTestBase.cs +++ b/Tests/Reqnroll.SystemTests/SystemTestBase.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Text.RegularExpressions; using FluentAssertions; using Microsoft.Extensions.DependencyInjection; @@ -22,6 +23,8 @@ public abstract class SystemTestBase protected TestRunConfiguration _testRunConfiguration = null!; protected CurrentVersionDriver _currentVersionDriver = null!; protected CompilationDriver _compilationDriver = null!; + protected BindingsDriver _bindingDriver = null!; + protected TestProjectFolders _testProjectFolders = null!; protected int _preparedTests = 0; @@ -79,6 +82,21 @@ protected virtual void TestInitialize() _executionDriver = _testContainer.GetService(); _vsTestExecutionDriver = _testContainer.GetService(); _compilationDriver = _testContainer.GetService(); + _testProjectFolders = _testContainer.GetService(); + _bindingDriver = _testContainer.GetService(); + } + + + [TestCleanup] + public void TestCleanupMethod() + { + TestCleanup(); + } + + protected virtual void TestCleanup() + { + if (TestContext.CurrentTestOutcome == UnitTestOutcome.Passed) + _folderCleaner.CleanSolutionFolder(); } protected void AddFeatureFileFromResource(string fileName, int? preparedTests = null) @@ -134,6 +152,41 @@ protected void AddScenario(string scenarioContent, int? preparedTests = null) UpdatePreparedTests(scenarioContent, preparedTests); } + protected void AddSimpleScenario() + { + AddScenario( + """ + Scenario: Sample Scenario + When something happens + """); + } + + protected void AddSimpleScenarioOutline(int numberOfExamples = 2) + { + var examples = numberOfExamples == 2 + ? """ + | me | + | you | + """ + : string.Join( + Environment.NewLine, + Enumerable.Range(1, numberOfExamples).Select(i => $" | example {i} |")); + AddScenario( + $""" + Scenario Outline: Scenario outline with examples + When something happens to + Examples: + | person | + {examples} + """); + } + + protected void AddSimpleScenarioAndOutline() + { + AddSimpleScenario(); + AddSimpleScenarioOutline(); + } + private void UpdatePreparedTests(string gherkinContent, int? preparedTests) { if (preparedTests == null) @@ -161,13 +214,33 @@ protected void ExecuteTests() protected void ShouldAllScenariosPass(int? expectedNrOfTestsSpec = null) { - if (expectedNrOfTestsSpec == null && _preparedTests == 0) + int expectedNrOfTests = ConfirmAllTestsRan(expectedNrOfTestsSpec); + _vsTestExecutionDriver.LastTestExecutionResult.Succeeded.Should().Be(expectedNrOfTests, "all tests should pass"); + } + + protected int ConfirmAllTestsRan(int? expectedNrOfTestsSpec) + { + if (expectedNrOfTestsSpec == null && _preparedTests == 0) throw new ArgumentException($"If {nameof(_preparedTests)} is not set, the {nameof(expectedNrOfTestsSpec)} is mandatory.", nameof(expectedNrOfTestsSpec)); var expectedNrOfTests = expectedNrOfTestsSpec ?? _preparedTests; _vsTestExecutionDriver.LastTestExecutionResult.Should().NotBeNull(); _vsTestExecutionDriver.LastTestExecutionResult.Total.Should().Be(expectedNrOfTests, $"the run should contain {expectedNrOfTests} tests"); - _vsTestExecutionDriver.LastTestExecutionResult.Succeeded.Should().Be(expectedNrOfTests, "all tests should pass"); - _folderCleaner.CleanSolutionFolder(); + return expectedNrOfTests; + } + + protected void AddHookBinding(string eventType, string? name = null, string code = "") + { + _projectsDriver.AddHookBinding(eventType, name, code: code); + } + + protected void AddPassingStepBinding(string scenarioBlock = "StepDefinition", string stepRegex = ".*") + { + _projectsDriver.AddPassingStepBinding(scenarioBlock, stepRegex); + } + + protected void AddBindingClass(string content) + { + _projectsDriver.AddBindingClass(content); } } From 1a042c4e5df5d159c6ae160dbec48bb2f93e1505 Mon Sep 17 00:00:00 2001 From: Gaspar Nagy Date: Wed, 17 Apr 2024 14:02:40 +0200 Subject: [PATCH 05/21] Make SystemTests temp folder configurable and use NUGET_PACKAGES env var to override global NuGet folder --- .../Reqnroll.TestProjectGenerator/ConfigurationDriver.cs | 2 ++ .../Reqnroll.TestProjectGenerator/Folders.cs | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/ConfigurationDriver.cs b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/ConfigurationDriver.cs index 04182607d..bbb60346e 100644 --- a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/ConfigurationDriver.cs +++ b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/ConfigurationDriver.cs @@ -1,10 +1,12 @@ using System; using System.Configuration; +using System.IO; namespace Reqnroll.TestProjectGenerator; public class ConfigurationDriver { + public string TempFolderPath => GetConfigSetting("tempFolder", Path.GetTempPath()); public string TestProjectFolderName => GetConfigSetting("testProjectFolder", "RR"); public string VSTestPath => GetConfigSetting("vstestPath", "Common7\\IDE\\CommonExtensions\\Microsoft\\TestWindow"); public string MsBuildPath => GetConfigSetting(nameof(MsBuildPath)); diff --git a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Folders.cs b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Folders.cs index ab12317cd..b0feb2c42 100644 --- a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Folders.cs +++ b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Folders.cs @@ -69,7 +69,9 @@ public string PackageFolder set => _packageFolder = value; } - public string SystemGlobalNuGetPackages => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages"); + public string SystemGlobalNuGetPackages => + Environment.GetEnvironmentVariable("NUGET_PACKAGES") ?? + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages"); public string Reqnroll { @@ -77,7 +79,7 @@ public string Reqnroll set => _reqnroll = value; } - public virtual string FolderToSaveGeneratedSolutions => Path.Combine(Path.GetTempPath(), _configurationDriver.TestProjectFolderName); + public virtual string FolderToSaveGeneratedSolutions => Path.Combine(_configurationDriver.TempFolderPath, _configurationDriver.TestProjectFolderName); public virtual string RunUniqueFolderToSaveGeneratedSolutions => Path.Combine(FolderToSaveGeneratedSolutions, _artifactNamingConvention.GetRunName(UniqueRunId)); public virtual string GlobalNuGetPackages => _configurationDriver.PipelineMode ? SystemGlobalNuGetPackages : _configurationDriver.GlobalNuGetPackages ?? Path.Combine(RunUniqueFolderToSaveGeneratedSolutions, ".nuget"); From c0fc4dae5c58462fc79ad00bbfe769ff72786f5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1sp=C3=A1r=20Nagy?= Date: Thu, 18 Apr 2024 01:16:01 +0200 Subject: [PATCH 06/21] Simplify test project targets (#105) * remove old build & CI files * remove Reqnroll.MsBuildNetSdk.IntegrationTests (replced by SystemTests) * simplify unit test target * remove unused nuget package sources * simplify unit test target * fix build * update NCrunch config * Simplify Specs * fix build * fix specs job * use GitHubActionsTestLogger * add trx logger for unit tests * fix spec tests * merge Reqnroll.ExternalData.ReqnrollPlugin.UnitTests to Reqnroll.PluginTests * Cleanup plugin integration tests * remove tests from Specs that are already handled by SystemTests * remove setup .NET from build --- .github/workflows/ci.yml | 132 ++------ ...lPlugin.IntegrationTest.v3.ncrunchproject} | 0 ...ReqnrollPlugin.UnitTests.v3.ncrunchproject | 8 - .../Reqnroll.GeneratorTests.v3.ncrunchproject | 3 - ...dNetSdk.IntegrationTests.v3.ncrunchproject | 8 - .../Reqnroll.PluginTests.v3.ncrunchproject | 3 - .../Reqnroll.RuntimeTests.v3.ncrunchproject | 3 - ...Generator.ReqnrollPlugin.v3.ncrunchproject | 8 - .ncrunch/Reqnroll.Specs.v3.ncrunchproject | 3 - ...TestProjectGenerator.Cli.v3.ncrunchproject | 3 - ...ectGenerator.Tests.net60.v3.ncrunchproject | 5 - ...tProjectGenerator.net471.v3.ncrunchproject | 5 - ...stProjectGenerator.net60.v3.ncrunchproject | 5 - ...llPlugin.IntegrationTest.v3.ncrunchproject | 3 - .ncrunch/Specs.v3.ncrunchproject | 8 - Build/Versioning.targets | 7 - Dockerfile | 32 -- .../AssemblyHooks.cs | 3 +- .../Features/ExternalDataFromCSV.feature | 0 .../Features/ExternalDataFromExcel-HU.feature | 0 .../Features/ExternalDataFromExcel.feature | 0 .../Features}/products.csv | 0 .../Features/products.xlsx | Bin ...ata.ReqnrollPlugin.IntegrationTest.csproj} | 20 +- .../Specs.sln | 0 .../StepDefinitions/PricingStepDefinitions.cs | 3 +- .../testdata.json | 0 ...ternalData.ReqnrollPlugin.UnitTests.csproj | 47 --- .../Steps/CommentBindings.cs | 36 --- .../Steps/RegistrationBindings.cs | 57 ---- ...rify.ReqnrollPlugin.IntegrationTest.csproj | 3 +- .../StepDefinitions.cs} | 6 +- Reqnroll.sln | 300 +----------------- .../Reqnroll.GeneratorTests.csproj | 4 +- .../App.config | 17 - .../Bindings/DoNothingBinding.cs | 21 -- .../CodeBehindFileGenerationTests.cs | 19 -- .../Features/dummy.feature | 7 - ...roll.MsBuildNetSdk.IntegrationTests.csproj | 95 ------ .../ExternalData}/CsvLoaderTests.cs | 6 +- .../DataSourceLoaderFactoryTests.cs | 3 +- .../DataSourceSelectorParserTests.cs | 2 +- .../Selectors/DataSourceSelectorTests.cs | 3 +- .../ExternalData}/ExcelLoaderTests.cs | 4 +- .../ExternalDataSpecificationTests.cs | 6 +- .../IncludeExternalDataTransformationTests.cs | 4 +- .../SampleFiles/products-empty.csv | 0 .../SampleFiles/products-invalid.csv | 0 .../SampleFiles/products-special.csv | 0 .../ExternalData/SampleFiles}/products.csv | 0 .../ExternalData}/SampleFiles/products.xlsx | Bin .../ScenarioTransformationTests.cs | 2 +- .../SpecificationProviderTests.cs | 3 +- .../Reqnroll.PluginTests.csproj | 21 +- .../Reqnroll.RuntimeTests.csproj | 2 +- .../Combination.cs | 11 - .../CombinationFeatureGenerator.cs | 19 -- .../CustomXUnitGeneratorProvider.cs | 45 --- .../GeneratorPlugin.cs | 30 -- .../MultiFeatureGenerator.cs | 207 ------------ .../MultiFeatureGeneratorProvider.cs | 43 --- ...roll.Specs.Generator.ReqnrollPlugin.csproj | 43 --- .../TestRunCombinations.cs | 35 -- .../Drivers/Parser/ParserDriver.cs | 7 +- .../Features/Build systems.feature | 33 -- .../Features/LanguageSupport.feature | 55 ---- .../Features/MultipleSpecsProjects.feature | 27 -- .../Features/RegressionTests/GH2571.feature | 76 ----- .../RegressionTests/NUnit/GH1052.feature | 2 + .../SingleFileGenerator Checks.feature | 12 - .../MsTest/DeploymentItem.feature | 1 - .../XUnit/XUnit2Provider.feature | 2 + Tests/Reqnroll.Specs/Reqnroll.Specs.csproj | 11 +- Tests/Reqnroll.Specs/Support/Hooks.cs | 11 + azure-pipelines.yml | 48 --- build.ps1 | 23 -- build.yml | 93 ------ clean.ps1 | 1 - dotnet_sdk_validation_build.yml | 45 --- nightly-build.yml | 58 ---- nuget.config | 8 - reqnroll.yml | 9 - runtests.ps1 | 14 - 83 files changed, 106 insertions(+), 1793 deletions(-) rename .ncrunch/{Reqnroll.TestProjectGenerator.Tests.net48.v3.ncrunchproject => Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest.v3.ncrunchproject} (100%) delete mode 100644 .ncrunch/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests.v3.ncrunchproject delete mode 100644 .ncrunch/Reqnroll.MsBuildNetSdk.IntegrationTests.v3.ncrunchproject delete mode 100644 .ncrunch/Reqnroll.Specs.Generator.ReqnrollPlugin.v3.ncrunchproject delete mode 100644 .ncrunch/Reqnroll.TestProjectGenerator.Tests.net60.v3.ncrunchproject delete mode 100644 .ncrunch/Reqnroll.TestProjectGenerator.net471.v3.ncrunchproject delete mode 100644 .ncrunch/Reqnroll.TestProjectGenerator.net60.v3.ncrunchproject delete mode 100644 .ncrunch/Specs.v3.ncrunchproject delete mode 100644 Build/Versioning.targets delete mode 100644 Dockerfile rename Plugins/Reqnroll.ExternalData/{sample/ExternalDataSample => Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest}/AssemblyHooks.cs (92%) rename Plugins/Reqnroll.ExternalData/{sample/ExternalDataSample => Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest}/Features/ExternalDataFromCSV.feature (100%) rename Plugins/Reqnroll.ExternalData/{sample/ExternalDataSample => Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest}/Features/ExternalDataFromExcel-HU.feature (100%) rename Plugins/Reqnroll.ExternalData/{sample/ExternalDataSample => Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest}/Features/ExternalDataFromExcel.feature (100%) rename Plugins/Reqnroll.ExternalData/{Reqnroll.ExternalData.ReqnrollPlugin.UnitTests/SampleFiles => Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest/Features}/products.csv (100%) rename Plugins/Reqnroll.ExternalData/{sample/ExternalDataSample => Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest}/Features/products.xlsx (100%) rename Plugins/Reqnroll.ExternalData/{sample/ExternalDataSample/Specs.csproj => Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest.csproj} (53%) rename Plugins/Reqnroll.ExternalData/{sample/ExternalDataSample => Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest}/Specs.sln (100%) rename Plugins/Reqnroll.ExternalData/{sample/ExternalDataSample => Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest}/StepDefinitions/PricingStepDefinitions.cs (97%) rename Plugins/Reqnroll.ExternalData/{sample/ExternalDataSample => Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest}/testdata.json (100%) delete mode 100644 Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests.csproj delete mode 100644 Plugins/Reqnroll.ExternalData/sample/ExternalDataSample/Steps/CommentBindings.cs delete mode 100644 Plugins/Reqnroll.ExternalData/sample/ExternalDataSample/Steps/RegistrationBindings.cs rename Plugins/Reqnroll.Verify/Reqnroll.Verify.ReqnrollPlugin.IntegrationTest/{Steps/Steps.cs => StepDefinitions/StepDefinitions.cs} (79%) delete mode 100644 Tests/Reqnroll.MsBuildNetSdk.IntegrationTests/App.config delete mode 100644 Tests/Reqnroll.MsBuildNetSdk.IntegrationTests/Bindings/DoNothingBinding.cs delete mode 100644 Tests/Reqnroll.MsBuildNetSdk.IntegrationTests/CodeBehindFileGenerationTests.cs delete mode 100644 Tests/Reqnroll.MsBuildNetSdk.IntegrationTests/Features/dummy.feature delete mode 100644 Tests/Reqnroll.MsBuildNetSdk.IntegrationTests/Reqnroll.MsBuildNetSdk.IntegrationTests.csproj rename {Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests => Tests/Reqnroll.PluginTests/ExternalData}/CsvLoaderTests.cs (96%) rename {Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests => Tests/Reqnroll.PluginTests/ExternalData}/DataSourceLoaderFactoryTests.cs (97%) rename {Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests => Tests/Reqnroll.PluginTests/ExternalData}/DataSources/Selectors/DataSourceSelectorParserTests.cs (88%) rename {Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests => Tests/Reqnroll.PluginTests/ExternalData}/DataSources/Selectors/DataSourceSelectorTests.cs (94%) rename {Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests => Tests/Reqnroll.PluginTests/ExternalData}/ExcelLoaderTests.cs (97%) rename {Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests => Tests/Reqnroll.PluginTests/ExternalData}/ExternalDataSpecificationTests.cs (96%) rename {Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests => Tests/Reqnroll.PluginTests/ExternalData}/IncludeExternalDataTransformationTests.cs (98%) rename {Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests => Tests/Reqnroll.PluginTests/ExternalData}/SampleFiles/products-empty.csv (100%) rename {Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests => Tests/Reqnroll.PluginTests/ExternalData}/SampleFiles/products-invalid.csv (100%) rename {Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests => Tests/Reqnroll.PluginTests/ExternalData}/SampleFiles/products-special.csv (100%) rename {Plugins/Reqnroll.ExternalData/sample/ExternalDataSample/Features => Tests/Reqnroll.PluginTests/ExternalData/SampleFiles}/products.csv (100%) rename {Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests => Tests/Reqnroll.PluginTests/ExternalData}/SampleFiles/products.xlsx (100%) rename {Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests => Tests/Reqnroll.PluginTests/ExternalData}/ScenarioTransformationTests.cs (99%) rename {Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests => Tests/Reqnroll.PluginTests/ExternalData}/SpecificationProviderTests.cs (98%) delete mode 100644 Tests/Reqnroll.Specs.Generator.ReqnrollPlugin/Combination.cs delete mode 100644 Tests/Reqnroll.Specs.Generator.ReqnrollPlugin/CombinationFeatureGenerator.cs delete mode 100644 Tests/Reqnroll.Specs.Generator.ReqnrollPlugin/CustomXUnitGeneratorProvider.cs delete mode 100644 Tests/Reqnroll.Specs.Generator.ReqnrollPlugin/GeneratorPlugin.cs delete mode 100644 Tests/Reqnroll.Specs.Generator.ReqnrollPlugin/MultiFeatureGenerator.cs delete mode 100644 Tests/Reqnroll.Specs.Generator.ReqnrollPlugin/MultiFeatureGeneratorProvider.cs delete mode 100644 Tests/Reqnroll.Specs.Generator.ReqnrollPlugin/Reqnroll.Specs.Generator.ReqnrollPlugin.csproj delete mode 100644 Tests/Reqnroll.Specs.Generator.ReqnrollPlugin/TestRunCombinations.cs delete mode 100644 Tests/Reqnroll.Specs/Features/Build systems.feature delete mode 100644 Tests/Reqnroll.Specs/Features/LanguageSupport.feature delete mode 100644 Tests/Reqnroll.Specs/Features/MultipleSpecsProjects.feature delete mode 100644 Tests/Reqnroll.Specs/Features/RegressionTests/GH2571.feature delete mode 100644 Tests/Reqnroll.Specs/Features/SingleFileGenerator Checks.feature delete mode 100644 azure-pipelines.yml delete mode 100644 build.ps1 delete mode 100644 build.yml delete mode 100644 clean.ps1 delete mode 100644 dotnet_sdk_validation_build.yml delete mode 100644 nightly-build.yml delete mode 100644 reqnroll.yml delete mode 100644 runtests.ps1 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c782b131d..bc33a26ac 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,15 +50,12 @@ jobs: build_params: ${{ steps.versions.outputs.build_params }} test_params: ${{ steps.versions.outputs.test_params }} specs_filter: ${{ steps.versions.outputs.specs_filter }} + gh_logger_settings: ${{ steps.versions.outputs.gh_logger_settings }} steps: - uses: actions/checkout@v4 with: fetch-depth: 0 # avoid shallow clone so nbgv can do its work. - - name: Setup .NET - uses: actions/setup-dotnet@v4 - with: - dotnet-version: 8.0.x - id: versions name: Calculate versions shell: pwsh @@ -115,22 +112,26 @@ jobs: } Write-Output "main_build_params=$mainBuildParams" >> $env:GITHUB_OUTPUT - - $testParams = "--no-build --verbosity normal -c $productConfig" + $gitHubActionsLoggerSettings = '"GitHubActions;summary.includePassedTests=true;summary.includeSkippedTests=true;annotations.titleFormat=[@traits.Category] @test;annotations.messageFormat=@error\n@trace"' + $testParams = "--no-build --verbosity normal -c $productConfig --logger $gitHubActionsLoggerSettings -- RunConfiguration.CollectSourceInformation=true" Write-Output "test_params=$testParams" >> $env:GITHUB_OUTPUT Write-Output "Test Params: $testParams" + - name: Restore dependencies run: dotnet restore + - name: Install Test Report Dependencies + run: | + dotnet add ./Tests/Reqnroll.RuntimeTests/Reqnroll.RuntimeTests.csproj package GitHubActionsTestLogger + dotnet add ./Tests/Reqnroll.PluginTests/Reqnroll.PluginTests.csproj package GitHubActionsTestLogger + dotnet add ./Tests/Reqnroll.GeneratorTests/Reqnroll.GeneratorTests.csproj package GitHubActionsTestLogger - name: Build run: dotnet build --no-restore ${{ steps.versions.outputs.main_build_params }} - name: Runtime Tests - run: dotnet test ./Tests/Reqnroll.RuntimeTests/Reqnroll.RuntimeTests.csproj ${{ steps.versions.outputs.test_params }} -f net6.0 - - name: Plugin Tests - run: dotnet test ./Tests/Reqnroll.PluginTests/Reqnroll.PluginTests.csproj ${{ steps.versions.outputs.test_params }} -f net6.0 + run: dotnet test ./Tests/Reqnroll.RuntimeTests/Reqnroll.RuntimeTests.csproj --logger "trx;LogFileName=runtimetests-results.trx" ${{ steps.versions.outputs.test_params }} - name: Generator Tests - run: dotnet test ./Tests/Reqnroll.GeneratorTests/Reqnroll.GeneratorTests.csproj ${{ steps.versions.outputs.test_params }} -f net6.0 - - name: ExternalData Plugin Tests - run: dotnet test ./Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests.csproj ${{ steps.versions.outputs.test_params }} -f net6.0 + run: dotnet test ./Tests/Reqnroll.GeneratorTests/Reqnroll.GeneratorTests.csproj --logger "trx;LogFileName=generatortests-results.trx" ${{ steps.versions.outputs.test_params }} + - name: Plugin Tests + run: dotnet test ./Tests/Reqnroll.PluginTests/Reqnroll.PluginTests.csproj --logger "trx;LogFileName=plugintests-results.trx" ${{ steps.versions.outputs.test_params }} - name: Upload packages uses: actions/upload-artifact@v4 with: @@ -143,43 +144,7 @@ jobs: name: build-trx path: "**/*.trx" - specs-xunit: - runs-on: ubuntu-latest - needs: build - - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 # avoid shallow clone so nbgv can do its work. - - name: Setup .NET - uses: actions/setup-dotnet@v4 - with: - dotnet-version: | - 6.0.x - - name: Restore dependencies - run: dotnet restore - - name: Build - run: dotnet build --no-restore ${{ needs.build.outputs.build_params }} - - name: Set .NET 6 SDK - run: dotnet new globaljson --sdk-version 6.0.418 - - name: xUnit Specs - shell: pwsh - run: dotnet test ./Tests/Reqnroll.Specs/Reqnroll.Specs.csproj ${{ needs.build.outputs.test_params }} -f net6.0 --filter "Category=xUnit&Category=Net60&Category!=requiresMsBuild${{ needs.build.outputs.specs_filter }}" --logger "trx;LogFileName=specs-xunit-results.trx" - - name: Publish xUnit Test Results - uses: EnricoMi/publish-unit-test-result-action@v2 - if: always() - with: - check_name: xUnit Specs - files: "**/specs-xunit-results.trx" - comment_mode: off - - name: Upload TRX files - uses: actions/upload-artifact@v4 - if: always() - with: - name: specs-xunit-trx - path: "**/specs-xunit-results.trx" - - specs-nunit: + specs: runs-on: ubuntu-latest needs: build @@ -187,69 +152,22 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 # avoid shallow clone so nbgv can do its work. - - name: Setup .NET - uses: actions/setup-dotnet@v4 - with: - dotnet-version: | - 6.0.x - - name: Restore dependencies - run: dotnet restore - - name: Build - run: dotnet build --no-restore ${{ needs.build.outputs.build_params }} - - name: Set .NET 6 SDK - run: dotnet new globaljson --sdk-version 6.0.418 - - name: NUnit Specs - shell: pwsh - run: dotnet test ./Tests/Reqnroll.Specs/Reqnroll.Specs.csproj ${{ needs.build.outputs.test_params }} -f net6.0 --filter "Category=NUnit3&Category=Net60&Category!=requiresMsBuild${{ needs.build.outputs.specs_filter }}" --logger "trx;LogFileName=specs-nunit-results.trx" - - name: Publish NUnit Test Results - uses: EnricoMi/publish-unit-test-result-action@v2 - if: always() - with: - check_name: NUnit Specs - files: "**/specs-nunit-results.trx" - comment_mode: off - - name: Upload TRX files - uses: actions/upload-artifact@v4 - if: always() - with: - name: specs-nunit-trx - path: "**/specs-nunit-results.trx" - - specs-mstest: - runs-on: ubuntu-latest - needs: build - - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 # avoid shallow clone so nbgv can do its work. - - name: Setup .NET - uses: actions/setup-dotnet@v4 - with: - dotnet-version: | - 6.0.x - name: Restore dependencies run: dotnet restore + - name: Install Test Report Dependencies + run: | + dotnet add ./Tests/Reqnroll.Specs/Reqnroll.Specs.csproj package GitHubActionsTestLogger - name: Build run: dotnet build --no-restore ${{ needs.build.outputs.build_params }} - - name: Set .NET 6 SDK - run: dotnet new globaljson --sdk-version 6.0.418 - - name: MsTest Specs + - name: Specs shell: pwsh - run: dotnet test ./Tests/Reqnroll.Specs/Reqnroll.Specs.csproj ${{ needs.build.outputs.test_params }} -f net6.0 --filter "Category=MsTest&Category=Net60&Category!=requiresMsBuild${{ needs.build.outputs.specs_filter }}" --logger "trx;LogFileName=specs-mstest-results.trx" - - name: Publish MSTest Test Results - uses: EnricoMi/publish-unit-test-result-action@v2 - if: always() - with: - check_name: MSTest Specs - files: "**/specs-mstest-results.trx" - comment_mode: off + run: dotnet test ./Tests/Reqnroll.Specs/Reqnroll.Specs.csproj --filter "Category!=quarantaine{{ needs.build.outputs.specs_filter }}" --logger "trx;LogFileName=specs-results.trx" ${{ needs.build.outputs.test_params }} - name: Upload TRX files uses: actions/upload-artifact@v4 if: always() with: - name: specs-mstest-trx - path: "**/specs-mstest-results.trx" + name: specs-trx + path: "**/specs-results.trx" system-tests-windows: runs-on: windows-latest @@ -272,9 +190,7 @@ jobs: run: dotnet build --no-restore ${{ needs.build.outputs.build_params }} - name: System Tests shell: pwsh - run: | - $gitHubActionsLoggerSettings = '"GitHubActions;summary.includePassedTests=true;summary.includeSkippedTests=true;annotations.titleFormat=[@traits.Category] @test;annotations.messageFormat=@error\n@trace"' - dotnet test ./Tests/Reqnroll.SystemTests/Reqnroll.SystemTests.csproj ${{ needs.build.outputs.test_params }} --logger "trx;LogFileName=systemtests-windows-results.trx" --logger $gitHubActionsLoggerSettings -- RunConfiguration.CollectSourceInformation=true + run: dotnet test ./Tests/Reqnroll.SystemTests/Reqnroll.SystemTests.csproj --logger "trx;LogFileName=systemtests-windows-results.trx" ${{ needs.build.outputs.test_params }} - name: Upload Test Result TRX Files uses: actions/upload-artifact@v4 if: always() @@ -304,9 +220,7 @@ jobs: run: dotnet build --no-restore ${{ needs.build.outputs.build_params }} - name: System Tests shell: pwsh - run: | - $gitHubActionsLoggerSettings = '"GitHubActions;summary.includePassedTests=true;summary.includeSkippedTests=true;annotations.titleFormat=[@traits.Category] @test;annotations.messageFormat=@error\n@trace"' - dotnet test ./Tests/Reqnroll.SystemTests/Reqnroll.SystemTests.csproj ${{ needs.build.outputs.test_params }} --filter "TestCategory!=MsBuild&TestCategory!=Net481" --logger "trx;LogFileName=systemtests-linux-results.trx" --logger $gitHubActionsLoggerSettings -- RunConfiguration.CollectSourceInformation=true + run: dotnet test ./Tests/Reqnroll.SystemTests/Reqnroll.SystemTests.csproj --filter "TestCategory!=MsBuild&TestCategory!=Net481" --logger "trx;LogFileName=systemtests-linux-results.trx" ${{ needs.build.outputs.test_params }} - name: Upload Test Result TRX Files uses: actions/upload-artifact@v4 if: always() diff --git a/.ncrunch/Reqnroll.TestProjectGenerator.Tests.net48.v3.ncrunchproject b/.ncrunch/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest.v3.ncrunchproject similarity index 100% rename from .ncrunch/Reqnroll.TestProjectGenerator.Tests.net48.v3.ncrunchproject rename to .ncrunch/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest.v3.ncrunchproject diff --git a/.ncrunch/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests.v3.ncrunchproject b/.ncrunch/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests.v3.ncrunchproject deleted file mode 100644 index 6c4b990aa..000000000 --- a/.ncrunch/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests.v3.ncrunchproject +++ /dev/null @@ -1,8 +0,0 @@ - - - - TargetFrameworks = net6.0 - - True - - \ No newline at end of file diff --git a/.ncrunch/Reqnroll.GeneratorTests.v3.ncrunchproject b/.ncrunch/Reqnroll.GeneratorTests.v3.ncrunchproject index 40d7f9349..9a0eea7aa 100644 --- a/.ncrunch/Reqnroll.GeneratorTests.v3.ncrunchproject +++ b/.ncrunch/Reqnroll.GeneratorTests.v3.ncrunchproject @@ -1,8 +1,5 @@  - - TargetFrameworks = net6.0 - True False diff --git a/.ncrunch/Reqnroll.MsBuildNetSdk.IntegrationTests.v3.ncrunchproject b/.ncrunch/Reqnroll.MsBuildNetSdk.IntegrationTests.v3.ncrunchproject deleted file mode 100644 index 6c4b990aa..000000000 --- a/.ncrunch/Reqnroll.MsBuildNetSdk.IntegrationTests.v3.ncrunchproject +++ /dev/null @@ -1,8 +0,0 @@ - - - - TargetFrameworks = net6.0 - - True - - \ No newline at end of file diff --git a/.ncrunch/Reqnroll.PluginTests.v3.ncrunchproject b/.ncrunch/Reqnroll.PluginTests.v3.ncrunchproject index 6c4b990aa..319cd523c 100644 --- a/.ncrunch/Reqnroll.PluginTests.v3.ncrunchproject +++ b/.ncrunch/Reqnroll.PluginTests.v3.ncrunchproject @@ -1,8 +1,5 @@  - - TargetFrameworks = net6.0 - True \ No newline at end of file diff --git a/.ncrunch/Reqnroll.RuntimeTests.v3.ncrunchproject b/.ncrunch/Reqnroll.RuntimeTests.v3.ncrunchproject index 40d7f9349..9a0eea7aa 100644 --- a/.ncrunch/Reqnroll.RuntimeTests.v3.ncrunchproject +++ b/.ncrunch/Reqnroll.RuntimeTests.v3.ncrunchproject @@ -1,8 +1,5 @@  - - TargetFrameworks = net6.0 - True False diff --git a/.ncrunch/Reqnroll.Specs.Generator.ReqnrollPlugin.v3.ncrunchproject b/.ncrunch/Reqnroll.Specs.Generator.ReqnrollPlugin.v3.ncrunchproject deleted file mode 100644 index 6c4b990aa..000000000 --- a/.ncrunch/Reqnroll.Specs.Generator.ReqnrollPlugin.v3.ncrunchproject +++ /dev/null @@ -1,8 +0,0 @@ - - - - TargetFrameworks = net6.0 - - True - - \ No newline at end of file diff --git a/.ncrunch/Reqnroll.Specs.v3.ncrunchproject b/.ncrunch/Reqnroll.Specs.v3.ncrunchproject index 6c4b990aa..319cd523c 100644 --- a/.ncrunch/Reqnroll.Specs.v3.ncrunchproject +++ b/.ncrunch/Reqnroll.Specs.v3.ncrunchproject @@ -1,8 +1,5 @@  - - TargetFrameworks = net6.0 - True \ No newline at end of file diff --git a/.ncrunch/Reqnroll.TestProjectGenerator.Cli.v3.ncrunchproject b/.ncrunch/Reqnroll.TestProjectGenerator.Cli.v3.ncrunchproject index 8cffa5048..d3fdf31e4 100644 --- a/.ncrunch/Reqnroll.TestProjectGenerator.Cli.v3.ncrunchproject +++ b/.ncrunch/Reqnroll.TestProjectGenerator.Cli.v3.ncrunchproject @@ -1,7 +1,4 @@  - - TargetFrameworks = net6.0 - \ No newline at end of file diff --git a/.ncrunch/Reqnroll.TestProjectGenerator.Tests.net60.v3.ncrunchproject b/.ncrunch/Reqnroll.TestProjectGenerator.Tests.net60.v3.ncrunchproject deleted file mode 100644 index 319cd523c..000000000 --- a/.ncrunch/Reqnroll.TestProjectGenerator.Tests.net60.v3.ncrunchproject +++ /dev/null @@ -1,5 +0,0 @@ - - - True - - \ No newline at end of file diff --git a/.ncrunch/Reqnroll.TestProjectGenerator.net471.v3.ncrunchproject b/.ncrunch/Reqnroll.TestProjectGenerator.net471.v3.ncrunchproject deleted file mode 100644 index 319cd523c..000000000 --- a/.ncrunch/Reqnroll.TestProjectGenerator.net471.v3.ncrunchproject +++ /dev/null @@ -1,5 +0,0 @@ - - - True - - \ No newline at end of file diff --git a/.ncrunch/Reqnroll.TestProjectGenerator.net60.v3.ncrunchproject b/.ncrunch/Reqnroll.TestProjectGenerator.net60.v3.ncrunchproject deleted file mode 100644 index 319cd523c..000000000 --- a/.ncrunch/Reqnroll.TestProjectGenerator.net60.v3.ncrunchproject +++ /dev/null @@ -1,5 +0,0 @@ - - - True - - \ No newline at end of file diff --git a/.ncrunch/Reqnroll.Verify.ReqnrollPlugin.IntegrationTest.v3.ncrunchproject b/.ncrunch/Reqnroll.Verify.ReqnrollPlugin.IntegrationTest.v3.ncrunchproject index 6c4b990aa..319cd523c 100644 --- a/.ncrunch/Reqnroll.Verify.ReqnrollPlugin.IntegrationTest.v3.ncrunchproject +++ b/.ncrunch/Reqnroll.Verify.ReqnrollPlugin.IntegrationTest.v3.ncrunchproject @@ -1,8 +1,5 @@  - - TargetFrameworks = net6.0 - True \ No newline at end of file diff --git a/.ncrunch/Specs.v3.ncrunchproject b/.ncrunch/Specs.v3.ncrunchproject deleted file mode 100644 index 6c4b990aa..000000000 --- a/.ncrunch/Specs.v3.ncrunchproject +++ /dev/null @@ -1,8 +0,0 @@ - - - - TargetFrameworks = net6.0 - - True - - \ No newline at end of file diff --git a/Build/Versioning.targets b/Build/Versioning.targets deleted file mode 100644 index ed47888b5..000000000 --- a/Build/Versioning.targets +++ /dev/null @@ -1,7 +0,0 @@ - - - - 3.5.0 - - - \ No newline at end of file diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index ddc00ccbc..000000000 --- a/Dockerfile +++ /dev/null @@ -1,32 +0,0 @@ -FROM mcr.microsoft.com/dotnet/core/sdk:3.1.100-buster - -RUN apt update \ - && apt install -y git mono-complete \ - && dotnet tool install --global PowerShell - -RUN wget -q https://packages.microsoft.com/config/ubuntu/18.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb \ - && dpkg -i packages-microsoft-prod.deb \ - && apt update \ - && apt install apt-transport-https -y \ - && apt update -RUN apt install dotnet-sdk-3.1 -y - -# RUN git clone --recurse-submodules--single-branch --branch updateLinuxBuild -j8 https://github.com/reqnroll/Reqnroll.git /src \ -# && ls -la - -WORKDIR /src - -COPY . . - -RUN git clean -fdx - -# build project -# RUN pwsh /src/build.ps1 - -RUN pwsh /src/build.ps1 - -#CMD /bin/sh -# CMD dotnet test /src/*.sln -v n --no-build --logger "trx;LogFileName=TestResults.trx" -# CMD dotnet test ./Tests/Reqnroll.Specs/Reqnroll.Specs.csproj -v n --no-build --logger "trx;LogFileName=TestResults.trx" --filter "BasicScenarioExecutionFeature_MSTest" -# CMD dotnet test Reqnroll.sln -v n --no-build --logger "trx;LogFileName=TestResults.trx" --filter "BasicScenarioExecutionFeature_MSTest" && /bin/sh -CMD /bin/sh diff --git a/Plugins/Reqnroll.ExternalData/sample/ExternalDataSample/AssemblyHooks.cs b/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest/AssemblyHooks.cs similarity index 92% rename from Plugins/Reqnroll.ExternalData/sample/ExternalDataSample/AssemblyHooks.cs rename to Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest/AssemblyHooks.cs index f3878e27f..6e3f1895a 100644 --- a/Plugins/Reqnroll.ExternalData/sample/ExternalDataSample/AssemblyHooks.cs +++ b/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest/AssemblyHooks.cs @@ -1,8 +1,7 @@ using System.Threading.Tasks; using NUnit.Framework; -using Reqnroll; -namespace Specs +namespace Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest { // This class is only required because in this sample application the generator // is not loaded from NuGet package. In a usual Reqnroll project it is not needed. diff --git a/Plugins/Reqnroll.ExternalData/sample/ExternalDataSample/Features/ExternalDataFromCSV.feature b/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest/Features/ExternalDataFromCSV.feature similarity index 100% rename from Plugins/Reqnroll.ExternalData/sample/ExternalDataSample/Features/ExternalDataFromCSV.feature rename to Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest/Features/ExternalDataFromCSV.feature diff --git a/Plugins/Reqnroll.ExternalData/sample/ExternalDataSample/Features/ExternalDataFromExcel-HU.feature b/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest/Features/ExternalDataFromExcel-HU.feature similarity index 100% rename from Plugins/Reqnroll.ExternalData/sample/ExternalDataSample/Features/ExternalDataFromExcel-HU.feature rename to Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest/Features/ExternalDataFromExcel-HU.feature diff --git a/Plugins/Reqnroll.ExternalData/sample/ExternalDataSample/Features/ExternalDataFromExcel.feature b/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest/Features/ExternalDataFromExcel.feature similarity index 100% rename from Plugins/Reqnroll.ExternalData/sample/ExternalDataSample/Features/ExternalDataFromExcel.feature rename to Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest/Features/ExternalDataFromExcel.feature diff --git a/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests/SampleFiles/products.csv b/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest/Features/products.csv similarity index 100% rename from Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests/SampleFiles/products.csv rename to Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest/Features/products.csv diff --git a/Plugins/Reqnroll.ExternalData/sample/ExternalDataSample/Features/products.xlsx b/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest/Features/products.xlsx similarity index 100% rename from Plugins/Reqnroll.ExternalData/sample/ExternalDataSample/Features/products.xlsx rename to Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest/Features/products.xlsx diff --git a/Plugins/Reqnroll.ExternalData/sample/ExternalDataSample/Specs.csproj b/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest.csproj similarity index 53% rename from Plugins/Reqnroll.ExternalData/sample/ExternalDataSample/Specs.csproj rename to Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest.csproj index b51b3fb20..0641b57a0 100644 --- a/Plugins/Reqnroll.ExternalData/sample/ExternalDataSample/Specs.csproj +++ b/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest.csproj @@ -1,10 +1,12 @@ - + - + - net6.0 + net8.0 + + XReqnroll.ExternalData.ReqnrollPlugin.IntegrationTest @@ -15,8 +17,8 @@ - - + + @@ -27,19 +29,19 @@ - - + + - + <_Reqnroll_TaskAssembly>..\..\Reqnroll.Tools.MsBuild.Generation\bin\$(Configuration)\$(_Reqnroll_Needed_MSBuildGenerator)\Reqnroll.Tools.MsBuild.Generation.dll - + diff --git a/Plugins/Reqnroll.ExternalData/sample/ExternalDataSample/Specs.sln b/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest/Specs.sln similarity index 100% rename from Plugins/Reqnroll.ExternalData/sample/ExternalDataSample/Specs.sln rename to Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest/Specs.sln diff --git a/Plugins/Reqnroll.ExternalData/sample/ExternalDataSample/StepDefinitions/PricingStepDefinitions.cs b/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest/StepDefinitions/PricingStepDefinitions.cs similarity index 97% rename from Plugins/Reqnroll.ExternalData/sample/ExternalDataSample/StepDefinitions/PricingStepDefinitions.cs rename to Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest/StepDefinitions/PricingStepDefinitions.cs index e2e0a0215..928c9b061 100644 --- a/Plugins/Reqnroll.ExternalData/sample/ExternalDataSample/StepDefinitions/PricingStepDefinitions.cs +++ b/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest/StepDefinitions/PricingStepDefinitions.cs @@ -2,9 +2,8 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; -using Reqnroll; -namespace Specs.StepDefinitions +namespace Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest.StepDefinitions { [Binding] public class PricingStepDefinitions diff --git a/Plugins/Reqnroll.ExternalData/sample/ExternalDataSample/testdata.json b/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest/testdata.json similarity index 100% rename from Plugins/Reqnroll.ExternalData/sample/ExternalDataSample/testdata.json rename to Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest/testdata.json diff --git a/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests.csproj b/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests.csproj deleted file mode 100644 index 8d9941699..000000000 --- a/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests.csproj +++ /dev/null @@ -1,47 +0,0 @@ - - - - $(Reqnroll_Test_TFM) - $(Reqnroll_KeyFile) - $(Reqnroll_EnableStrongNameSigning) - $(Reqnroll_PublicSign) - false - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - - - diff --git a/Plugins/Reqnroll.ExternalData/sample/ExternalDataSample/Steps/CommentBindings.cs b/Plugins/Reqnroll.ExternalData/sample/ExternalDataSample/Steps/CommentBindings.cs deleted file mode 100644 index 4a28b5f11..000000000 --- a/Plugins/Reqnroll.ExternalData/sample/ExternalDataSample/Steps/CommentBindings.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Reqnroll; -using Reqnroll.Infrastructure; - -namespace Specs.Steps -{ - [Binding] - public class CommentBindings - { - private readonly ScenarioContext _scenarioContext; - - public CommentBindings(ScenarioContext scenarioContext) - { - _scenarioContext = scenarioContext; - } - - [Given(@"a comment with text (.*)")] - public void GivenACommentWithText(string text) - { - } - - [Given(@"the screen height of (.*)px")] - public void GivenTheScreenHeightOfPx(int height) - { - } - - [When(@"the comment box is shown")] - public void WhenTheCommentBoxIsShown() - { - } - - [Then(@"the the comment size should not exceed (.*) lines")] - public void ThenTheTheCommentSizeShouldNotExceedLines(int lines) - { - } - } -} \ No newline at end of file diff --git a/Plugins/Reqnroll.ExternalData/sample/ExternalDataSample/Steps/RegistrationBindings.cs b/Plugins/Reqnroll.ExternalData/sample/ExternalDataSample/Steps/RegistrationBindings.cs deleted file mode 100644 index 90eb31be6..000000000 --- a/Plugins/Reqnroll.ExternalData/sample/ExternalDataSample/Steps/RegistrationBindings.cs +++ /dev/null @@ -1,57 +0,0 @@ -using NUnit.Framework; -using Reqnroll; - -namespace Specs.Steps -{ - [Binding] - public class RegistrationBindings - { - private readonly ScenarioContext _scenarioContext; - - private string _name; - private string _email; - private string _errorMessage = ""; - - public RegistrationBindings(ScenarioContext scenarioContext) - { - _scenarioContext = scenarioContext; - } - [Given(@"a visitor registering as ""(.*)"" with email (.*)")] - public void GivenAVisitorRegisteringAsWithEmail(string name, string email) - { - this._email = email; - this._name = name; - } - - [When(@"the registration completes")] - public void WhenTheRegistrationCompletes() - { - //Simulate some email validation - if (!_email.Contains("@")) - { - _email = null; - _errorMessage = "Invalid Email"; - } - } - - [Then(@"the account system should record (.*) related to user ""(.*)""")] - public void ThenTheAccountSystemShouldRecordEmailToUser(string email, string name) - { - Assert.AreEqual(name, _name); - Assert.AreEqual(email, _email); - } - - [Then(@"the account system should not record (.*)")] - public void ThenTheAccountSystemShouldNotRecord(string p0) - { - Assert.IsNull(_email); - } - - [Then(@"the error response should be ""(.*)""")] - public void ThenTheErrorResponseShouldBe(string expectedErrorMessage) - { - Assert.AreEqual(expectedErrorMessage, _errorMessage); - } - - } -} \ No newline at end of file diff --git a/Plugins/Reqnroll.Verify/Reqnroll.Verify.ReqnrollPlugin.IntegrationTest/Reqnroll.Verify.ReqnrollPlugin.IntegrationTest.csproj b/Plugins/Reqnroll.Verify/Reqnroll.Verify.ReqnrollPlugin.IntegrationTest/Reqnroll.Verify.ReqnrollPlugin.IntegrationTest.csproj index 8977b5fcb..c035f9d91 100644 --- a/Plugins/Reqnroll.Verify/Reqnroll.Verify.ReqnrollPlugin.IntegrationTest/Reqnroll.Verify.ReqnrollPlugin.IntegrationTest.csproj +++ b/Plugins/Reqnroll.Verify/Reqnroll.Verify.ReqnrollPlugin.IntegrationTest/Reqnroll.Verify.ReqnrollPlugin.IntegrationTest.csproj @@ -4,7 +4,7 @@ - net6.0 + net8.0 true @@ -47,7 +47,6 @@ - diff --git a/Plugins/Reqnroll.Verify/Reqnroll.Verify.ReqnrollPlugin.IntegrationTest/Steps/Steps.cs b/Plugins/Reqnroll.Verify/Reqnroll.Verify.ReqnrollPlugin.IntegrationTest/StepDefinitions/StepDefinitions.cs similarity index 79% rename from Plugins/Reqnroll.Verify/Reqnroll.Verify.ReqnrollPlugin.IntegrationTest/Steps/Steps.cs rename to Plugins/Reqnroll.Verify/Reqnroll.Verify.ReqnrollPlugin.IntegrationTest/StepDefinitions/StepDefinitions.cs index 492023486..f5f56e96e 100644 --- a/Plugins/Reqnroll.Verify/Reqnroll.Verify.ReqnrollPlugin.IntegrationTest/Steps/Steps.cs +++ b/Plugins/Reqnroll.Verify/Reqnroll.Verify.ReqnrollPlugin.IntegrationTest/StepDefinitions/StepDefinitions.cs @@ -1,9 +1,7 @@ -using Reqnroll; - -namespace Reqnroll.Verify.ReqnrollPlugin.IntegrationTest.Steps; +namespace Reqnroll.Verify.ReqnrollPlugin.IntegrationTest.StepDefinitions; [Binding] -internal class Steps +internal class StepDefinitions { [When("I try Verify with Reqnroll")] public async Task ITryVerifyWithReqnroll() diff --git a/Reqnroll.sln b/Reqnroll.sln index 36de45414..4828a1c46 100644 --- a/Reqnroll.sln +++ b/Reqnroll.sln @@ -10,17 +10,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig .gitignore = .gitignore - build.ps1 = build.ps1 CHANGELOG.md = CHANGELOG.md - clean.ps1 = clean.ps1 CommonAssemblyInfo.cs = CommonAssemblyInfo.cs - global.json = global.json LICENSE = LICENSE nuget.config = nuget.config README.md = README.md reqnroll-config.json = reqnroll-config.json Reqnroll.sln.DotSettings = Reqnroll.sln.DotSettings - version.json = version.json EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UnitTests", "UnitTests", "{0359B7D7-7E29-48E9-8DF9-7D1FACFA5CFA}" @@ -30,7 +26,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{D87E7021 Directory.Build.props = Directory.Build.props Directory.Build.rsp = Directory.Build.rsp Directory.Build.targets = Directory.Build.targets - Build\Versioning.targets = Build\Versioning.targets EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Reqnroll.Specs", "Tests\Reqnroll.Specs\Reqnroll.Specs.csproj", "{2D7D31B3-E8D0-445A-BB24-216ABA3CB778}" @@ -67,8 +62,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Plugins", "Plugins", "{8BE0 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Reqnroll.xUnit.ReqnrollPlugin", "Plugins\Reqnroll.xUnit.ReqnrollPlugin\Reqnroll.xUnit.ReqnrollPlugin.csproj", "{D649B28A-352C-4CB0-B6F2-0570DD181F60}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Reqnroll.Specs.Generator.ReqnrollPlugin", "Tests\Reqnroll.Specs.Generator.ReqnrollPlugin\Reqnroll.Specs.Generator.ReqnrollPlugin.csproj", "{03A229D6-BB43-47CC-AAEF-B7265652AD5A}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Reqnroll.NUnit.ReqnrollPlugin", "Plugins\Reqnroll.NUnit.ReqnrollPlugin\Reqnroll.NUnit.ReqnrollPlugin.csproj", "{D151832C-15D6-4869-9999-B245443BC23E}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Reqnroll.MSTest.ReqnrollPlugin", "Plugins\Reqnroll.MSTest.ReqnrollPlugin\Reqnroll.MSTest.ReqnrollPlugin.csproj", "{EC77355C-D442-416A-AE67-E42D5FAD0FB9}" @@ -83,14 +76,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Reqnroll.PluginTests", "Tes EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Reqnroll.CustomPlugin", "Installer\Reqnroll.CustomPlugin\Reqnroll.CustomPlugin.csproj", "{FA9DFEFA-4F35-4A57-91AE-64C7B5D41EAA}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Reqnroll.MsBuildNetSdk.IntegrationTests", "Tests\Reqnroll.MsBuildNetSdk.IntegrationTests\Reqnroll.MsBuildNetSdk.IntegrationTests.csproj", "{02A08F81-B90F-4EB3-9C30-CE7447DE2012}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AzurePipelines", "AzurePipelines", "{48E162BC-9431-450D-8353-66D396DCB5FE}" - ProjectSection(SolutionItems) = preProject - azure-pipelines.yml = azure-pipelines.yml - build.yml = build.yml - EndProjectSection -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Reqnroll.Autofac.ReqnrollPlugin", "Plugins\Reqnroll.Autofac.ReqnrollPlugin\Reqnroll.Autofac.ReqnrollPlugin.csproj", "{EA16606D-6F1C-4A4D-B7A1-8A08B448CD86}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Reqnroll.Templates.DotNet", "Templates\Reqnroll.Templates.DotNet\Reqnroll.Templates.DotNet.csproj", "{B7F589EB-524F-4BAD-9419-6576F6527670}" @@ -103,11 +88,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ExternalData", "ExternalDat EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Reqnroll.ExternalData.ReqnrollPlugin", "Plugins\Reqnroll.ExternalData\Reqnroll.ExternalData.ReqnrollPlugin\Reqnroll.ExternalData.ReqnrollPlugin.csproj", "{AF7CACE6-1F26-454D-A814-19CFCF7C5810}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Reqnroll.ExternalData.ReqnrollPlugin.UnitTests", "Plugins\Reqnroll.ExternalData\Reqnroll.ExternalData.ReqnrollPlugin.UnitTests\Reqnroll.ExternalData.ReqnrollPlugin.UnitTests.csproj", "{C3B1DA26-4F12-443E-9FBA-898393520CE7}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Specs", "Plugins\Reqnroll.ExternalData\sample\ExternalDataSample\Specs.csproj", "{C681C731-49F2-4C93-A730-467251741457}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Sample", "Sample", "{35D24F50-A29E-428A-8649-275C73C1E646}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest", "Plugins\Reqnroll.ExternalData\Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest\Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest.csproj", "{C681C731-49F2-4C93-A730-467251741457}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Verify", "Verify", "{229D5199-8A48-4174-AE8F-0B4C91170751}" EndProject @@ -134,397 +115,125 @@ EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU - Debug-MSTest|Any CPU = Debug-MSTest|Any CPU - Debug-NUnit|Any CPU = Debug-NUnit|Any CPU - Debug-XUnit|Any CPU = Debug-XUnit|Any CPU Release|Any CPU = Release|Any CPU - VS2010IntegrationTest|Any CPU = VS2010IntegrationTest|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {2D7D31B3-E8D0-445A-BB24-216ABA3CB778}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2D7D31B3-E8D0-445A-BB24-216ABA3CB778}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2D7D31B3-E8D0-445A-BB24-216ABA3CB778}.Debug-MSTest|Any CPU.ActiveCfg = Debug-MSTest|Any CPU - {2D7D31B3-E8D0-445A-BB24-216ABA3CB778}.Debug-MSTest|Any CPU.Build.0 = Debug-MSTest|Any CPU - {2D7D31B3-E8D0-445A-BB24-216ABA3CB778}.Debug-NUnit|Any CPU.ActiveCfg = Debug-NUnit|Any CPU - {2D7D31B3-E8D0-445A-BB24-216ABA3CB778}.Debug-NUnit|Any CPU.Build.0 = Debug-NUnit|Any CPU - {2D7D31B3-E8D0-445A-BB24-216ABA3CB778}.Debug-XUnit|Any CPU.ActiveCfg = Debug-XUnit|Any CPU - {2D7D31B3-E8D0-445A-BB24-216ABA3CB778}.Debug-XUnit|Any CPU.Build.0 = Debug-XUnit|Any CPU {2D7D31B3-E8D0-445A-BB24-216ABA3CB778}.Release|Any CPU.ActiveCfg = Release|Any CPU {2D7D31B3-E8D0-445A-BB24-216ABA3CB778}.Release|Any CPU.Build.0 = Release|Any CPU - {2D7D31B3-E8D0-445A-BB24-216ABA3CB778}.VS2010IntegrationTest|Any CPU.ActiveCfg = Debug|Any CPU - {2D7D31B3-E8D0-445A-BB24-216ABA3CB778}.VS2010IntegrationTest|Any CPU.Build.0 = Debug|Any CPU {48E0CB65-33D4-49CD-9E22-1AD49D8684B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {48E0CB65-33D4-49CD-9E22-1AD49D8684B0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {48E0CB65-33D4-49CD-9E22-1AD49D8684B0}.Debug-MSTest|Any CPU.ActiveCfg = Debug|Any CPU - {48E0CB65-33D4-49CD-9E22-1AD49D8684B0}.Debug-MSTest|Any CPU.Build.0 = Debug|Any CPU - {48E0CB65-33D4-49CD-9E22-1AD49D8684B0}.Debug-NUnit|Any CPU.ActiveCfg = Debug|Any CPU - {48E0CB65-33D4-49CD-9E22-1AD49D8684B0}.Debug-NUnit|Any CPU.Build.0 = Debug|Any CPU - {48E0CB65-33D4-49CD-9E22-1AD49D8684B0}.Debug-XUnit|Any CPU.ActiveCfg = Debug|Any CPU - {48E0CB65-33D4-49CD-9E22-1AD49D8684B0}.Debug-XUnit|Any CPU.Build.0 = Debug|Any CPU {48E0CB65-33D4-49CD-9E22-1AD49D8684B0}.Release|Any CPU.ActiveCfg = Release|Any CPU {48E0CB65-33D4-49CD-9E22-1AD49D8684B0}.Release|Any CPU.Build.0 = Release|Any CPU - {48E0CB65-33D4-49CD-9E22-1AD49D8684B0}.VS2010IntegrationTest|Any CPU.ActiveCfg = Debug|Any CPU - {48E0CB65-33D4-49CD-9E22-1AD49D8684B0}.VS2010IntegrationTest|Any CPU.Build.0 = Debug|Any CPU {7E637287-6E34-4BEC-8226-B052062AEA1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7E637287-6E34-4BEC-8226-B052062AEA1F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7E637287-6E34-4BEC-8226-B052062AEA1F}.Debug-MSTest|Any CPU.ActiveCfg = Debug|Any CPU - {7E637287-6E34-4BEC-8226-B052062AEA1F}.Debug-MSTest|Any CPU.Build.0 = Debug|Any CPU - {7E637287-6E34-4BEC-8226-B052062AEA1F}.Debug-NUnit|Any CPU.ActiveCfg = Debug|Any CPU - {7E637287-6E34-4BEC-8226-B052062AEA1F}.Debug-NUnit|Any CPU.Build.0 = Debug|Any CPU - {7E637287-6E34-4BEC-8226-B052062AEA1F}.Debug-XUnit|Any CPU.ActiveCfg = Debug|Any CPU - {7E637287-6E34-4BEC-8226-B052062AEA1F}.Debug-XUnit|Any CPU.Build.0 = Debug|Any CPU {7E637287-6E34-4BEC-8226-B052062AEA1F}.Release|Any CPU.ActiveCfg = Release|Any CPU {7E637287-6E34-4BEC-8226-B052062AEA1F}.Release|Any CPU.Build.0 = Release|Any CPU - {7E637287-6E34-4BEC-8226-B052062AEA1F}.VS2010IntegrationTest|Any CPU.ActiveCfg = Debug|Any CPU - {7E637287-6E34-4BEC-8226-B052062AEA1F}.VS2010IntegrationTest|Any CPU.Build.0 = Debug|Any CPU {ABA918DA-C436-4EE2-B23D-FD0B24403B27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {ABA918DA-C436-4EE2-B23D-FD0B24403B27}.Debug|Any CPU.Build.0 = Debug|Any CPU - {ABA918DA-C436-4EE2-B23D-FD0B24403B27}.Debug-MSTest|Any CPU.ActiveCfg = Debug|Any CPU - {ABA918DA-C436-4EE2-B23D-FD0B24403B27}.Debug-MSTest|Any CPU.Build.0 = Debug|Any CPU - {ABA918DA-C436-4EE2-B23D-FD0B24403B27}.Debug-NUnit|Any CPU.ActiveCfg = Debug|Any CPU - {ABA918DA-C436-4EE2-B23D-FD0B24403B27}.Debug-NUnit|Any CPU.Build.0 = Debug|Any CPU - {ABA918DA-C436-4EE2-B23D-FD0B24403B27}.Debug-XUnit|Any CPU.ActiveCfg = Debug|Any CPU - {ABA918DA-C436-4EE2-B23D-FD0B24403B27}.Debug-XUnit|Any CPU.Build.0 = Debug|Any CPU {ABA918DA-C436-4EE2-B23D-FD0B24403B27}.Release|Any CPU.ActiveCfg = Release|Any CPU {ABA918DA-C436-4EE2-B23D-FD0B24403B27}.Release|Any CPU.Build.0 = Release|Any CPU - {ABA918DA-C436-4EE2-B23D-FD0B24403B27}.VS2010IntegrationTest|Any CPU.ActiveCfg = Debug|Any CPU - {ABA918DA-C436-4EE2-B23D-FD0B24403B27}.VS2010IntegrationTest|Any CPU.Build.0 = Debug|Any CPU {413EE28C-4F89-4C6F-BA1E-2CDEE4CD43B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {413EE28C-4F89-4C6F-BA1E-2CDEE4CD43B4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {413EE28C-4F89-4C6F-BA1E-2CDEE4CD43B4}.Debug-MSTest|Any CPU.ActiveCfg = Debug|Any CPU - {413EE28C-4F89-4C6F-BA1E-2CDEE4CD43B4}.Debug-MSTest|Any CPU.Build.0 = Debug|Any CPU - {413EE28C-4F89-4C6F-BA1E-2CDEE4CD43B4}.Debug-NUnit|Any CPU.ActiveCfg = Debug|Any CPU - {413EE28C-4F89-4C6F-BA1E-2CDEE4CD43B4}.Debug-NUnit|Any CPU.Build.0 = Debug|Any CPU - {413EE28C-4F89-4C6F-BA1E-2CDEE4CD43B4}.Debug-XUnit|Any CPU.ActiveCfg = Debug|Any CPU - {413EE28C-4F89-4C6F-BA1E-2CDEE4CD43B4}.Debug-XUnit|Any CPU.Build.0 = Debug|Any CPU {413EE28C-4F89-4C6F-BA1E-2CDEE4CD43B4}.Release|Any CPU.ActiveCfg = Release|Any CPU {413EE28C-4F89-4C6F-BA1E-2CDEE4CD43B4}.Release|Any CPU.Build.0 = Release|Any CPU - {413EE28C-4F89-4C6F-BA1E-2CDEE4CD43B4}.VS2010IntegrationTest|Any CPU.ActiveCfg = Debug|Any CPU - {413EE28C-4F89-4C6F-BA1E-2CDEE4CD43B4}.VS2010IntegrationTest|Any CPU.Build.0 = Debug|Any CPU {F0A50949-8FE1-4048-9A3E-BB0498843669}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F0A50949-8FE1-4048-9A3E-BB0498843669}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F0A50949-8FE1-4048-9A3E-BB0498843669}.Debug-MSTest|Any CPU.ActiveCfg = Debug|Any CPU - {F0A50949-8FE1-4048-9A3E-BB0498843669}.Debug-MSTest|Any CPU.Build.0 = Debug|Any CPU - {F0A50949-8FE1-4048-9A3E-BB0498843669}.Debug-NUnit|Any CPU.ActiveCfg = Debug|Any CPU - {F0A50949-8FE1-4048-9A3E-BB0498843669}.Debug-NUnit|Any CPU.Build.0 = Debug|Any CPU - {F0A50949-8FE1-4048-9A3E-BB0498843669}.Debug-XUnit|Any CPU.ActiveCfg = Debug|Any CPU - {F0A50949-8FE1-4048-9A3E-BB0498843669}.Debug-XUnit|Any CPU.Build.0 = Debug|Any CPU {F0A50949-8FE1-4048-9A3E-BB0498843669}.Release|Any CPU.ActiveCfg = Release|Any CPU {F0A50949-8FE1-4048-9A3E-BB0498843669}.Release|Any CPU.Build.0 = Release|Any CPU - {F0A50949-8FE1-4048-9A3E-BB0498843669}.VS2010IntegrationTest|Any CPU.ActiveCfg = Debug|Any CPU - {F0A50949-8FE1-4048-9A3E-BB0498843669}.VS2010IntegrationTest|Any CPU.Build.0 = Debug|Any CPU {D719B8EA-6C89-4B81-AABD-65ACF562F521}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D719B8EA-6C89-4B81-AABD-65ACF562F521}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D719B8EA-6C89-4B81-AABD-65ACF562F521}.Debug-MSTest|Any CPU.ActiveCfg = Debug|Any CPU - {D719B8EA-6C89-4B81-AABD-65ACF562F521}.Debug-MSTest|Any CPU.Build.0 = Debug|Any CPU - {D719B8EA-6C89-4B81-AABD-65ACF562F521}.Debug-NUnit|Any CPU.ActiveCfg = Debug|Any CPU - {D719B8EA-6C89-4B81-AABD-65ACF562F521}.Debug-NUnit|Any CPU.Build.0 = Debug|Any CPU - {D719B8EA-6C89-4B81-AABD-65ACF562F521}.Debug-XUnit|Any CPU.ActiveCfg = Debug|Any CPU - {D719B8EA-6C89-4B81-AABD-65ACF562F521}.Debug-XUnit|Any CPU.Build.0 = Debug|Any CPU {D719B8EA-6C89-4B81-AABD-65ACF562F521}.Release|Any CPU.ActiveCfg = Release|Any CPU {D719B8EA-6C89-4B81-AABD-65ACF562F521}.Release|Any CPU.Build.0 = Release|Any CPU - {D719B8EA-6C89-4B81-AABD-65ACF562F521}.VS2010IntegrationTest|Any CPU.ActiveCfg = Debug|Any CPU - {D719B8EA-6C89-4B81-AABD-65ACF562F521}.VS2010IntegrationTest|Any CPU.Build.0 = Debug|Any CPU {FA04B445-D04C-4075-9EB0-4D0A70E47FC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FA04B445-D04C-4075-9EB0-4D0A70E47FC4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FA04B445-D04C-4075-9EB0-4D0A70E47FC4}.Debug-MSTest|Any CPU.ActiveCfg = Debug|Any CPU - {FA04B445-D04C-4075-9EB0-4D0A70E47FC4}.Debug-MSTest|Any CPU.Build.0 = Debug|Any CPU - {FA04B445-D04C-4075-9EB0-4D0A70E47FC4}.Debug-NUnit|Any CPU.ActiveCfg = Debug|Any CPU - {FA04B445-D04C-4075-9EB0-4D0A70E47FC4}.Debug-NUnit|Any CPU.Build.0 = Debug|Any CPU - {FA04B445-D04C-4075-9EB0-4D0A70E47FC4}.Debug-XUnit|Any CPU.ActiveCfg = Debug|Any CPU - {FA04B445-D04C-4075-9EB0-4D0A70E47FC4}.Debug-XUnit|Any CPU.Build.0 = Debug|Any CPU {FA04B445-D04C-4075-9EB0-4D0A70E47FC4}.Release|Any CPU.ActiveCfg = Release|Any CPU {FA04B445-D04C-4075-9EB0-4D0A70E47FC4}.Release|Any CPU.Build.0 = Release|Any CPU - {FA04B445-D04C-4075-9EB0-4D0A70E47FC4}.VS2010IntegrationTest|Any CPU.ActiveCfg = Debug|Any CPU - {FA04B445-D04C-4075-9EB0-4D0A70E47FC4}.VS2010IntegrationTest|Any CPU.Build.0 = Debug|Any CPU {AE2FCDBB-0933-4751-BF17-8D9BF9B2055A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AE2FCDBB-0933-4751-BF17-8D9BF9B2055A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AE2FCDBB-0933-4751-BF17-8D9BF9B2055A}.Debug-MSTest|Any CPU.ActiveCfg = Debug|Any CPU - {AE2FCDBB-0933-4751-BF17-8D9BF9B2055A}.Debug-MSTest|Any CPU.Build.0 = Debug|Any CPU - {AE2FCDBB-0933-4751-BF17-8D9BF9B2055A}.Debug-NUnit|Any CPU.ActiveCfg = Debug|Any CPU - {AE2FCDBB-0933-4751-BF17-8D9BF9B2055A}.Debug-NUnit|Any CPU.Build.0 = Debug|Any CPU - {AE2FCDBB-0933-4751-BF17-8D9BF9B2055A}.Debug-XUnit|Any CPU.ActiveCfg = Debug|Any CPU - {AE2FCDBB-0933-4751-BF17-8D9BF9B2055A}.Debug-XUnit|Any CPU.Build.0 = Debug|Any CPU {AE2FCDBB-0933-4751-BF17-8D9BF9B2055A}.Release|Any CPU.ActiveCfg = Release|Any CPU {AE2FCDBB-0933-4751-BF17-8D9BF9B2055A}.Release|Any CPU.Build.0 = Release|Any CPU - {AE2FCDBB-0933-4751-BF17-8D9BF9B2055A}.VS2010IntegrationTest|Any CPU.ActiveCfg = Debug|Any CPU - {AE2FCDBB-0933-4751-BF17-8D9BF9B2055A}.VS2010IntegrationTest|Any CPU.Build.0 = Debug|Any CPU {1B51F6C9-226E-479B-9212-E6C02A1E6633}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1B51F6C9-226E-479B-9212-E6C02A1E6633}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1B51F6C9-226E-479B-9212-E6C02A1E6633}.Debug-MSTest|Any CPU.ActiveCfg = Debug|Any CPU - {1B51F6C9-226E-479B-9212-E6C02A1E6633}.Debug-MSTest|Any CPU.Build.0 = Debug|Any CPU - {1B51F6C9-226E-479B-9212-E6C02A1E6633}.Debug-NUnit|Any CPU.ActiveCfg = Debug|Any CPU - {1B51F6C9-226E-479B-9212-E6C02A1E6633}.Debug-NUnit|Any CPU.Build.0 = Debug|Any CPU - {1B51F6C9-226E-479B-9212-E6C02A1E6633}.Debug-XUnit|Any CPU.ActiveCfg = Debug|Any CPU - {1B51F6C9-226E-479B-9212-E6C02A1E6633}.Debug-XUnit|Any CPU.Build.0 = Debug|Any CPU {1B51F6C9-226E-479B-9212-E6C02A1E6633}.Release|Any CPU.ActiveCfg = Release|Any CPU {1B51F6C9-226E-479B-9212-E6C02A1E6633}.Release|Any CPU.Build.0 = Release|Any CPU - {1B51F6C9-226E-479B-9212-E6C02A1E6633}.VS2010IntegrationTest|Any CPU.ActiveCfg = Debug|Any CPU - {1B51F6C9-226E-479B-9212-E6C02A1E6633}.VS2010IntegrationTest|Any CPU.Build.0 = Debug|Any CPU {D649B28A-352C-4CB0-B6F2-0570DD181F60}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D649B28A-352C-4CB0-B6F2-0570DD181F60}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D649B28A-352C-4CB0-B6F2-0570DD181F60}.Debug-MSTest|Any CPU.ActiveCfg = Debug|Any CPU - {D649B28A-352C-4CB0-B6F2-0570DD181F60}.Debug-MSTest|Any CPU.Build.0 = Debug|Any CPU - {D649B28A-352C-4CB0-B6F2-0570DD181F60}.Debug-NUnit|Any CPU.ActiveCfg = Debug|Any CPU - {D649B28A-352C-4CB0-B6F2-0570DD181F60}.Debug-NUnit|Any CPU.Build.0 = Debug|Any CPU - {D649B28A-352C-4CB0-B6F2-0570DD181F60}.Debug-XUnit|Any CPU.ActiveCfg = Debug|Any CPU - {D649B28A-352C-4CB0-B6F2-0570DD181F60}.Debug-XUnit|Any CPU.Build.0 = Debug|Any CPU {D649B28A-352C-4CB0-B6F2-0570DD181F60}.Release|Any CPU.ActiveCfg = Release|Any CPU {D649B28A-352C-4CB0-B6F2-0570DD181F60}.Release|Any CPU.Build.0 = Release|Any CPU - {D649B28A-352C-4CB0-B6F2-0570DD181F60}.VS2010IntegrationTest|Any CPU.ActiveCfg = Release|Any CPU - {D649B28A-352C-4CB0-B6F2-0570DD181F60}.VS2010IntegrationTest|Any CPU.Build.0 = Release|Any CPU - {03A229D6-BB43-47CC-AAEF-B7265652AD5A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {03A229D6-BB43-47CC-AAEF-B7265652AD5A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {03A229D6-BB43-47CC-AAEF-B7265652AD5A}.Debug-MSTest|Any CPU.ActiveCfg = Debug-MSTest|Any CPU - {03A229D6-BB43-47CC-AAEF-B7265652AD5A}.Debug-MSTest|Any CPU.Build.0 = Debug-MSTest|Any CPU - {03A229D6-BB43-47CC-AAEF-B7265652AD5A}.Debug-NUnit|Any CPU.ActiveCfg = Debug-NUnit|Any CPU - {03A229D6-BB43-47CC-AAEF-B7265652AD5A}.Debug-NUnit|Any CPU.Build.0 = Debug-NUnit|Any CPU - {03A229D6-BB43-47CC-AAEF-B7265652AD5A}.Debug-XUnit|Any CPU.ActiveCfg = Debug-XUnit|Any CPU - {03A229D6-BB43-47CC-AAEF-B7265652AD5A}.Debug-XUnit|Any CPU.Build.0 = Debug-XUnit|Any CPU - {03A229D6-BB43-47CC-AAEF-B7265652AD5A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {03A229D6-BB43-47CC-AAEF-B7265652AD5A}.Release|Any CPU.Build.0 = Release|Any CPU - {03A229D6-BB43-47CC-AAEF-B7265652AD5A}.VS2010IntegrationTest|Any CPU.ActiveCfg = Debug|Any CPU - {03A229D6-BB43-47CC-AAEF-B7265652AD5A}.VS2010IntegrationTest|Any CPU.Build.0 = Debug|Any CPU {D151832C-15D6-4869-9999-B245443BC23E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D151832C-15D6-4869-9999-B245443BC23E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D151832C-15D6-4869-9999-B245443BC23E}.Debug-MSTest|Any CPU.ActiveCfg = Debug|Any CPU - {D151832C-15D6-4869-9999-B245443BC23E}.Debug-MSTest|Any CPU.Build.0 = Debug|Any CPU - {D151832C-15D6-4869-9999-B245443BC23E}.Debug-NUnit|Any CPU.ActiveCfg = Debug|Any CPU - {D151832C-15D6-4869-9999-B245443BC23E}.Debug-NUnit|Any CPU.Build.0 = Debug|Any CPU - {D151832C-15D6-4869-9999-B245443BC23E}.Debug-XUnit|Any CPU.ActiveCfg = Debug|Any CPU - {D151832C-15D6-4869-9999-B245443BC23E}.Debug-XUnit|Any CPU.Build.0 = Debug|Any CPU {D151832C-15D6-4869-9999-B245443BC23E}.Release|Any CPU.ActiveCfg = Release|Any CPU {D151832C-15D6-4869-9999-B245443BC23E}.Release|Any CPU.Build.0 = Release|Any CPU - {D151832C-15D6-4869-9999-B245443BC23E}.VS2010IntegrationTest|Any CPU.ActiveCfg = Release|Any CPU - {D151832C-15D6-4869-9999-B245443BC23E}.VS2010IntegrationTest|Any CPU.Build.0 = Release|Any CPU {EC77355C-D442-416A-AE67-E42D5FAD0FB9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {EC77355C-D442-416A-AE67-E42D5FAD0FB9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EC77355C-D442-416A-AE67-E42D5FAD0FB9}.Debug-MSTest|Any CPU.ActiveCfg = Debug|Any CPU - {EC77355C-D442-416A-AE67-E42D5FAD0FB9}.Debug-MSTest|Any CPU.Build.0 = Debug|Any CPU - {EC77355C-D442-416A-AE67-E42D5FAD0FB9}.Debug-NUnit|Any CPU.ActiveCfg = Debug|Any CPU - {EC77355C-D442-416A-AE67-E42D5FAD0FB9}.Debug-NUnit|Any CPU.Build.0 = Debug|Any CPU - {EC77355C-D442-416A-AE67-E42D5FAD0FB9}.Debug-XUnit|Any CPU.ActiveCfg = Debug|Any CPU - {EC77355C-D442-416A-AE67-E42D5FAD0FB9}.Debug-XUnit|Any CPU.Build.0 = Debug|Any CPU {EC77355C-D442-416A-AE67-E42D5FAD0FB9}.Release|Any CPU.ActiveCfg = Release|Any CPU {EC77355C-D442-416A-AE67-E42D5FAD0FB9}.Release|Any CPU.Build.0 = Release|Any CPU - {EC77355C-D442-416A-AE67-E42D5FAD0FB9}.VS2010IntegrationTest|Any CPU.ActiveCfg = Release|Any CPU - {EC77355C-D442-416A-AE67-E42D5FAD0FB9}.VS2010IntegrationTest|Any CPU.Build.0 = Release|Any CPU {5CD2408D-6A5A-4824-8F28-A2421088BEF0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5CD2408D-6A5A-4824-8F28-A2421088BEF0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5CD2408D-6A5A-4824-8F28-A2421088BEF0}.Debug-MSTest|Any CPU.ActiveCfg = Debug|Any CPU - {5CD2408D-6A5A-4824-8F28-A2421088BEF0}.Debug-MSTest|Any CPU.Build.0 = Debug|Any CPU - {5CD2408D-6A5A-4824-8F28-A2421088BEF0}.Debug-NUnit|Any CPU.ActiveCfg = Debug|Any CPU - {5CD2408D-6A5A-4824-8F28-A2421088BEF0}.Debug-NUnit|Any CPU.Build.0 = Debug|Any CPU - {5CD2408D-6A5A-4824-8F28-A2421088BEF0}.Debug-XUnit|Any CPU.ActiveCfg = Debug|Any CPU - {5CD2408D-6A5A-4824-8F28-A2421088BEF0}.Debug-XUnit|Any CPU.Build.0 = Debug|Any CPU {5CD2408D-6A5A-4824-8F28-A2421088BEF0}.Release|Any CPU.ActiveCfg = Release|Any CPU {5CD2408D-6A5A-4824-8F28-A2421088BEF0}.Release|Any CPU.Build.0 = Release|Any CPU - {5CD2408D-6A5A-4824-8F28-A2421088BEF0}.VS2010IntegrationTest|Any CPU.ActiveCfg = Debug|Any CPU - {5CD2408D-6A5A-4824-8F28-A2421088BEF0}.VS2010IntegrationTest|Any CPU.Build.0 = Debug|Any CPU {556F5CA5-5497-4F32-9926-77A9369AB09C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {556F5CA5-5497-4F32-9926-77A9369AB09C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {556F5CA5-5497-4F32-9926-77A9369AB09C}.Debug-MSTest|Any CPU.ActiveCfg = Debug|Any CPU - {556F5CA5-5497-4F32-9926-77A9369AB09C}.Debug-MSTest|Any CPU.Build.0 = Debug|Any CPU - {556F5CA5-5497-4F32-9926-77A9369AB09C}.Debug-NUnit|Any CPU.ActiveCfg = Debug|Any CPU - {556F5CA5-5497-4F32-9926-77A9369AB09C}.Debug-NUnit|Any CPU.Build.0 = Debug|Any CPU - {556F5CA5-5497-4F32-9926-77A9369AB09C}.Debug-XUnit|Any CPU.ActiveCfg = Debug|Any CPU - {556F5CA5-5497-4F32-9926-77A9369AB09C}.Debug-XUnit|Any CPU.Build.0 = Debug|Any CPU {556F5CA5-5497-4F32-9926-77A9369AB09C}.Release|Any CPU.ActiveCfg = Release|Any CPU {556F5CA5-5497-4F32-9926-77A9369AB09C}.Release|Any CPU.Build.0 = Release|Any CPU - {556F5CA5-5497-4F32-9926-77A9369AB09C}.VS2010IntegrationTest|Any CPU.ActiveCfg = Debug|Any CPU - {556F5CA5-5497-4F32-9926-77A9369AB09C}.VS2010IntegrationTest|Any CPU.Build.0 = Debug|Any CPU {69473C7E-DBA7-478B-AE6D-C185AA910FD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {69473C7E-DBA7-478B-AE6D-C185AA910FD2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {69473C7E-DBA7-478B-AE6D-C185AA910FD2}.Debug-MSTest|Any CPU.ActiveCfg = Debug|Any CPU - {69473C7E-DBA7-478B-AE6D-C185AA910FD2}.Debug-MSTest|Any CPU.Build.0 = Debug|Any CPU - {69473C7E-DBA7-478B-AE6D-C185AA910FD2}.Debug-NUnit|Any CPU.ActiveCfg = Debug|Any CPU - {69473C7E-DBA7-478B-AE6D-C185AA910FD2}.Debug-NUnit|Any CPU.Build.0 = Debug|Any CPU - {69473C7E-DBA7-478B-AE6D-C185AA910FD2}.Debug-XUnit|Any CPU.ActiveCfg = Debug|Any CPU - {69473C7E-DBA7-478B-AE6D-C185AA910FD2}.Debug-XUnit|Any CPU.Build.0 = Debug|Any CPU {69473C7E-DBA7-478B-AE6D-C185AA910FD2}.Release|Any CPU.ActiveCfg = Release|Any CPU {69473C7E-DBA7-478B-AE6D-C185AA910FD2}.Release|Any CPU.Build.0 = Release|Any CPU - {69473C7E-DBA7-478B-AE6D-C185AA910FD2}.VS2010IntegrationTest|Any CPU.ActiveCfg = Debug|Any CPU - {69473C7E-DBA7-478B-AE6D-C185AA910FD2}.VS2010IntegrationTest|Any CPU.Build.0 = Debug|Any CPU {204B619C-6D4B-4384-B1E9-E231BA0081BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {204B619C-6D4B-4384-B1E9-E231BA0081BF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {204B619C-6D4B-4384-B1E9-E231BA0081BF}.Debug-MSTest|Any CPU.ActiveCfg = Debug|Any CPU - {204B619C-6D4B-4384-B1E9-E231BA0081BF}.Debug-MSTest|Any CPU.Build.0 = Debug|Any CPU - {204B619C-6D4B-4384-B1E9-E231BA0081BF}.Debug-NUnit|Any CPU.ActiveCfg = Debug|Any CPU - {204B619C-6D4B-4384-B1E9-E231BA0081BF}.Debug-NUnit|Any CPU.Build.0 = Debug|Any CPU - {204B619C-6D4B-4384-B1E9-E231BA0081BF}.Debug-XUnit|Any CPU.ActiveCfg = Debug|Any CPU - {204B619C-6D4B-4384-B1E9-E231BA0081BF}.Debug-XUnit|Any CPU.Build.0 = Debug|Any CPU {204B619C-6D4B-4384-B1E9-E231BA0081BF}.Release|Any CPU.ActiveCfg = Release|Any CPU {204B619C-6D4B-4384-B1E9-E231BA0081BF}.Release|Any CPU.Build.0 = Release|Any CPU - {204B619C-6D4B-4384-B1E9-E231BA0081BF}.VS2010IntegrationTest|Any CPU.ActiveCfg = Debug|Any CPU - {204B619C-6D4B-4384-B1E9-E231BA0081BF}.VS2010IntegrationTest|Any CPU.Build.0 = Debug|Any CPU {FA9DFEFA-4F35-4A57-91AE-64C7B5D41EAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FA9DFEFA-4F35-4A57-91AE-64C7B5D41EAA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FA9DFEFA-4F35-4A57-91AE-64C7B5D41EAA}.Debug-MSTest|Any CPU.ActiveCfg = Debug|Any CPU - {FA9DFEFA-4F35-4A57-91AE-64C7B5D41EAA}.Debug-MSTest|Any CPU.Build.0 = Debug|Any CPU - {FA9DFEFA-4F35-4A57-91AE-64C7B5D41EAA}.Debug-NUnit|Any CPU.ActiveCfg = Debug|Any CPU - {FA9DFEFA-4F35-4A57-91AE-64C7B5D41EAA}.Debug-NUnit|Any CPU.Build.0 = Debug|Any CPU - {FA9DFEFA-4F35-4A57-91AE-64C7B5D41EAA}.Debug-XUnit|Any CPU.ActiveCfg = Debug|Any CPU - {FA9DFEFA-4F35-4A57-91AE-64C7B5D41EAA}.Debug-XUnit|Any CPU.Build.0 = Debug|Any CPU {FA9DFEFA-4F35-4A57-91AE-64C7B5D41EAA}.Release|Any CPU.ActiveCfg = Release|Any CPU {FA9DFEFA-4F35-4A57-91AE-64C7B5D41EAA}.Release|Any CPU.Build.0 = Release|Any CPU - {FA9DFEFA-4F35-4A57-91AE-64C7B5D41EAA}.VS2010IntegrationTest|Any CPU.ActiveCfg = Debug|Any CPU - {FA9DFEFA-4F35-4A57-91AE-64C7B5D41EAA}.VS2010IntegrationTest|Any CPU.Build.0 = Debug|Any CPU - {02A08F81-B90F-4EB3-9C30-CE7447DE2012}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {02A08F81-B90F-4EB3-9C30-CE7447DE2012}.Debug|Any CPU.Build.0 = Debug|Any CPU - {02A08F81-B90F-4EB3-9C30-CE7447DE2012}.Debug-MSTest|Any CPU.ActiveCfg = Debug|Any CPU - {02A08F81-B90F-4EB3-9C30-CE7447DE2012}.Debug-MSTest|Any CPU.Build.0 = Debug|Any CPU - {02A08F81-B90F-4EB3-9C30-CE7447DE2012}.Debug-NUnit|Any CPU.ActiveCfg = Debug|Any CPU - {02A08F81-B90F-4EB3-9C30-CE7447DE2012}.Debug-NUnit|Any CPU.Build.0 = Debug|Any CPU - {02A08F81-B90F-4EB3-9C30-CE7447DE2012}.Debug-XUnit|Any CPU.ActiveCfg = Debug|Any CPU - {02A08F81-B90F-4EB3-9C30-CE7447DE2012}.Debug-XUnit|Any CPU.Build.0 = Debug|Any CPU - {02A08F81-B90F-4EB3-9C30-CE7447DE2012}.Release|Any CPU.ActiveCfg = Release|Any CPU - {02A08F81-B90F-4EB3-9C30-CE7447DE2012}.Release|Any CPU.Build.0 = Release|Any CPU - {02A08F81-B90F-4EB3-9C30-CE7447DE2012}.VS2010IntegrationTest|Any CPU.ActiveCfg = Debug|Any CPU - {02A08F81-B90F-4EB3-9C30-CE7447DE2012}.VS2010IntegrationTest|Any CPU.Build.0 = Debug|Any CPU {EA16606D-6F1C-4A4D-B7A1-8A08B448CD86}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {EA16606D-6F1C-4A4D-B7A1-8A08B448CD86}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EA16606D-6F1C-4A4D-B7A1-8A08B448CD86}.Debug-MSTest|Any CPU.ActiveCfg = Debug|Any CPU - {EA16606D-6F1C-4A4D-B7A1-8A08B448CD86}.Debug-MSTest|Any CPU.Build.0 = Debug|Any CPU - {EA16606D-6F1C-4A4D-B7A1-8A08B448CD86}.Debug-NUnit|Any CPU.ActiveCfg = Debug|Any CPU - {EA16606D-6F1C-4A4D-B7A1-8A08B448CD86}.Debug-NUnit|Any CPU.Build.0 = Debug|Any CPU - {EA16606D-6F1C-4A4D-B7A1-8A08B448CD86}.Debug-XUnit|Any CPU.ActiveCfg = Debug|Any CPU - {EA16606D-6F1C-4A4D-B7A1-8A08B448CD86}.Debug-XUnit|Any CPU.Build.0 = Debug|Any CPU {EA16606D-6F1C-4A4D-B7A1-8A08B448CD86}.Release|Any CPU.ActiveCfg = Release|Any CPU {EA16606D-6F1C-4A4D-B7A1-8A08B448CD86}.Release|Any CPU.Build.0 = Release|Any CPU - {EA16606D-6F1C-4A4D-B7A1-8A08B448CD86}.VS2010IntegrationTest|Any CPU.ActiveCfg = Release|Any CPU - {EA16606D-6F1C-4A4D-B7A1-8A08B448CD86}.VS2010IntegrationTest|Any CPU.Build.0 = Release|Any CPU {B7F589EB-524F-4BAD-9419-6576F6527670}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B7F589EB-524F-4BAD-9419-6576F6527670}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B7F589EB-524F-4BAD-9419-6576F6527670}.Debug-MSTest|Any CPU.ActiveCfg = Debug|Any CPU - {B7F589EB-524F-4BAD-9419-6576F6527670}.Debug-MSTest|Any CPU.Build.0 = Debug|Any CPU - {B7F589EB-524F-4BAD-9419-6576F6527670}.Debug-NUnit|Any CPU.ActiveCfg = Debug|Any CPU - {B7F589EB-524F-4BAD-9419-6576F6527670}.Debug-NUnit|Any CPU.Build.0 = Debug|Any CPU - {B7F589EB-524F-4BAD-9419-6576F6527670}.Debug-XUnit|Any CPU.ActiveCfg = Debug|Any CPU - {B7F589EB-524F-4BAD-9419-6576F6527670}.Debug-XUnit|Any CPU.Build.0 = Debug|Any CPU {B7F589EB-524F-4BAD-9419-6576F6527670}.Release|Any CPU.ActiveCfg = Release|Any CPU {B7F589EB-524F-4BAD-9419-6576F6527670}.Release|Any CPU.Build.0 = Release|Any CPU - {B7F589EB-524F-4BAD-9419-6576F6527670}.VS2010IntegrationTest|Any CPU.ActiveCfg = Debug|Any CPU - {B7F589EB-524F-4BAD-9419-6576F6527670}.VS2010IntegrationTest|Any CPU.Build.0 = Debug|Any CPU {A3931239-01D7-4277-9A7C-44D751BA746A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A3931239-01D7-4277-9A7C-44D751BA746A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A3931239-01D7-4277-9A7C-44D751BA746A}.Debug-MSTest|Any CPU.ActiveCfg = Debug|Any CPU - {A3931239-01D7-4277-9A7C-44D751BA746A}.Debug-MSTest|Any CPU.Build.0 = Debug|Any CPU - {A3931239-01D7-4277-9A7C-44D751BA746A}.Debug-NUnit|Any CPU.ActiveCfg = Debug|Any CPU - {A3931239-01D7-4277-9A7C-44D751BA746A}.Debug-NUnit|Any CPU.Build.0 = Debug|Any CPU - {A3931239-01D7-4277-9A7C-44D751BA746A}.Debug-XUnit|Any CPU.ActiveCfg = Debug|Any CPU - {A3931239-01D7-4277-9A7C-44D751BA746A}.Debug-XUnit|Any CPU.Build.0 = Debug|Any CPU {A3931239-01D7-4277-9A7C-44D751BA746A}.Release|Any CPU.ActiveCfg = Release|Any CPU {A3931239-01D7-4277-9A7C-44D751BA746A}.Release|Any CPU.Build.0 = Release|Any CPU - {A3931239-01D7-4277-9A7C-44D751BA746A}.VS2010IntegrationTest|Any CPU.ActiveCfg = Release|Any CPU - {A3931239-01D7-4277-9A7C-44D751BA746A}.VS2010IntegrationTest|Any CPU.Build.0 = Release|Any CPU {AF7CACE6-1F26-454D-A814-19CFCF7C5810}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AF7CACE6-1F26-454D-A814-19CFCF7C5810}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AF7CACE6-1F26-454D-A814-19CFCF7C5810}.Debug-MSTest|Any CPU.ActiveCfg = Debug|Any CPU - {AF7CACE6-1F26-454D-A814-19CFCF7C5810}.Debug-MSTest|Any CPU.Build.0 = Debug|Any CPU - {AF7CACE6-1F26-454D-A814-19CFCF7C5810}.Debug-NUnit|Any CPU.ActiveCfg = Debug|Any CPU - {AF7CACE6-1F26-454D-A814-19CFCF7C5810}.Debug-NUnit|Any CPU.Build.0 = Debug|Any CPU - {AF7CACE6-1F26-454D-A814-19CFCF7C5810}.Debug-XUnit|Any CPU.ActiveCfg = Debug|Any CPU - {AF7CACE6-1F26-454D-A814-19CFCF7C5810}.Debug-XUnit|Any CPU.Build.0 = Debug|Any CPU {AF7CACE6-1F26-454D-A814-19CFCF7C5810}.Release|Any CPU.ActiveCfg = Release|Any CPU {AF7CACE6-1F26-454D-A814-19CFCF7C5810}.Release|Any CPU.Build.0 = Release|Any CPU - {AF7CACE6-1F26-454D-A814-19CFCF7C5810}.VS2010IntegrationTest|Any CPU.ActiveCfg = Release|Any CPU - {AF7CACE6-1F26-454D-A814-19CFCF7C5810}.VS2010IntegrationTest|Any CPU.Build.0 = Release|Any CPU - {C3B1DA26-4F12-443E-9FBA-898393520CE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C3B1DA26-4F12-443E-9FBA-898393520CE7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C3B1DA26-4F12-443E-9FBA-898393520CE7}.Debug-MSTest|Any CPU.ActiveCfg = Debug|Any CPU - {C3B1DA26-4F12-443E-9FBA-898393520CE7}.Debug-MSTest|Any CPU.Build.0 = Debug|Any CPU - {C3B1DA26-4F12-443E-9FBA-898393520CE7}.Debug-NUnit|Any CPU.ActiveCfg = Debug|Any CPU - {C3B1DA26-4F12-443E-9FBA-898393520CE7}.Debug-NUnit|Any CPU.Build.0 = Debug|Any CPU - {C3B1DA26-4F12-443E-9FBA-898393520CE7}.Debug-XUnit|Any CPU.ActiveCfg = Debug|Any CPU - {C3B1DA26-4F12-443E-9FBA-898393520CE7}.Debug-XUnit|Any CPU.Build.0 = Debug|Any CPU - {C3B1DA26-4F12-443E-9FBA-898393520CE7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C3B1DA26-4F12-443E-9FBA-898393520CE7}.Release|Any CPU.Build.0 = Release|Any CPU - {C3B1DA26-4F12-443E-9FBA-898393520CE7}.VS2010IntegrationTest|Any CPU.ActiveCfg = Debug|Any CPU - {C3B1DA26-4F12-443E-9FBA-898393520CE7}.VS2010IntegrationTest|Any CPU.Build.0 = Debug|Any CPU {C681C731-49F2-4C93-A730-467251741457}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C681C731-49F2-4C93-A730-467251741457}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C681C731-49F2-4C93-A730-467251741457}.Debug-MSTest|Any CPU.ActiveCfg = Debug|Any CPU - {C681C731-49F2-4C93-A730-467251741457}.Debug-MSTest|Any CPU.Build.0 = Debug|Any CPU - {C681C731-49F2-4C93-A730-467251741457}.Debug-NUnit|Any CPU.ActiveCfg = Debug|Any CPU - {C681C731-49F2-4C93-A730-467251741457}.Debug-NUnit|Any CPU.Build.0 = Debug|Any CPU - {C681C731-49F2-4C93-A730-467251741457}.Debug-XUnit|Any CPU.ActiveCfg = Debug|Any CPU - {C681C731-49F2-4C93-A730-467251741457}.Debug-XUnit|Any CPU.Build.0 = Debug|Any CPU {C681C731-49F2-4C93-A730-467251741457}.Release|Any CPU.ActiveCfg = Release|Any CPU {C681C731-49F2-4C93-A730-467251741457}.Release|Any CPU.Build.0 = Release|Any CPU - {C681C731-49F2-4C93-A730-467251741457}.VS2010IntegrationTest|Any CPU.ActiveCfg = Debug|Any CPU - {C681C731-49F2-4C93-A730-467251741457}.VS2010IntegrationTest|Any CPU.Build.0 = Debug|Any CPU {93476F8A-EBF1-4DB4-929F-77F5B96D08E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {93476F8A-EBF1-4DB4-929F-77F5B96D08E0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {93476F8A-EBF1-4DB4-929F-77F5B96D08E0}.Debug-MSTest|Any CPU.ActiveCfg = Debug|Any CPU - {93476F8A-EBF1-4DB4-929F-77F5B96D08E0}.Debug-MSTest|Any CPU.Build.0 = Debug|Any CPU - {93476F8A-EBF1-4DB4-929F-77F5B96D08E0}.Debug-NUnit|Any CPU.ActiveCfg = Debug|Any CPU - {93476F8A-EBF1-4DB4-929F-77F5B96D08E0}.Debug-NUnit|Any CPU.Build.0 = Debug|Any CPU - {93476F8A-EBF1-4DB4-929F-77F5B96D08E0}.Debug-XUnit|Any CPU.ActiveCfg = Debug|Any CPU - {93476F8A-EBF1-4DB4-929F-77F5B96D08E0}.Debug-XUnit|Any CPU.Build.0 = Debug|Any CPU {93476F8A-EBF1-4DB4-929F-77F5B96D08E0}.Release|Any CPU.ActiveCfg = Release|Any CPU {93476F8A-EBF1-4DB4-929F-77F5B96D08E0}.Release|Any CPU.Build.0 = Release|Any CPU - {93476F8A-EBF1-4DB4-929F-77F5B96D08E0}.VS2010IntegrationTest|Any CPU.ActiveCfg = Debug|Any CPU - {93476F8A-EBF1-4DB4-929F-77F5B96D08E0}.VS2010IntegrationTest|Any CPU.Build.0 = Debug|Any CPU {68E341C2-12C9-4DAC-AB04-A11C7CF94F30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {68E341C2-12C9-4DAC-AB04-A11C7CF94F30}.Debug|Any CPU.Build.0 = Debug|Any CPU - {68E341C2-12C9-4DAC-AB04-A11C7CF94F30}.Debug-MSTest|Any CPU.ActiveCfg = Debug|Any CPU - {68E341C2-12C9-4DAC-AB04-A11C7CF94F30}.Debug-MSTest|Any CPU.Build.0 = Debug|Any CPU - {68E341C2-12C9-4DAC-AB04-A11C7CF94F30}.Debug-NUnit|Any CPU.ActiveCfg = Debug|Any CPU - {68E341C2-12C9-4DAC-AB04-A11C7CF94F30}.Debug-NUnit|Any CPU.Build.0 = Debug|Any CPU - {68E341C2-12C9-4DAC-AB04-A11C7CF94F30}.Debug-XUnit|Any CPU.ActiveCfg = Debug|Any CPU - {68E341C2-12C9-4DAC-AB04-A11C7CF94F30}.Debug-XUnit|Any CPU.Build.0 = Debug|Any CPU {68E341C2-12C9-4DAC-AB04-A11C7CF94F30}.Release|Any CPU.ActiveCfg = Release|Any CPU {68E341C2-12C9-4DAC-AB04-A11C7CF94F30}.Release|Any CPU.Build.0 = Release|Any CPU - {68E341C2-12C9-4DAC-AB04-A11C7CF94F30}.VS2010IntegrationTest|Any CPU.ActiveCfg = Debug|Any CPU - {68E341C2-12C9-4DAC-AB04-A11C7CF94F30}.VS2010IntegrationTest|Any CPU.Build.0 = Debug|Any CPU {56BECF00-09F0-463A-A720-BED4495E51E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {56BECF00-09F0-463A-A720-BED4495E51E8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {56BECF00-09F0-463A-A720-BED4495E51E8}.Debug-MSTest|Any CPU.ActiveCfg = Debug|Any CPU - {56BECF00-09F0-463A-A720-BED4495E51E8}.Debug-MSTest|Any CPU.Build.0 = Debug|Any CPU - {56BECF00-09F0-463A-A720-BED4495E51E8}.Debug-NUnit|Any CPU.ActiveCfg = Debug|Any CPU - {56BECF00-09F0-463A-A720-BED4495E51E8}.Debug-NUnit|Any CPU.Build.0 = Debug|Any CPU - {56BECF00-09F0-463A-A720-BED4495E51E8}.Debug-XUnit|Any CPU.ActiveCfg = Debug|Any CPU - {56BECF00-09F0-463A-A720-BED4495E51E8}.Debug-XUnit|Any CPU.Build.0 = Debug|Any CPU {56BECF00-09F0-463A-A720-BED4495E51E8}.Release|Any CPU.ActiveCfg = Release|Any CPU {56BECF00-09F0-463A-A720-BED4495E51E8}.Release|Any CPU.Build.0 = Release|Any CPU - {56BECF00-09F0-463A-A720-BED4495E51E8}.VS2010IntegrationTest|Any CPU.ActiveCfg = Debug|Any CPU - {56BECF00-09F0-463A-A720-BED4495E51E8}.VS2010IntegrationTest|Any CPU.Build.0 = Debug|Any CPU {2FCD6A28-2134-435E-A205-3D55A6F5A389}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2FCD6A28-2134-435E-A205-3D55A6F5A389}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2FCD6A28-2134-435E-A205-3D55A6F5A389}.Debug-MSTest|Any CPU.ActiveCfg = Debug|Any CPU - {2FCD6A28-2134-435E-A205-3D55A6F5A389}.Debug-MSTest|Any CPU.Build.0 = Debug|Any CPU - {2FCD6A28-2134-435E-A205-3D55A6F5A389}.Debug-NUnit|Any CPU.ActiveCfg = Debug|Any CPU - {2FCD6A28-2134-435E-A205-3D55A6F5A389}.Debug-NUnit|Any CPU.Build.0 = Debug|Any CPU - {2FCD6A28-2134-435E-A205-3D55A6F5A389}.Debug-XUnit|Any CPU.ActiveCfg = Debug|Any CPU - {2FCD6A28-2134-435E-A205-3D55A6F5A389}.Debug-XUnit|Any CPU.Build.0 = Debug|Any CPU {2FCD6A28-2134-435E-A205-3D55A6F5A389}.Release|Any CPU.ActiveCfg = Release|Any CPU {2FCD6A28-2134-435E-A205-3D55A6F5A389}.Release|Any CPU.Build.0 = Release|Any CPU - {2FCD6A28-2134-435E-A205-3D55A6F5A389}.VS2010IntegrationTest|Any CPU.ActiveCfg = Debug|Any CPU - {2FCD6A28-2134-435E-A205-3D55A6F5A389}.VS2010IntegrationTest|Any CPU.Build.0 = Debug|Any CPU {C073A609-8A6A-4707-86B0-7BB32EF8ACEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C073A609-8A6A-4707-86B0-7BB32EF8ACEE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C073A609-8A6A-4707-86B0-7BB32EF8ACEE}.Debug-MSTest|Any CPU.ActiveCfg = Debug|Any CPU - {C073A609-8A6A-4707-86B0-7BB32EF8ACEE}.Debug-MSTest|Any CPU.Build.0 = Debug|Any CPU - {C073A609-8A6A-4707-86B0-7BB32EF8ACEE}.Debug-NUnit|Any CPU.ActiveCfg = Debug|Any CPU - {C073A609-8A6A-4707-86B0-7BB32EF8ACEE}.Debug-NUnit|Any CPU.Build.0 = Debug|Any CPU - {C073A609-8A6A-4707-86B0-7BB32EF8ACEE}.Debug-XUnit|Any CPU.ActiveCfg = Debug|Any CPU - {C073A609-8A6A-4707-86B0-7BB32EF8ACEE}.Debug-XUnit|Any CPU.Build.0 = Debug|Any CPU {C073A609-8A6A-4707-86B0-7BB32EF8ACEE}.Release|Any CPU.ActiveCfg = Release|Any CPU {C073A609-8A6A-4707-86B0-7BB32EF8ACEE}.Release|Any CPU.Build.0 = Release|Any CPU - {C073A609-8A6A-4707-86B0-7BB32EF8ACEE}.VS2010IntegrationTest|Any CPU.ActiveCfg = Debug|Any CPU - {C073A609-8A6A-4707-86B0-7BB32EF8ACEE}.VS2010IntegrationTest|Any CPU.Build.0 = Debug|Any CPU {C658B37D-FD36-4868-9070-4EB452FAE526}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C658B37D-FD36-4868-9070-4EB452FAE526}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C658B37D-FD36-4868-9070-4EB452FAE526}.Debug-MSTest|Any CPU.ActiveCfg = Debug|Any CPU - {C658B37D-FD36-4868-9070-4EB452FAE526}.Debug-MSTest|Any CPU.Build.0 = Debug|Any CPU - {C658B37D-FD36-4868-9070-4EB452FAE526}.Debug-NUnit|Any CPU.ActiveCfg = Debug|Any CPU - {C658B37D-FD36-4868-9070-4EB452FAE526}.Debug-NUnit|Any CPU.Build.0 = Debug|Any CPU - {C658B37D-FD36-4868-9070-4EB452FAE526}.Debug-XUnit|Any CPU.ActiveCfg = Debug|Any CPU - {C658B37D-FD36-4868-9070-4EB452FAE526}.Debug-XUnit|Any CPU.Build.0 = Debug|Any CPU {C658B37D-FD36-4868-9070-4EB452FAE526}.Release|Any CPU.ActiveCfg = Release|Any CPU {C658B37D-FD36-4868-9070-4EB452FAE526}.Release|Any CPU.Build.0 = Release|Any CPU - {C658B37D-FD36-4868-9070-4EB452FAE526}.VS2010IntegrationTest|Any CPU.ActiveCfg = Debug|Any CPU - {C658B37D-FD36-4868-9070-4EB452FAE526}.VS2010IntegrationTest|Any CPU.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -539,7 +248,6 @@ Global {FA04B445-D04C-4075-9EB0-4D0A70E47FC4} = {974420E8-FEEA-4564-B4CE-BE06F7457E3B} {1B51F6C9-226E-479B-9212-E6C02A1E6633} = {974420E8-FEEA-4564-B4CE-BE06F7457E3B} {D649B28A-352C-4CB0-B6F2-0570DD181F60} = {8BE0FE31-6A52-452E-BE71-B8C64A3ED402} - {03A229D6-BB43-47CC-AAEF-B7265652AD5A} = {A10B5CD6-38EC-4D7E-9D1C-2EBA8017E437} {D151832C-15D6-4869-9999-B245443BC23E} = {8BE0FE31-6A52-452E-BE71-B8C64A3ED402} {EC77355C-D442-416A-AE67-E42D5FAD0FB9} = {8BE0FE31-6A52-452E-BE71-B8C64A3ED402} {5CD2408D-6A5A-4824-8F28-A2421088BEF0} = {8BE0FE31-6A52-452E-BE71-B8C64A3ED402} @@ -547,16 +255,12 @@ Global {69473C7E-DBA7-478B-AE6D-C185AA910FD2} = {8BE0FE31-6A52-452E-BE71-B8C64A3ED402} {204B619C-6D4B-4384-B1E9-E231BA0081BF} = {0359B7D7-7E29-48E9-8DF9-7D1FACFA5CFA} {FA9DFEFA-4F35-4A57-91AE-64C7B5D41EAA} = {DCE0C3C4-5BC6-4A30-86BE-3FEFF4677A01} - {02A08F81-B90F-4EB3-9C30-CE7447DE2012} = {A10B5CD6-38EC-4D7E-9D1C-2EBA8017E437} - {48E162BC-9431-450D-8353-66D396DCB5FE} = {577A0375-1436-446C-802B-3C75C8CEF94F} {EA16606D-6F1C-4A4D-B7A1-8A08B448CD86} = {8BE0FE31-6A52-452E-BE71-B8C64A3ED402} {B7F589EB-524F-4BAD-9419-6576F6527670} = {05275A0B-57E4-484C-B181-227697C7B89A} {A3931239-01D7-4277-9A7C-44D751BA746A} = {8BE0FE31-6A52-452E-BE71-B8C64A3ED402} {16EAF4F9-9860-4BB9-8992-98522E68E570} = {8BE0FE31-6A52-452E-BE71-B8C64A3ED402} {AF7CACE6-1F26-454D-A814-19CFCF7C5810} = {16EAF4F9-9860-4BB9-8992-98522E68E570} - {C3B1DA26-4F12-443E-9FBA-898393520CE7} = {16EAF4F9-9860-4BB9-8992-98522E68E570} - {C681C731-49F2-4C93-A730-467251741457} = {35D24F50-A29E-428A-8649-275C73C1E646} - {35D24F50-A29E-428A-8649-275C73C1E646} = {16EAF4F9-9860-4BB9-8992-98522E68E570} + {C681C731-49F2-4C93-A730-467251741457} = {16EAF4F9-9860-4BB9-8992-98522E68E570} {229D5199-8A48-4174-AE8F-0B4C91170751} = {8BE0FE31-6A52-452E-BE71-B8C64A3ED402} {93476F8A-EBF1-4DB4-929F-77F5B96D08E0} = {229D5199-8A48-4174-AE8F-0B4C91170751} {68E341C2-12C9-4DAC-AB04-A11C7CF94F30} = {229D5199-8A48-4174-AE8F-0B4C91170751} diff --git a/Tests/Reqnroll.GeneratorTests/Reqnroll.GeneratorTests.csproj b/Tests/Reqnroll.GeneratorTests/Reqnroll.GeneratorTests.csproj index fed139e41..919c7e9be 100644 --- a/Tests/Reqnroll.GeneratorTests/Reqnroll.GeneratorTests.csproj +++ b/Tests/Reqnroll.GeneratorTests/Reqnroll.GeneratorTests.csproj @@ -1,6 +1,6 @@ - + - $(Reqnroll_Test_TFM) + net8.0 Reqnroll.GeneratorTests $(Reqnroll_KeyFile) $(Reqnroll_EnableStrongNameSigning) diff --git a/Tests/Reqnroll.MsBuildNetSdk.IntegrationTests/App.config b/Tests/Reqnroll.MsBuildNetSdk.IntegrationTests/App.config deleted file mode 100644 index 41fc85e03..000000000 --- a/Tests/Reqnroll.MsBuildNetSdk.IntegrationTests/App.config +++ /dev/null @@ -1,17 +0,0 @@ - - - -
- - - - - - - - - - - diff --git a/Tests/Reqnroll.MsBuildNetSdk.IntegrationTests/Bindings/DoNothingBinding.cs b/Tests/Reqnroll.MsBuildNetSdk.IntegrationTests/Bindings/DoNothingBinding.cs deleted file mode 100644 index c1146ea26..000000000 --- a/Tests/Reqnroll.MsBuildNetSdk.IntegrationTests/Bindings/DoNothingBinding.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace Reqnroll.MsBuildNetSdk.IntegrationTests.Features -{ - [Binding] - public class DoNothingBinding - { - [Given(".*"), When(".*"), Then(".*")] - public void EmptyStep() - { - } - - [Given(".*"), When(".*"), Then(".*")] - public void EmptyStep(string multiLineStringParam) - { - } - - [Given(".*"), When(".*"), Then(".*")] - public void EmptyStep(DataTable tableParam) - { - } - } -} \ No newline at end of file diff --git a/Tests/Reqnroll.MsBuildNetSdk.IntegrationTests/CodeBehindFileGenerationTests.cs b/Tests/Reqnroll.MsBuildNetSdk.IntegrationTests/CodeBehindFileGenerationTests.cs deleted file mode 100644 index 985b1053c..000000000 --- a/Tests/Reqnroll.MsBuildNetSdk.IntegrationTests/CodeBehindFileGenerationTests.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Reflection; - -using Reqnroll.MsBuildNetSdk.IntegrationTests.Features; -using Xunit; - -namespace Reqnroll.MsBuildNetSdk.IntegrationTests -{ - - public class CodeBehindFileGenerationTests - { - [Fact] - public void TestIfCodeBehindFilesWasGeneratedAndCompiled() - { - var assemblyHoldingThisClass = Assembly.GetExecutingAssembly(); - var typeOfGeneratedFeatureFile = assemblyHoldingThisClass.GetType(typeof(DummyFeatureFileToTestMSBuildNetsdkCodebehindFileGenerationFeature).FullName); - Assert.NotNull(typeOfGeneratedFeatureFile); - } - } -} diff --git a/Tests/Reqnroll.MsBuildNetSdk.IntegrationTests/Features/dummy.feature b/Tests/Reqnroll.MsBuildNetSdk.IntegrationTests/Features/dummy.feature deleted file mode 100644 index 2f1cf31c0..000000000 --- a/Tests/Reqnroll.MsBuildNetSdk.IntegrationTests/Features/dummy.feature +++ /dev/null @@ -1,7 +0,0 @@ -Feature: Dummy feature file to test MSBuild netsdk codebehind file generation - -Scenario: Dummy scenario - Given I have a net.sdk style project - When the project is build - Then msbuild integration will generate code behind files - And nest generated codebehind files under corresponding feature file diff --git a/Tests/Reqnroll.MsBuildNetSdk.IntegrationTests/Reqnroll.MsBuildNetSdk.IntegrationTests.csproj b/Tests/Reqnroll.MsBuildNetSdk.IntegrationTests/Reqnroll.MsBuildNetSdk.IntegrationTests.csproj deleted file mode 100644 index 87568b7d9..000000000 --- a/Tests/Reqnroll.MsBuildNetSdk.IntegrationTests/Reqnroll.MsBuildNetSdk.IntegrationTests.csproj +++ /dev/null @@ -1,95 +0,0 @@ - - - - - - - - $(Reqnroll_Specs_TFM) - Always - false - true - true - true - - - - - - - - false - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers - - - all - runtime; build; native; contentfiles; analyzers - - - - - - - - Always - - - - - - - - <_Reqnroll_PluginTFM Condition=" '$(MSBuildRuntimeType)' == 'Core'">netcoreapp3.1 - <_Reqnroll_PluginTFM Condition=" '$(MSBuildRuntimeType)' != 'Core'">net462 - - - - - - - - - - - - - - <_Reqnroll_TaskFolder Condition=" '$(MSBuildRuntimeType)' == 'Core' And '$(_Reqnroll_TaskFolder)' == ''">netcoreapp3.1 - <_Reqnroll_TaskFolder Condition=" '$(MSBuildRuntimeType)' != 'Core' And '$(_Reqnroll_TaskFolder)' == ''">net462 - <_Reqnroll_TaskAssembly>..\..\Reqnroll.Tools.MsBuild.Generation\bin\$(Configuration)\$(_Reqnroll_TaskFolder)\Reqnroll.Tools.MsBuild.Generation.dll - - - - - - - - - - PreBuild; - $(BuildDependsOn) - - - PreBuild; - $(RebuildDependsOn) - - - - diff --git a/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests/CsvLoaderTests.cs b/Tests/Reqnroll.PluginTests/ExternalData/CsvLoaderTests.cs similarity index 96% rename from Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests/CsvLoaderTests.cs rename to Tests/Reqnroll.PluginTests/ExternalData/CsvLoaderTests.cs index 39973d95a..4e66d7c5b 100644 --- a/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests/CsvLoaderTests.cs +++ b/Tests/Reqnroll.PluginTests/ExternalData/CsvLoaderTests.cs @@ -1,15 +1,15 @@ using System; -using System.Globalization; using System.IO; using System.Reflection; +using Reqnroll.ExternalData.ReqnrollPlugin; using Reqnroll.ExternalData.ReqnrollPlugin.Loaders; using Xunit; -namespace Reqnroll.ExternalData.ReqnrollPlugin.UnitTests +namespace Reqnroll.PluginTests.ExternalData { public class CsvLoaderTests { - private static readonly string SampleFilesFolder = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? ".", "SampleFiles"); + private static readonly string SampleFilesFolder = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? ".", "ExternalData", "SampleFiles"); private string _productsSampleFilePath = Path.Combine(SampleFilesFolder, "products.csv"); private string SampleFeatureFilePathInSampleFileFolder => diff --git a/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests/DataSourceLoaderFactoryTests.cs b/Tests/Reqnroll.PluginTests/ExternalData/DataSourceLoaderFactoryTests.cs similarity index 97% rename from Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests/DataSourceLoaderFactoryTests.cs rename to Tests/Reqnroll.PluginTests/ExternalData/DataSourceLoaderFactoryTests.cs index 20e80086f..f574f71c6 100644 --- a/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests/DataSourceLoaderFactoryTests.cs +++ b/Tests/Reqnroll.PluginTests/ExternalData/DataSourceLoaderFactoryTests.cs @@ -1,10 +1,11 @@ using System; using Reqnroll.BoDi; +using Reqnroll.ExternalData.ReqnrollPlugin; using Reqnroll.ExternalData.ReqnrollPlugin.DataSources; using Reqnroll.ExternalData.ReqnrollPlugin.Loaders; using Xunit; -namespace Reqnroll.ExternalData.ReqnrollPlugin.UnitTests +namespace Reqnroll.PluginTests.ExternalData { public class DataSourceLoaderFactoryTests { diff --git a/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests/DataSources/Selectors/DataSourceSelectorParserTests.cs b/Tests/Reqnroll.PluginTests/ExternalData/DataSources/Selectors/DataSourceSelectorParserTests.cs similarity index 88% rename from Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests/DataSources/Selectors/DataSourceSelectorParserTests.cs rename to Tests/Reqnroll.PluginTests/ExternalData/DataSources/Selectors/DataSourceSelectorParserTests.cs index d742f5589..2615edf6e 100644 --- a/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests/DataSources/Selectors/DataSourceSelectorParserTests.cs +++ b/Tests/Reqnroll.PluginTests/ExternalData/DataSources/Selectors/DataSourceSelectorParserTests.cs @@ -2,7 +2,7 @@ using Reqnroll.ExternalData.ReqnrollPlugin.DataSources.Selectors; using Xunit; -namespace Reqnroll.ExternalData.ReqnrollPlugin.UnitTests.DataSources.Selectors +namespace Reqnroll.PluginTests.ExternalData.DataSources.Selectors { public class DataSourceSelectorParserTests { diff --git a/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests/DataSources/Selectors/DataSourceSelectorTests.cs b/Tests/Reqnroll.PluginTests/ExternalData/DataSources/Selectors/DataSourceSelectorTests.cs similarity index 94% rename from Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests/DataSources/Selectors/DataSourceSelectorTests.cs rename to Tests/Reqnroll.PluginTests/ExternalData/DataSources/Selectors/DataSourceSelectorTests.cs index 27bbd88c5..7d4b6a969 100644 --- a/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests/DataSources/Selectors/DataSourceSelectorTests.cs +++ b/Tests/Reqnroll.PluginTests/ExternalData/DataSources/Selectors/DataSourceSelectorTests.cs @@ -1,10 +1,11 @@ using System; using System.Collections.Generic; +using Reqnroll.ExternalData.ReqnrollPlugin; using Reqnroll.ExternalData.ReqnrollPlugin.DataSources; using Reqnroll.ExternalData.ReqnrollPlugin.DataSources.Selectors; using Xunit; -namespace Reqnroll.ExternalData.ReqnrollPlugin.UnitTests.DataSources.Selectors +namespace Reqnroll.PluginTests.ExternalData.DataSources.Selectors { public class DataSourceSelectorTests { diff --git a/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests/ExcelLoaderTests.cs b/Tests/Reqnroll.PluginTests/ExternalData/ExcelLoaderTests.cs similarity index 97% rename from Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests/ExcelLoaderTests.cs rename to Tests/Reqnroll.PluginTests/ExternalData/ExcelLoaderTests.cs index 5d81919e4..a5e3aac98 100644 --- a/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests/ExcelLoaderTests.cs +++ b/Tests/Reqnroll.PluginTests/ExternalData/ExcelLoaderTests.cs @@ -5,11 +5,11 @@ using Reqnroll.ExternalData.ReqnrollPlugin.Loaders; using Xunit; -namespace Reqnroll.ExternalData.ReqnrollPlugin.UnitTests +namespace Reqnroll.PluginTests.ExternalData { public class ExcelLoaderTests { - private static readonly string SampleFilesFolder = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? ".", "SampleFiles"); + private static readonly string SampleFilesFolder = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? ".", "ExternalData", "SampleFiles"); private readonly string _productsSampleFilePath = Path.Combine(SampleFilesFolder, "products.xlsx"); private ExcelLoader CreateSut() => new(); diff --git a/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests/ExternalDataSpecificationTests.cs b/Tests/Reqnroll.PluginTests/ExternalData/ExternalDataSpecificationTests.cs similarity index 96% rename from Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests/ExternalDataSpecificationTests.cs rename to Tests/Reqnroll.PluginTests/ExternalData/ExternalDataSpecificationTests.cs index 86857ecd2..ef6ea0af8 100644 --- a/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests/ExternalDataSpecificationTests.cs +++ b/Tests/Reqnroll.PluginTests/ExternalData/ExternalDataSpecificationTests.cs @@ -5,11 +5,11 @@ using Reqnroll.ExternalData.ReqnrollPlugin.DataSources.Selectors; using Xunit; -namespace Reqnroll.ExternalData.ReqnrollPlugin.UnitTests +namespace Reqnroll.PluginTests.ExternalData { public class ExternalDataSpecificationTests { - private ReqnrollPlugin.DataSources.DataTable CreateProductDataTable() + private Reqnroll.ExternalData.ReqnrollPlugin.DataSources.DataTable CreateProductDataTable() { return new(new []{ "product", "price", "color"}) { @@ -22,7 +22,7 @@ private ReqnrollPlugin.DataSources.DataTable CreateProductDataTable() }; } - private ReqnrollPlugin.DataSources.DataTable CreateUserDataTable() + private Reqnroll.ExternalData.ReqnrollPlugin.DataSources.DataTable CreateUserDataTable() { return new(new []{ "name" }) { diff --git a/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests/IncludeExternalDataTransformationTests.cs b/Tests/Reqnroll.PluginTests/ExternalData/IncludeExternalDataTransformationTests.cs similarity index 98% rename from Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests/IncludeExternalDataTransformationTests.cs rename to Tests/Reqnroll.PluginTests/ExternalData/IncludeExternalDataTransformationTests.cs index 3271e9530..0b8b81918 100644 --- a/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests/IncludeExternalDataTransformationTests.cs +++ b/Tests/Reqnroll.PluginTests/ExternalData/IncludeExternalDataTransformationTests.cs @@ -8,7 +8,7 @@ using Reqnroll.Parser; using Xunit; -namespace Reqnroll.ExternalData.ReqnrollPlugin.UnitTests +namespace Reqnroll.PluginTests.ExternalData { public class IncludeExternalDataTransformationTests { @@ -25,7 +25,7 @@ public IncludeExternalDataTransformationTests() private IncludeExternalDataTransformation CreateSut() => new(_specificationProviderMock.Object); - private ReqnrollPlugin.DataSources.DataTable CreateProductDataTable() + private Reqnroll.ExternalData.ReqnrollPlugin.DataSources.DataTable CreateProductDataTable() { return new(new []{"product", "price"}) { diff --git a/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests/SampleFiles/products-empty.csv b/Tests/Reqnroll.PluginTests/ExternalData/SampleFiles/products-empty.csv similarity index 100% rename from Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests/SampleFiles/products-empty.csv rename to Tests/Reqnroll.PluginTests/ExternalData/SampleFiles/products-empty.csv diff --git a/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests/SampleFiles/products-invalid.csv b/Tests/Reqnroll.PluginTests/ExternalData/SampleFiles/products-invalid.csv similarity index 100% rename from Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests/SampleFiles/products-invalid.csv rename to Tests/Reqnroll.PluginTests/ExternalData/SampleFiles/products-invalid.csv diff --git a/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests/SampleFiles/products-special.csv b/Tests/Reqnroll.PluginTests/ExternalData/SampleFiles/products-special.csv similarity index 100% rename from Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests/SampleFiles/products-special.csv rename to Tests/Reqnroll.PluginTests/ExternalData/SampleFiles/products-special.csv diff --git a/Plugins/Reqnroll.ExternalData/sample/ExternalDataSample/Features/products.csv b/Tests/Reqnroll.PluginTests/ExternalData/SampleFiles/products.csv similarity index 100% rename from Plugins/Reqnroll.ExternalData/sample/ExternalDataSample/Features/products.csv rename to Tests/Reqnroll.PluginTests/ExternalData/SampleFiles/products.csv diff --git a/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests/SampleFiles/products.xlsx b/Tests/Reqnroll.PluginTests/ExternalData/SampleFiles/products.xlsx similarity index 100% rename from Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests/SampleFiles/products.xlsx rename to Tests/Reqnroll.PluginTests/ExternalData/SampleFiles/products.xlsx diff --git a/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests/ScenarioTransformationTests.cs b/Tests/Reqnroll.PluginTests/ExternalData/ScenarioTransformationTests.cs similarity index 99% rename from Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests/ScenarioTransformationTests.cs rename to Tests/Reqnroll.PluginTests/ExternalData/ScenarioTransformationTests.cs index 0913eefbb..d1f65c0d2 100644 --- a/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests/ScenarioTransformationTests.cs +++ b/Tests/Reqnroll.PluginTests/ExternalData/ScenarioTransformationTests.cs @@ -6,7 +6,7 @@ using Reqnroll.Parser; using Xunit; -namespace Reqnroll.ExternalData.ReqnrollPlugin.UnitTests +namespace Reqnroll.PluginTests.ExternalData { public class ScenarioTransformationTests { diff --git a/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests/SpecificationProviderTests.cs b/Tests/Reqnroll.PluginTests/ExternalData/SpecificationProviderTests.cs similarity index 98% rename from Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests/SpecificationProviderTests.cs rename to Tests/Reqnroll.PluginTests/ExternalData/SpecificationProviderTests.cs index ab80774c2..892b77d8e 100644 --- a/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.UnitTests/SpecificationProviderTests.cs +++ b/Tests/Reqnroll.PluginTests/ExternalData/SpecificationProviderTests.cs @@ -1,12 +1,13 @@ using System; using Gherkin.Ast; using Moq; +using Reqnroll.ExternalData.ReqnrollPlugin; using Reqnroll.ExternalData.ReqnrollPlugin.DataSources; using Reqnroll.ExternalData.ReqnrollPlugin.DataSources.Selectors; using Reqnroll.ExternalData.ReqnrollPlugin.Loaders; using Xunit; -namespace Reqnroll.ExternalData.ReqnrollPlugin.UnitTests +namespace Reqnroll.PluginTests.ExternalData { public class SpecificationProviderTests { diff --git a/Tests/Reqnroll.PluginTests/Reqnroll.PluginTests.csproj b/Tests/Reqnroll.PluginTests/Reqnroll.PluginTests.csproj index acd9f7e1c..6812d7518 100644 --- a/Tests/Reqnroll.PluginTests/Reqnroll.PluginTests.csproj +++ b/Tests/Reqnroll.PluginTests/Reqnroll.PluginTests.csproj @@ -1,6 +1,6 @@ - $(Reqnroll_Test_TFM) + net8.0 Reqnroll.PluginTests $(Reqnroll_KeyFile) $(Reqnroll_EnableStrongNameSigning) @@ -10,6 +10,7 @@ + @@ -33,4 +34,22 @@ + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + diff --git a/Tests/Reqnroll.RuntimeTests/Reqnroll.RuntimeTests.csproj b/Tests/Reqnroll.RuntimeTests/Reqnroll.RuntimeTests.csproj index c2943172e..de56b850a 100644 --- a/Tests/Reqnroll.RuntimeTests/Reqnroll.RuntimeTests.csproj +++ b/Tests/Reqnroll.RuntimeTests/Reqnroll.RuntimeTests.csproj @@ -1,6 +1,6 @@ - $(Reqnroll_Test_TFM) + net8.0 Reqnroll.RuntimeTests $(Reqnroll_KeyFile) $(Reqnroll_EnableStrongNameSigning) diff --git a/Tests/Reqnroll.Specs.Generator.ReqnrollPlugin/Combination.cs b/Tests/Reqnroll.Specs.Generator.ReqnrollPlugin/Combination.cs deleted file mode 100644 index 5493e269a..000000000 --- a/Tests/Reqnroll.Specs.Generator.ReqnrollPlugin/Combination.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Reqnroll.Specs.Generator.ReqnrollPlugin -{ - public class Combination - { - public string ProjectFormat { get; set; } - public string TargetFramework { get; set; } - public string ProgrammingLanguage { get; set; } - public string UnitTestProvider { get; set; } - public string ConfigFormat { get; set; } - } -} \ No newline at end of file diff --git a/Tests/Reqnroll.Specs.Generator.ReqnrollPlugin/CombinationFeatureGenerator.cs b/Tests/Reqnroll.Specs.Generator.ReqnrollPlugin/CombinationFeatureGenerator.cs deleted file mode 100644 index a42d85073..000000000 --- a/Tests/Reqnroll.Specs.Generator.ReqnrollPlugin/CombinationFeatureGenerator.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Reqnroll.Configuration; -using Reqnroll.Generator; -using Reqnroll.Generator.CodeDom; -using Reqnroll.Generator.Generation; -using Reqnroll.Generator.Interfaces; -using Reqnroll.Generator.UnitTestConverter; - -namespace Reqnroll.Specs.Generator.ReqnrollPlugin -{ - public class CombinationFeatureGenerator : UnitTestFeatureGenerator - { - - public CombinationFeatureGenerator(CodeDomHelper codeDomHelper, ReqnrollConfiguration reqnrollConfiguration, IDecoratorRegistry decoratorRegistry, Combination combination, ProjectSettings projectSettings) : - base(new CustomXUnitGeneratorProvider(codeDomHelper, combination, projectSettings), codeDomHelper, reqnrollConfiguration, decoratorRegistry) - { - - } - } -} diff --git a/Tests/Reqnroll.Specs.Generator.ReqnrollPlugin/CustomXUnitGeneratorProvider.cs b/Tests/Reqnroll.Specs.Generator.ReqnrollPlugin/CustomXUnitGeneratorProvider.cs deleted file mode 100644 index 9094aefa0..000000000 --- a/Tests/Reqnroll.Specs.Generator.ReqnrollPlugin/CustomXUnitGeneratorProvider.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System.CodeDom; -using Reqnroll.Generator; -using Reqnroll.Generator.CodeDom; -using Reqnroll.Generator.Interfaces; -using Reqnroll.Generator.UnitTestProvider; - -namespace Reqnroll.Specs.Generator.ReqnrollPlugin -{ - class CustomXUnitGeneratorProvider : XUnit2TestGeneratorProvider - { - private readonly Combination _combination; - - public CustomXUnitGeneratorProvider(CodeDomHelper codeDomHelper, Combination combination, ProjectSettings projectSettings) : base(codeDomHelper) - { - _combination = combination; - } - - public override void FinalizeTestClass(TestClassGenerationContext generationContext) - { - base.FinalizeTestClass(generationContext); - - if (_combination != null) - { - string programminLanguageEnum = $"Reqnroll.TestProjectGenerator.ProgrammingLanguage.{_combination.ProgrammingLanguage}"; - string projectFormatEnum = $"Reqnroll.TestProjectGenerator.Data.ProjectFormat.{_combination.ProjectFormat}"; - string targetFrameworkEnum = $"Reqnroll.TestProjectGenerator.Data.TargetFramework.{_combination.TargetFramework}"; - string unitTestProviderEnum = $"Reqnroll.TestProjectGenerator.UnitTestProvider.{_combination.UnitTestProvider}"; - string configFormat = $"Reqnroll.TestProjectGenerator.ConfigurationFormat.{_combination.ConfigFormat}"; - - generationContext.ScenarioInitializeMethod.Statements.Add( - new CodeMethodInvokeExpression( - new CodeMethodReferenceExpression( - new CodePropertyReferenceExpression( - new CodePropertyReferenceExpression( - new CodeFieldReferenceExpression(null, generationContext.TestRunnerField.Name), - "ScenarioContext"), - "ScenarioContainer"), - "RegisterInstanceAs", - new CodeTypeReference("Reqnroll.TestProjectGenerator.TestRunConfiguration")), - new CodeVariableReferenceExpression( - $"new Reqnroll.TestProjectGenerator.TestRunConfiguration(){{ ProgrammingLanguage = {programminLanguageEnum}, ProjectFormat = {projectFormatEnum}, TargetFramework = {targetFrameworkEnum}, UnitTestProvider = {unitTestProviderEnum}, ConfigurationFormat = {configFormat} }}"))); - } - } - } -} \ No newline at end of file diff --git a/Tests/Reqnroll.Specs.Generator.ReqnrollPlugin/GeneratorPlugin.cs b/Tests/Reqnroll.Specs.Generator.ReqnrollPlugin/GeneratorPlugin.cs deleted file mode 100644 index 09c6c0f6e..000000000 --- a/Tests/Reqnroll.Specs.Generator.ReqnrollPlugin/GeneratorPlugin.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Reqnroll.Generator.Plugins; -using Reqnroll.Generator.UnitTestConverter; -using Reqnroll.Infrastructure; -using Reqnroll.Specs.Generator.ReqnrollPlugin; -using Reqnroll.UnitTestProvider; - -[assembly: GeneratorPlugin(typeof(GeneratorPlugin))] - - -namespace Reqnroll.Specs.Generator.ReqnrollPlugin -{ - public class GeneratorPlugin : IGeneratorPlugin - { - public void Initialize(GeneratorPluginEvents generatorPluginEvents, GeneratorPluginParameters generatorPluginParameters, UnitTestProviderConfiguration unitTestProviderConfiguration) - { - //unitTestProviderConfiguration.UseUnitTestProvider("specs-multiple-configurations"); - generatorPluginEvents.RegisterDependencies += GeneratorPluginEvents_RegisterDependencies; - generatorPluginEvents.CustomizeDependencies += GeneratorPluginEvents_CustomizeDependencies; - } - - private void GeneratorPluginEvents_CustomizeDependencies(object sender, CustomizeDependenciesEventArgs e) - { - e.ObjectContainer.RegisterTypeAs("specs-multiple-configurations"); - } - - private void GeneratorPluginEvents_RegisterDependencies(object sender, RegisterDependenciesEventArgs e) - { - } - } -} diff --git a/Tests/Reqnroll.Specs.Generator.ReqnrollPlugin/MultiFeatureGenerator.cs b/Tests/Reqnroll.Specs.Generator.ReqnrollPlugin/MultiFeatureGenerator.cs deleted file mode 100644 index a9860366c..000000000 --- a/Tests/Reqnroll.Specs.Generator.ReqnrollPlugin/MultiFeatureGenerator.cs +++ /dev/null @@ -1,207 +0,0 @@ -using Gherkin.Ast; -using System; -using System.CodeDom; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.InteropServices; -using Reqnroll.Generator; -using Reqnroll.Generator.Generation; -using Reqnroll.Generator.UnitTestConverter; -using Reqnroll.Parser; - -namespace Reqnroll.Specs.Generator.ReqnrollPlugin -{ - internal class MultiFeatureGenerator : IFeatureGenerator - { - private readonly IFeatureGenerator _defaultFeatureGenerator; - private readonly KeyValuePair[] _featureGenerators; - private readonly List _unitTestProviderTags = new List { "xunit", "mstest", "nunit3" }; - - public MultiFeatureGenerator(IEnumerable> featureGenerators, IFeatureGenerator defaultFeatureGenerator) - { - _defaultFeatureGenerator = defaultFeatureGenerator; - _featureGenerators = featureGenerators.ToArray(); - - foreach (var featureGenerator in _featureGenerators) - { - if (featureGenerator.Value is UnitTestFeatureGenerator unitTestFeatureGenerator) - { - unitTestFeatureGenerator.TestClassNameFormat += $"_{featureGenerator.Key.UnitTestProvider}_{featureGenerator.Key.TargetFramework}_{featureGenerator.Key.ProjectFormat}_{featureGenerator.Key.ProgrammingLanguage}"; - } - } - } - - public CodeNamespace GenerateUnitTestFixture(ReqnrollDocument reqnrollDocument, string testClassName, string targetNamespace) - { - CodeNamespace result = null; - bool onlyFullframework = false; - - var reqnrollFeature = reqnrollDocument.ReqnrollFeature; - bool onlyDotNetCore = false; - if (reqnrollFeature.HasTags()) - { - if (reqnrollFeature.Tags.Where(t => t.Name == "@SingleTestConfiguration").Any()) - { - return _defaultFeatureGenerator.GenerateUnitTestFixture(reqnrollDocument, testClassName, targetNamespace); - } - - onlyFullframework = HasFeatureTag(reqnrollFeature, "@fullframework"); - onlyDotNetCore = HasFeatureTag(reqnrollFeature, "@dotnetcore"); - } - - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - onlyFullframework = false; - onlyDotNetCore = true; - } - - var tagsOfFeature = reqnrollFeature.Tags.Select(t => t.Name); - var unitTestProviders = tagsOfFeature.Where(t => _unitTestProviderTags.Where(utpt => string.Compare(t, "@" + utpt, StringComparison.CurrentCultureIgnoreCase) == 0).Any()); - - foreach (var featureGenerator in GetFilteredFeatureGenerator(unitTestProviders, onlyFullframework, onlyDotNetCore)) - { - var clonedDocument = CloneDocumentAndAddTags(reqnrollDocument, featureGenerator.Key); - - - var featureGeneratorResult = featureGenerator.Value.GenerateUnitTestFixture(clonedDocument, testClassName, targetNamespace); - - if (result == null) - { - result = featureGeneratorResult; - } - else - { - foreach (CodeTypeDeclaration type in featureGeneratorResult.Types) - { - result.Types.Add(type); - } - } - } - - if (result == null) - { - result = new CodeNamespace(targetNamespace); - } - - return result; - } - - private ReqnrollDocument CloneDocumentAndAddTags(ReqnrollDocument reqnrollDocument, Combination combination) - { - var tags = new List(); - var reqnrollFeature = reqnrollDocument.ReqnrollFeature; - tags.AddRange(reqnrollFeature.Tags); - if (!HasFeatureTag(reqnrollDocument.ReqnrollFeature, "@" + combination.UnitTestProvider)) - tags.Add(new Tag(null, "@" + combination.UnitTestProvider)); - foreach (string otherUnitTestProvider in _unitTestProviderTags.Where(utp => !utp.Equals(combination.UnitTestProvider, StringComparison.InvariantCultureIgnoreCase))) - { - tags.RemoveAll(t => ("@" + otherUnitTestProvider).Equals(t.Name, StringComparison.InvariantCultureIgnoreCase)); - } - if (!HasFeatureTag(reqnrollDocument.ReqnrollFeature, "@" + combination.TargetFramework)) - tags.Add(new Tag(null, "@" + combination.TargetFramework)); - var feature = new ReqnrollFeature(tags.ToArray(), - reqnrollFeature.Location, - reqnrollFeature.Language, - reqnrollFeature.Keyword, - reqnrollFeature.Name, - reqnrollFeature.Description, - reqnrollFeature.Children.ToArray()); - - return new ReqnrollDocument(feature, reqnrollDocument.Comments.ToArray(), reqnrollDocument.DocumentLocation); - } - - private IEnumerable> GetFilteredFeatureGenerator(IEnumerable unitTestProviders, bool onlyFullframework, bool onlyDotNetCore) - { - if (!unitTestProviders.Any()) - { - foreach (var featureGenerator in _featureGenerators) - { - if (onlyFullframework) - { - if (featureGenerator.Key.TargetFramework == TestRunCombinations.TfmEnumValuenet462) - { - yield return featureGenerator; - } - } - else - { - if (onlyDotNetCore) - { - if (ShouldCompileForNetCore21(featureGenerator.Key) - || ShouldCompileForNetCore31(featureGenerator.Key) || ShouldCompileForNet50(featureGenerator.Key) || ShouldCompileForNet60(featureGenerator.Key)) - { - yield return featureGenerator; - } - } - else - { - yield return featureGenerator; - } - } - } - } - - foreach (string unitTestProvider in unitTestProviders) - { - foreach (var featureGenerator in _featureGenerators) - { - if (IsForUnitTestProvider(featureGenerator, unitTestProvider)) - { - if (onlyFullframework) - { - if (featureGenerator.Key.TargetFramework == TestRunCombinations.TfmEnumValuenet462) - { - yield return featureGenerator; - } - } - else - { - if (onlyDotNetCore) - { - if (ShouldCompileForNetCore21(featureGenerator.Key) - || ShouldCompileForNetCore31(featureGenerator.Key) || ShouldCompileForNet50(featureGenerator.Key) || ShouldCompileForNet60(featureGenerator.Key)) - { - yield return featureGenerator; - } - } - else - { - yield return featureGenerator; - } - } - } - } - } - } - public bool ShouldCompileForNetCore21(Combination combination) - { - return combination.TargetFramework == TestRunCombinations.TfmEnumValueNetCore21 - && RuntimeInformation.IsOSPlatform(OSPlatform.Windows); - } - - public bool ShouldCompileForNetCore31(Combination combination) - { - return combination.TargetFramework == TestRunCombinations.TfmEnumValueNetCore31; - } - - public bool ShouldCompileForNet50(Combination combination) - { - return combination.TargetFramework == TestRunCombinations.TfmEnumValueNet50; - } - - public bool ShouldCompileForNet60(Combination combination) - { - return combination.TargetFramework == TestRunCombinations.TfmEnumValueNet60; - } - - private bool IsForUnitTestProvider(KeyValuePair featureGenerator, string unitTestProvider) - { - return string.Compare("@" + featureGenerator.Key.UnitTestProvider, unitTestProvider, StringComparison.CurrentCultureIgnoreCase) == 0; - } - - private bool HasFeatureTag(ReqnrollFeature reqnrollFeature, string tag) - { - return reqnrollFeature.Tags.Any(t => string.Compare(t.Name, tag, StringComparison.CurrentCultureIgnoreCase) == 0); - } - } -} diff --git a/Tests/Reqnroll.Specs.Generator.ReqnrollPlugin/MultiFeatureGeneratorProvider.cs b/Tests/Reqnroll.Specs.Generator.ReqnrollPlugin/MultiFeatureGeneratorProvider.cs deleted file mode 100644 index f029726e7..000000000 --- a/Tests/Reqnroll.Specs.Generator.ReqnrollPlugin/MultiFeatureGeneratorProvider.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System.Collections.Generic; -using Reqnroll.BoDi; -using Reqnroll.Configuration; -using Reqnroll.Generator.CodeDom; -using Reqnroll.Generator.Interfaces; -using Reqnroll.Generator.UnitTestConverter; -using Reqnroll.Parser; - -namespace Reqnroll.Specs.Generator.ReqnrollPlugin -{ - internal class MultiFeatureGeneratorProvider : IFeatureGeneratorProvider - { - - - private readonly MultiFeatureGenerator _multiFeatureGenerator; - - public MultiFeatureGeneratorProvider(IObjectContainer container) - { - var featureGenerators = new List>(); - - foreach (var combination in TestRunCombinations.List) - { - var combinationFeatureGenerator = new CombinationFeatureGenerator(container.Resolve(), container.Resolve(), container.Resolve(), combination, container.Resolve()); - featureGenerators.Add(new KeyValuePair(combination, combinationFeatureGenerator)); - } - - _multiFeatureGenerator = new MultiFeatureGenerator(featureGenerators, new CombinationFeatureGenerator(container.Resolve(), container.Resolve(), container.Resolve(), null, container.Resolve())); - } - - - public bool CanGenerate(ReqnrollDocument reqnrollDocument) - { - return true; - } - - public IFeatureGenerator CreateGenerator(ReqnrollDocument reqnrollDocument) - { - return _multiFeatureGenerator; - } - - public int Priority => PriorityValues.Normal; - } -} \ No newline at end of file diff --git a/Tests/Reqnroll.Specs.Generator.ReqnrollPlugin/Reqnroll.Specs.Generator.ReqnrollPlugin.csproj b/Tests/Reqnroll.Specs.Generator.ReqnrollPlugin/Reqnroll.Specs.Generator.ReqnrollPlugin.csproj deleted file mode 100644 index 5a409dd19..000000000 --- a/Tests/Reqnroll.Specs.Generator.ReqnrollPlugin/Reqnroll.Specs.Generator.ReqnrollPlugin.csproj +++ /dev/null @@ -1,43 +0,0 @@ - - - $(Reqnroll_Tools_TFM) - false - true - $(TargetsForTfmSpecificContentInPackage);PackPublishOutput - - - Debug;Release;Debug-XUnit;Debug-MSTest;Debug-NUnit - - - - $(DefineConstants);XUNIT_SPECS;MSTEST_SPECS;NUNIT_SPECS - - - - $(DefineConstants);NUNIT_SPECS - bin\Debug - - - - $(DefineConstants);MSTEST_SPECS - bin\Debug - - - - $(DefineConstants);XUNIT_SPECS - bin\Debug - - - - - All - - - - - - - - - - diff --git a/Tests/Reqnroll.Specs.Generator.ReqnrollPlugin/TestRunCombinations.cs b/Tests/Reqnroll.Specs.Generator.ReqnrollPlugin/TestRunCombinations.cs deleted file mode 100644 index 3c3fa35f0..000000000 --- a/Tests/Reqnroll.Specs.Generator.ReqnrollPlugin/TestRunCombinations.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Collections.Generic; - -namespace Reqnroll.Specs.Generator.ReqnrollPlugin -{ - public class TestRunCombinations - { - public const string TfmEnumValuenet462 = "Net462"; - public const string TfmEnumValueNetCore21 = "Netcoreapp21"; - public const string TfmEnumValueNetCore31 = "Netcoreapp31"; - public const string TfmEnumValueNet50 = "Net50"; - public const string TfmEnumValueNet60 = "Net60"; - - public static List List { get; } = new List() - { -#if XUNIT_SPECS - new Combination() {ProgrammingLanguage = "CSharp73", ProjectFormat = "Old", TargetFramework = TfmEnumValuenet462, UnitTestProvider = "xUnit", ConfigFormat = "Json"}, - new Combination() {ProgrammingLanguage = "CSharp73", ProjectFormat = "New", TargetFramework = TfmEnumValuenet462, UnitTestProvider = "xUnit", ConfigFormat = "Json"}, - new Combination() {ProgrammingLanguage = "CSharp73", ProjectFormat = "New", TargetFramework = TfmEnumValueNetCore31, UnitTestProvider = "xUnit", ConfigFormat = "Json"}, - new Combination() {ProgrammingLanguage = "CSharp", ProjectFormat = "New", TargetFramework = TfmEnumValueNet60, UnitTestProvider = "xUnit", ConfigFormat = "Json"}, -#endif - -#if MSTEST_SPECS - new Combination() {ProgrammingLanguage = "CSharp73", ProjectFormat = "New", TargetFramework = TfmEnumValuenet462, UnitTestProvider = "MSTest", ConfigFormat = "Json"}, - new Combination() {ProgrammingLanguage = "CSharp73", ProjectFormat = "New", TargetFramework = TfmEnumValueNetCore31, UnitTestProvider = "MSTest", ConfigFormat = "Json"}, - new Combination() {ProgrammingLanguage = "CSharp", ProjectFormat = "New", TargetFramework = TfmEnumValueNet60, UnitTestProvider = "MSTest", ConfigFormat = "Json"}, -#endif - -#if NUNIT_SPECS - new Combination() {ProgrammingLanguage = "CSharp73", ProjectFormat = "New", TargetFramework = TfmEnumValuenet462, UnitTestProvider = "NUnit3", ConfigFormat = "Json"}, - new Combination() {ProgrammingLanguage = "CSharp73", ProjectFormat = "New", TargetFramework = TfmEnumValueNetCore31, UnitTestProvider = "NUnit3", ConfigFormat = "Json"}, - new Combination() {ProgrammingLanguage = "CSharp", ProjectFormat = "New", TargetFramework = TfmEnumValueNet60, UnitTestProvider = "NUnit3", ConfigFormat = "Json"}, -#endif - }; - } -} diff --git a/Tests/Reqnroll.Specs/Drivers/Parser/ParserDriver.cs b/Tests/Reqnroll.Specs/Drivers/Parser/ParserDriver.cs index be3084a8a..4583f1333 100644 --- a/Tests/Reqnroll.Specs/Drivers/Parser/ParserDriver.cs +++ b/Tests/Reqnroll.Specs/Drivers/Parser/ParserDriver.cs @@ -44,9 +44,10 @@ public void AssertParsedFeatureEqualTo(string parsedFeatureXml) { const string ns1 = "xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\""; const string ns2 = "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""; - - string expected = parsedFeatureXml.Replace("\r", "").Replace(ns1, "").Replace(ns2, ""); - string got = SerializeDocument(ParsedDocument).Replace("\r", "").Replace(ns1, "").Replace(ns2, ""); + string Normalize(string value) + => value.Replace("\r", "").Replace(@"\r", "").Replace(ns1, "").Replace(ns2, ""); + string expected = Normalize(parsedFeatureXml); + string got = Normalize(SerializeDocument(ParsedDocument)); got.Should().Be(expected); } diff --git a/Tests/Reqnroll.Specs/Features/Build systems.feature b/Tests/Reqnroll.Specs/Features/Build systems.feature deleted file mode 100644 index faa987ff5..000000000 --- a/Tests/Reqnroll.Specs/Features/Build systems.feature +++ /dev/null @@ -1,33 +0,0 @@ -@dotnetcore -Feature: Build systems - -@quarantaine -@requiresMsBuild -@globalusingdirective #MSBuild for VS2019 and Mono throws error CS8652: The feature 'global using directive' is currently in Preview and unsupported. -Scenario: Use MSBuild for compiling - Given there is a scenario in a feature file - And all steps are bound and pass - - When I compile the solution using 'MSBuild' - - Then no compilation errors are reported - -Scenario: Use dotnet build for compiling - Given there is a scenario in a feature file - And all steps are bound and pass - - When I compile the solution using 'dotnet build' - - Then no compilation errors are reported - - -Scenario: Use dotnet msbuild for compiling - Given there is a scenario in a feature file - And all steps are bound and pass - - When I compile the solution using 'dotnet msbuild' - - Then no compilation errors are reported - - - diff --git a/Tests/Reqnroll.Specs/Features/LanguageSupport.feature b/Tests/Reqnroll.Specs/Features/LanguageSupport.feature deleted file mode 100644 index e4bfb1deb..000000000 --- a/Tests/Reqnroll.Specs/Features/LanguageSupport.feature +++ /dev/null @@ -1,55 +0,0 @@ -Feature: .NET Language Code-Behind Generation Support - -@quarantaine -Scenario Outline: A project with a single scenario should compile successfully in all supported languages and build systems - Given I have a '' test project - And there is a feature file in the project as - """ - Feature: Simple Feature - Scenario: Simple Scenario - When I do something - """ - And all steps are bound and pass - - When I compile the solution using '' - And I execute the tests - - Then the execution summary should contain - | Total | Succeeded | - | 1 | 1 | - -Examples: - | Description | Language | Build Command | - | C# with dotnet build | C# | dotnet build | - | VB.NET with dotnet build | VB.NET | dotnet build | - -Examples: - | Description | Language | Build Command | - | C# with dotnet msbuild | C# | dotnet msbuild | - | VB.NET with dotnet msuild | VB.NET | dotnet msbuild | - -# duplicated scenario to be able to filter it out on CI build -@quarantaine -@globalusingdirective #MSBuild for VS2019 and Mono throws error CS8652: The feature 'global using directive' is currently in Preview and unsupported. -@requiresMsBuild -Scenario Outline: A project with a single scenario should compile successfully in all supported languages and build systems (MsBuild) - Given I have a '' test project - And there is a feature file in the project as - """ - Feature: Simple Feature - Scenario: Simple Scenario - When I do something - """ - And all steps are bound and pass - - When I compile the solution using '' - And I execute the tests - - Then the execution summary should contain - | Total | Succeeded | - | 1 | 1 | - -Examples: - | Description | Language | Build Command | - | C# with MSBuild | C# | MSBuild | - | VB.NET with MSBuild | VB.NET | MSBuild | diff --git a/Tests/Reqnroll.Specs/Features/MultipleSpecsProjects.feature b/Tests/Reqnroll.Specs/Features/MultipleSpecsProjects.feature deleted file mode 100644 index bac6830e7..000000000 --- a/Tests/Reqnroll.Specs/Features/MultipleSpecsProjects.feature +++ /dev/null @@ -1,27 +0,0 @@ -Feature: Multiple Specs Projects - -@quarantaine -Scenario Outline: Two projects with the same unit test provider - Given I have Specs.Project.A and Specs.Project.B using the same unit test provider - And Specs.Project.B references Specs.Project.A - When I build the solution using '' - Then the build should succeed - - Examples: - | Build Tool | - | dotnet build | - | dotnet msbuild | - -# duplicated scenario to be able to filter it out on CI build -@quarantaine -@requiresMsBuild -@globalusingdirective #MSBuild for VS2019 and Mono throws error CS8652: The feature 'global using directive' is currently in Preview and unsupported. -Scenario Outline: Two projects with the same unit test provider (MsBuild) - Given I have Specs.Project.A and Specs.Project.B using the same unit test provider - And Specs.Project.B references Specs.Project.A - When I build the solution using '' - Then the build should succeed - - Examples: - | Build Tool | - | MSBuild | diff --git a/Tests/Reqnroll.Specs/Features/RegressionTests/GH2571.feature b/Tests/Reqnroll.Specs/Features/RegressionTests/GH2571.feature deleted file mode 100644 index a43e14b6a..000000000 --- a/Tests/Reqnroll.Specs/Features/RegressionTests/GH2571.feature +++ /dev/null @@ -1,76 +0,0 @@ -Feature: GH2571 - -Parameters of Scenario Outlines contained in multi-line text that are nested inside of XML are not handled properly - https://github.com/reqnroll/Reqnroll/issues/2571 - - -Scenario: GH2571 - - Given there is a feature file in the project as - """ - Feature: parameters in DocStrings - Scenario Outline: nested angle brackets surround parameter names - Given the the package contains the web.config file of the following content - \"\"\" - - - - - - - \"\"\" - When the user gets the debug status - Then the customErrors element should be - - Examples: - | Mode | customErrors | - | Off | | - | RemoteOnly | | - """ - - And the following binding class - """ - using FluentAssertions; - using Reqnroll; - - [Binding] - public class ScenarioOutlineBug2571StepDefinitions - { - private string _docString; - private string _customErrorString; - - public ScenarioOutlineBug2571StepDefinitions() - { - - } - - - [Given("the the package contains the web.config file of the following content")] - public void GivenMyXMLIs(string scenarioText) - { - - _docString = scenarioText; - } - - - [When("the user gets the debug status")] - public void WhenIProcessXML() - { - - int x = _docString.IndexOf(" - $(Reqnroll_Specs_TFM) + net8.0 Reqnroll.Specs $(Reqnroll_KeyFile) false $(Reqnroll_PublicSign) Reqnroll.Specs true - Debug;Release;Debug-XUnit;Debug-MSTest;Debug-NUnit false true Always @@ -34,8 +33,8 @@ - + @@ -58,7 +57,7 @@ - <_Reqnroll_Needed_MSBuildGenerator Condition=" '$(MSBuildRuntimeType)' == 'Core'">$(Reqnroll_Core_Generator_TFM) + <_Reqnroll_Needed_MSBuildGenerator Condition=" '$(MSBuildRuntimeType)' == 'Core'">net6.0 <_Reqnroll_Needed_MSBuildGenerator Condition=" '$(MSBuildRuntimeType)' != 'Core'">$(Reqnroll_FullFramework_Generator_TFM) @@ -74,11 +73,11 @@ - + - + diff --git a/Tests/Reqnroll.Specs/Support/Hooks.cs b/Tests/Reqnroll.Specs/Support/Hooks.cs index a407bc287..a20729e6b 100644 --- a/Tests/Reqnroll.Specs/Support/Hooks.cs +++ b/Tests/Reqnroll.Specs/Support/Hooks.cs @@ -1,5 +1,6 @@ using System; using Reqnroll.TestProjectGenerator; +using Reqnroll.TestProjectGenerator.Data; using Reqnroll.TestProjectGenerator.Helpers; namespace Reqnroll.Specs.Support; @@ -22,6 +23,16 @@ public Hooks(ScenarioContext scenarioContext, CurrentVersionDriver currentVersio [BeforeScenario] public void BeforeScenario() { + _scenarioContext.ScenarioContainer.RegisterInstanceAs( + new TestRunConfiguration + { + ProgrammingLanguage = TestProjectGenerator.ProgrammingLanguage.CSharp, + ProjectFormat = ProjectFormat.New, + TargetFramework = TargetFramework.Net80, + UnitTestProvider = TestProjectGenerator.UnitTestProvider.MSTest, + ConfigurationFormat = ConfigurationFormat.Json + }); + _currentVersionDriver.NuGetVersion = NuGetPackageVersion.Version; _currentVersionDriver.ReqnrollNuGetVersion = NuGetPackageVersion.Version; _scenarioContext.ScenarioContainer.RegisterTypeAs(); diff --git a/azure-pipelines.yml b/azure-pipelines.yml deleted file mode 100644 index b76243dec..000000000 --- a/azure-pipelines.yml +++ /dev/null @@ -1,48 +0,0 @@ -name: $(GitAssemblyInformationalVersion) - - -resources: -- repo: self - clean: "true" - -trigger: - batch: true - branches: - include: - - master - paths: - exclude: - - docs/* - - .readthedocs.yml - -pr: - branches: - include: - - '*' - paths: - exclude: - - docs/* - - .readthedocs.yml - -jobs: -#- template: build.yml -# parameters: -# name: macOS -# artifactFileName: '$(Build.ArtifactStagingDirectory)/Reqnroll-macOS.zip' -# pool: -# name: 'Hosted macOS' - -- template: build.yml - parameters: - name: Windows - artifactFileName: '$(Build.ArtifactStagingDirectory)/Reqnroll-Windows.zip' - appInsightsInstrumentationKey: $(AppInsightsInstrumentationKey) - pool: - vmImage: 'windows-latest' - -- template: build.yml - parameters: - name: Linux - artifactFileName: '$(Build.ArtifactStagingDirectory)/Reqnroll-Linux.zip' - pool: - vmImage: 'ubuntu-latest' diff --git a/build.ps1 b/build.ps1 deleted file mode 100644 index d8b810f87..000000000 --- a/build.ps1 +++ /dev/null @@ -1,23 +0,0 @@ -param ( - [string]$Configuration = "Debug", - [string]$appInsightsInstrumentationKey = "" -) - -$additionalOptions = "" - -if ($IsLinux) { - $additionalOptions = "-p:EnableSourceControlManagerQueries=false -p:EnableSourceLink=false -p:DeterministicSourcePaths=false" -} - -if ($appInsightsInstrumentationKey) { - if ($additionalOptions){ - $additionalOptions = "$($additionalOptions) -property:AppInsightsInstrumentationKey=$($appInsightsInstrumentationKey)" - } - else { - $additionalOptions = "-property:AppInsightsInstrumentationKey=$($appInsightsInstrumentationKey)" - } -} - -Write-Host "dotnet build ./Reqnroll.sln --no-restore -property:Configuration=$Configuration -bl:msbuild.$Configuration.binlog -nodeReuse:false -v n --no-incremental $additionalOptions" - -& dotnet build ./Reqnroll.sln --no-restore -property:Configuration=$Configuration -bl:msbuild.$Configuration.binlog -nodeReuse:false -v n $additionalOptions diff --git a/build.yml b/build.yml deleted file mode 100644 index 3b2c6339c..000000000 --- a/build.yml +++ /dev/null @@ -1,93 +0,0 @@ -parameters: - name: '' - pool: '' - nugetVersion: 5.8.0 - net6SdkVersion: 6.0.x - artifactFileName: '' - appInsightsInstrumentationKey: '' - publishArtifacts: true - -jobs: -- job: ${{ parameters.name }} - pool: ${{ parameters.pool }} - steps: - - checkout: self - submodules: true - clean: true - - - task: CmdLine@2 - displayName: 'git log' - inputs: - script: 'git log -2' - - - task: NuGetToolInstaller@0 - displayName: 'Use NuGet' - inputs: - versionSpec: ${{ parameters.nugetVersion }} - - - task: UseDotNet@2 - displayName: 'Use .NET 6 SDK' - inputs: - packageType: sdk - version: ${{ parameters.net6SdkVersion }} - includePreviewVersions: true - - - task: PowerShell@2 - displayName: 'dotnet --info' - inputs: - targetType: Inline - pwsh: true - script: dotnet --info - - - task: PowerShell@2 - inputs: - targetType: Inline - pwsh: true - script: dotnet restore --configfile $(Build.SourcesDirectory)\nuget.config - displayName: 'dotnet restore' - - - task: PowerShell@2 - inputs: - filePath: build.ps1 - workingDirectory: ./ - arguments: Debug -appInsightsInstrumentationKey '${{ parameters.appInsightsInstrumentationKey }}' - pwsh: true - displayName: 'build Debug' - - - task: PowerShell@2 - inputs: - filePath: build.ps1 - workingDirectory: ./ - arguments: Release -appInsightsInstrumentationKey '${{ parameters.appInsightsInstrumentationKey }}' - pwsh: true - displayName: 'build Release' - - - task: ArchiveFiles@2 - displayName: 'Archive $(Build.SourcesDirectory)' - condition: and(succeeded(), ${{ parameters.publishArtifacts}}) - inputs: - rootFolderOrFile: '$(Build.SourcesDirectory)' - archiveFile: ${{ parameters.artifactFileName }} - - - task: PublishBuildArtifacts@1 - displayName: 'Publish Artifact: Reqnroll.CI-Sources' - condition: and(succeeded(), ${{ parameters.publishArtifacts}}) - inputs: - PathtoPublish: ${{ parameters.artifactFileName }} - artifactType: container - ArtifactName: 'Reqnroll.CI-BuildResult-${{ parameters.name }}' - - - task: CopyFiles@2 - displayName: 'Copy binlogs' - condition: ${{ parameters.publishArtifacts}} - inputs: - contents: '$(Build.SourcesDirectory)/*.binlog' - targetFolder: '$(Build.SourcesDirectory)/binlogs' - - - task: PublishBuildArtifacts@1 - displayName: 'Publish Artifact: Reqnroll.CI-Binlogs' - condition: ${{ parameters.publishArtifacts}} - inputs: - PathtoPublish: '$(Build.SourcesDirectory)/binlogs' - artifactType: container - ArtifactName: 'Reqnroll.CI-Binlogs-${{ parameters.name }}' diff --git a/clean.ps1 b/clean.ps1 deleted file mode 100644 index ef74c3228..000000000 --- a/clean.ps1 +++ /dev/null @@ -1 +0,0 @@ -git clean -fdx . \ No newline at end of file diff --git a/dotnet_sdk_validation_build.yml b/dotnet_sdk_validation_build.yml deleted file mode 100644 index e757d44c7..000000000 --- a/dotnet_sdk_validation_build.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: $(GitAssemblyInformationalVersion) - -resources: -- repo: self - clean: true - -trigger: none -pr: none -schedules: -- cron: "0 0 * * *" - displayName: Daily midnight build - branches: - include: - - master - -jobs: -- template: build.yml - parameters: - name: SDK_3_1_102 - artifactFileName: '$(Build.ArtifactStagingDirectory)/Reqnroll-Windows.zip' - appInsightsInstrumentationKey: $(AppInsightsInstrumentationKey) - sdkVersion: 3.1.102 - publishArtifacts: false - pool: - vmImage: 'windows-latest' - -- template: build.yml - parameters: - name: SDK_3_1_200 - artifactFileName: '$(Build.ArtifactStagingDirectory)/Reqnroll-Windows.zip' - appInsightsInstrumentationKey: $(AppInsightsInstrumentationKey) - sdkVersion: 3.1.x - publishArtifacts: false - pool: - vmImage: 'windows-latest' - -- template: build.yml - parameters: - name: SDK_5_0_100_preview_1 - artifactFileName: '$(Build.ArtifactStagingDirectory)/Reqnroll-Windows.zip' - appInsightsInstrumentationKey: $(AppInsightsInstrumentationKey) - sdkVersion: 5.x - publishArtifacts: false - pool: - vmImage: 'windows-latest' \ No newline at end of file diff --git a/nightly-build.yml b/nightly-build.yml deleted file mode 100644 index 49f029fb0..000000000 --- a/nightly-build.yml +++ /dev/null @@ -1,58 +0,0 @@ -# ASP.NET -# Build and test ASP.NET projects. -# Add steps that publish symbols, save build artifacts, deploy, and more: -# https://docs.microsoft.com/azure/devops/pipelines/apps/aspnet/build-aspnet-4 - -name: 0.2.$(Rev:r) - -trigger: none -pr: none - -# The time zone for cron schedules is UTC -# Cron expression is a space-delimited expression with five entries: mm HH DD MM DW -schedules: -- cron: "00 4 * * Mon-Fri" - displayName: Reqnroll Nightly build - branches: - include: - - master - always: true - -pool: - vmImage: 'windows-latest' - -workspace: - clean: 'all' - -variables: - sdkVersion: 3.1.201 - net5SdkVersion: 5.0.102 - net6SdkVersion: 6.x - -steps: -- checkout: self - submodules: true - -- task: UseDotNet@2 - displayName: 'Use .NET Core SDK: $(sdkVersion)' - inputs: - version: '$(sdkVersion)' - -- task: UseDotNet@2 - displayName: 'Use .NET 5 SDK: $(net5SdkVersion)' - inputs: - version: '$(net5SdkVersion)' - -- task: UseDotNet@2 - displayName: 'Use .NET 6 SDK: $(net6SdkVersion)' - inputs: - version: '$(net6SdkVersion)' - -- task: DotNetCoreCLI@2 - displayName: 'dotnet build Release' - inputs: - projects: Reqnroll.sln - arguments: --configuration Release - -- task: WhiteSource Bolt@20 - displayName: 'WhiteSource Bolt - Reqnroll' diff --git a/nuget.config b/nuget.config index 235d37c05..27a5cefad 100644 --- a/nuget.config +++ b/nuget.config @@ -2,15 +2,7 @@ - - - - - - - - \ No newline at end of file diff --git a/reqnroll.yml b/reqnroll.yml deleted file mode 100644 index 50aedb548..000000000 --- a/reqnroll.yml +++ /dev/null @@ -1,9 +0,0 @@ -name: Reqnroll -desc: Reqnroll is a pragmatic BDD solution for .NET -site: https://www.reqnroll.net -tags: -- bdd -- testing -upforgrabs: - name: up-for-grabs - link: https://github.com/reqnroll/Reqnroll/issues?q=is%3Aopen+is%3Aissue+label%3Aup-for-grabs \ No newline at end of file diff --git a/runtests.ps1 b/runtests.ps1 deleted file mode 100644 index 07e1e711b..000000000 --- a/runtests.ps1 +++ /dev/null @@ -1,14 +0,0 @@ - -$errorCode = dotnet test --no-build ./Tests/Reqnroll.RuntimeTests/Reqnroll.RuntimeTests.csproj -f net471 - -if ($errorCode -gt 0) { - Write-Host "Runtime Tests for .NET 4.7.1 failed" - exit -} - -$errorCode = dotnet test --no-build ./Tests/Reqnroll.RuntimeTests/Reqnroll.RuntimeTests.csproj -f netcoreapp2.0 - -if ($errorCode -gt 0) { - Write-Host "Runtime Tests for .NET Core 2.0 failed" - exit -} \ No newline at end of file From b484a1d6c5858fb2abf34042b2c9b7f2296d4619 Mon Sep 17 00:00:00 2001 From: Jacob Duijzer Date: Fri, 19 Apr 2024 10:17:50 +0200 Subject: [PATCH 07/21] Update index.md Fix an issue with variable types. The example starts with double, but ends up with decimals. The example does not work, and might lead to confussion. Changed the double to decimal in the first code block, to match it with the rest of the example. --- docs/quickstart/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/quickstart/index.md b/docs/quickstart/index.md index 6c802a6ea..df36ff447 100644 --- a/docs/quickstart/index.md +++ b/docs/quickstart/index.md @@ -33,13 +33,13 @@ namespace ReqnrollQuickstart.App; public class PriceCalculator { // the item prices are hard coded for now - private readonly Dictionary _priceTable = new() + private readonly Dictionary _priceTable = new() { { "Electric guitar", 180.0 }, { "Guitar pick", 1.5 } }; - public double CalculatePrice(Dictionary basket) + public decimal CalculatePrice(Dictionary basket) { throw new NotImplementedException(); } From 2c254eedd97d69bfde0289374d142ff177079b60 Mon Sep 17 00:00:00 2001 From: Chris Rudolphi <1702962+clrudolphi@users.noreply.github.com> Date: Mon, 22 Apr 2024 06:29:37 -0500 Subject: [PATCH 08/21] Fix 81 - modified CucumberExpressionParameterTypeRegistry to handle multiple custom types used as cucumber expressions when those types share the same short name. (#104) --- CHANGELOG.md | 2 +- ...CucumberExpressionParameterTypeRegistry.cs | 23 ++-- ...berExpressionParameterTypeRegistryTests.cs | 128 +++++++++++------- 3 files changed, 94 insertions(+), 59 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ada8ba44..7bc04007f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ test run (global) context instead of the test thread context. * Support for PriorityAttribute in MsTest adapter * Support for Scenario Outline / DataRowAttribute in MsTest adapter -* Fix for #81 in which Cucumber Expressions fail when two enums with the same short name (differing namespaces) are used as parameters +* Fix for #81 in which Cucumber Expressions fail when two enums or two custom types with the same short name (differing namespaces) are used as parameters * Fix: Adding @ignore to an Examples block generates invalid code for NUnit v3+ (#103) # v1.0.1 - 2024-02-16 diff --git a/Reqnroll/Bindings/CucumberExpressions/CucumberExpressionParameterTypeRegistry.cs b/Reqnroll/Bindings/CucumberExpressions/CucumberExpressionParameterTypeRegistry.cs index 56699730f..182654df6 100644 --- a/Reqnroll/Bindings/CucumberExpressions/CucumberExpressionParameterTypeRegistry.cs +++ b/Reqnroll/Bindings/CucumberExpressions/CucumberExpressionParameterTypeRegistry.cs @@ -87,7 +87,7 @@ private Dictionary InitializeR // get custom user transformations (both for built-in types and for custom types) var userTransformations = _bindingRegistry.GetStepTransformations() - .SelectMany(t => GetUserTransformations(t, aliases)); + .SelectMany(t => GetUserTransformations(t, builtInTransformations, aliases)); var parameterTypes = builtInTransformations .Concat(enumTypes) @@ -100,9 +100,11 @@ private Dictionary InitializeR return parameterTypes; } - private IEnumerable GetUserTransformations(IStepArgumentTransformationBinding t, Dictionary aliases) + private IEnumerable GetUserTransformations(IStepArgumentTransformationBinding t, BuiltInCucumberExpressionParameterTypeTransformation[] builtInTransformations, Dictionary aliases) { - yield return new UserDefinedCucumberExpressionParameterTypeTransformation(t); + bool IsUserDefinedType(IBindingType type) => !builtInTransformations.Any(b => b.TargetType.Name == type.Name); + var name = IsUserDefinedType(t.Method.ReturnType) && (t.Name == t.Method.ReturnType.Name || t.Name == null) ? t.Method.ReturnType.FullName : t.Name; + yield return new UserDefinedCucumberExpressionParameterTypeTransformation(t, name); // If the custom user transformations is for a built-in type, we also expose it with the // short name (e.g {int}) and not only with the type name (e.g. {Int32}). @@ -130,12 +132,17 @@ public IParameterType LookupByTypeName(string name) if (_parameterTypesByName.Value.TryGetValue(name, out var parameterType)) return parameterType; //enum keys contain the Fullname of the type, try matching on the short name: - var matchingEnums = _parameterTypesByName.Value.Where(kvp => kvp.Value.ParameterType.IsEnum && (kvp.Key.EndsWith("." + name) || kvp.Key.EndsWith("+" + name))).ToArray(); - if (matchingEnums.Length == 0) { return null; } - if (matchingEnums.Length == 1) { return matchingEnums[0].Value; } - if (matchingEnums.Length > 1) + var matchingEntries = _parameterTypesByName.Value.Where(kvp => kvp.Key.EndsWith("." + name) || kvp.Key.EndsWith("+" + name)).ToArray(); + if (matchingEntries.Length == 0) { return null; } + if (matchingEntries.Length == 1) { return matchingEntries[0].Value; } + if (matchingEntries.Length > 1) { - throw new ReqnrollException($"Ambiguous enum in cucumber expression. Multiple enums share the same short name '{name}'. Use the enum's full name in the cucumber expression or define a [StepArgumentTransformation] with the chosen type and the short name."); + if (matchingEntries[0].Value.ParameterType.IsEnum) + { + throw new ReqnrollException($"Ambiguous enum in cucumber expression. Multiple enums share the same short name '{name}'. Use the enum's full name in the cucumber expression or define a [StepArgumentTransformation] with the chosen type and the short name."); + } + else + throw new ReqnrollException($"Ambiguous type used in cucumber expressions. Multiple step method parameter types share the same short name '{name}'. Use a uniquely named StepArgumentTransformation for each. "); } return null; } diff --git a/Tests/Reqnroll.RuntimeTests/Bindings/CucumberExpressions/CucumberExpressionParameterTypeRegistryTests.cs b/Tests/Reqnroll.RuntimeTests/Bindings/CucumberExpressions/CucumberExpressionParameterTypeRegistryTests.cs index 44e66dcff..72781052a 100644 --- a/Tests/Reqnroll.RuntimeTests/Bindings/CucumberExpressions/CucumberExpressionParameterTypeRegistryTests.cs +++ b/Tests/Reqnroll.RuntimeTests/Bindings/CucumberExpressions/CucumberExpressionParameterTypeRegistryTests.cs @@ -9,7 +9,8 @@ using Reqnroll.Infrastructure; using Xunit; -namespace Reqnroll.RuntimeTests.Bindings.CucumberExpressions { +namespace Reqnroll.RuntimeTests.Bindings.CucumberExpressions +{ public class CucumberExpressionParameterTypeRegistryTests { @@ -47,15 +48,15 @@ public void Should_not_error_on_multiple_enums_of_the_same_name() var sut = CreateSut(); IBindingMethod enumUsingBindingMethod1 = new RuntimeBindingMethod(typeof(SampleEnumUsingClass).GetMethod(nameof(SampleEnumUsingClass.MethodUsingSampleColorEnum1))); sut.OnBindingMethodProcessed(enumUsingBindingMethod1); - IBindingMethod enumUsingBindingMethod2 = new RuntimeBindingMethod(typeof(CucumberAddtionalExpressions.EnumCucumberExpressions).GetMethod(nameof(CucumberAddtionalExpressions.EnumCucumberExpressions.MethodUsingSampleColorEnum2))); + IBindingMethod enumUsingBindingMethod2 = new RuntimeBindingMethod(typeof(CucumberAddtionalExpressions.KlasWithCucumberExpressions).GetMethod(nameof(CucumberAddtionalExpressions.KlasWithCucumberExpressions.MethodUsingSampleColorEnum2))); sut.OnBindingMethodProcessed(enumUsingBindingMethod2); var paramTypes = sut.GetParameterTypes().Where(pt => pt.ParameterType.IsEnum).ToList(); - + paramTypes.Should().HaveCount(2); } [Fact] - public void ParameterTypeRegistry_should_identify_error_when_given_multiple_bindings_with_an_ambiguous_enum_as_a_parameter() + public void ParameterTypeRegistry_should_identify_error_when_given_multiple_bindings_with_an_ambiguous_enum_as_a_parameter() { var expression = "I have {SampleColorEnum} cucumbers in my belly"; var containerBuilder = new ContainerBuilder(new CucumberExpressionIntegrationTests.TestDependencyProvider()); @@ -66,9 +67,66 @@ public void ParameterTypeRegistry_should_identify_error_when_given_multiple_bind var bindingRegistry = globalContainer.Resolve(); // set up first method binding that uses an ambiguous enum parameter + SetupBoundMethod(expression, bindingRegistry, bindingSourceProcessor, typeof(SampleEnumUsingClass), nameof(SampleEnumUsingClass.MethodUsingSampleColorEnum1)); + + // set up second method binding that uses an ambiguous enum parameter + SetupBoundMethod(expression, bindingRegistry, bindingSourceProcessor, typeof(CucumberAddtionalExpressions.KlasWithCucumberExpressions), nameof(CucumberAddtionalExpressions.KlasWithCucumberExpressions.MethodUsingSampleColorEnum2)); + + bindingSourceProcessor.BuildingCompleted(); + + bindingRegistry.IsValid.Should().BeFalse(); + var stepDefs = bindingRegistry.GetStepDefinitions().ToArray(); + stepDefs.Count().Should().Be(2); + stepDefs.All(sd => sd.SourceExpression == expression).Should().BeTrue(); + stepDefs.All(sd => sd.IsValid == false).Should().BeTrue(); + stepDefs.All(sd => sd.ErrorMessage.StartsWith("Ambiguous enum")).Should().BeTrue(); + } + + [Fact] + public void ParameterTypeRegistry_should_identify_error_when_given_multiple_bindings_with_an_ambiguous_type_as_a_parameter() + { + var expression = "a user {SampleUser} is registered"; + var containerBuilder = new ContainerBuilder(new CucumberExpressionIntegrationTests.TestDependencyProvider()); + var globalContainer = containerBuilder.CreateGlobalContainer(GetType().Assembly); + + var bindingSourceProcessor = globalContainer.Resolve(); + + var bindingRegistry = globalContainer.Resolve(); + + IStepArgumentTransformationBinding transformation1 = new StepArgumentTransformationBinding( + "user ([A-Z][a-z]+)", + new RuntimeBindingMethod(typeof(Reqnroll.RuntimeTests.Bindings.CucumberExpressions.SampleUser).GetMethod(nameof(Reqnroll.RuntimeTests.Bindings.CucumberExpressions.SampleUser.Create)))); + + // set up first method binding that uses an ambiguous enum parameter + SetupBoundMethod(expression, bindingRegistry, bindingSourceProcessor, typeof(CucumberExpressionIntegrationTests.SampleBindings), nameof(CucumberExpressionIntegrationTests.SampleBindings.StepDefWithCustomClassParam), transformation1); + + IStepArgumentTransformationBinding transformation2 = new StepArgumentTransformationBinding( + "user ([A-Z][a-z]+)", + new RuntimeBindingMethod(typeof(Reqnroll.RuntimeTests.Bindings.CucumberAddtionalExpressions.SampleUser).GetMethod(nameof(Reqnroll.RuntimeTests.Bindings.CucumberAddtionalExpressions.SampleUser.Create)))); + + // set up second method binding that uses an ambiguous enum parameter + SetupBoundMethod(expression, bindingRegistry, bindingSourceProcessor, typeof(CucumberAddtionalExpressions.KlasWithCucumberExpressions), nameof(CucumberAddtionalExpressions.KlasWithCucumberExpressions.MethodUsingSampleUser), transformation2); + + bindingSourceProcessor.BuildingCompleted(); + + bindingRegistry.IsValid.Should().BeFalse(); + var stepDefs = bindingRegistry.GetStepDefinitions().ToArray(); + stepDefs.Count().Should().Be(2); + stepDefs.All(sd => sd.SourceExpression == expression).Should().BeTrue(); + stepDefs.All(sd => sd.IsValid == false).Should().BeTrue(); + stepDefs.All(sd => sd.ErrorMessage.StartsWith("Ambiguous type used in cucumber expressions")).Should().BeTrue(); + } + + + private static void SetupBoundMethod(string expression, IBindingRegistry bindingRegistry, IRuntimeBindingSourceProcessor bindingSourceProcessor, Type testType, string methodName, IStepArgumentTransformationBinding transformation = null) + { + if (transformation != null) + { + bindingRegistry.RegisterStepArgumentTransformationBinding(transformation); + } var bindingSourceMethod = new BindingSourceMethod { - BindingMethod = new RuntimeBindingMethod(typeof(CucumberExpressionIntegrationTests.SampleBindings).GetMethod(nameof(CucumberExpressionIntegrationTests.SampleBindings.StepDefWithEnumParam))), + BindingMethod = new RuntimeBindingMethod(testType.GetMethod(methodName)), IsPublic = true, Attributes = new[] { @@ -97,58 +155,28 @@ public void ParameterTypeRegistry_should_identify_error_when_given_multiple_bind IsClass = true }); bindingSourceProcessor.ProcessMethod(bindingSourceMethod); - - // set up second method binding that uses an ambiguous enum parameter - var second_bindingSourceMethod = new BindingSourceMethod - { - BindingMethod = new RuntimeBindingMethod(typeof(CucumberAddtionalExpressions.EnumCucumberExpressions).GetMethod(nameof(CucumberAddtionalExpressions.EnumCucumberExpressions.MethodUsingSampleColorEnum2))), - IsPublic = true, - Attributes = new[] - { - new BindingSourceAttribute - { - AttributeType = new RuntimeBindingType(typeof(GivenAttribute)), - AttributeValues = new IBindingSourceAttributeValueProvider[] - { - new BindingSourceAttributeValueProvider(expression) - } - } - } - }; - bindingSourceProcessor.ProcessType( - new BindingSourceType - { - BindingType = new RuntimeBindingType(typeof(CucumberAddtionalExpressions.EnumCucumberExpressions)), - Attributes = new[] - { - new BindingSourceAttribute - { - AttributeType = new RuntimeBindingType(typeof(BindingAttribute)) - } - }, - IsPublic = true, - IsClass = true - }); - bindingSourceProcessor.ProcessMethod(second_bindingSourceMethod); - - - bindingSourceProcessor.BuildingCompleted(); - - bindingRegistry.IsValid.Should().BeFalse(); - var stepDefs = bindingRegistry.GetStepDefinitions().ToArray(); - stepDefs.Count().Should().Be(2); - stepDefs.All(sd => sd.SourceExpression == expression).Should().BeTrue(); - stepDefs.All(sd => sd.IsValid == false).Should().BeTrue(); - stepDefs.All(sd => sd.ErrorMessage.StartsWith("Ambiguous enum")).Should().BeTrue(); } } } namespace Reqnroll.RuntimeTests.Bindings.CucumberAddtionalExpressions { - public class EnumCucumberExpressions + public class KlasWithCucumberExpressions { public enum SampleColorEnum { Yellow, Brown }; - + public void MethodUsingSampleColorEnum2(SampleColorEnum color) { } + + public void MethodUsingSampleUser(SampleUser user) { } + } + + public class SampleUser + { + public string UserName { get; } + + public SampleUser(string userName) + { + UserName = userName; + } + public static SampleUser Create(string userName) => new(userName); } } \ No newline at end of file From 629f009ac8a427a0ceea577ab9d0c40e319789ee Mon Sep 17 00:00:00 2001 From: Don Alvarez Date: Mon, 22 Apr 2024 14:28:19 -0700 Subject: [PATCH 09/21] Update README.md (#110) Add link to Migrating from SpecFlow page --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 99329e310..224de1f8c 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ Reqnroll works on all major operating systems (Windows, Linux, macOS), on all co * [Quickstart guide](https://go.reqnroll.net/quickstart) * [Reqnroll website](https://reqnroll.net/) * [Reqnroll documentation](https://docs.reqnroll.net/) +* [Migrating from SpecFlow](https://docs.reqnroll.net/latest/guides/migrating-from-specflow.html) * [Release notes](https://go.reqnroll.net/release-notes) * [IDE setup instructions for Reqnroll](https://go.reqnroll.net/doc-setup-ide) @@ -34,4 +35,4 @@ Reqnroll for VisualStudio is licensed under the [BSD 3-Clause License](LICENSE). Copyright (c) 2024 Reqnroll -This project is based on the [SpecFlow](https://github.com/SpecFlowOSS/SpecFlow) framework. \ No newline at end of file +This project is based on the [SpecFlow](https://github.com/SpecFlowOSS/SpecFlow) framework. From 8fb02efadd90d933f29e5089f3ba1c2e76816934 Mon Sep 17 00:00:00 2001 From: Chris Rudolphi <1702962+clrudolphi@users.noreply.github.com> Date: Mon, 29 Apr 2024 09:48:35 -0500 Subject: [PATCH 10/21] Fix 111 ignore attr not inherited from rule (#113) * Added test case with an @ignore'd Rule * Modified code generator to check ScenarioInfo.CombinedTags property * Updated Changelog --- CHANGELOG.md | 1 + Reqnroll.Generator/Generation/UnitTestMethodGenerator.cs | 4 +++- .../Reqnroll.SystemTests/Generation/GenerationTestBase.cs | 8 ++++++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bc04007f..68b8079b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ * Support for Scenario Outline / DataRowAttribute in MsTest adapter * Fix for #81 in which Cucumber Expressions fail when two enums or two custom types with the same short name (differing namespaces) are used as parameters * Fix: Adding @ignore to an Examples block generates invalid code for NUnit v3+ (#103) +* Fix: #111 @ignore attribute is not inherited to the scenarios from Rule # v1.0.1 - 2024-02-16 diff --git a/Reqnroll.Generator/Generation/UnitTestMethodGenerator.cs b/Reqnroll.Generator/Generation/UnitTestMethodGenerator.cs index ba2ef7e3a..252c6ca34 100644 --- a/Reqnroll.Generator/Generation/UnitTestMethodGenerator.cs +++ b/Reqnroll.Generator/Generation/UnitTestMethodGenerator.cs @@ -277,8 +277,10 @@ internal void GenerateTestMethodBody(TestClassGenerationContext generationContex var tagsOfScenarioVariableReferenceExpression = new CodeVariableReferenceExpression(GeneratorConstants.SCENARIO_TAGS_VARIABLE_NAME); var featureFileTagFieldReferenceExpression = new CodeFieldReferenceExpression(null, GeneratorConstants.FEATURE_TAGS_VARIABLE_NAME); + var scenarioCombinedTagsPropertyExpression = new CodePropertyReferenceExpression(new CodeVariableReferenceExpression("scenarioInfo"), "CombinedTags"); + var tagHelperReference = new CodeTypeReferenceExpression(_codeDomHelper.GetGlobalizedTypeName(typeof(TagHelper))); - var scenarioTagIgnoredCheckStatement = new CodeMethodInvokeExpression(tagHelperReference, nameof(TagHelper.ContainsIgnoreTag), tagsOfScenarioVariableReferenceExpression); + var scenarioTagIgnoredCheckStatement = new CodeMethodInvokeExpression(tagHelperReference, nameof(TagHelper.ContainsIgnoreTag), scenarioCombinedTagsPropertyExpression); var featureTagIgnoredCheckStatement = new CodeMethodInvokeExpression(tagHelperReference, nameof(TagHelper.ContainsIgnoreTag), featureFileTagFieldReferenceExpression); var ifIsIgnoredStatement = new CodeConditionStatement( diff --git a/Tests/Reqnroll.SystemTests/Generation/GenerationTestBase.cs b/Tests/Reqnroll.SystemTests/Generation/GenerationTestBase.cs index 561f42714..1df2aff18 100644 --- a/Tests/Reqnroll.SystemTests/Generation/GenerationTestBase.cs +++ b/Tests/Reqnroll.SystemTests/Generation/GenerationTestBase.cs @@ -84,6 +84,11 @@ When the step Examples: | result | | fails | + + @ignore + Rule: Scenario in this Rule should be Ignored + Scenario: Ruleignored scenario + When the step passes """); _projectsDriver.AddPassingStepBinding(stepRegex: "the step passes"); _projectsDriver.AddFailingStepBinding(stepRegex: "the step fails"); @@ -134,6 +139,9 @@ When the step _vsTestExecutionDriver.LastTestExecutionResult.LeafTestResults .Should().ContainSingle(tr => tr.TestName.StartsWith("ExampleIgnored")) .Which.Outcome.Should().Be(expectedIgnoredOutcome); + _vsTestExecutionDriver.LastTestExecutionResult.LeafTestResults + .Should().ContainSingle(tr => tr.TestName.StartsWith("Ruleignored")) + .Which.Outcome.Should().Be(expectedIgnoredOutcome); AssertIgnoredScenarioOutlineExampleHandled(); } From cd519f32c91ad7abbfcd6f9fdd12d85e7b81882b Mon Sep 17 00:00:00 2001 From: obligaron Date: Thu, 2 May 2024 09:09:20 +0200 Subject: [PATCH 11/21] UnitTests: Check if SDK version is installed and if not ignore the test (#109) * UnitTests: Check if SDK version is installed and if not ignore the test * SystemTests: Introduce SkippableTestMethodAttribute * UnitTests: Only ignore missing sdks when PipeLineMode is not set * UnitTest: Check PipelineMode in TestMethod --- .../ProjectTests.cs | 370 ++++++++++-------- ...Reqnroll.TestProjectGenerator.Tests.csproj | 1 + .../SolutionTests.cs | 6 +- .../DotNetSdkNotInstalledException.cs | 24 ++ .../Driver/CompilationDriver.cs | 8 +- .../FilesystemWriter/SolutionWriter.cs | 8 +- .../Portability/PortabilityTestBase.cs | 64 ++- 7 files changed, 301 insertions(+), 180 deletions(-) create mode 100644 Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/DotNetSdkNotInstalledException.cs diff --git a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator.Tests/ProjectTests.cs b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator.Tests/ProjectTests.cs index 1797b9360..1ec4cf57b 100644 --- a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator.Tests/ProjectTests.cs +++ b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator.Tests/ProjectTests.cs @@ -35,193 +35,220 @@ public ProjectTests() return (solution, project, folder); } - [Fact] + private SolutionWriter CreateSolutionWriter() => new SolutionWriter(new Mock().Object); + + private void RunSkippableTest(Action test) + { + try + { + test(); + } + catch (DotNetSdkNotInstalledException ex) + { + Skip.IfNot(new ConfigurationDriver().PipelineMode, ex.ToString()); + } + } + + [SkippableFact] public void AddNuGetPackageToProjectInNewFormat() { - - var (solution, project, solutionFolder) = CreateEmptySolutionAndProject(ProjectFormat.New, ProgrammingLanguage.CSharp); + RunSkippableTest(() => + { + var (solution, project, solutionFolder) = CreateEmptySolutionAndProject(ProjectFormat.New, ProgrammingLanguage.CSharp); - project.AddNuGetPackage("Reqnroll", "2.3.1", new NuGetPackageAssembly("Reqnroll, Version=2.3.1.0, Culture=neutral, PublicKeyToken=0778194805d6db41, processorArchitecture=MSIL", "net45\\Reqnroll.dll")); + project.AddNuGetPackage("Reqnroll", "2.3.1", new NuGetPackageAssembly("Reqnroll, Version=2.3.1.0, Culture=neutral, PublicKeyToken=0778194805d6db41, processorArchitecture=MSIL", "net45\\Reqnroll.dll")); - new SolutionWriter(new Mock().Object).WriteToFileSystem(solution, solutionFolder); + CreateSolutionWriter().WriteToFileSystem(solution, solutionFolder); - var projectFileContent = GetProjectFileContent(solutionFolder, project); + var projectFileContent = GetProjectFileContent(solutionFolder, project); - projectFileContent.Should().Contain(""); + projectFileContent.Should().Contain(""); + }); } - [Fact] + [SkippableFact] public void AddNuGetPackageToProjectInOldFormat() { - - var (solution, project, solutionFolder) = CreateEmptySolutionAndProject(ProjectFormat.Old, ProgrammingLanguage.CSharp); + RunSkippableTest(() => + { + var (solution, project, solutionFolder) = CreateEmptySolutionAndProject(ProjectFormat.Old, ProgrammingLanguage.CSharp); - project.AddNuGetPackage("Reqnroll", "2.3.1", new NuGetPackageAssembly("Reqnroll, Version=2.3.1.0, Culture=neutral, PublicKeyToken=0778194805d6db41, processorArchitecture=MSIL", "net45\\Reqnroll.dll")); + project.AddNuGetPackage("Reqnroll", "2.3.1", new NuGetPackageAssembly("Reqnroll, Version=2.3.1.0, Culture=neutral, PublicKeyToken=0778194805d6db41, processorArchitecture=MSIL", "net45\\Reqnroll.dll")); - new SolutionWriter(new Mock().Object).WriteToFileSystem(solution, solutionFolder); + CreateSolutionWriter().WriteToFileSystem(solution, solutionFolder); - var projectFileContent = GetProjectFileContent(solutionFolder, project); + var projectFileContent = GetProjectFileContent(solutionFolder, project); - projectFileContent.Should().Contain(""); - projectFileContent.Should().Match("**..\\packages\\Reqnroll.2.3.1\\lib\\net45\\Reqnroll.dll**"); + projectFileContent.Should().Contain(""); + projectFileContent.Should().Match("**..\\packages\\Reqnroll.2.3.1\\lib\\net45\\Reqnroll.dll**"); + }); } - [Fact] + [SkippableFact] public void AddNuGetPackageWithMSBuildFilesToProjectInOldFormat() { + RunSkippableTest(() => + { + var (solution, project, solutionFolder) = CreateEmptySolutionAndProject(ProjectFormat.Old, ProgrammingLanguage.CSharp); - var (solution, project, solutionFolder) = CreateEmptySolutionAndProject(ProjectFormat.Old, ProgrammingLanguage.CSharp); + project.AddNuGetPackage( + "Reqnroll.Tools.MsBuild.Generation", + "2.3.2-preview20180328"); - project.AddNuGetPackage( - "Reqnroll.Tools.MsBuild.Generation", - "2.3.2-preview20180328"); + CreateSolutionWriter().WriteToFileSystem(solution, solutionFolder); - new SolutionWriter(new Mock().Object).WriteToFileSystem(solution, solutionFolder); + var projectFileContent = GetProjectFileContent(solutionFolder, project); - var projectFileContent = GetProjectFileContent(solutionFolder, project); - - projectFileContent.Should().Contain(""); - projectFileContent.Should().Contain(""); - + projectFileContent.Should().Contain(""); + projectFileContent.Should().Contain(""); + }); } - [Fact] + [SkippableFact] public void AddReferenceToProjectInNewFormat() { - - var (solution, project, solutionFolder) = CreateEmptySolutionAndProject(ProjectFormat.New, ProgrammingLanguage.CSharp); + RunSkippableTest(() => + { + var (solution, project, solutionFolder) = CreateEmptySolutionAndProject(ProjectFormat.New, ProgrammingLanguage.CSharp); - project.AddReference("System.Configuration"); + project.AddReference("System.Configuration"); - new SolutionWriter(new Mock().Object).WriteToFileSystem(solution, solutionFolder); + CreateSolutionWriter().WriteToFileSystem(solution, solutionFolder); - var projectFileContent = GetProjectFileContent(solutionFolder, project); + var projectFileContent = GetProjectFileContent(solutionFolder, project); - projectFileContent.Should().Contain(""); + projectFileContent.Should().Contain(""); + }); } - [Fact] + [SkippableFact] public void AddReferenceToProjectInOldFormat() { + RunSkippableTest(() => + { + var (solution, project, solutionFolder) = CreateEmptySolutionAndProject(ProjectFormat.Old, ProgrammingLanguage.CSharp); - var (solution, project, solutionFolder) = CreateEmptySolutionAndProject(ProjectFormat.Old, ProgrammingLanguage.CSharp); + project.AddReference("System.Configuration"); - project.AddReference("System.Configuration"); + CreateSolutionWriter().WriteToFileSystem(solution, solutionFolder); - new SolutionWriter(new Mock().Object).WriteToFileSystem(solution, solutionFolder); + var projectFileContent = GetProjectFileContent(solutionFolder, project); - var projectFileContent = GetProjectFileContent(solutionFolder, project); - - projectFileContent.Should().Contain(""); + projectFileContent.Should().Contain(""); + }); } - [Fact] + [SkippableFact] public void AddFileToProjectInOldFormat() { + RunSkippableTest(() => + { + var (solution, project, solutionFolder) = CreateEmptySolutionAndProject(ProjectFormat.Old, ProgrammingLanguage.CSharp); - var (solution, project, solutionFolder) = CreateEmptySolutionAndProject(ProjectFormat.Old, ProgrammingLanguage.CSharp); - - var projectFile = new ProjectFile("File.cs", "Compile", "//no code"); + var projectFile = new ProjectFile("File.cs", "Compile", "//no code"); - project.AddFile(projectFile); + project.AddFile(projectFile); - new SolutionWriter(new Mock().Object).WriteToFileSystem(solution, solutionFolder); + CreateSolutionWriter().WriteToFileSystem(solution, solutionFolder); - var projectFileContent = GetProjectFileContent(solutionFolder, project); - var filePath = Path.Combine(GetProjectFolderPath(solutionFolder, project), "File.cs"); + var projectFileContent = GetProjectFileContent(solutionFolder, project); + var filePath = Path.Combine(GetProjectFolderPath(solutionFolder, project), "File.cs"); - projectFileContent.Should().Contain(""); - File.Exists(filePath).Should().BeTrue(); - File.ReadAllText(filePath).Should().Contain("//no code"); - + projectFileContent.Should().Contain(""); + File.Exists(filePath).Should().BeTrue(); + File.ReadAllText(filePath).Should().Contain("//no code"); + }); } - [Fact] + [SkippableFact] public void AddFileToProjectInNewFormat() { + RunSkippableTest(() => + { + var (solution, project, solutionFolder) = CreateEmptySolutionAndProject(ProjectFormat.New, ProgrammingLanguage.CSharp); - var (solution, project, solutionFolder) = CreateEmptySolutionAndProject(ProjectFormat.New, ProgrammingLanguage.CSharp); - - var projectFile = new ProjectFile("File.cs", "Compile", "//no code"); + var projectFile = new ProjectFile("File.cs", "Compile", "//no code"); - project.AddFile(projectFile); + project.AddFile(projectFile); - new SolutionWriter(new Mock().Object).WriteToFileSystem(solution, solutionFolder); + CreateSolutionWriter().WriteToFileSystem(solution, solutionFolder); - var projectFileContent = GetProjectFileContent(solutionFolder, project); - var filePath = Path.Combine(GetProjectFolderPath(solutionFolder, project), "File.cs"); + var projectFileContent = GetProjectFileContent(solutionFolder, project); + var filePath = Path.Combine(GetProjectFolderPath(solutionFolder, project), "File.cs"); - projectFileContent.Should().NotContain(" + { + var (solution, project, solutionFolder) = CreateEmptySolutionAndProject(ProjectFormat.Old, ProgrammingLanguage.CSharp); - var (solution, project, solutionFolder) = CreateEmptySolutionAndProject(ProjectFormat.Old, ProgrammingLanguage.CSharp); - - var projectFile = new ProjectFile(Path.Combine("Folder","File.cs"), "Compile", "//no code"); + var projectFile = new ProjectFile(Path.Combine("Folder", "File.cs"), "Compile", "//no code"); - project.AddFile(projectFile); + project.AddFile(projectFile); - new SolutionWriter(new Mock().Object).WriteToFileSystem(solution, solutionFolder); + CreateSolutionWriter().WriteToFileSystem(solution, solutionFolder); - var projectFileContent = GetProjectFileContent(solutionFolder, project); - var filePath = Path.Combine(GetProjectFolderPath(solutionFolder, project), "Folder", "File.cs"); + var projectFileContent = GetProjectFileContent(solutionFolder, project); + var filePath = Path.Combine(GetProjectFolderPath(solutionFolder, project), "Folder", "File.cs"); - projectFileContent.Should().Contain(""); - File.Exists(filePath).Should().BeTrue(); - File.ReadAllText(filePath).Should().Contain("//no code"); - + projectFileContent.Should().Contain(""); + File.Exists(filePath).Should().BeTrue(); + File.ReadAllText(filePath).Should().Contain("//no code"); + }); } - [Fact] + [SkippableFact] public void AddFileInFolderToProjectInNewFormat() { + RunSkippableTest(() => + { + var (solution, project, solutionFolder) = CreateEmptySolutionAndProject(ProjectFormat.New, ProgrammingLanguage.CSharp); - var (solution, project, solutionFolder) = CreateEmptySolutionAndProject(ProjectFormat.New, ProgrammingLanguage.CSharp); - - var projectFile = new ProjectFile(Path.Combine("Folder", "File.cs"), "Compile", "//no code"); + var projectFile = new ProjectFile(Path.Combine("Folder", "File.cs"), "Compile", "//no code"); - project.AddFile(projectFile); + project.AddFile(projectFile); - new SolutionWriter(new Mock().Object).WriteToFileSystem(solution, solutionFolder); + CreateSolutionWriter().WriteToFileSystem(solution, solutionFolder); - var projectFileContent = GetProjectFileContent(solutionFolder, project); - var filePath = Path.Combine(GetProjectFolderPath(solutionFolder, project), "Folder", "File.cs"); + var projectFileContent = GetProjectFileContent(solutionFolder, project); + var filePath = Path.Combine(GetProjectFolderPath(solutionFolder, project), "Folder", "File.cs"); - projectFileContent.Should().NotContain(" + { + var (solution, project, solutionFolder) = CreateEmptySolutionAndProject(ProjectFormat.New, ProgrammingLanguage.CSharp); - new SolutionWriter(new Mock().Object).WriteToFileSystem(solution, solutionFolder); + CreateSolutionWriter().WriteToFileSystem(solution, solutionFolder); - string projectFileContent = GetProjectFileContent(solutionFolder, project); + string projectFileContent = GetProjectFileContent(solutionFolder, project); - projectFileContent.Should().Contain(""); + projectFileContent.Should().Contain(""); + }); } - [Fact] + [SkippableFact] public void CreateEmptyCSharpCore3_1ProjectInNewFormat() { - var (solution, project, solutionFolder) = CreateEmptySolutionAndProject(ProjectFormat.New, ProgrammingLanguage.CSharp, TargetFramework.Netcoreapp31); + RunSkippableTest(() => + { + var (solution, project, solutionFolder) = CreateEmptySolutionAndProject(ProjectFormat.New, ProgrammingLanguage.CSharp, TargetFramework.Netcoreapp31); - new SolutionWriter(new Mock().Object).WriteToFileSystem(solution, solutionFolder); + CreateSolutionWriter().WriteToFileSystem(solution, solutionFolder); - string projectFileContent = GetProjectFileContent(solutionFolder, project); + string projectFileContent = GetProjectFileContent(solutionFolder, project); - projectFileContent.Should() - .Contain("\r\n \r\n netcoreapp3.1\r\n \r\n"); + projectFileContent.Should() + .Contain("\r\n \r\n netcoreapp3.1\r\n \r\n"); + }); } - [Fact] + [SkippableFact] public void CreateEmptyCSharpNet50ProjectInNewFormat() { - var (solution, project, solutionFolder) = CreateEmptySolutionAndProject(ProjectFormat.New, ProgrammingLanguage.CSharp73, TargetFramework.Net50); + RunSkippableTest(() => + { + var (solution, project, solutionFolder) = CreateEmptySolutionAndProject(ProjectFormat.New, ProgrammingLanguage.CSharp73, TargetFramework.Net50); - new SolutionWriter(new Mock().Object).WriteToFileSystem(solution, solutionFolder); + CreateSolutionWriter().WriteToFileSystem(solution, solutionFolder); - string projectFileContent = GetProjectFileContent(solutionFolder, project); + string projectFileContent = GetProjectFileContent(solutionFolder, project); - projectFileContent.Should() - .Contain("\r\n \r\n net5.0\r\n 7.3\r\n \r\n"); + projectFileContent.Should() + .Contain("\r\n \r\n net5.0\r\n 7.3\r\n \r\n"); + }); } - [Fact] + [SkippableFact] public void CreateEmptyCSharpNet60ProjectInNewFormat() { - var (solution, project, solutionFolder) = CreateEmptySolutionAndProject(ProjectFormat.New, ProgrammingLanguage.CSharp, TargetFramework.Net60); + RunSkippableTest(() => + { + var (solution, project, solutionFolder) = CreateEmptySolutionAndProject(ProjectFormat.New, ProgrammingLanguage.CSharp, TargetFramework.Net60); - new SolutionWriter(new Mock().Object).WriteToFileSystem(solution, solutionFolder); + CreateSolutionWriter().WriteToFileSystem(solution, solutionFolder); - string projectFileContent = GetProjectFileContent(solutionFolder, project); + string projectFileContent = GetProjectFileContent(solutionFolder, project); - projectFileContent.Should() - .Contain("\r\n \r\n net6.0\r\n enable\r\n enable\r\n \r\n"); + projectFileContent.Should() + .Contain("\r\n \r\n net6.0\r\n enable\r\n enable\r\n \r\n"); + }); } - [Fact] + [SkippableFact] public void CreateEmptyCSharpNet70ProjectInNewFormat() { - var (solution, project, solutionFolder) = CreateEmptySolutionAndProject(ProjectFormat.New, ProgrammingLanguage.CSharp, TargetFramework.Net70); + RunSkippableTest(() => + { + var (solution, project, solutionFolder) = CreateEmptySolutionAndProject(ProjectFormat.New, ProgrammingLanguage.CSharp, TargetFramework.Net70); - new SolutionWriter(new Mock().Object).WriteToFileSystem(solution, solutionFolder); + CreateSolutionWriter().WriteToFileSystem(solution, solutionFolder); - string projectFileContent = GetProjectFileContent(solutionFolder, project); + string projectFileContent = GetProjectFileContent(solutionFolder, project); - projectFileContent.Should() - .Contain("\r\n \r\n net7.0\r\n enable\r\n enable\r\n \r\n"); + projectFileContent.Should() + .Contain("\r\n \r\n net7.0\r\n enable\r\n enable\r\n \r\n"); + }); } - [Fact] + [SkippableFact] public void CreateEmptyCSharpNet80ProjectInNewFormat() { - var (solution, project, solutionFolder) = CreateEmptySolutionAndProject(ProjectFormat.New, ProgrammingLanguage.CSharp, TargetFramework.Net80); + RunSkippableTest(() => + { + var (solution, project, solutionFolder) = CreateEmptySolutionAndProject(ProjectFormat.New, ProgrammingLanguage.CSharp, TargetFramework.Net80); - new SolutionWriter(new Mock().Object).WriteToFileSystem(solution, solutionFolder); + CreateSolutionWriter().WriteToFileSystem(solution, solutionFolder); - string projectFileContent = GetProjectFileContent(solutionFolder, project); + string projectFileContent = GetProjectFileContent(solutionFolder, project); - projectFileContent.Should() - .Contain("\r\n \r\n net8.0\r\n enable\r\n enable\r\n \r\n"); + projectFileContent.Should() + .Contain("\r\n \r\n net8.0\r\n enable\r\n enable\r\n \r\n"); + }); } - [Fact] + [SkippableFact] public void CreateEmptyCSharpNet481ProjectInNewFormat() { - var (solution, project, solutionFolder) = CreateEmptySolutionAndProject(ProjectFormat.New, ProgrammingLanguage.CSharp73, TargetFramework.Net481); + RunSkippableTest(() => + { + var (solution, project, solutionFolder) = CreateEmptySolutionAndProject(ProjectFormat.New, ProgrammingLanguage.CSharp73, TargetFramework.Net481); - new SolutionWriter(new Mock().Object).WriteToFileSystem(solution, solutionFolder); + CreateSolutionWriter().WriteToFileSystem(solution, solutionFolder); - string projectFileContent = GetProjectFileContent(solutionFolder, project); + string projectFileContent = GetProjectFileContent(solutionFolder, project); - projectFileContent.Should() - .Contain("\r\n \r\n net481\r\n 7.3\r\n \r\n"); + projectFileContent.Should() + .Contain("\r\n \r\n net481\r\n 7.3\r\n \r\n"); + }); } - [Fact] + [SkippableFact] public void CreateEmptyCSharpNet462ProjectInNewFormat() { - var (solution, project, solutionFolder) = CreateEmptySolutionAndProject(ProjectFormat.New, ProgrammingLanguage.CSharp73, TargetFramework.Net462); + RunSkippableTest(() => + { + var (solution, project, solutionFolder) = CreateEmptySolutionAndProject(ProjectFormat.New, ProgrammingLanguage.CSharp73, TargetFramework.Net462); - new SolutionWriter(new Mock().Object).WriteToFileSystem(solution, solutionFolder); + CreateSolutionWriter().WriteToFileSystem(solution, solutionFolder); - string projectFileContent = GetProjectFileContent(solutionFolder, project); + string projectFileContent = GetProjectFileContent(solutionFolder, project); - projectFileContent.Should() - .Contain("\r\n \r\n net462\r\n 7.3\r\n \r\n"); + projectFileContent.Should() + .Contain("\r\n \r\n net462\r\n 7.3\r\n \r\n"); + }); } - [Fact] + [SkippableFact] public void CreateEmptyCSharpNet472ProjectInNewFormat() { - var (solution, project, solutionFolder) = CreateEmptySolutionAndProject(ProjectFormat.New, ProgrammingLanguage.CSharp73, TargetFramework.Net472); + RunSkippableTest(() => + { + var (solution, project, solutionFolder) = CreateEmptySolutionAndProject(ProjectFormat.New, ProgrammingLanguage.CSharp73, TargetFramework.Net472); - new SolutionWriter(new Mock().Object).WriteToFileSystem(solution, solutionFolder); + CreateSolutionWriter().WriteToFileSystem(solution, solutionFolder); - string projectFileContent = GetProjectFileContent(solutionFolder, project); + string projectFileContent = GetProjectFileContent(solutionFolder, project); - projectFileContent.Should() - .Contain("\r\n \r\n net472\r\n 7.3\r\n \r\n"); + projectFileContent.Should() + .Contain("\r\n \r\n net472\r\n 7.3\r\n \r\n"); + }); } - [Fact] + [SkippableFact] public void CreateEmptyFSharpProjectInNewFormat() { - var (solution, project, solutionFolder) = CreateEmptySolutionAndProject(ProjectFormat.New, ProgrammingLanguage.FSharp); + RunSkippableTest(() => + { + var (solution, project, solutionFolder) = CreateEmptySolutionAndProject(ProjectFormat.New, ProgrammingLanguage.FSharp); - new SolutionWriter(new Mock().Object).WriteToFileSystem(solution, solutionFolder); + CreateSolutionWriter().WriteToFileSystem(solution, solutionFolder); - string projectFileContent = GetProjectFileContent(solutionFolder, project); - projectFileContent.Should().Contain(""); - projectFileContent.Should().Contain("net462"); - projectFileContent.Should().Contain("\r\n \r\n "); + string projectFileContent = GetProjectFileContent(solutionFolder, project); + projectFileContent.Should().Contain(""); + projectFileContent.Should().Contain("net462"); + projectFileContent.Should().Contain("\r\n \r\n "); + }); } - [Fact] + [SkippableFact] public void CreateEmptyVbProjectInNewFormat() { - var (solution, project, solutionFolder) = CreateEmptySolutionAndProject(ProjectFormat.New, ProgrammingLanguage.VB); + RunSkippableTest(() => + { + var (solution, project, solutionFolder) = CreateEmptySolutionAndProject(ProjectFormat.New, ProgrammingLanguage.VB); - new SolutionWriter(new Mock().Object).WriteToFileSystem(solution, solutionFolder); + CreateSolutionWriter().WriteToFileSystem(solution, solutionFolder); - string projectFileContent = GetProjectFileContent(solutionFolder, project); + string projectFileContent = GetProjectFileContent(solutionFolder, project); - projectFileContent.Should() - .Contain("\r\n \r\n ProjectName\r\n net462\r\n \r\n"); + projectFileContent.Should() + .Contain("\r\n \r\n ProjectName\r\n net462\r\n \r\n"); + }); } } - - } diff --git a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator.Tests/Reqnroll.TestProjectGenerator.Tests.csproj b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator.Tests/Reqnroll.TestProjectGenerator.Tests.csproj index 12dae46f5..af180213a 100644 --- a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator.Tests/Reqnroll.TestProjectGenerator.Tests.csproj +++ b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator.Tests/Reqnroll.TestProjectGenerator.Tests.csproj @@ -14,6 +14,7 @@ all runtime; build; native; contentfiles; analyzers + diff --git a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator.Tests/SolutionTests.cs b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator.Tests/SolutionTests.cs index f1773e3d8..799de72fe 100644 --- a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator.Tests/SolutionTests.cs +++ b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator.Tests/SolutionTests.cs @@ -10,6 +10,8 @@ namespace Reqnroll.TestProjectGenerator.Tests { public class SolutionTests { + private SolutionWriter CreateSolutionWriter() => new SolutionWriter(new Mock().Object); + [Fact] public void CreateEmptySolution() { @@ -17,7 +19,7 @@ public void CreateEmptySolution() var solution = new Solution("SolutionName"); - var solutionWriter = new SolutionWriter(new Mock().Object); + var solutionWriter = CreateSolutionWriter(); solutionWriter.WriteToFileSystem(solution, folder); @@ -39,7 +41,7 @@ public void CreateSolutionWithProject(ProgrammingLanguage programmingLanguage, s solution.AddProject(project); - var solutionWriter = new SolutionWriter(new Mock().Object); + var solutionWriter = CreateSolutionWriter(); solutionWriter.WriteToFileSystem(solution, folder); diff --git a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/DotNetSdkNotInstalledException.cs b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/DotNetSdkNotInstalledException.cs new file mode 100644 index 000000000..ffb398484 --- /dev/null +++ b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/DotNetSdkNotInstalledException.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Reqnroll.TestProjectGenerator +{ + /// + /// Is thrown when a needed SDK/TargetFramework is not installed. + /// + /// + /// Is used to ignore tests, if the needed SDKs/TargetFrameworks are not installed on the local machine. + /// This helps to improve the (first time) developer experience. + /// + public class DotNetSdkNotInstalledException : ProjectCreationNotPossibleException + { + public DotNetSdkNotInstalledException(string message) : base(message) + { + } + + public DotNetSdkNotInstalledException(string message, Exception innerException) : base(message, innerException) + { + } + } +} diff --git a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Driver/CompilationDriver.cs b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Driver/CompilationDriver.cs index 3cf6432e5..8eefe799e 100644 --- a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Driver/CompilationDriver.cs +++ b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Driver/CompilationDriver.cs @@ -1,4 +1,5 @@ using System; +using System.Text.RegularExpressions; using FluentAssertions; namespace Reqnroll.TestProjectGenerator.Driver @@ -36,8 +37,13 @@ public void CompileSolutionTimes(uint times, BuildTool buildTool = DefaultBuildT for (uint time = 0; time < times; time++) { _compilationResultDriver.CompileResult = _compiler.Run(usedBuildTool, treatWarningsAsErrors); - if (failOnError) + if (failOnError && !_compilationResultDriver.CompileResult.IsSuccessful) + { + var missingSdk = Regex.Match(_compilationResultDriver.CompileResult.Output, @"(MSB3644: .* not found\.)"); + if (missingSdk.Success) + throw new DotNetSdkNotInstalledException(missingSdk.Value); _compilationResultDriver.CompileResult.IsSuccessful.Should().BeTrue($"Compilation should succeed. Build errors: {Environment.NewLine}{_compilationResultDriver.CompileResult.ErrorLines}"); + } } } diff --git a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/FilesystemWriter/SolutionWriter.cs b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/FilesystemWriter/SolutionWriter.cs index 34e646b4c..ad0932a43 100644 --- a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/FilesystemWriter/SolutionWriter.cs +++ b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/FilesystemWriter/SolutionWriter.cs @@ -54,8 +54,12 @@ public string WriteToFileSystem(Solution solution, string outputPath) DisableUsingSdkFromEnvironmentVariable(); var createSolutionCommand = DotNet.New(_outputWriter).Solution().InFolder(outputPath).WithName(solution.Name).Build(); - createSolutionCommand.ExecuteWithRetry(1, TimeSpan.FromSeconds(1), - (innerException) => new ProjectCreationNotPossibleException("Could not create solution.", innerException) ); + createSolutionCommand.ExecuteWithRetry(1, TimeSpan.FromSeconds(1), (innerException) => + { + if (innerException is AggregateException aggregateException && aggregateException.InnerExceptions.Any(x => x.InnerException.Message.Contains("Install the [" + sdk.Version))) + return new DotNetSdkNotInstalledException($"Sdk Version \"{sdk.Version}\" is not installed", innerException); + return new ProjectCreationNotPossibleException("Could not create solution.", innerException); + }); string solutionFilePath = Path.Combine(outputPath, $"{solution.Name}.sln"); WriteProjects(solution, outputPath, solutionFilePath); diff --git a/Tests/Reqnroll.SystemTests/Portability/PortabilityTestBase.cs b/Tests/Reqnroll.SystemTests/Portability/PortabilityTestBase.cs index cfb178601..d230a6777 100644 --- a/Tests/Reqnroll.SystemTests/Portability/PortabilityTestBase.cs +++ b/Tests/Reqnroll.SystemTests/Portability/PortabilityTestBase.cs @@ -1,5 +1,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using Reqnroll.TestProjectGenerator; using Reqnroll.TestProjectGenerator.Driver; +using System; namespace Reqnroll.SystemTests.Portability; @@ -9,51 +11,75 @@ namespace Reqnroll.SystemTests.Portability; [TestCategory("Portability")] public abstract class PortabilityTestBase : SystemTestBase { + private void RunSkippableTest(Action test) + { + try + { + test(); + } + catch (DotNetSdkNotInstalledException ex) + { + if (!new ConfigurationDriver().PipelineMode) + Assert.Inconclusive(ex.ToString()); + } + } + [TestMethod] public void GeneratorAllIn_sample_can_be_handled() { - PrepareGeneratorAllInSamples(); + RunSkippableTest(() => + { + PrepareGeneratorAllInSamples(); - ExecuteTests(); + ExecuteTests(); - ShouldAllScenariosPass(); + ShouldAllScenariosPass(); + }); } [TestMethod] [TestCategory("MsBuild")] public void GeneratorAllIn_sample_can_be_compiled_with_MsBuild() { - PrepareGeneratorAllInSamples(); - _compilationDriver.SetBuildTool(BuildTool.MSBuild); - - _compilationDriver.CompileSolution(); + RunSkippableTest(() => + { + PrepareGeneratorAllInSamples(); + _compilationDriver.SetBuildTool(BuildTool.MSBuild); + _compilationDriver.CompileSolution(); + }); } [TestMethod] [TestCategory("DotnetMSBuild")] public void GeneratorAllIn_sample_can_be_compiled_with_DotnetMSBuild() { - PrepareGeneratorAllInSamples(); - _compilationDriver.SetBuildTool(BuildTool.DotnetMSBuild); + RunSkippableTest(() => + { + PrepareGeneratorAllInSamples(); + _compilationDriver.SetBuildTool(BuildTool.DotnetMSBuild); - _compilationDriver.CompileSolution(); + _compilationDriver.CompileSolution(); + }); } #region Test before/after test run hooks (.NET Framework version of Reqnroll is subscribed to assembly unload) [TestMethod] public void TestRun_hooks_are_executed() { - AddSimpleScenario(); - AddPassingStepBinding(); - AddHookBinding("BeforeTestRun"); - AddHookBinding("AfterTestRun"); + RunSkippableTest(() => + { + AddSimpleScenario(); + AddPassingStepBinding(); + AddHookBinding("BeforeTestRun"); + AddHookBinding("AfterTestRun"); - ExecuteTests(); + ExecuteTests(); - _bindingDriver.AssertExecutedHooksEqual( - "BeforeTestRun", - "AfterTestRun"); - ShouldAllScenariosPass(); + _bindingDriver.AssertExecutedHooksEqual( + "BeforeTestRun", + "AfterTestRun"); + ShouldAllScenariosPass(); + }); } #endregion } From cf4ed2e40d9a48329e27eb651003d5d9d33feb00 Mon Sep 17 00:00:00 2001 From: mharwig <119603920+mharwig@users.noreply.github.com> Date: Tue, 7 May 2024 22:45:26 +0200 Subject: [PATCH 12/21] External data plugin, support for JSON files (#118) * ExternalData: Added JsonLoader to external data plugin. * ExternalData: Updated externaldata documentation * ExternalData: Added entry concerning json support for external data to changelog * ExternalData: JsonLoader: Replaced \r\n with Environment.NewLine * ExternalData: JsonDataTableGenerator: Removed general catch of System.NullReferenceException. It is not needed due to specific null checks in GenerateHeader and GenerateRecordsFromNestedObjects * ExternalData: JsonLoader: Pass JsonReaderException as inner exception --------- Co-authored-by: Martin Harwig --- CHANGELOG.md | 1 + .../Features/ExternalDataFromJson.feature | 38 +++++ .../Features/products-nested-dataset.json | 53 +++++++ .../Features/products.json | 33 +++++ .../ExternalDataGeneratorPlugin.cs | 1 + .../Loaders/JsonDataTableGenerator.cs | 102 +++++++++++++ .../Loaders/JsonLoader.cs | 81 +++++++++++ ...eqnroll.ExternalData.ReqnrollPlugin.csproj | 1 + .../ExternalData/JsonLoaderTests.cs | 136 ++++++++++++++++++ .../SampleFiles/products-invalid.json | 32 +++++ .../SampleFiles/products-nested-dataset.json | 50 +++++++ .../ExternalData/SampleFiles/products.json | 33 +++++ .../Reqnroll.PluginTests.csproj | 9 ++ .../jsonDatasetWithNestedObjectArrays.png | Bin 0 -> 4183 bytes docs/_static/images/jsonDatasetWithSpace.png | Bin 0 -> 3010 bytes docs/integrations/externaldata.md | 50 ++++++- 16 files changed, 619 insertions(+), 1 deletion(-) create mode 100644 Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest/Features/ExternalDataFromJson.feature create mode 100644 Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest/Features/products-nested-dataset.json create mode 100644 Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest/Features/products.json create mode 100644 Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin/Loaders/JsonDataTableGenerator.cs create mode 100644 Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin/Loaders/JsonLoader.cs create mode 100644 Tests/Reqnroll.PluginTests/ExternalData/JsonLoaderTests.cs create mode 100644 Tests/Reqnroll.PluginTests/ExternalData/SampleFiles/products-invalid.json create mode 100644 Tests/Reqnroll.PluginTests/ExternalData/SampleFiles/products-nested-dataset.json create mode 100644 Tests/Reqnroll.PluginTests/ExternalData/SampleFiles/products.json create mode 100644 docs/_static/images/jsonDatasetWithNestedObjectArrays.png create mode 100644 docs/_static/images/jsonDatasetWithSpace.png diff --git a/CHANGELOG.md b/CHANGELOG.md index 68b8079b5..2c589f6cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ * Fix for #81 in which Cucumber Expressions fail when two enums or two custom types with the same short name (differing namespaces) are used as parameters * Fix: Adding @ignore to an Examples block generates invalid code for NUnit v3+ (#103) * Fix: #111 @ignore attribute is not inherited to the scenarios from Rule +* Support for JSON files added to SpecFlow.ExternalData # v1.0.1 - 2024-02-16 diff --git a/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest/Features/ExternalDataFromJson.feature b/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest/Features/ExternalDataFromJson.feature new file mode 100644 index 000000000..2c67ec647 --- /dev/null +++ b/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest/Features/ExternalDataFromJson.feature @@ -0,0 +1,38 @@ +Feature: External Data from Json file + +@DataSource:products.json +Scenario: The basket price is calculated correctly + The scenario will be treated as a scenario outline with the examples from the json file. + The first object array is used by default from the json file. + Given the price of is € + And the customer has put 1 pcs of to the basket + When the basket price is calculated + Then the basket price should be € + +@DataSource:products.json @DataSet:other_products +Scenario: The basket price is calculated correctly for other products + The scenario will be treated as a scenario outline with the examples from the json file. + The "other_products" object array is used from the json file. + Given the price of is € + And the customer has put 1 pcs of to the basket + When the basket price is calculated + Then the basket price should be € + + +@DataSource:products-nested-dataset.json +Scenario: The basket price is calculated correctly for products in nested products json + The scenario will be treated as a scenario outline with the examples from the json file. + The first object array is used by default from the json file. + Given the price of is € + And the customer has put 1 pcs of to the basket + When the basket price is calculated + Then the basket price should be € + +@DataSource:products-nested-dataset.json @DataSet:products.varieties +Scenario: The basket price is calculated correctly for products.varieties in nested products json + The scenario will be treated as a scenario outline with the examples from the json file. + The products.varieties is used from the json file. + Given the price of is € + And the customer has put 1 pcs of to the basket + When the basket price is calculated + Then the basket price should be € diff --git a/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest/Features/products-nested-dataset.json b/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest/Features/products-nested-dataset.json new file mode 100644 index 000000000..79afec351 --- /dev/null +++ b/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest/Features/products-nested-dataset.json @@ -0,0 +1,53 @@ +{ + "products": + [ + { + "product": "Chocolate", + "color": "brown", + "total price": "3.15", + "varieties": + [ + { + "name": "Dark Chocolate", + "price": "1.6" + }, + { + "name": "Milk Chocolate", + "price": "1.55" + } + ] + }, + { + "product": "Apple", + "color": "red", + "total price": "2.5", + "varieties": + [ + { + "name": "Pink Lady", + "price": "1.0" + }, + { + "name": "Fuji", + "price": "1.5" + } + ] + }, + { + "product": "Orange", + "color": "orange", + "total price": "2.9", + "varieties": + [ + { + "name": "Seville Orange", + "price": "1.3" + }, + { + "name": "Tangerine", + "price": "1.6" + } + ] + } + ] +} diff --git a/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest/Features/products.json b/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest/Features/products.json new file mode 100644 index 000000000..7abf4b3f2 --- /dev/null +++ b/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest/Features/products.json @@ -0,0 +1,33 @@ +{ + "products": + [ + { + "product": "Chocolate", + "price": "2.5", + "color": "brown" + }, + { + "product": "Apple", + "price": "1.0", + "color": "red" + }, + { + "product": "Orange", + "price": "1.2", + "color": "orange" + } + ], + "other products": + [ + { + "product": "Cookie", + "price": "2.5", + "color": "brown" + }, + { + "product": "Bananas", + "price": "1.0", + "color": "yellow" + } + ] +} diff --git a/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin/ExternalDataGeneratorPlugin.cs b/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin/ExternalDataGeneratorPlugin.cs index d22470efc..a39da3c18 100644 --- a/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin/ExternalDataGeneratorPlugin.cs +++ b/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin/ExternalDataGeneratorPlugin.cs @@ -23,6 +23,7 @@ public void Initialize(GeneratorPluginEvents generatorPluginEvents, GeneratorPlu args.ObjectContainer.RegisterTypeAs(); args.ObjectContainer.RegisterTypeAs("CSV"); args.ObjectContainer.RegisterTypeAs("Excel"); + args.ObjectContainer.RegisterTypeAs("JSON"); }; } } diff --git a/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin/Loaders/JsonDataTableGenerator.cs b/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin/Loaders/JsonDataTableGenerator.cs new file mode 100644 index 000000000..e3baed6a4 --- /dev/null +++ b/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin/Loaders/JsonDataTableGenerator.cs @@ -0,0 +1,102 @@ +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json.Linq; +using Reqnroll.ExternalData.ReqnrollPlugin.DataSources; + +namespace Reqnroll.ExternalData.ReqnrollPlugin.Loaders +{ + + public class JsonDataTableGenerator + { + public DataSources.DataTable FlattenDataSetToDataTable(JObject originalJson, string[] objectPath) + { + var header = GenerateHeader(originalJson, objectPath); + var dataTable = new DataSources.DataTable(header.ToArray()); + var records = GenerateRecordsFromNestedObjects(originalJson, objectPath, new List()); + foreach (var record in records) dataTable.Items.Add(record); + + return dataTable; + } + + private static List GenerateHeader(JObject originalJson, string[] objectPath) + { + var currentObject = originalJson; + + List header = new(); + while (objectPath.Length > 0) + { + var currentObjectArray = currentObject[objectPath.First()]?.ToObject(); + + if (currentObjectArray == null) + throw new ExternalDataPluginException($"Expected {objectPath.First()} to be in json object"); + + objectPath = objectPath.Skip(1).ToArray(); + + if (currentObjectArray.First == null) + throw new ExternalDataPluginException("Empty object arrays are not supported"); + + currentObject = currentObjectArray.First.ToObject(); + header.AddRange(GetPropertiesExcludingToBeFlattenedArrayObject(currentObject, objectPath).Select(p => p.Name)); + } + + return header; + } + + private List GenerateRecordsFromNestedObjects( + JObject currentObject, + string[] dataSetObjectPath, + List currentRecords) + { + if (dataSetObjectPath.Length == 0) + return currentRecords; + + var objectArrayPropertyName = dataSetObjectPath.First(); + var remainingObjectArrays = dataSetObjectPath.Skip(1).ToArray(); + + var objectArray = currentObject[objectArrayPropertyName]?.ToObject(); + if (objectArray == null) + throw new ExternalDataPluginException($"Expected object array property {objectArrayPropertyName} inside {currentObject}"); + + var result = new List(); + foreach (var jObject in objectArray.Select(i => i.ToObject())) + { + + var objectProperties = GetPropertiesExcludingToBeFlattenedArrayObject(jObject, remainingObjectArrays); + + var objectRecord = new DataRecord(); + foreach (var property in objectProperties) + objectRecord.Fields[property.Name] = new DataValue(property.Value); + + List intermediateResult; + if (currentRecords.Any()) + intermediateResult = AppendFieldsToCurrentDataRecords(currentRecords, objectRecord); + else + intermediateResult = new List { objectRecord }; + + result.AddRange(GenerateRecordsFromNestedObjects(jObject, remainingObjectArrays, intermediateResult)); + } + + return result; + } + + private static List AppendFieldsToCurrentDataRecords(List currentRecords, DataRecord objectRecord) + { + var result = new List(); + foreach (var record in currentRecords) + { + var updatedRecord = new DataRecord(record.Fields); + objectRecord.Fields.ToList().ForEach(x => updatedRecord.Fields.Add(x.Key, x.Value)); + result.Add(updatedRecord); + } + + return result; + } + + private static List GetPropertiesExcludingToBeFlattenedArrayObject( + JObject childJson, + string[] remainingDataSets) + { + return childJson.Properties().Where(p => p.Name != remainingDataSets.FirstOrDefault()).ToList(); + } + } +} \ No newline at end of file diff --git a/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin/Loaders/JsonLoader.cs b/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin/Loaders/JsonLoader.cs new file mode 100644 index 000000000..023b51e21 --- /dev/null +++ b/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin/Loaders/JsonLoader.cs @@ -0,0 +1,81 @@ +using System.Linq; +using System.Collections.Generic; +using Newtonsoft.Json.Linq; +using Reqnroll.ExternalData.ReqnrollPlugin.DataSources; +using Newtonsoft.Json; + +namespace Reqnroll.ExternalData.ReqnrollPlugin.Loaders +{ + public class JsonLoader : FileBasedLoader + { + public JsonLoader() : base("Json", ".json") + { + + } + + protected override DataSource LoadDataSourceFromFilePath(string filePath, string sourceFilePath) + { + var fileContent = ReadTextFileContent(filePath); + + return LoadJsonDataSource(fileContent, sourceFilePath); + } + + private List DetermineDataSets(JObject jsonObject, string dataSetPrepend) + { + var dataSets = new List(); + foreach (var array in GetArraysFromObject(jsonObject)) + { + var firstArrayEntry = array.Children().FirstOrDefault(); + + if (firstArrayEntry == null || firstArrayEntry.Type != JTokenType.Object) + continue; + + var firstArrayObject = firstArrayEntry.ToObject(); + + var dataSetPath = string.IsNullOrWhiteSpace(dataSetPrepend) ? ((JProperty)array.Parent!).Name : $"{dataSetPrepend}.{array.Path}"; + + dataSets.Add(dataSetPath); + + foreach (var nestedDataSetPath in DetermineDataSets(firstArrayObject, dataSetPath)) + dataSets.Add(nestedDataSetPath); + } + + return dataSets; + } + + private static IEnumerable GetArraysFromObject(JObject jsonObject) + { + return jsonObject.Properties().Where(p => p.Value.Type == JTokenType.Array).Select(a => a.Value); + } + + private DataSource LoadJsonDataSource(string fileContent, string sourceFilePath) + { + JObject fileJson = ParseJson(fileContent, sourceFilePath); + var dataSets = DetermineDataSets(fileJson, ""); + var dataSetsRecord = new DataRecord(); + var jsonDataTableGenerator = new JsonDataTableGenerator(); + foreach (var dataSetPath in dataSets) + { + var dataTable = jsonDataTableGenerator.FlattenDataSetToDataTable(fileJson, dataSetPath.Split('.')); + dataSetsRecord.Fields[dataSetPath] = new DataValue(dataTable); + } + + return new DataSource(dataSetsRecord, dataSets.First()); + } + + private static JObject ParseJson(string fileContent, string sourceFilePath) + { + JObject fileJson; + try + { + fileJson = JObject.Parse(fileContent); + } + catch (JsonReaderException jsonReaderException) + { + throw new ExternalDataPluginException($"Failed to parse json file {sourceFilePath}", jsonReaderException); + } + + return fileJson; + } + } +} \ No newline at end of file diff --git a/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin/Reqnroll.ExternalData.ReqnrollPlugin.csproj b/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin/Reqnroll.ExternalData.ReqnrollPlugin.csproj index 11c077ab3..de7e5ad16 100644 --- a/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin/Reqnroll.ExternalData.ReqnrollPlugin.csproj +++ b/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin/Reqnroll.ExternalData.ReqnrollPlugin.csproj @@ -15,6 +15,7 @@ + diff --git a/Tests/Reqnroll.PluginTests/ExternalData/JsonLoaderTests.cs b/Tests/Reqnroll.PluginTests/ExternalData/JsonLoaderTests.cs new file mode 100644 index 000000000..80c188323 --- /dev/null +++ b/Tests/Reqnroll.PluginTests/ExternalData/JsonLoaderTests.cs @@ -0,0 +1,136 @@ +using System; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using Newtonsoft.Json; +using Reqnroll.ExternalData.ReqnrollPlugin; +using Reqnroll.ExternalData.ReqnrollPlugin.Loaders; +using Xunit; + +namespace Reqnroll.PluginTests.ExternalData +{ + public class JsonLoaderTests + { + private static readonly string SampleFilesFolder = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? ".", "ExternalData", "SampleFiles"); + private readonly string _productsSampleFilePath = Path.Combine(SampleFilesFolder, "products.json"); + private readonly string _productsInvalidSampleFilePath = Path.Combine(SampleFilesFolder, "products-invalid.json"); + private readonly string _nestedProductsSampleFilePath = Path.Combine(SampleFilesFolder, "products-nested-dataset.json"); + + private string SampleFeatureFilePathInSampleFileFolder => + Path.Combine(Path.GetDirectoryName(_productsSampleFilePath) ?? ".", "Sample.feature"); + + private JsonLoader CreateSut() => new(); + + [Fact] + public void Can_read_simple_json_file() + { + var sut = CreateSut(); + var result = sut.LoadDataSource(_productsSampleFilePath, null); + + Assert.NotNull(result); + Assert.True(result.IsDataRecord); + Assert.True(result.AsDataRecord.Fields.ContainsKey("products")); + var worksheetResult = result.AsDataRecord.Fields["products"]; + Assert.True(worksheetResult.IsDataTable); + Assert.Equal(3, worksheetResult.AsDataTable.Items.Count); + Assert.Equal("Chocolate", worksheetResult.AsDataTable.Items[0].Fields["product"].AsString()); + Assert.Equal("2.5", worksheetResult.AsDataTable.Items[0].Fields["price"].AsString(CultureInfo.GetCultureInfo("en-us"))); + Assert.Equal("brown", worksheetResult.AsDataTable.Items[0].Fields["color"].AsString()); + } + + [Fact] + public void Can_handle_properties_with_spaces() + { + var sut = CreateSut(); + var result = sut.LoadDataSource(_productsSampleFilePath, null); + + Assert.NotNull(result); + Assert.True(result.IsDataRecord); + Assert.True(result.AsDataRecord.Fields.ContainsKey("other products")); + var worksheetResult = result.AsDataRecord.Fields["other products"]; + Assert.True(worksheetResult.IsDataTable); + Assert.Equal(2, worksheetResult.AsDataTable.Items.Count); + Assert.Equal("Cookie", worksheetResult.AsDataTable.Items[0].Fields["product"].AsString()); + Assert.Equal("2.5", worksheetResult.AsDataTable.Items[0].Fields["price"].AsString(CultureInfo.GetCultureInfo("en-us"))); + Assert.Equal("brown", worksheetResult.AsDataTable.Items[0].Fields["color"].AsString()); + } + + [Fact] + public void Can_handle_relative_path() + { + var sut = CreateSut(); + var result = sut.LoadDataSource( + Path.GetFileName(_productsSampleFilePath), + SampleFeatureFilePathInSampleFileFolder); + + Assert.True(result.IsDataRecord); + } + + + [Fact] + public void Returns_name_of_first_object_array_as_default_data_set() + { + var sut = CreateSut(); + var result = sut.LoadDataSource(_productsSampleFilePath, null); + + Assert.NotNull(result); + Assert.Equal("products", result.DefaultDataSet); + } + + [Fact] + public void Can_read_products_from_json_with_nested_dataset() + { + var sut = CreateSut(); + var result = sut.LoadDataSource(_nestedProductsSampleFilePath, null); + + Assert.NotNull(result); + Assert.True(result.IsDataRecord); + Assert.True(result.AsDataRecord.Fields.ContainsKey("products")); + var worksheetResult = result.AsDataRecord.Fields["products"]; + Assert.True(worksheetResult.IsDataTable); + Assert.Equal(3, worksheetResult.AsDataTable.Items.Count); + var firstItem = worksheetResult.AsDataTable.Items[0]; + Assert.Equal(3, firstItem.Fields.Count); + Assert.Equal("Chocolate", firstItem.Fields["product"].AsString()); + Assert.Equal("brown", firstItem.Fields["color"].AsString()); + Assert.Equal($"[{Environment.NewLine} {{{Environment.NewLine} \"name\": \"Dark Chocolate\",{Environment.NewLine} \"price\": \"1.6\"{Environment.NewLine} }},{Environment.NewLine} {{{Environment.NewLine} \"name\": \"Milk Chocolate\",{Environment.NewLine} \"price\": \"1.55\"{Environment.NewLine} }}{Environment.NewLine}]", firstItem.Fields["varieties"].AsString()); + } + + [Fact] + public void Reads_nested_dataset() + { + var sut = CreateSut(); + var result = sut.LoadDataSource(_nestedProductsSampleFilePath, null); + + Assert.NotNull(result); + Assert.True(result.IsDataRecord); + Assert.True(result.AsDataRecord.Fields.ContainsKey("products.varieties")); + var worksheetResult = result.AsDataRecord.Fields["products.varieties"]; + Assert.True(worksheetResult.IsDataTable); + Assert.Equal(6, worksheetResult.AsDataTable.Items.Count); + var firstItem = worksheetResult.AsDataTable.Items.First(); + Assert.Equal(4, firstItem.Fields.Count); + Assert.Equal("Chocolate", firstItem.Fields["product"].AsString()); + Assert.Equal("brown", firstItem.Fields["color"].AsString()); + Assert.Equal("1.6", firstItem.Fields["price"].AsString(CultureInfo.GetCultureInfo("en-us"))); + Assert.Equal("Dark Chocolate", firstItem.Fields["name"].AsString()); + + var lastItem = worksheetResult.AsDataTable.Items.Last(); + Assert.Equal(4, lastItem.Fields.Count); + Assert.Equal("Orange", lastItem.Fields["product"].AsString()); + Assert.Equal("orange", lastItem.Fields["color"].AsString()); + Assert.Equal("1.6", lastItem.Fields["price"].AsString(CultureInfo.GetCultureInfo("en-us"))); + Assert.Equal("Tangerine", lastItem.Fields["name"].AsString()); + } + + [Fact] + public void Can_handle_invalid_json_file_format() + { + var sut = CreateSut(); + + var externalDataPluginException = Assert.Throws(() => sut.LoadDataSource(_productsInvalidSampleFilePath, null)); + Assert.IsType(externalDataPluginException.InnerException); + } + } +} diff --git a/Tests/Reqnroll.PluginTests/ExternalData/SampleFiles/products-invalid.json b/Tests/Reqnroll.PluginTests/ExternalData/SampleFiles/products-invalid.json new file mode 100644 index 000000000..a35073557 --- /dev/null +++ b/Tests/Reqnroll.PluginTests/ExternalData/SampleFiles/products-invalid.json @@ -0,0 +1,32 @@ +{ + "products": + [ + { + "product": "Chocolate", + "price": "2.5", + "color": "brown", + { + "product": "Apple", + "price": "1.0", + "color": "red" + }, + { + "product": "Orange", + "price": "1.2", + "color": "orange" + } + ], + "other products": + [ + { + "product": "Cookie", + "price": "2.5", + "color": "brown" + }, + { + "product": "Bananas", + "price": "1.0", + "color": "yellow" + } + ] +} diff --git a/Tests/Reqnroll.PluginTests/ExternalData/SampleFiles/products-nested-dataset.json b/Tests/Reqnroll.PluginTests/ExternalData/SampleFiles/products-nested-dataset.json new file mode 100644 index 000000000..f6d290d4b --- /dev/null +++ b/Tests/Reqnroll.PluginTests/ExternalData/SampleFiles/products-nested-dataset.json @@ -0,0 +1,50 @@ +{ + "products": + [ + { + "product": "Chocolate", + "color": "brown", + "varieties": + [ + { + "name": "Dark Chocolate", + "price": "1.6" + }, + { + "name": "Milk Chocolate", + "price": "1.55" + } + ] + }, + { + "product": "Apple", + "color": "red", + "varieties": + [ + { + "name": "Pink Lady", + "price": "1.0" + }, + { + "name": "Fuji", + "price": "1.5" + } + ] + }, + { + "product": "Orange", + "color": "orange", + "varieties": + [ + { + "name": "Seville Orange", + "price": "1.3" + }, + { + "name": "Tangerine", + "price": "1.6" + } + ] + } + ] +} diff --git a/Tests/Reqnroll.PluginTests/ExternalData/SampleFiles/products.json b/Tests/Reqnroll.PluginTests/ExternalData/SampleFiles/products.json new file mode 100644 index 000000000..7abf4b3f2 --- /dev/null +++ b/Tests/Reqnroll.PluginTests/ExternalData/SampleFiles/products.json @@ -0,0 +1,33 @@ +{ + "products": + [ + { + "product": "Chocolate", + "price": "2.5", + "color": "brown" + }, + { + "product": "Apple", + "price": "1.0", + "color": "red" + }, + { + "product": "Orange", + "price": "1.2", + "color": "orange" + } + ], + "other products": + [ + { + "product": "Cookie", + "price": "2.5", + "color": "brown" + }, + { + "product": "Bananas", + "price": "1.0", + "color": "yellow" + } + ] +} diff --git a/Tests/Reqnroll.PluginTests/Reqnroll.PluginTests.csproj b/Tests/Reqnroll.PluginTests/Reqnroll.PluginTests.csproj index 6812d7518..856f60172 100644 --- a/Tests/Reqnroll.PluginTests/Reqnroll.PluginTests.csproj +++ b/Tests/Reqnroll.PluginTests/Reqnroll.PluginTests.csproj @@ -35,6 +35,12 @@ + + PreserveNewest + + + PreserveNewest + PreserveNewest @@ -47,6 +53,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest diff --git a/docs/_static/images/jsonDatasetWithNestedObjectArrays.png b/docs/_static/images/jsonDatasetWithNestedObjectArrays.png new file mode 100644 index 0000000000000000000000000000000000000000..9dc674e8a71ce8315321d2012c97ddf9a179bc84 GIT binary patch literal 4183 zcmaJ^c|6oz+n>3GHWZ1nm9-E>WgS~~SyEBPHmDwyZN!XW2=^^ZMhi;WqHtRWF=HF0 z?3#p|GJ`BLG0b4n7_)eOy6@-xynnpUduGn(cdqZb&h@*_xz6ueQeB*EMD`xt3j%>e z>})MvK_D;+=uW!@0K~=JQ4M&2;jT6opqh`eivY3fmbs%j2!zG#86fu0?^LH=qCFY3d3SyU@))-;7vOV{G)(+{HzvdbmUaWPTTnf~K#-Rlpr)K-7?u-n*Yj<7q|Z;)i`2?e z6P{#9n8;t9L3`D|*a}%&w&h9<1a5Ip29qA@yWS-{jl1zPj83pF!XNMEMZ^@in5ro* ztv5!f#W{tS&3ZRURB`rLDMbE|Iqrmi>`spU!O<+#BQZPZt)QADT<$~&Ny;IOl}?uW z5MhGPg0Ey~Df!SH#iPxSX1hNz5j}ICMYEuO)xWv&7q1j|7OM=o@5BFN>JzPGdL4NV zc_V4R7w94XcW7iMujNU&?0yjv4a@>gFyU~j9C26IW8Y3Q5!|={eIo${nei6;KZd7s zgaG~%mo@#PF{;wi9qAI~SqqmrqJN8uSxvGqOY%zv3qw|~RLkFI#dNmIht&#`G!oAU zLjGQxu#!wW(BZwduSipPgrexulXdjf>%}c~$jniHSAUAS8~Nt^q|8Cjl+|br>q$eh zTC4V^%9WneaTc@+uY*5_`s`Va3yfg8%!h7Il)qwo{1Y@vf_2aEkI@sDqMK~b!=d!4 zMm_6#u9!oG+S9?8wdckZ zMoQzrqgVOu^;5-gLKgAVWZ6QloCs#Ot4?-H(w&I^>m;n4|{QOnYj+&VOzSZte^5w4y7X5zfbe0-sE-c^kO=H>y_RV_xwTAn6e%AQl6%j4phmW$(MIM63 zR*z7!Z$!71F>R0!?3~h7;<^(j&%}KhGb(MKio?#MLu+E$a9V~oXS(KsZ7!rLx#LG` zjp(<+2`zVz!|svb#x28?1c_kU*4qYE?Dw1 zV{V(3QPaOl4li8rZzU0Q=_S&{pMZA*6)6s!{FXQ2*U{wY9P~ zp2igaUNEuL)9KcAG{U+2jCtyi4ZcmhU6N%&3}9ZbAC|Noe2Bz#%&iQ`x-|N%XEwy& zf^Tuy%84BH5aXVxD8|&OR^%Hu7-{5;;+Kp`(-i@Ged!7bEVTjb`MGM-Z_U zO9eljr}+!D>3Z=-i5$`8as*bnM^zqrM~gTbsgTB+=V$o0g)_ctn8fR+p}DC}EsiY2 z-Z5&)ZSU03=MUQ+4(M(~Ik(2yg;sIuA?wblM)JUyBj0^>n|%@WZCj^60^;oZJm%SQR=af})ZZ#)*hVy`yvN)EYskC84Hjvk<5oMdvpR13$= z=X^U2W&w=R1q0M-1kQHqv{Z^h1`4%r^eGc+5+8Y)1;+8{#<@gjHgj?$d^1*Y&h`~& zjoQ4-bYumX{lTXi9O^}gPKVq!j2w!~G4-@ZFFj#iH+2xWsoNJn3^UTzE?6~QeBggM znjrZU?L(=)O>jOTj|(5l<4nw7cIvBJd@41YG23vv6E6+-X;+yq$h>q;a*;ezEGAbq z3?twO8uw7k^i8jM)`!kbnb>^5gj`CYBQ_fz%&RzLpcJ`|M`XI&xtR28 zG;evtUVNE<n>i)dL>i7`42X?^Zj|5i=w6vBGw4C&YsZEt z?_RuEoggq&T9{2+AnIgv$+cu-bT+mSjjELJ!ljraP2XY?$F4O%N*H=p{WsTaZ&>gV zvJKBL>l<6K1ig)+O>a~UZCw4EAglcwX90gxs%viJLF|?5wYBt)O{3Z}|0h2NA19n& zoRpXwr&JuBTR$&07Ex#tPJ8SgwS1(?A#QZ(;&1nv7tehO6WiG{ zsB?7_Y>aSYVBPM&sIBOj^|j@_u+OF(W;l@c8}qhynJJk6dMlQGH=>E?w1A zs?NEu{8QATgh#wGv#PgESj2;FHKC^lL9ac_^DCGSudYhvk&Hyf1($UR*8gh#0e!Y@ zPr;qsr;N;_lh=pH&xvvZ*ZHgaPLglX_FFetaaF03o;G!E?$r9qn#_Ek`|sEJ&g}EU zKFUp5v}N3K(MR_vu4-<@Z5$f2ODfZ9RL5BsmiXXq=+}}1mJ{*Lc+akaUv;9-e;eh~jXg`hZ3aDd0pYqJOfFd(l`;NZVW0VkjA6G`X(P@}K_nik{&d$YV=#S!8+@|qeJAhSB504m zq1u{pT6ay@rQY4LA;cC2hki9uu3!hnuf$P;Q;C?aPT>dOO!MLG=-4X)9d(#dc5` z9OZ%6rakAZVUEj7Nt+i0j@X*~N$ar-<5%W?T&c*9^NtVSEN&{4={#})m@IvP@#wOP z**2&CY|5WyrtZfeNuJ_L{knJZG~;nkz|WGYA70ih!N-hUyXMqh5dL)Px}y2KLFal9 zz2O^`(gE+ie~5|^7r$dz9{xSC^||oC2r+JLSwUDyCS+Rwv#G(!_xyAf&Uw*0gR4y6 z>emz#ND*T{@r__2kWRE|{;n+QHF$#h*2Au3dPIwIEfF;P)ZcNSz5U|i_IpnOI*4_3YAnQqGC0IR#DZIh5`Z~vbS zbQ^@}(ChGUx&31Q}t@FLL_y>YagHxdg=amTA8JKRXMB&jEnok*f47w?`k1% zh$>|B&6DaD^xwc;e&p6c4e0}@6vrGTx>$ycL%Onr=+1!;CFQln;M7bzXJ%^D{z*>7 z$8DF9U~f z{jiuVTYZjjRY${wcgs1==^V))U3nG3(h0nHBU8nD`I16b40jfFdA0JJJQG^|4o{$S z-2n5Fci66weQu#OXa(D&kr;Nh+l;@cmy0KWQ28a*5+_>wGpp?x#jybgQsH5w8xcbZ*Zqo4+6(LRPQP;_jkp1C*6WxcI<(_Qvg{`8*< z=po|dZy~|x${f@u3+vJl7+NEhrNebv>oD9UWUQ0fXICsj4E=z6NSqn|8j-;nB8IB; z+L|0sJ^0B|7G4#I&%MVE1~43wd{I>P>;aan&ry1)F(JFHp@)%B!!X}xCye!`O;I0s zn%KErvFUFmH&dEl4k+QX!_2835*=m&QsZw|cU<<4`}dRp_LC|nBCvPWE5}UuR~cd! zbLC;Dobb#~bHe!lvNi;;w%{*oCp>?Ld`~!XWZPvyKb_oCl z3NPU-eJz1wUg2f&j#o5FUrged-`gpFFkj(HVWyg6)K@cTm;c;WJ*NI!UePtyO?fq( z!XYVmsjCV}EKTlmZ-j|ZnCY<)O-I#Nf1n&^)5*bVzksv=008@Ktj*5= z01N?*7y0)=Ux)a|y8?`G#>xz!bjvJ20#AUcy(s`ta|C~YypX)FM%^RBz#s+; z=NC0(2Lo&RIa~+{p)oMf4+i5>{XhuX&p`tqhr@+vU@$PVp94c@!eEe}Ocr&I6@aK9 zVl-G&!{M^fL@I=-sR3DB77L0S4MokOvWOrN3KZi91BpZs070l{W`a~An#y5eARmwc zwV#NFXh9H-hJm17wU-36EO?l;2LS+t-|TkSyDJaz03dMI#@y63%4elGD`J>&Fdg0l z9J9TOba7Vg6GhnFy!ZU_na0o}pGnlSB_BfcPQgOgve0uL4FzSF$;tVh>>L}lq{C~M zB9oEdmPx>KvD&yF%5qB#Vns;7-~L)gJ(0%Ub5(Da!+y-qyPqs%UU>C*M6gdeBtA5_ zE8!}3Cp@L;1bDZ!NX3FY=V&Z#e|2{LxpL{94Kst;=hRex(?uY z&)F}`Y?N&;6DXUdS^TVAfqnHmey;HmAv1Mua%;& zv1XCe1ALWmmd;_Xk$X$C?TVxCdbTQ65}2#=w6@5<Tc!E3K5!&V$HhC6 z>1JTmRnlTC1`^TwiR+e$pbuuUOf>n`s(VGC_7_@q=S-lEFfRsN6t=m9wQtlQ)5PkO zz?3|tjTq1T>Y3Cqei57`T*W27f8vjhXBMnrSIUp}3!pB}BT%64CfsFs>bz_1&RMQi z*3|0Li%A=^ z_6^NB<;HfQ1FEqbzxNk}%{{5A7ft)F0;J}N3HLVS7eu%(C(DQ4EQ2dGz{WA&3f3*SQeplEiWwRkYroi8WdGFn!~&b9$JOj;22 ze~2-Pa#gJK`DQQ;Y}T|(#ieL0*+0_Mk&)I?%rJ~{6;JI=dqHE@H0VDtt@e{h^@wKk z|DBg=&?6~Jvv`uTUsOcwAZmU5pbECo>{E5EMjkB&CwPO%FBZ+tU`Kww#@06s8HvX} zd%xT>U5K=dcTg#5dIkBcjVr+*z4@wVz$MA_(pCP&N>_x>^0gJN=E09R-*3HgdFcU^ z6I)7H1<3wukp{a91_10e!>ESg^0wbZN`7$0_*MydZKWv#MjJ~LZA)o8O_{AF4WS|> zN4h@9w)~`rEa-ie14EJCzU(7O#bH~;(p~lDrp7wH?D(JPaxySEW-I;df$l)J#oXs18NC=) z`ru}JXIP^_94&r3i25S=Cf@X#1M{DbmZ@85ilgm|{h(k!f-lliI-ammQ~EoZ(JH+SOO=9t^#EgJ`hR2P*!M`uZr8%@2mma6)V`&iMY6s+W@ckFCJ z=o#N!Sbk{P^sAsVays+RT~tAC(sj?-;#xq7d~WVyeX2t*~O5MIMo03gp?GhH^Mt zBg`nOu?t%KGowf9^aJzd0kvhf>CM_;psu`NRCLKJd^v3N+*0plcfRVF9kr~q+g#jJ zL}K^8k9aCdsCrJ(p{88HH{m2%?QXv*{4uhp|_Y7hw!z~`52#yW`~lQ747CLA(yVKatLhttuiJ`#ODxMx2@W1XxL!c zV;Dqb3G47r4chTVs9{ZX^LXci%e=$KzowsCqVvSIlM5H`{auLo((678SD)AkihY8W zoQ$C!3o>k^93~A-rOkU>9?HWQK!-xzi6Xo#B);Qt8c`RSKzGQt!14CIW!P>9JWHdl zyn-L%bVmDC#{3v8s6&y4Vln@2uM8)Ow9Yg^l=%7Zb-5>MW?qQetueQ;xMt2+q|Mfg zWaP!#vc6Y4$(T$Of&#y=`FLwJyH0NGhEY%@VQH~TY%#NMBO}w#k|OYr^{Nz3RJ{+{VJe KoMLwV`u_n-Opd7l literal 0 HcmV?d00001 diff --git a/docs/integrations/externaldata.md b/docs/integrations/externaldata.md index 993aad73c..ebe524bf7 100644 --- a/docs/integrations/externaldata.md +++ b/docs/integrations/externaldata.md @@ -23,6 +23,12 @@ Standard RFC 4180 CSV format is supported with a header line (plugin uses [CsvHe Both XLSX and XLS is supported (plugin uses [ExcelDataReader](https://github.com/ExcelDataReader/ExcelDataReader) to parse the files). ``` +- JSON files (format 'JSON', extension .json) + +```{note} +Object arrays and nested object arrays are supported (plugin uses [JObject.Parse](https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Linq/JObject.cs) to parse the files). +``` + ## Tags The following tags can be used to specify the external source: @@ -38,7 +44,7 @@ The path is a relative path to the folder of the **feature files**. - `@DataFormat:format` - This tag only needs to be used if the format cannot be identified from the file extension. -- `@DataSet:data-set-name` - This tag is applicable to *Excel files only*. It is used to select the worksheet of the Excel file you wish to use. By **default**, the first worksheet in an Excel file is targeted. +- `@DataSet:data-set-name` - This tag is applicable to *Excel and Json files only*. For Excel it is used to select the worksheet of the Excel file you wish to use. By **default**, the first worksheet in an Excel file is targeted. For Json it is used to select the object array you wish to use. By **default**, the first object array in a Json file is targeted. - `@DataField:name-in-feature-file=name-in-source-file` - This tag can be used to "rename" columns of the external data source. @@ -189,3 +195,45 @@ Forgatókönyv: The basket price is calculated correctly Akkor the basket price should be € ```` + +### Json files + +You can use Json files the same way as you do with Excel files with some minor differences: + +- Only Object arrays and nested Object are supported. Arrays of simple types, empty arrays, etc. are not supported. + +- Json files with multiple object arrays are supported, you can use the `@DataSet:array-property-name` to select the object array you wish to target. The plugin uses the **first** object array by **default**. Nested object arrays can be specified by appending property names using a '.'. + +- Use underscores in the `@DataSet` tag instead of spaces if the array property name name contains spaces. + +The below example shows a Json file with object arrays and we wish to target the last array labelled *"other products"*. We do this by using the `@DataSet:other_products` tag. Note the use of (_) instead of space: + +![product json](/_static/images/jsonDatasetWithSpace.png) + +````Gherkin + +@DataSource:products.json @DataSet:other_products +Scenario: The basket price is calculated correctly for other products + Given the price of is € + And the customer has put 1 pcs of to the basket + When the basket price is calculated + Then the basket price should be € + +```` + +The below example shows a Json file with nested object arrays and we wish to target the inner array labelled *"varieties"*. We do this by using the `@DataSet:products.varieties` tag. Note the use of . to append nested property names: + +![product.varieties json](/_static/images/jsonDatasetWithNestedObjectArrays.png) + +````Gherkin + +@DataSource:products-nested-dataset.json @DataSet:products.varieties +Scenario: The basket price is calculated correctly for products.varieties in nested products json + Given the price of is € + And the customer has put 1 pcs of to the basket + When the basket price is calculated + Then the basket price should be € + + +```` + From 2795fe7abe76d36d6c77fd996e9a131fb6accccd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1sp=C3=A1r=20Nagy?= Date: Fri, 10 May 2024 10:44:36 +0200 Subject: [PATCH 13/21] fix method name sources in UnitTestFeatureGenerator --- Reqnroll.Generator/Generation/UnitTestFeatureGenerator.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Reqnroll.Generator/Generation/UnitTestFeatureGenerator.cs b/Reqnroll.Generator/Generation/UnitTestFeatureGenerator.cs index 0eb430d51..baba0e0b8 100644 --- a/Reqnroll.Generator/Generation/UnitTestFeatureGenerator.cs +++ b/Reqnroll.Generator/Generation/UnitTestFeatureGenerator.cs @@ -7,7 +7,6 @@ using Reqnroll.Generator.CodeDom; using Reqnroll.Generator.UnitTestConverter; using Reqnroll.Generator.UnitTestProvider; -using Reqnroll.Infrastructure; using Reqnroll.Parser; using Reqnroll.Tracing; @@ -127,7 +126,7 @@ private void SetupScenarioCleanupMethod(TestClassGenerationContext generationCon //await testRunner.CollectScenarioErrorsAsync(); var expression = new CodeMethodInvokeExpression( testRunnerField, - nameof(TestRunner.CollectScenarioErrorsAsync)); + nameof(ITestRunner.CollectScenarioErrorsAsync)); _codeDomHelper.MarkCodeMethodInvokeExpressionAsAwait(expression); @@ -296,7 +295,7 @@ private void SetupScenarioInitializeMethod(TestClassGenerationContext generation scenarioInitializeMethod.Statements.Add( new CodeMethodInvokeExpression( testRunnerField, - nameof(ITestExecutionEngine.OnScenarioInitialize), + nameof(ITestRunner.OnScenarioInitialize), new CodeVariableReferenceExpression("scenarioInfo"))); } @@ -313,7 +312,7 @@ private void SetupScenarioStartMethod(TestClassGenerationContext generationConte var testRunnerField = _scenarioPartHelper.GetTestRunnerExpression(); var expression = new CodeMethodInvokeExpression( testRunnerField, - nameof(ITestExecutionEngine.OnScenarioStartAsync)); + nameof(ITestRunner.OnScenarioStartAsync)); _codeDomHelper.MarkCodeMethodInvokeExpressionAsAwait(expression); From 0aecb3480ce0ff8cd41eee34a8c592c92ebb3956 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1sp=C3=A1r=20Nagy?= Date: Fri, 10 May 2024 12:04:09 +0200 Subject: [PATCH 14/21] small improvement in CodeDomHelper to be able to chain async calls --- Reqnroll.Generator/CodeDom/CodeDomHelper.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Reqnroll.Generator/CodeDom/CodeDomHelper.cs b/Reqnroll.Generator/CodeDom/CodeDomHelper.cs index 7829c115f..9d649dbbb 100644 --- a/Reqnroll.Generator/CodeDom/CodeDomHelper.cs +++ b/Reqnroll.Generator/CodeDom/CodeDomHelper.cs @@ -250,7 +250,7 @@ public bool IsVoid(CodeTypeReference codeTypeReference) return typeof(void).FullName!.Equals(codeTypeReference.BaseType); } - public void MarkCodeMethodInvokeExpressionAsAwait(CodeMethodInvokeExpression expression) + public CodeMethodInvokeExpression MarkCodeMethodInvokeExpressionAsAwait(CodeMethodInvokeExpression expression) { if (expression.Method.TargetObject is CodeVariableReferenceExpression variableExpression) { @@ -276,6 +276,7 @@ public void MarkCodeMethodInvokeExpressionAsAwait(CodeMethodInvokeExpression exp { expression.Method.TargetObject = GetAwaitedMethodThisTargetObject(expression.Method.TargetObject); } + return expression; } private CodeExpression GetAwaitedMethodThisTargetObject(CodeExpression thisExpression) From c9e4984203b0deb5525bb22431e49bfe7a3f68db Mon Sep 17 00:00:00 2001 From: obligaron Date: Mon, 13 May 2024 17:31:58 +0200 Subject: [PATCH 15/21] Capture ExecutionContext after every binding invoke (#126) --- CHANGELOG.md | 1 + Reqnroll/Bindings/BindingDelegateInvoker.cs | 20 ++++++------ .../Bindings/BindingInvokerTests.cs | 31 +++++++++++++------ 3 files changed, 34 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c589f6cb..bacad49b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ * Fix: Adding @ignore to an Examples block generates invalid code for NUnit v3+ (#103) * Fix: #111 @ignore attribute is not inherited to the scenarios from Rule * Support for JSON files added to SpecFlow.ExternalData +* Fix: #120 Capture ExecutionContext after every binding invoke # v1.0.1 - 2024-02-16 diff --git a/Reqnroll/Bindings/BindingDelegateInvoker.cs b/Reqnroll/Bindings/BindingDelegateInvoker.cs index bfbf10da1..3e9f770e5 100644 --- a/Reqnroll/Bindings/BindingDelegateInvoker.cs +++ b/Reqnroll/Bindings/BindingDelegateInvoker.cs @@ -22,16 +22,18 @@ public virtual async Task InvokeDelegateAsync(Delegate bindingDelegate, // The ExecutionContext only flows down, so async binding methods cannot directly change it, but even if all binding method // is async the constructor of the binding classes are run in sync, so they should be able to change the ExecutionContext. // (The binding classes are created as part of the 'bindingDelegate' this method receives. - - try - { - return await InvokeInExecutionContext(executionContext?.Value, () => CreateDelegateInvocationTask(bindingDelegate, invokeArgs)); - } - finally + return await InvokeInExecutionContext(executionContext?.Value, () => { - if (executionContext != null) - executionContext.Value = ExecutionContext.Capture(); - } + try + { + return CreateDelegateInvocationTask(bindingDelegate, invokeArgs); + } + finally + { + if (executionContext != null) + executionContext.Value = ExecutionContext.Capture(); + } + }); } private Task InvokeInExecutionContext(ExecutionContext executionContext, Func> callback) diff --git a/Tests/Reqnroll.RuntimeTests/Bindings/BindingInvokerTests.cs b/Tests/Reqnroll.RuntimeTests/Bindings/BindingInvokerTests.cs index af6bbfbb4..e965c7f1f 100644 --- a/Tests/Reqnroll.RuntimeTests/Bindings/BindingInvokerTests.cs +++ b/Tests/Reqnroll.RuntimeTests/Bindings/BindingInvokerTests.cs @@ -283,35 +283,35 @@ class StepDefClassWithAsyncLocal public string LoadedValue { get; set; } // ReSharper disable once UnusedMember.Local - public void SetAsyncLocal_Sync(AsyncLocalType asyncLocalType) + public void SetAsyncLocal_Sync(AsyncLocalType asyncLocalType, string content) { switch (asyncLocalType) { case AsyncLocalType.Uninitialized: - _uninitializedAsyncLocal.Value = "42"; + _uninitializedAsyncLocal.Value = content; break; case AsyncLocalType.CtorInitialized: - _ctorInitializedAsyncLocal.Value = "42"; + _ctorInitializedAsyncLocal.Value = content; break; case AsyncLocalType.Boxed: - _boxedAsyncLocal.Value!.Value = "42"; + _boxedAsyncLocal.Value!.Value = content; break; } } // ReSharper disable once UnusedMember.Local - public async Task SetAsyncLocal_Async(AsyncLocalType asyncLocalType) + public async Task SetAsyncLocal_Async(AsyncLocalType asyncLocalType, string content) { switch (asyncLocalType) { case AsyncLocalType.Uninitialized: - _uninitializedAsyncLocal.Value = "42"; + _uninitializedAsyncLocal.Value = content; break; case AsyncLocalType.CtorInitialized: - _ctorInitializedAsyncLocal.Value = "42"; + _ctorInitializedAsyncLocal.Value = content; break; case AsyncLocalType.Boxed: - _boxedAsyncLocal.Value!.Value = "42"; + _boxedAsyncLocal.Value!.Value = content; break; } await Task.Delay(1); @@ -352,13 +352,26 @@ public async Task ExecutionContext_is_flowing_down_correctly_to_step_definitions var contextManager = CreateContextManagerWith(); if (setAs != null) - await InvokeBindingAsync(sut, contextManager, typeof(StepDefClassWithAsyncLocal), "SetAsyncLocal_" + setAs, asyncLocalType); + await InvokeBindingAsync(sut, contextManager, typeof(StepDefClassWithAsyncLocal), "SetAsyncLocal_" + setAs, asyncLocalType, "42"); await InvokeBindingAsync(sut, contextManager, typeof(StepDefClassWithAsyncLocal), nameof(StepDefClassWithAsyncLocal.GetAsyncLocal_Async), asyncLocalType, expectedResult); var stepDefClass = contextManager.ScenarioContext.ScenarioContainer.Resolve(); stepDefClass.LoadedValue.Should().Be(expectedResult, $"Error was not propagated for {description}"); } + [Fact] + public async Task ExecutionContext_can_be_changed_multiple_times() + { + var sut = CreateSut(); + var contextManager = CreateContextManagerWith(); + + await InvokeBindingAsync(sut, contextManager, typeof(StepDefClassWithAsyncLocal), "SetAsyncLocal_Sync", AsyncLocalType.Uninitialized, "14"); + await InvokeBindingAsync(sut, contextManager, typeof(StepDefClassWithAsyncLocal), "SetAsyncLocal_Sync", AsyncLocalType.Uninitialized, "42"); + await InvokeBindingAsync(sut, contextManager, typeof(StepDefClassWithAsyncLocal), nameof(StepDefClassWithAsyncLocal.GetAsyncLocal_Async), AsyncLocalType.Uninitialized, "42"); + var stepDefClass = contextManager.ScenarioContext.ScenarioContainer.Resolve(); + stepDefClass.LoadedValue.Should().Be("42", $"Error was not propagated"); + } + #endregion #region Exception Handling related tests - regression tests for SF2649 From b7e41d4ed23bf889d25433915f9eef3e3e629391 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1sp=C3=A1r=20Nagy?= Date: Tue, 14 May 2024 18:48:16 +0200 Subject: [PATCH 16/21] Add NUnit & xUnit core tests to portability suite --- .../Portability/PortabilityTestBase.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Tests/Reqnroll.SystemTests/Portability/PortabilityTestBase.cs b/Tests/Reqnroll.SystemTests/Portability/PortabilityTestBase.cs index d230a6777..cf3f6494c 100644 --- a/Tests/Reqnroll.SystemTests/Portability/PortabilityTestBase.cs +++ b/Tests/Reqnroll.SystemTests/Portability/PortabilityTestBase.cs @@ -25,10 +25,15 @@ private void RunSkippableTest(Action test) } [TestMethod] - public void GeneratorAllIn_sample_can_be_handled() + [DataRow(UnitTestProvider.MSTest)] + [DataRow(UnitTestProvider.NUnit3)] + [DataRow(UnitTestProvider.xUnit)] + public void GeneratorAllIn_sample_can_be_handled(UnitTestProvider unitTestProvider) { RunSkippableTest(() => { + _testRunConfiguration.UnitTestProvider = unitTestProvider; + PrepareGeneratorAllInSamples(); ExecuteTests(); From a645d60068a414da14c22570a6c75eca9879b30c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1sp=C3=A1r=20Nagy?= Date: Tue, 14 May 2024 19:00:38 +0200 Subject: [PATCH 17/21] Temporarily disabled tests until https://github.com/reqnroll/Reqnroll/issues/132 is resolved --- .../Reqnroll.SystemTests/Portability/PortabilityTestBase.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Tests/Reqnroll.SystemTests/Portability/PortabilityTestBase.cs b/Tests/Reqnroll.SystemTests/Portability/PortabilityTestBase.cs index cf3f6494c..2d3de7740 100644 --- a/Tests/Reqnroll.SystemTests/Portability/PortabilityTestBase.cs +++ b/Tests/Reqnroll.SystemTests/Portability/PortabilityTestBase.cs @@ -2,6 +2,7 @@ using Reqnroll.TestProjectGenerator; using Reqnroll.TestProjectGenerator.Driver; using System; +using System.Runtime.InteropServices; namespace Reqnroll.SystemTests.Portability; @@ -32,6 +33,11 @@ public void GeneratorAllIn_sample_can_be_handled(UnitTestProvider unitTestProvid { RunSkippableTest(() => { + //TODO: Temporarily disabled tests until https://github.com/reqnroll/Reqnroll/issues/132 is resolved + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && + unitTestProvider == UnitTestProvider.xUnit) + Assert.Inconclusive("Temporarily disabled tests until https://github.com/reqnroll/Reqnroll/issues/132 is resolved"); + _testRunConfiguration.UnitTestProvider = unitTestProvider; PrepareGeneratorAllInSamples(); From e4e7eaa1a583cd016f1c5af149b67c4c77829a78 Mon Sep 17 00:00:00 2001 From: obligaron Date: Wed, 15 May 2024 20:23:55 +0200 Subject: [PATCH 18/21] MsTest: Replace DelayedFixtureTearDown special case with ClassCleanupBehavior.EndOfClass (#128) --- CHANGELOG.md | 1 + .../Reqnroll.MSTest.nuspec | 6 +++--- .../MsTestRuntimeProvider.cs | 2 -- .../NUnitRuntimeProvider.cs | 2 -- .../XUnitRuntimeProvider.cs | 2 -- .../UnitTestProvider/MsTestGeneratorProvider.cs | 6 +++++- .../ProjectBuilder.cs | 2 +- Reqnroll/Infrastructure/TestExecutionEngine.cs | 16 ---------------- .../UnitTestProvider/IUnitTestRuntimeProvider.cs | 1 - .../TestRunContainerBuilderTests.cs | 5 ----- docs/integrations/mstest.md | 2 +- 11 files changed, 11 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bacad49b7..bbbfa808b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ * Fix: #111 @ignore attribute is not inherited to the scenarios from Rule * Support for JSON files added to SpecFlow.ExternalData * Fix: #120 Capture ExecutionContext after every binding invoke +* MsTest: Use ClassCleanupBehavior.EndOfClass instead of custom implementation (preparation for MsTest v4.0) # v1.0.1 - 2024-02-16 diff --git a/Plugins/Reqnroll.MSTest.Generator.ReqnrollPlugin/Reqnroll.MSTest.nuspec b/Plugins/Reqnroll.MSTest.Generator.ReqnrollPlugin/Reqnroll.MSTest.nuspec index 63360359a..94e7a2a45 100644 --- a/Plugins/Reqnroll.MSTest.Generator.ReqnrollPlugin/Reqnroll.MSTest.nuspec +++ b/Plugins/Reqnroll.MSTest.Generator.ReqnrollPlugin/Reqnroll.MSTest.nuspec @@ -19,17 +19,17 @@ - + - + - + diff --git a/Plugins/Reqnroll.MSTest.ReqnrollPlugin/MsTestRuntimeProvider.cs b/Plugins/Reqnroll.MSTest.ReqnrollPlugin/MsTestRuntimeProvider.cs index ee8be201d..b6db73f7d 100644 --- a/Plugins/Reqnroll.MSTest.ReqnrollPlugin/MsTestRuntimeProvider.cs +++ b/Plugins/Reqnroll.MSTest.ReqnrollPlugin/MsTestRuntimeProvider.cs @@ -19,7 +19,5 @@ public void TestIgnore(string message) { TestInconclusive(message); // there is no dynamic "Ignore" in mstest } - - public bool DelayedFixtureTearDown => true; } } \ No newline at end of file diff --git a/Plugins/Reqnroll.NUnit.ReqnrollPlugin/NUnitRuntimeProvider.cs b/Plugins/Reqnroll.NUnit.ReqnrollPlugin/NUnitRuntimeProvider.cs index 081fe7439..a54df615e 100644 --- a/Plugins/Reqnroll.NUnit.ReqnrollPlugin/NUnitRuntimeProvider.cs +++ b/Plugins/Reqnroll.NUnit.ReqnrollPlugin/NUnitRuntimeProvider.cs @@ -19,7 +19,5 @@ public void TestIgnore(string message) { Assert.Ignore(message); } - - public bool DelayedFixtureTearDown => false; } } \ No newline at end of file diff --git a/Plugins/Reqnroll.xUnit.ReqnrollPlugin/XUnitRuntimeProvider.cs b/Plugins/Reqnroll.xUnit.ReqnrollPlugin/XUnitRuntimeProvider.cs index c84221bb0..99efacd30 100644 --- a/Plugins/Reqnroll.xUnit.ReqnrollPlugin/XUnitRuntimeProvider.cs +++ b/Plugins/Reqnroll.xUnit.ReqnrollPlugin/XUnitRuntimeProvider.cs @@ -19,7 +19,5 @@ public void TestIgnore(string message) { Skip.If(true, message); } - - public bool DelayedFixtureTearDown => false; } } \ No newline at end of file diff --git a/Reqnroll.Generator/UnitTestProvider/MsTestGeneratorProvider.cs b/Reqnroll.Generator/UnitTestProvider/MsTestGeneratorProvider.cs index d38b6b0c8..beb0cb675 100644 --- a/Reqnroll.Generator/UnitTestProvider/MsTestGeneratorProvider.cs +++ b/Reqnroll.Generator/UnitTestProvider/MsTestGeneratorProvider.cs @@ -13,6 +13,8 @@ public class MsTestGeneratorProvider : IUnitTestGeneratorProvider protected internal const string PROPERTY_ATTR = "Microsoft.VisualStudio.TestTools.UnitTesting.TestPropertyAttribute"; protected internal const string TESTFIXTURESETUP_ATTR = "Microsoft.VisualStudio.TestTools.UnitTesting.ClassInitializeAttribute"; protected internal const string TESTFIXTURETEARDOWN_ATTR = "Microsoft.VisualStudio.TestTools.UnitTesting.ClassCleanupAttribute"; + protected internal const string CLASSCLEANUPBEHAVIOR_ENUM = "Microsoft.VisualStudio.TestTools.UnitTesting.ClassCleanupBehavior"; + protected internal const string CLASSCLEANUPBEHAVIOR_ENDOFCLASS = "EndOfClass"; protected internal const string TESTSETUP_ATTR = "Microsoft.VisualStudio.TestTools.UnitTesting.TestInitializeAttribute"; protected internal const string TESTTEARDOWN_ATTR = "Microsoft.VisualStudio.TestTools.UnitTesting.TestCleanupAttribute"; protected internal const string IGNORE_ATTR = "Microsoft.VisualStudio.TestTools.UnitTesting.IgnoreAttribute"; @@ -112,7 +114,9 @@ public virtual void SetTestClassInitializeMethod(TestClassGenerationContext gene public void SetTestClassCleanupMethod(TestClassGenerationContext generationContext) { generationContext.TestClassCleanupMethod.Attributes |= MemberAttributes.Static; - CodeDomHelper.AddAttribute(generationContext.TestClassCleanupMethod, TESTFIXTURETEARDOWN_ATTR); + // [Microsoft.VisualStudio.TestTools.UnitTesting.ClassCleanupAttribute(Microsoft.VisualStudio.TestTools.UnitTesting.ClassCleanupBehavior.EndOfClass)] + var attribute = CodeDomHelper.AddAttribute(generationContext.TestClassCleanupMethod, TESTFIXTURETEARDOWN_ATTR); + attribute.Arguments.Add(new CodeAttributeArgument(new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(CLASSCLEANUPBEHAVIOR_ENUM), CLASSCLEANUPBEHAVIOR_ENDOFCLASS))); } diff --git a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/ProjectBuilder.cs b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/ProjectBuilder.cs index ef8329902..92f716480 100644 --- a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/ProjectBuilder.cs +++ b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/ProjectBuilder.cs @@ -16,7 +16,7 @@ public class ProjectBuilder public const string NUnit3TestAdapterPackageName = "NUnit3TestAdapter"; public const string NUnit3TestAdapterPackageVersion = "3.17.0"; private const string XUnitPackageVersion = "2.4.2"; - private const string MSTestPackageVersion = "2.1.2"; + private const string MSTestPackageVersion = "2.2.8"; private const string InternalJsonPackageName = "SpecFlow.Internal.Json"; private const string InternalJsonVersion = "1.0.8"; private readonly BindingsGeneratorFactory _bindingsGeneratorFactory; diff --git a/Reqnroll/Infrastructure/TestExecutionEngine.cs b/Reqnroll/Infrastructure/TestExecutionEngine.cs index b201dc037..e36fe9649 100644 --- a/Reqnroll/Infrastructure/TestExecutionEngine.cs +++ b/Reqnroll/Infrastructure/TestExecutionEngine.cs @@ -141,15 +141,6 @@ public virtual async Task OnTestRunEndAsync() public virtual async Task OnFeatureStartAsync(FeatureInfo featureInfo) { - // if the unit test provider would execute the fixture teardown code - // only delayed (at the end of the execution), we automatically close - // the current feature if necessary - if (_unitTestRuntimeProvider.DelayedFixtureTearDown && FeatureContext != null) - { - await OnFeatureEndAsync(); - } - - _contextManager.InitializeFeatureContext(featureInfo); _testThreadExecutionEventPublisher.PublishEvent(new FeatureStartedEvent(FeatureContext)); @@ -159,13 +150,6 @@ public virtual async Task OnFeatureStartAsync(FeatureInfo featureInfo) public virtual async Task OnFeatureEndAsync() { - // if the unit test provider would execute the fixture teardown code - // only delayed (at the end of the execution), we ignore the - // feature-end call, if the feature has been closed already - if (_unitTestRuntimeProvider.DelayedFixtureTearDown && - FeatureContext == null) - return; - await FireEventsAsync(HookType.AfterFeature); if (_reqnrollConfiguration.TraceTimings) diff --git a/Reqnroll/UnitTestProvider/IUnitTestRuntimeProvider.cs b/Reqnroll/UnitTestProvider/IUnitTestRuntimeProvider.cs index 702bba205..4cebfa4b0 100644 --- a/Reqnroll/UnitTestProvider/IUnitTestRuntimeProvider.cs +++ b/Reqnroll/UnitTestProvider/IUnitTestRuntimeProvider.cs @@ -5,6 +5,5 @@ public interface IUnitTestRuntimeProvider void TestPending(string message); void TestInconclusive(string message); void TestIgnore(string message); - bool DelayedFixtureTearDown { get; } } } \ No newline at end of file diff --git a/Tests/Reqnroll.RuntimeTests/Infrastructure/TestRunContainerBuilderTests.cs b/Tests/Reqnroll.RuntimeTests/Infrastructure/TestRunContainerBuilderTests.cs index 8a5b9fc97..fe91167ce 100644 --- a/Tests/Reqnroll.RuntimeTests/Infrastructure/TestRunContainerBuilderTests.cs +++ b/Tests/Reqnroll.RuntimeTests/Infrastructure/TestRunContainerBuilderTests.cs @@ -111,11 +111,6 @@ public void TestIgnore(string message) { throw new NotImplementedException(); } - - public bool DelayedFixtureTearDown - { - get { throw new NotImplementedException(); } - } } public void Dispose() diff --git a/docs/integrations/mstest.md b/docs/integrations/mstest.md index e108f8aaa..664466ad6 100644 --- a/docs/integrations/mstest.md +++ b/docs/integrations/mstest.md @@ -1,6 +1,6 @@ # MSTest -Reqnroll supports MsTest V2. +Reqnroll supports MsTest V2 (NuGet Version 2.2.8 or higher). Documentation for MSTest can be found [here](https://docs.microsoft.com/en-us/visualstudio/test/unit-test-your-code?view=vs-2019). From a8241b8134b8a1f37362f52e89db50701cf3768d Mon Sep 17 00:00:00 2001 From: obligaron Date: Tue, 21 May 2024 11:20:06 +0200 Subject: [PATCH 19/21] Fix StackOverflowException when using [StepArgumentTransformation] with same input and output type (#136) --- CHANGELOG.md | 1 + .../Bindings/StepArgumentTypeConverter.cs | 15 ++-- .../CucumberExpressionIntegrationTests.cs | 80 +++++++++++++++++++ 3 files changed, 91 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bbbfa808b..21531040e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ * Support for JSON files added to SpecFlow.ExternalData * Fix: #120 Capture ExecutionContext after every binding invoke * MsTest: Use ClassCleanupBehavior.EndOfClass instead of custom implementation (preparation for MsTest v4.0) +* Fix: #71 StackOverflowException when using [StepArgumentTransformation] with same input and output type (for example string) # v1.0.1 - 2024-02-16 diff --git a/Reqnroll/Bindings/StepArgumentTypeConverter.cs b/Reqnroll/Bindings/StepArgumentTypeConverter.cs index 1d7a5cdc3..ea4d8a568 100644 --- a/Reqnroll/Bindings/StepArgumentTypeConverter.cs +++ b/Reqnroll/Bindings/StepArgumentTypeConverter.cs @@ -38,11 +38,16 @@ protected virtual IStepArgumentTransformationBinding GetMatchingStepTransformati } public async Task ConvertAsync(object value, IBindingType typeToConvertTo, CultureInfo cultureInfo) + { + return await ConvertAsync(value, typeToConvertTo, cultureInfo, null); + } + + private async Task ConvertAsync(object value, IBindingType typeToConvertTo, CultureInfo cultureInfo, IStepArgumentTransformationBinding lastBindingUsed) { if (value == null) throw new ArgumentNullException(nameof(value)); var stepTransformation = GetMatchingStepTransformation(value, typeToConvertTo, true); - if (stepTransformation != null) + if (stepTransformation != null && lastBindingUsed != stepTransformation) return await DoTransformAsync(stepTransformation, value, cultureInfo); if (typeToConvertTo is RuntimeBindingType convertToType && convertToType.Type.IsInstanceOfType(value)) @@ -55,16 +60,16 @@ private async Task DoTransformAsync(IStepArgumentTransformationBinding s { object[] arguments; if (stepTransformation.Regex != null && value is string stringValue) - arguments = await GetStepTransformationArgumentsFromRegexAsync(stepTransformation, stringValue, cultureInfo); + arguments = await GetStepTransformationArgumentsFromRegexAsync(stepTransformation, stringValue, cultureInfo, stepTransformation); else - arguments = new[] { await ConvertAsync(value, stepTransformation.Method.Parameters.ElementAtOrDefault(0)?.Type ?? new RuntimeBindingType(typeof(object)), cultureInfo)}; + arguments = new[] { await ConvertAsync(value, stepTransformation.Method.Parameters.ElementAtOrDefault(0)?.Type ?? new RuntimeBindingType(typeof(object)), cultureInfo, stepTransformation) }; var result = await bindingInvoker.InvokeBindingAsync(stepTransformation, contextManager, arguments, testTracer, new DurationHolder()); return result; } - private async Task GetStepTransformationArgumentsFromRegexAsync(IStepArgumentTransformationBinding stepTransformation, string stepSnippet, CultureInfo cultureInfo) + private async Task GetStepTransformationArgumentsFromRegexAsync(IStepArgumentTransformationBinding stepTransformation, string stepSnippet, CultureInfo cultureInfo, IStepArgumentTransformationBinding lastBindingUsed) { var match = stepTransformation.Regex.Match(stepSnippet); var argumentStrings = match.Groups.Cast().Skip(1).Select(g => g.Value).ToList(); @@ -74,7 +79,7 @@ private async Task GetStepTransformationArgumentsFromRegexAsync(IStepA for (int i = 0; i < argumentStrings.Count; i++) { - result[i] = await ConvertAsync(argumentStrings[i], bindingParameters[i].Type, cultureInfo); + result[i] = await ConvertAsync(argumentStrings[i], bindingParameters[i].Type, cultureInfo, lastBindingUsed); } return result; diff --git a/Tests/Reqnroll.RuntimeTests/Bindings/CucumberExpressions/CucumberExpressionIntegrationTests.cs b/Tests/Reqnroll.RuntimeTests/Bindings/CucumberExpressions/CucumberExpressionIntegrationTests.cs index 7e1095ee8..c81fb54aa 100644 --- a/Tests/Reqnroll.RuntimeTests/Bindings/CucumberExpressions/CucumberExpressionIntegrationTests.cs +++ b/Tests/Reqnroll.RuntimeTests/Bindings/CucumberExpressions/CucumberExpressionIntegrationTests.cs @@ -57,6 +57,11 @@ public override bool Equals(object obj) public override int GetHashCode() => UserName.GetHashCode(); } +public record SampleComplexUser(string Firstname, string Surname, int Age, int Height) +{ + public static SampleComplexUser Create(string firstname, string surname, int age, int height) => new(firstname, surname, age, height); +} + public class CucumberExpressionIntegrationTests { public class SampleBindings @@ -100,10 +105,25 @@ public void StepDefWithCustomClassParam(SampleUser userParam) ExecutedParams.Add((userParam, typeof(SampleUser))); } + public void StepDefWithCustomComplexClassParam(SampleComplexUser userParam) + { + ExecutedParams.Add((userParam, typeof(SampleComplexUser))); + } + public int ConvertFortyTwo() { return 42; } + + public string ConvertToStringLowercase(string str) + { + return str.ToLower(); + } + + public int ConvertToIntPlus100(int nr) + { + return nr + 100; + } } public class TestDependencyProvider : DefaultDependencyProvider @@ -423,4 +443,64 @@ public async void Should_match_step_with_customized_built_in_parameter_with_simp sampleBindings.ExecutedParams.Should().Contain(expectedParam); } + + [Fact] + public async void Should_match_step_with_customized_built_in_parameter_without_recursion_string() + { + var expression = "there is a user {string} registered"; + var stepText = "there is a user 'Marvin' registered"; + var expectedParam = ("marvin", typeof(string)); + var methodName = nameof(SampleBindings.StepDefWithStringParam); + + IStepArgumentTransformationBinding transformation = new StepArgumentTransformationBinding( + (string)null, + new RuntimeBindingMethod(typeof(SampleBindings).GetMethod(nameof(SampleBindings.ConvertToStringLowercase)))); + + var sampleBindings = await PerformStepExecution(methodName, expression, stepText, new[] { transformation }); + + sampleBindings.ExecutedParams.Should().Contain(expectedParam); + } + + [Fact] + public async void Should_match_step_with_customized_built_in_parameter_without_recursion_int32() + { + var expression = "I have {int} cucumbers in my belly"; + var stepText = "I have 43 cucumbers in my belly"; + var expectedParam = (143, typeof(int)); + var methodName = nameof(SampleBindings.StepDefWithIntParam); + + IStepArgumentTransformationBinding transformation = new StepArgumentTransformationBinding( + (string)null, + new RuntimeBindingMethod(typeof(SampleBindings).GetMethod(nameof(SampleBindings.ConvertToIntPlus100)))); + + var sampleBindings = await PerformStepExecution(methodName, expression, stepText, new[] { transformation }); + + sampleBindings.ExecutedParams.Should().Contain(expectedParam); + } + + [Fact] + public async void Should_match_step_with_custom_parameter_with_additional_step_arguments() + { + var expression = "there is a {user} registered"; + var stepText = "there is a user Marvin Smith he is 27 years old and 175 height registered"; + var expectedParam = (new SampleComplexUser("marvin", "smith", 127, 275), typeof(SampleComplexUser)); + var methodName = nameof(SampleBindings.StepDefWithCustomComplexClassParam); + + IStepArgumentTransformationBinding transformationStringWithouthBlanks = new StepArgumentTransformationBinding( + (string)null, + new RuntimeBindingMethod(typeof(SampleBindings).GetMethod(nameof(SampleBindings.ConvertToStringLowercase)))); + + IStepArgumentTransformationBinding transformationIntPlus100 = new StepArgumentTransformationBinding( + (string)null, + new RuntimeBindingMethod(typeof(SampleBindings).GetMethod(nameof(SampleBindings.ConvertToIntPlus100)))); + + IStepArgumentTransformationBinding transformationSampleComplexUser = new StepArgumentTransformationBinding( + "user ([A-Za-z]+) ([A-Za-z]+) he is ([0-9]+) years old and ([0-9]+) height", + new RuntimeBindingMethod(typeof(SampleComplexUser).GetMethod(nameof(SampleComplexUser.Create))), + "user"); + + var sampleBindings = await PerformStepExecution(methodName, expression, stepText, new[] { transformationStringWithouthBlanks, transformationIntPlus100, transformationSampleComplexUser }); + + sampleBindings.ExecutedParams.Should().Contain(expectedParam); + } } From 36d355f1d5becce80e5af04a162f0dfc0afa2d46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1sp=C3=A1r=20Nagy?= Date: Tue, 21 May 2024 11:33:44 +0200 Subject: [PATCH 20/21] Reduce target framework of Reqnroll to netstandard2.0 (#130) * remove obsolete and unnecessary if pragmas * remove .NET 6.0 from target frameworks * Add PlatformInformation, fix ns Reqnroll.Compatibility to Reqnroll.PlatformCompatibility * Reduce target framework of Reqnroll project to netstandard2.0 * TEMP: Add platform information * Improve error diagnostics for plugin loader errors * Revert "TEMP: Add platform information" This reverts commit 6e08680b6e318eed052756c7d0b03d5548898b77. * add word to dictionary * Treat Mono as .NET Framework * small improvement on MainDotNetFrameworkDescription * Reduce target framework of plugin projects to netstandard2.0 * further fixes and simplifications * Replace netcoreapp3.1 with netstandard2.0 for tools and generator * Remove net6.0 from tools and generator * Temporary fix for build problem * Proper fix for build problem * project cleanup * Reduce target framework of generator projects to netstandard2.0 * Make Reqnroll.ExternalData.ReqnrollPlugin single target (netstandard2.0) * update plugins docs * cleanup targets * Make Reqnroll.MSTest.Generator.ReqnrollPlugin single target (netstandard2.0) * cleanup targets * Make Reqnroll.NUnit.Generator.ReqnrollPlugin single target (netstandard2.0) * Make Reqnroll.xUnit.Generator.ReqnrollPlugin single target (netstandard2.0) * cleanup targets * extend CHANGELOG * update ncrunch settings * small fix for Reqnroll.SpecFlowCompatibility --- .gitignore | 1 + ...l.Autofac.ReqnrollPlugin.v3.ncrunchproject | 7 +--- .../Reqnroll.CustomPlugin.v3.ncrunchproject | 3 -- ...ernalData.ReqnrollPlugin.v3.ncrunchproject | 7 +--- .ncrunch/Reqnroll.Generator.v3.ncrunchproject | 7 +--- .../Reqnroll.GeneratorTests.v3.ncrunchproject | 1 - ...Generator.ReqnrollPlugin.v3.ncrunchproject | 3 +- ...ll.MSTest.ReqnrollPlugin.v3.ncrunchproject | 3 -- ...Generator.ReqnrollPlugin.v3.ncrunchproject | 5 +-- ...oll.NUnit.ReqnrollPlugin.v3.ncrunchproject | 5 +-- .../Reqnroll.PluginTests.v3.ncrunchproject | 12 ++++++- ...Generator.ReqnrollPlugin.v3.ncrunchproject | 3 +- ...atibility.ReqnrollPlugin.v3.ncrunchproject | 3 +- ...eqnroll.Templates.DotNet.v3.ncrunchproject | 3 -- ...Tools.MsBuild.Generation.v3.ncrunchproject | 3 +- ...ll.Verify.ReqnrollPlugin.v3.ncrunchproject | 3 +- ...l.Windsor.ReqnrollPlugin.v3.ncrunchproject | 7 +--- .ncrunch/Reqnroll.v3.ncrunchproject | 6 +--- ...Generator.ReqnrollPlugin.v3.ncrunchproject | 5 +-- ...oll.xUnit.ReqnrollPlugin.v3.ncrunchproject | 5 +-- CHANGELOG.md | 1 + Directory.Build.props | 29 ++--------------- Directory.Build.targets | 10 +----- .../Reqnroll.CustomPlugin.csproj | 2 +- .../Reqnroll.CustomPlugin.nuspec | 26 +++------------ .../Reqnroll.Autofac.ReqnrollPlugin.csproj | 2 +- .../Reqnroll.Autofac.nuspec | 16 ++-------- .../build/Reqnroll.Autofac.targets | 3 +- ...Data.ReqnrollPlugin.IntegrationTest.csproj | 10 +++--- ...eqnroll.ExternalData.ReqnrollPlugin.csproj | 2 +- .../Reqnroll.ExternalData.nuspec | 4 +-- .../build/Reqnroll.ExternalData.targets | 4 +-- ...oll.MSTest.Generator.ReqnrollPlugin.csproj | 2 +- .../Reqnroll.MSTest.nuspec | 21 ++---------- .../build/MSTest.AssemblyHooks.template.cs | 10 +++++- .../build/Reqnroll.MsTest.targets | 6 ++-- .../Reqnroll.MSTest.ReqnrollPlugin.csproj | 3 +- ...roll.NUnit.Generator.ReqnrollPlugin.csproj | 2 +- .../Reqnroll.NUnit.nuspec | 21 ++---------- .../build/Reqnroll.NUnit.targets | 6 ++-- .../Reqnroll.NUnit.ReqnrollPlugin.csproj | 6 +--- .../GeneratorPlugin.cs | 2 -- ...patibility.Generator.ReqnrollPlugin.csproj | 14 ++------ .../Reqnroll.SpecFlowCompatibility.nuspec | 16 +++------- .../Reqnroll.SpecFlowCompatibility.targets | 2 +- ...pecFlowCompatibility.ReqnrollPlugin.csproj | 12 +++---- ...rify.ReqnrollPlugin.IntegrationTest.csproj | 19 ++++++----- .../Reqnroll.Verify.ReqnrollPlugin.csproj | 4 +-- .../Reqnroll.Verify.nuspec | 11 ++----- .../build/Reqnroll.Verify.targets | 2 +- .../Reqnroll.Windsor.ReqnrollPlugin.csproj | 2 +- .../Reqnroll.Windsor.nuspec | 15 ++------- .../build/Reqnroll.Windsor.targets | 4 +-- ...roll.xUnit.Generator.ReqnrollPlugin.csproj | 2 +- .../Reqnroll.xUnit.nuspec | 24 ++------------ .../build/Reqnroll.xUnit.targets | 6 ++-- .../Reqnroll.xUnit.ReqnrollPlugin.csproj | 4 ++- .../DefaultDependencyProvider.cs | 3 ++ .../Plugins/GeneratorPluginLoader.cs | 13 +++----- Reqnroll.Generator/Reqnroll.Generator.csproj | 32 ++----------------- .../Reqnroll.TestProjectGenerator.Cli.csproj | 2 +- ...Reqnroll.TestProjectGenerator.Tests.csproj | 2 +- .../Reqnroll.TestProjectGenerator.csproj | 7 ---- .../Reqnroll.Tools.MsBuild.Generation.csproj | 32 ++++++++++--------- .../Reqnroll.Tools.MsBuild.Generation.nuspec | 12 +++---- .../Reqnroll.Tools.MsBuild.Generation.props | 2 +- Reqnroll.sln.DotSettings | 3 +- Reqnroll/AssemblyAttributes.cs | 9 ------ Reqnroll/Bindings/BindingInvoker.cs | 5 --- .../Discovery/BindingSourceProcessor.cs | 2 +- .../RuntimeBindingRegistryBuilder.cs | 2 +- Reqnroll/Bindings/StepContext.cs | 2 +- Reqnroll/Configuration/ConfigurationLoader.cs | 2 +- .../DefaultDependencyProvider.cs | 2 ++ .../Infrastructure/TestExecutionEngine.cs | 2 +- .../CultureInfoHelper.cs | 2 +- Reqnroll/PlatformCompatibility/EnumHelper.cs | 2 +- .../PlatformCompatibility/ExceptionHelper.cs | 2 +- Reqnroll/PlatformCompatibility/MonoHelper.cs | 2 +- .../PlatformCompatibility/PlatformHelper.cs | 14 ++++++++ .../PlatformInformation.cs | 26 +++++++++++++++ .../DotNetFrameworkPluginAssemblyLoader.cs | 8 +++++ Reqnroll/Plugins/IPluginAssemblyLoader.cs | 7 ++++ Reqnroll/Plugins/PluginAssemblyLoader.cs | 8 +++++ Reqnroll/Plugins/PluginAssemblyResolver.cs | 4 --- Reqnroll/Plugins/RuntimePluginLoader.cs | 11 +++---- .../Plugins/RuntimePluginLocationMerger.cs | 7 +--- Reqnroll/Reqnroll.csproj | 23 +++---------- Reqnroll/Reqnroll.nuspec | 22 ++++--------- Reqnroll/Table.cs | 6 ---- Reqnroll/Tracing/AnsiColor/AnsiColor.cs | 5 +-- Reqnroll/Tracing/LanguageHelper.cs | 3 +- .../Reqnroll.GeneratorTests.csproj | 13 -------- .../Generator/GeneratorPluginLoaderTests.cs | 5 +-- .../Infrastructure/WindsorPluginTests.cs | 2 +- .../Reqnroll.RuntimeTests.csproj | 12 ------- Tests/Reqnroll.Specs/Reqnroll.Specs.csproj | 12 +++---- docs/extend/plugins.md | 31 ++++++++++++------ 98 files changed, 271 insertions(+), 507 deletions(-) create mode 100644 Reqnroll/PlatformCompatibility/PlatformHelper.cs create mode 100644 Reqnroll/PlatformCompatibility/PlatformInformation.cs create mode 100644 Reqnroll/Plugins/DotNetFrameworkPluginAssemblyLoader.cs create mode 100644 Reqnroll/Plugins/IPluginAssemblyLoader.cs create mode 100644 Reqnroll/Plugins/PluginAssemblyLoader.cs diff --git a/.gitignore b/.gitignore index 9b65f193d..c42377080 100644 --- a/.gitignore +++ b/.gitignore @@ -385,3 +385,4 @@ docs/_build/* /docs/Lib/ /docs/Scripts/ /docs/pyvenv.cfg +nCrunchTemp*.csproj diff --git a/.ncrunch/Reqnroll.Autofac.ReqnrollPlugin.v3.ncrunchproject b/.ncrunch/Reqnroll.Autofac.ReqnrollPlugin.v3.ncrunchproject index 6c4b990aa..95a483b43 100644 --- a/.ncrunch/Reqnroll.Autofac.ReqnrollPlugin.v3.ncrunchproject +++ b/.ncrunch/Reqnroll.Autofac.ReqnrollPlugin.v3.ncrunchproject @@ -1,8 +1,3 @@  - - - TargetFrameworks = net6.0 - - True - + \ No newline at end of file diff --git a/.ncrunch/Reqnroll.CustomPlugin.v3.ncrunchproject b/.ncrunch/Reqnroll.CustomPlugin.v3.ncrunchproject index 6c4b990aa..319cd523c 100644 --- a/.ncrunch/Reqnroll.CustomPlugin.v3.ncrunchproject +++ b/.ncrunch/Reqnroll.CustomPlugin.v3.ncrunchproject @@ -1,8 +1,5 @@  - - TargetFrameworks = net6.0 - True \ No newline at end of file diff --git a/.ncrunch/Reqnroll.ExternalData.ReqnrollPlugin.v3.ncrunchproject b/.ncrunch/Reqnroll.ExternalData.ReqnrollPlugin.v3.ncrunchproject index 6c4b990aa..95a483b43 100644 --- a/.ncrunch/Reqnroll.ExternalData.ReqnrollPlugin.v3.ncrunchproject +++ b/.ncrunch/Reqnroll.ExternalData.ReqnrollPlugin.v3.ncrunchproject @@ -1,8 +1,3 @@  - - - TargetFrameworks = net6.0 - - True - + \ No newline at end of file diff --git a/.ncrunch/Reqnroll.Generator.v3.ncrunchproject b/.ncrunch/Reqnroll.Generator.v3.ncrunchproject index 5448450ce..95a483b43 100644 --- a/.ncrunch/Reqnroll.Generator.v3.ncrunchproject +++ b/.ncrunch/Reqnroll.Generator.v3.ncrunchproject @@ -1,8 +1,3 @@  - - - TargetFrameworks = net6.0 - - False - + \ No newline at end of file diff --git a/.ncrunch/Reqnroll.GeneratorTests.v3.ncrunchproject b/.ncrunch/Reqnroll.GeneratorTests.v3.ncrunchproject index 9a0eea7aa..5e6d62816 100644 --- a/.ncrunch/Reqnroll.GeneratorTests.v3.ncrunchproject +++ b/.ncrunch/Reqnroll.GeneratorTests.v3.ncrunchproject @@ -1,6 +1,5 @@  True - False \ No newline at end of file diff --git a/.ncrunch/Reqnroll.MSTest.Generator.ReqnrollPlugin.v3.ncrunchproject b/.ncrunch/Reqnroll.MSTest.Generator.ReqnrollPlugin.v3.ncrunchproject index 5448450ce..e586040f3 100644 --- a/.ncrunch/Reqnroll.MSTest.Generator.ReqnrollPlugin.v3.ncrunchproject +++ b/.ncrunch/Reqnroll.MSTest.Generator.ReqnrollPlugin.v3.ncrunchproject @@ -1,8 +1,7 @@  - TargetFrameworks = net6.0 + TargetFrameworks = netstandard2.0 - False \ No newline at end of file diff --git a/.ncrunch/Reqnroll.MSTest.ReqnrollPlugin.v3.ncrunchproject b/.ncrunch/Reqnroll.MSTest.ReqnrollPlugin.v3.ncrunchproject index 5448450ce..0bcc569d0 100644 --- a/.ncrunch/Reqnroll.MSTest.ReqnrollPlugin.v3.ncrunchproject +++ b/.ncrunch/Reqnroll.MSTest.ReqnrollPlugin.v3.ncrunchproject @@ -1,8 +1,5 @@  - - TargetFrameworks = net6.0 - False \ No newline at end of file diff --git a/.ncrunch/Reqnroll.NUnit.Generator.ReqnrollPlugin.v3.ncrunchproject b/.ncrunch/Reqnroll.NUnit.Generator.ReqnrollPlugin.v3.ncrunchproject index 5448450ce..e9934c101 100644 --- a/.ncrunch/Reqnroll.NUnit.Generator.ReqnrollPlugin.v3.ncrunchproject +++ b/.ncrunch/Reqnroll.NUnit.Generator.ReqnrollPlugin.v3.ncrunchproject @@ -1,8 +1,5 @@  - - TargetFrameworks = net6.0 - - False + False \ No newline at end of file diff --git a/.ncrunch/Reqnroll.NUnit.ReqnrollPlugin.v3.ncrunchproject b/.ncrunch/Reqnroll.NUnit.ReqnrollPlugin.v3.ncrunchproject index 5448450ce..e9934c101 100644 --- a/.ncrunch/Reqnroll.NUnit.ReqnrollPlugin.v3.ncrunchproject +++ b/.ncrunch/Reqnroll.NUnit.ReqnrollPlugin.v3.ncrunchproject @@ -1,8 +1,5 @@  - - TargetFrameworks = net6.0 - - False + False \ No newline at end of file diff --git a/.ncrunch/Reqnroll.PluginTests.v3.ncrunchproject b/.ncrunch/Reqnroll.PluginTests.v3.ncrunchproject index 319cd523c..79ff1b6af 100644 --- a/.ncrunch/Reqnroll.PluginTests.v3.ncrunchproject +++ b/.ncrunch/Reqnroll.PluginTests.v3.ncrunchproject @@ -1,5 +1,15 @@  - True + + ExternalData\SampleFiles\**.* + + + + Reqnroll.PluginTests.Generator.GeneratorPluginLoaderTests.LoadPlugin_LoadXUnitSuccessfully + + + Reqnroll.PluginTests.Infrastructure.WindsorPluginTests.Can_load_Windsor_plugin + + \ No newline at end of file diff --git a/.ncrunch/Reqnroll.SpecFlowCompatibility.Generator.ReqnrollPlugin.v3.ncrunchproject b/.ncrunch/Reqnroll.SpecFlowCompatibility.Generator.ReqnrollPlugin.v3.ncrunchproject index 6c4b990aa..e586040f3 100644 --- a/.ncrunch/Reqnroll.SpecFlowCompatibility.Generator.ReqnrollPlugin.v3.ncrunchproject +++ b/.ncrunch/Reqnroll.SpecFlowCompatibility.Generator.ReqnrollPlugin.v3.ncrunchproject @@ -1,8 +1,7 @@  - TargetFrameworks = net6.0 + TargetFrameworks = netstandard2.0 - True \ No newline at end of file diff --git a/.ncrunch/Reqnroll.SpecFlowCompatibility.ReqnrollPlugin.v3.ncrunchproject b/.ncrunch/Reqnroll.SpecFlowCompatibility.ReqnrollPlugin.v3.ncrunchproject index 6c4b990aa..e586040f3 100644 --- a/.ncrunch/Reqnroll.SpecFlowCompatibility.ReqnrollPlugin.v3.ncrunchproject +++ b/.ncrunch/Reqnroll.SpecFlowCompatibility.ReqnrollPlugin.v3.ncrunchproject @@ -1,8 +1,7 @@  - TargetFrameworks = net6.0 + TargetFrameworks = netstandard2.0 - True \ No newline at end of file diff --git a/.ncrunch/Reqnroll.Templates.DotNet.v3.ncrunchproject b/.ncrunch/Reqnroll.Templates.DotNet.v3.ncrunchproject index 6c4b990aa..319cd523c 100644 --- a/.ncrunch/Reqnroll.Templates.DotNet.v3.ncrunchproject +++ b/.ncrunch/Reqnroll.Templates.DotNet.v3.ncrunchproject @@ -1,8 +1,5 @@  - - TargetFrameworks = net6.0 - True \ No newline at end of file diff --git a/.ncrunch/Reqnroll.Tools.MsBuild.Generation.v3.ncrunchproject b/.ncrunch/Reqnroll.Tools.MsBuild.Generation.v3.ncrunchproject index 5448450ce..e586040f3 100644 --- a/.ncrunch/Reqnroll.Tools.MsBuild.Generation.v3.ncrunchproject +++ b/.ncrunch/Reqnroll.Tools.MsBuild.Generation.v3.ncrunchproject @@ -1,8 +1,7 @@  - TargetFrameworks = net6.0 + TargetFrameworks = netstandard2.0 - False \ No newline at end of file diff --git a/.ncrunch/Reqnroll.Verify.ReqnrollPlugin.v3.ncrunchproject b/.ncrunch/Reqnroll.Verify.ReqnrollPlugin.v3.ncrunchproject index 6c4b990aa..e586040f3 100644 --- a/.ncrunch/Reqnroll.Verify.ReqnrollPlugin.v3.ncrunchproject +++ b/.ncrunch/Reqnroll.Verify.ReqnrollPlugin.v3.ncrunchproject @@ -1,8 +1,7 @@  - TargetFrameworks = net6.0 + TargetFrameworks = netstandard2.0 - True \ No newline at end of file diff --git a/.ncrunch/Reqnroll.Windsor.ReqnrollPlugin.v3.ncrunchproject b/.ncrunch/Reqnroll.Windsor.ReqnrollPlugin.v3.ncrunchproject index 6c4b990aa..95a483b43 100644 --- a/.ncrunch/Reqnroll.Windsor.ReqnrollPlugin.v3.ncrunchproject +++ b/.ncrunch/Reqnroll.Windsor.ReqnrollPlugin.v3.ncrunchproject @@ -1,8 +1,3 @@  - - - TargetFrameworks = net6.0 - - True - + \ No newline at end of file diff --git a/.ncrunch/Reqnroll.v3.ncrunchproject b/.ncrunch/Reqnroll.v3.ncrunchproject index e586040f3..95a483b43 100644 --- a/.ncrunch/Reqnroll.v3.ncrunchproject +++ b/.ncrunch/Reqnroll.v3.ncrunchproject @@ -1,7 +1,3 @@  - - - TargetFrameworks = netstandard2.0 - - + \ No newline at end of file diff --git a/.ncrunch/Reqnroll.xUnit.Generator.ReqnrollPlugin.v3.ncrunchproject b/.ncrunch/Reqnroll.xUnit.Generator.ReqnrollPlugin.v3.ncrunchproject index 5448450ce..cff5044ed 100644 --- a/.ncrunch/Reqnroll.xUnit.Generator.ReqnrollPlugin.v3.ncrunchproject +++ b/.ncrunch/Reqnroll.xUnit.Generator.ReqnrollPlugin.v3.ncrunchproject @@ -1,8 +1,5 @@  - - TargetFrameworks = net6.0 - - False + False \ No newline at end of file diff --git a/.ncrunch/Reqnroll.xUnit.ReqnrollPlugin.v3.ncrunchproject b/.ncrunch/Reqnroll.xUnit.ReqnrollPlugin.v3.ncrunchproject index 5448450ce..cff5044ed 100644 --- a/.ncrunch/Reqnroll.xUnit.ReqnrollPlugin.v3.ncrunchproject +++ b/.ncrunch/Reqnroll.xUnit.ReqnrollPlugin.v3.ncrunchproject @@ -1,8 +1,5 @@  - - TargetFrameworks = net6.0 - - False + False \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 21531040e..37652b5a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ * Fix: #111 @ignore attribute is not inherited to the scenarios from Rule * Support for JSON files added to SpecFlow.ExternalData * Fix: #120 Capture ExecutionContext after every binding invoke +* Allow creating single target (netstandard2.0) plugins * MsTest: Use ClassCleanupBehavior.EndOfClass instead of custom implementation (preparation for MsTest v4.0) * Fix: #71 StackOverflowException when using [StepArgumentTransformation] with same input and output type (for example string) diff --git a/Directory.Build.props b/Directory.Build.props index 3d9127a80..0395695cd 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -36,32 +36,9 @@ - net462 - - net6.0 - - - $(Reqnroll_FullFramework_TFM) - netstandard2.0 - - $(Reqnroll_FullFramework_TFM) - netcoreapp3.1 - - $(Reqnroll_FullFramework_TFM) - netcoreapp3.1;net6.0 - - $(Reqnroll_FullFramework_TFM) - $(Reqnroll_Net6_TFM) - - $(Reqnroll_FullFramework_TFM) - netcoreapp3.1 - $(Reqnroll_Net6_TFM) - - $(Reqnroll_FullFramework_Runtime_TFM);$(Reqnroll_Core_Runtime_TFM);$(Reqnroll_Net6_TFM) - $(Reqnroll_FullFramework_Generator_TFM);$(Reqnroll_Core_Generator_TFM);$(Reqnroll_Net6_TFM) - $(Reqnroll_FullFramework_Test_TFM);$(Reqnroll_Core_Test_TFM) - $(Reqnroll_Net6_Specs_TFM) - $(Reqnroll_FullFramework_Tools_TFM);$(Reqnroll_Core_Tools_TFM);$(Reqnroll_Net6_Tools_TFM) + + net462 + netstandard2.0 true diff --git a/Directory.Build.targets b/Directory.Build.targets index c01395040..4d2d2a4c0 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -8,16 +8,8 @@ $(NuspecProperties);author=$(Reqnroll_Authors) $(NuspecProperties);owner=$(Reqnroll_Owners) - $(NuspecProperties);Reqnroll_Net6_TFM=$(Reqnroll_Net6_TFM) - - $(NuspecProperties);Reqnroll_FullFramework_Runtime_TFM=$(Reqnroll_FullFramework_Runtime_TFM) - $(NuspecProperties);Reqnroll_Core_Runtime_TFM=$(Reqnroll_Core_Runtime_TFM) - - $(NuspecProperties);Reqnroll_FullFramework_Generator_TFM=$(Reqnroll_FullFramework_Generator_TFM) - $(NuspecProperties);Reqnroll_Core_Generator_TFM=$(Reqnroll_Core_Generator_TFM) - + $(NuspecProperties);Reqnroll_FullFramework_Tools_TFM=$(Reqnroll_FullFramework_Tools_TFM) $(NuspecProperties);Reqnroll_Core_Tools_TFM=$(Reqnroll_Core_Tools_TFM) - $(NuspecProperties);Reqnroll_Net6_Tools_TFM=$(Reqnroll_Net6_Tools_TFM) diff --git a/Installer/Reqnroll.CustomPlugin/Reqnroll.CustomPlugin.csproj b/Installer/Reqnroll.CustomPlugin/Reqnroll.CustomPlugin.csproj index 497cd9a02..bc4b0f3d0 100644 --- a/Installer/Reqnroll.CustomPlugin/Reqnroll.CustomPlugin.csproj +++ b/Installer/Reqnroll.CustomPlugin/Reqnroll.CustomPlugin.csproj @@ -1,7 +1,7 @@ - $(Reqnroll_Generator_TFM) + netstandard2.0 $(MSBuildThisFileDirectory)Reqnroll.CustomPlugin.nuspec true diff --git a/Installer/Reqnroll.CustomPlugin/Reqnroll.CustomPlugin.nuspec b/Installer/Reqnroll.CustomPlugin/Reqnroll.CustomPlugin.nuspec index 2b424d2c9..d37fd9818 100644 --- a/Installer/Reqnroll.CustomPlugin/Reqnroll.CustomPlugin.nuspec +++ b/Installer/Reqnroll.CustomPlugin/Reqnroll.CustomPlugin.nuspec @@ -17,33 +17,15 @@ $copyright$ - - - - - - - - - - - - - - - - - - - - - - + + + + diff --git a/Plugins/Reqnroll.Autofac.ReqnrollPlugin/Reqnroll.Autofac.ReqnrollPlugin.csproj b/Plugins/Reqnroll.Autofac.ReqnrollPlugin/Reqnroll.Autofac.ReqnrollPlugin.csproj index 486be9ecb..a5fd3ba84 100644 --- a/Plugins/Reqnroll.Autofac.ReqnrollPlugin/Reqnroll.Autofac.ReqnrollPlugin.csproj +++ b/Plugins/Reqnroll.Autofac.ReqnrollPlugin/Reqnroll.Autofac.ReqnrollPlugin.csproj @@ -1,6 +1,6 @@ - $(Reqnroll_Runtime_TFM) + netstandard2.0 $(Reqnroll_KeyFile) $(Reqnroll_EnableStrongNameSigning) $(Reqnroll_PublicSign) diff --git a/Plugins/Reqnroll.Autofac.ReqnrollPlugin/Reqnroll.Autofac.nuspec b/Plugins/Reqnroll.Autofac.ReqnrollPlugin/Reqnroll.Autofac.nuspec index 7b6bd05d4..20aaf3f77 100644 --- a/Plugins/Reqnroll.Autofac.ReqnrollPlugin/Reqnroll.Autofac.nuspec +++ b/Plugins/Reqnroll.Autofac.ReqnrollPlugin/Reqnroll.Autofac.nuspec @@ -16,29 +16,17 @@ LICENSE reqnroll autofac di dependency injection - - - - - - - - - - - - - - + + diff --git a/Plugins/Reqnroll.Autofac.ReqnrollPlugin/build/Reqnroll.Autofac.targets b/Plugins/Reqnroll.Autofac.ReqnrollPlugin/build/Reqnroll.Autofac.targets index c1700e37d..1ff367c1d 100644 --- a/Plugins/Reqnroll.Autofac.ReqnrollPlugin/build/Reqnroll.Autofac.targets +++ b/Plugins/Reqnroll.Autofac.ReqnrollPlugin/build/Reqnroll.Autofac.targets @@ -1,8 +1,7 @@ - <_Reqnroll_AutofacPluginFramework Condition=" '$(TargetFrameworkIdentifier)' == '.NETCoreApp' ">netstandard2.0 - <_Reqnroll_AutofacPluginFramework Condition=" '$(TargetFrameworkIdentifier)' == '.NETFramework' ">net462 + <_Reqnroll_AutofacPluginFramework>netstandard2.0 <_Reqnroll_AutofacPluginPath>$(MSBuildThisFileDirectory)\..\lib\$(_Reqnroll_AutofacPluginFramework)\Reqnroll.Autofac.ReqnrollPlugin.dll diff --git a/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest.csproj b/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest.csproj index 0641b57a0..abf210a9a 100644 --- a/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest.csproj +++ b/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest.csproj @@ -24,21 +24,21 @@ - <_Reqnroll_Needed_MSBuildGenerator Condition=" '$(MSBuildRuntimeType)' == 'Core'">$(Reqnroll_Core_Generator_TFM) - <_Reqnroll_Needed_MSBuildGenerator Condition=" '$(MSBuildRuntimeType)' != 'Core'">$(Reqnroll_FullFramework_Generator_TFM) + <_Reqnroll_Needed_MSBuildGenerator Condition=" '$(MSBuildRuntimeType)' == 'Core'">$(Reqnroll_Core_Tools_TFM) + <_Reqnroll_Needed_MSBuildGenerator Condition=" '$(MSBuildRuntimeType)' != 'Core'">$(Reqnroll_FullFramework_Tools_TFM) - + - + - <_Reqnroll_TaskAssembly>..\..\Reqnroll.Tools.MsBuild.Generation\bin\$(Configuration)\$(_Reqnroll_Needed_MSBuildGenerator)\Reqnroll.Tools.MsBuild.Generation.dll + <_Reqnroll_TaskAssembly>..\..\Reqnroll.Tools.MsBuild.Generation\bin\$(Configuration)\$(_Reqnroll_Needed_MSBuildGenerator)\tasks\Reqnroll.Tools.MsBuild.Generation.dll diff --git a/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin/Reqnroll.ExternalData.ReqnrollPlugin.csproj b/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin/Reqnroll.ExternalData.ReqnrollPlugin.csproj index de7e5ad16..37ba8799a 100644 --- a/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin/Reqnroll.ExternalData.ReqnrollPlugin.csproj +++ b/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin/Reqnroll.ExternalData.ReqnrollPlugin.csproj @@ -1,6 +1,6 @@ - $(Reqnroll_Generator_TFM) + netstandard2.0 $(MSBuildThisFileDirectory)Reqnroll.ExternalData.nuspec $(Reqnroll_KeyFile) $(Reqnroll_EnableStrongNameSigning) diff --git a/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin/Reqnroll.ExternalData.nuspec b/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin/Reqnroll.ExternalData.nuspec index ba9370439..51ee2bd2b 100644 --- a/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin/Reqnroll.ExternalData.nuspec +++ b/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin/Reqnroll.ExternalData.nuspec @@ -21,9 +21,7 @@ - - - + diff --git a/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin/build/Reqnroll.ExternalData.targets b/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin/build/Reqnroll.ExternalData.targets index e2525b081..887d91c64 100644 --- a/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin/build/Reqnroll.ExternalData.targets +++ b/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin/build/Reqnroll.ExternalData.targets @@ -1,9 +1,7 @@ - <_ExternalDataGeneratorPluginFramework Condition=" '$(MSBuildRuntimeType)' == 'Core'">netcoreapp3.1 - <_ExternalDataGeneratorPluginFramework Condition=" '$(MSBuildRuntimeType)' != 'Core'">net462 + <_ExternalDataGeneratorPluginFramework>netstandard2.0 <_ExternalDataGeneratorPluginPath>$(MSBuildThisFileDirectory)$(_ExternalDataGeneratorPluginFramework)\Reqnroll.ExternalData.ReqnrollPlugin.dll - \ No newline at end of file diff --git a/Plugins/Reqnroll.MSTest.Generator.ReqnrollPlugin/Reqnroll.MSTest.Generator.ReqnrollPlugin.csproj b/Plugins/Reqnroll.MSTest.Generator.ReqnrollPlugin/Reqnroll.MSTest.Generator.ReqnrollPlugin.csproj index 8b415f5d0..216d841d0 100644 --- a/Plugins/Reqnroll.MSTest.Generator.ReqnrollPlugin/Reqnroll.MSTest.Generator.ReqnrollPlugin.csproj +++ b/Plugins/Reqnroll.MSTest.Generator.ReqnrollPlugin/Reqnroll.MSTest.Generator.ReqnrollPlugin.csproj @@ -1,6 +1,6 @@ - $(Reqnroll_Generator_TFM) + netstandard2.0 $(MSBuildThisFileDirectory)Reqnroll.MSTest.nuspec $(Reqnroll_KeyFile) $(Reqnroll_EnableStrongNameSigning) diff --git a/Plugins/Reqnroll.MSTest.Generator.ReqnrollPlugin/Reqnroll.MSTest.nuspec b/Plugins/Reqnroll.MSTest.Generator.ReqnrollPlugin/Reqnroll.MSTest.nuspec index 94e7a2a45..accdf6f86 100644 --- a/Plugins/Reqnroll.MSTest.Generator.ReqnrollPlugin/Reqnroll.MSTest.nuspec +++ b/Plugins/Reqnroll.MSTest.Generator.ReqnrollPlugin/Reqnroll.MSTest.nuspec @@ -16,35 +16,20 @@ reqnroll mstest $copyright$ - - - - - - - - - - - - - - + - - - - + + diff --git a/Plugins/Reqnroll.MSTest.Generator.ReqnrollPlugin/build/MSTest.AssemblyHooks.template.cs b/Plugins/Reqnroll.MSTest.Generator.ReqnrollPlugin/build/MSTest.AssemblyHooks.template.cs index c67a9d7e7..93076d137 100644 --- a/Plugins/Reqnroll.MSTest.Generator.ReqnrollPlugin/build/MSTest.AssemblyHooks.template.cs +++ b/Plugins/Reqnroll.MSTest.Generator.ReqnrollPlugin/build/MSTest.AssemblyHooks.template.cs @@ -20,7 +20,15 @@ public static async Task AssemblyInitializeAsync(TestContext testContext) var currentAssembly = typeof(PROJECT_ROOT_NAMESPACE_MSTestAssemblyHooks).Assembly; var containerBuilder = new MsTestContainerBuilder(testContext); - await global::Reqnroll.TestRunnerManager.OnTestRunStartAsync(currentAssembly, containerBuilder: containerBuilder); + try + { + await global::Reqnroll.TestRunnerManager.OnTestRunStartAsync(currentAssembly, containerBuilder: containerBuilder); + } + catch (System.Exception ex) + { + // wrap the exception because MsTest swallows the outer exception + throw new System.AggregateException(ex); + } } [AssemblyCleanup] diff --git a/Plugins/Reqnroll.MSTest.Generator.ReqnrollPlugin/build/Reqnroll.MsTest.targets b/Plugins/Reqnroll.MSTest.Generator.ReqnrollPlugin/build/Reqnroll.MsTest.targets index b0e411f8c..7be4cb065 100644 --- a/Plugins/Reqnroll.MSTest.Generator.ReqnrollPlugin/build/Reqnroll.MsTest.targets +++ b/Plugins/Reqnroll.MSTest.Generator.ReqnrollPlugin/build/Reqnroll.MsTest.targets @@ -17,12 +17,10 @@ - <_Reqnroll_MsTestGeneratorPlugin Condition=" '$(MSBuildRuntimeType)' == 'Core'">netcoreapp3.1 - <_Reqnroll_MsTestGeneratorPlugin Condition=" '$(MSBuildRuntimeType)' != 'Core'">net462 + <_Reqnroll_MsTestGeneratorPlugin>netstandard2.0 <_Reqnroll_MsTestGeneratorPluginPath>$(MSBuildThisFileDirectory)\$(_Reqnroll_MsTestGeneratorPlugin)\Reqnroll.MSTest.Generator.ReqnrollPlugin.dll - <_Reqnroll_MsTestRuntimePlugin Condition=" '$(TargetFrameworkIdentifier)' == '.NETCoreApp' ">netstandard2.0 - <_Reqnroll_MsTestRuntimePlugin Condition=" '$(TargetFrameworkIdentifier)' == '.NETFramework' ">net462 + <_Reqnroll_MsTestRuntimePlugin>netstandard2.0 <_Reqnroll_MsTestRuntimePluginPath>$(MSBuildThisFileDirectory)\..\lib\$(_Reqnroll_MsTestRuntimePlugin)\Reqnroll.MSTest.ReqnrollPlugin.dll $(MSBuildThisFileDirectory)MSTest.AssemblyHooks$(DefaultLanguageSourceExtension) diff --git a/Plugins/Reqnroll.MSTest.ReqnrollPlugin/Reqnroll.MSTest.ReqnrollPlugin.csproj b/Plugins/Reqnroll.MSTest.ReqnrollPlugin/Reqnroll.MSTest.ReqnrollPlugin.csproj index 1de872a2c..928684ecc 100644 --- a/Plugins/Reqnroll.MSTest.ReqnrollPlugin/Reqnroll.MSTest.ReqnrollPlugin.csproj +++ b/Plugins/Reqnroll.MSTest.ReqnrollPlugin/Reqnroll.MSTest.ReqnrollPlugin.csproj @@ -1,6 +1,6 @@ - $(Reqnroll_Runtime_TFM) + netstandard2.0 $(Reqnroll_KeyFile) $(Reqnroll_EnableStrongNameSigning) $(Reqnroll_PublicSign) @@ -15,7 +15,6 @@ all runtime; build; native; contentfiles; analyzers - diff --git a/Plugins/Reqnroll.NUnit.Generator.ReqnrollPlugin/Reqnroll.NUnit.Generator.ReqnrollPlugin.csproj b/Plugins/Reqnroll.NUnit.Generator.ReqnrollPlugin/Reqnroll.NUnit.Generator.ReqnrollPlugin.csproj index 5cb39fec0..243b1dec7 100644 --- a/Plugins/Reqnroll.NUnit.Generator.ReqnrollPlugin/Reqnroll.NUnit.Generator.ReqnrollPlugin.csproj +++ b/Plugins/Reqnroll.NUnit.Generator.ReqnrollPlugin/Reqnroll.NUnit.Generator.ReqnrollPlugin.csproj @@ -1,6 +1,6 @@ - $(Reqnroll_Generator_TFM) + netstandard2.0 $(MSBuildThisFileDirectory)Reqnroll.NUnit.nuspec $(Reqnroll_KeyFile) $(Reqnroll_EnableStrongNameSigning) diff --git a/Plugins/Reqnroll.NUnit.Generator.ReqnrollPlugin/Reqnroll.NUnit.nuspec b/Plugins/Reqnroll.NUnit.Generator.ReqnrollPlugin/Reqnroll.NUnit.nuspec index 5618ab73b..dc12ee964 100644 --- a/Plugins/Reqnroll.NUnit.Generator.ReqnrollPlugin/Reqnroll.NUnit.nuspec +++ b/Plugins/Reqnroll.NUnit.Generator.ReqnrollPlugin/Reqnroll.NUnit.nuspec @@ -16,35 +16,20 @@ reqnroll nunit $copyright$ - - - - - - - - - - - - - - + - - - - + + diff --git a/Plugins/Reqnroll.NUnit.Generator.ReqnrollPlugin/build/Reqnroll.NUnit.targets b/Plugins/Reqnroll.NUnit.Generator.ReqnrollPlugin/build/Reqnroll.NUnit.targets index c9273cf1d..30c4f4456 100644 --- a/Plugins/Reqnroll.NUnit.Generator.ReqnrollPlugin/build/Reqnroll.NUnit.targets +++ b/Plugins/Reqnroll.NUnit.Generator.ReqnrollPlugin/build/Reqnroll.NUnit.targets @@ -17,12 +17,10 @@ - <_Reqnroll_NUnitGeneratorPlugin Condition=" '$(MSBuildRuntimeType)' == 'Core'">netcoreapp3.1 - <_Reqnroll_NUnitGeneratorPlugin Condition=" '$(MSBuildRuntimeType)' != 'Core'">net462 + <_Reqnroll_NUnitGeneratorPlugin>netstandard2.0 <_Reqnroll_NUnitGeneratorPluginPath>$(MSBuildThisFileDirectory)\$(_Reqnroll_NUnitGeneratorPlugin)\Reqnroll.NUnit.Generator.ReqnrollPlugin.dll - <_Reqnroll_NUnitRuntimePlugin Condition=" '$(TargetFrameworkIdentifier)' == '.NETCoreApp' ">netstandard2.0 - <_Reqnroll_NUnitRuntimePlugin Condition=" '$(TargetFrameworkIdentifier)' == '.NETFramework' ">net462 + <_Reqnroll_NUnitRuntimePlugin>netstandard2.0 <_Reqnroll_NUnitRuntimePluginPath>$(MSBuildThisFileDirectory)\..\lib\$(_Reqnroll_NUnitRuntimePlugin)\Reqnroll.NUnit.ReqnrollPlugin.dll $(MSBuildThisFileDirectory)NUnit.AssemblyHooks$(DefaultLanguageSourceExtension) diff --git a/Plugins/Reqnroll.NUnit.ReqnrollPlugin/Reqnroll.NUnit.ReqnrollPlugin.csproj b/Plugins/Reqnroll.NUnit.ReqnrollPlugin/Reqnroll.NUnit.ReqnrollPlugin.csproj index 6842cfb26..9eb93ad40 100644 --- a/Plugins/Reqnroll.NUnit.ReqnrollPlugin/Reqnroll.NUnit.ReqnrollPlugin.csproj +++ b/Plugins/Reqnroll.NUnit.ReqnrollPlugin/Reqnroll.NUnit.ReqnrollPlugin.csproj @@ -1,9 +1,6 @@ - $(Reqnroll_Runtime_TFM) - $(Reqnroll_KeyFile) - $(Reqnroll_EnableStrongNameSigning) - $(Reqnroll_PublicSign) + netstandard2.0 $(Reqnroll_KeyFile) $(Reqnroll_EnableStrongNameSigning) $(Reqnroll_PublicSign) @@ -18,7 +15,6 @@ all runtime; build; native; contentfiles; analyzers - diff --git a/Plugins/Reqnroll.SpecFlowCompatibility.Generator.ReqnrollPlugin/GeneratorPlugin.cs b/Plugins/Reqnroll.SpecFlowCompatibility.Generator.ReqnrollPlugin/GeneratorPlugin.cs index 4887e2234..b724beca8 100644 --- a/Plugins/Reqnroll.SpecFlowCompatibility.Generator.ReqnrollPlugin/GeneratorPlugin.cs +++ b/Plugins/Reqnroll.SpecFlowCompatibility.Generator.ReqnrollPlugin/GeneratorPlugin.cs @@ -13,7 +13,6 @@ public class GeneratorPlugin : IGeneratorPlugin { public void Initialize(GeneratorPluginEvents generatorPluginEvents, GeneratorPluginParameters generatorPluginParameters, UnitTestProviderConfiguration unitTestProviderConfiguration) { -#if NETFRAMEWORK generatorPluginEvents.ConfigurationDefaults += (_, args) => { var configuration = args.ReqnrollProjectConfiguration.ReqnrollConfiguration; @@ -24,6 +23,5 @@ public void Initialize(GeneratorPluginEvents generatorPluginEvents, GeneratorPlu loader.UpdateFromAppConfig(configuration, configSection); } }; -#endif } } \ No newline at end of file diff --git a/Plugins/Reqnroll.SpecFlowCompatibility.Generator.ReqnrollPlugin/Reqnroll.SpecFlowCompatibility.Generator.ReqnrollPlugin.csproj b/Plugins/Reqnroll.SpecFlowCompatibility.Generator.ReqnrollPlugin/Reqnroll.SpecFlowCompatibility.Generator.ReqnrollPlugin.csproj index 440c2abbd..3d871baa8 100644 --- a/Plugins/Reqnroll.SpecFlowCompatibility.Generator.ReqnrollPlugin/Reqnroll.SpecFlowCompatibility.Generator.ReqnrollPlugin.csproj +++ b/Plugins/Reqnroll.SpecFlowCompatibility.Generator.ReqnrollPlugin/Reqnroll.SpecFlowCompatibility.Generator.ReqnrollPlugin.csproj @@ -1,6 +1,6 @@  - $(Reqnroll_Generator_TFM) + netstandard2.0;net462 $(MSBuildThisFileDirectory)Reqnroll.SpecFlowCompatibility.nuspec $(Reqnroll_KeyFile) $(Reqnroll_EnableStrongNameSigning) @@ -17,22 +17,12 @@ all runtime; build; native; contentfiles; analyzers - - - false - - - - - - - AppConfig\%(RecursiveDir)%(Filename)%(Extension) - + diff --git a/Plugins/Reqnroll.SpecFlowCompatibility.Generator.ReqnrollPlugin/Reqnroll.SpecFlowCompatibility.nuspec b/Plugins/Reqnroll.SpecFlowCompatibility.Generator.ReqnrollPlugin/Reqnroll.SpecFlowCompatibility.nuspec index d6b91b9bc..ee051fbd3 100644 --- a/Plugins/Reqnroll.SpecFlowCompatibility.Generator.ReqnrollPlugin/Reqnroll.SpecFlowCompatibility.nuspec +++ b/Plugins/Reqnroll.SpecFlowCompatibility.Generator.ReqnrollPlugin/Reqnroll.SpecFlowCompatibility.nuspec @@ -22,23 +22,17 @@ - - - - - - - + + + - - - - + + diff --git a/Plugins/Reqnroll.SpecFlowCompatibility.Generator.ReqnrollPlugin/build/Reqnroll.SpecFlowCompatibility.targets b/Plugins/Reqnroll.SpecFlowCompatibility.Generator.ReqnrollPlugin/build/Reqnroll.SpecFlowCompatibility.targets index 76b58aad8..77cfcf43e 100644 --- a/Plugins/Reqnroll.SpecFlowCompatibility.Generator.ReqnrollPlugin/build/Reqnroll.SpecFlowCompatibility.targets +++ b/Plugins/Reqnroll.SpecFlowCompatibility.Generator.ReqnrollPlugin/build/Reqnroll.SpecFlowCompatibility.targets @@ -2,7 +2,7 @@ - <_Reqnroll_SpecFlowCompatibilityGeneratorPlugin Condition=" '$(MSBuildRuntimeType)' == 'Core'">netcoreapp3.1 + <_Reqnroll_SpecFlowCompatibilityGeneratorPlugin Condition=" '$(MSBuildRuntimeType)' == 'Core'">netstandard2.0 <_Reqnroll_SpecFlowCompatibilityGeneratorPlugin Condition=" '$(MSBuildRuntimeType)' != 'Core'">net462 <_Reqnroll_SpecFlowCompatibilityGeneratorPluginPath>$(MSBuildThisFileDirectory)\$(_Reqnroll_SpecFlowCompatibilityGeneratorPlugin)\Reqnroll.SpecFlowCompatibility.Generator.ReqnrollPlugin.dll diff --git a/Plugins/Reqnroll.SpecFlowCompatibility.ReqnrollPlugin/Reqnroll.SpecFlowCompatibility.ReqnrollPlugin.csproj b/Plugins/Reqnroll.SpecFlowCompatibility.ReqnrollPlugin/Reqnroll.SpecFlowCompatibility.ReqnrollPlugin.csproj index 45e89d292..f36633d45 100644 --- a/Plugins/Reqnroll.SpecFlowCompatibility.ReqnrollPlugin/Reqnroll.SpecFlowCompatibility.ReqnrollPlugin.csproj +++ b/Plugins/Reqnroll.SpecFlowCompatibility.ReqnrollPlugin/Reqnroll.SpecFlowCompatibility.ReqnrollPlugin.csproj @@ -1,6 +1,6 @@  - $(Reqnroll_Runtime_TFM) + netstandard2.0;net462 $(Reqnroll_KeyFile) $(Reqnroll_EnableStrongNameSigning) $(Reqnroll_PublicSign) @@ -18,15 +18,11 @@ - - - - - + - - + + \ No newline at end of file diff --git a/Plugins/Reqnroll.Verify/Reqnroll.Verify.ReqnrollPlugin.IntegrationTest/Reqnroll.Verify.ReqnrollPlugin.IntegrationTest.csproj b/Plugins/Reqnroll.Verify/Reqnroll.Verify.ReqnrollPlugin.IntegrationTest/Reqnroll.Verify.ReqnrollPlugin.IntegrationTest.csproj index c035f9d91..fb0f900c3 100644 --- a/Plugins/Reqnroll.Verify/Reqnroll.Verify.ReqnrollPlugin.IntegrationTest/Reqnroll.Verify.ReqnrollPlugin.IntegrationTest.csproj +++ b/Plugins/Reqnroll.Verify/Reqnroll.Verify.ReqnrollPlugin.IntegrationTest/Reqnroll.Verify.ReqnrollPlugin.IntegrationTest.csproj @@ -1,4 +1,4 @@ - + @@ -30,19 +30,22 @@ - <_Reqnroll_Needed_MSBuildGenerator Condition=" '$(MSBuildRuntimeType)' == 'Core'">$(Reqnroll_Core_Generator_TFM) - <_Reqnroll_Needed_MSBuildGenerator Condition=" '$(MSBuildRuntimeType)' != 'Core'">$(Reqnroll_FullFramework_Generator_TFM) + <_Reqnroll_Needed_MSBuildGenerator Condition=" '$(MSBuildRuntimeType)' == 'Core'">$(Reqnroll_Core_Tools_TFM) + <_Reqnroll_Needed_MSBuildGenerator Condition=" '$(MSBuildRuntimeType)' != 'Core'">$(Reqnroll_FullFramework_Tools_TFM) + + <_VerifyGeneratorPluginFramework Condition=" '$(MSBuildRuntimeType)' == 'Core'">netstandard2.0 + <_VerifyGeneratorPluginFramework Condition=" '$(MSBuildRuntimeType)' != 'Core'">net462 - - + + - - + + @@ -50,7 +53,7 @@ - <_Reqnroll_TaskAssembly>..\..\Reqnroll.Tools.MsBuild.Generation\bin\$(Configuration)\$(_Reqnroll_Needed_MSBuildGenerator)\Reqnroll.Tools.MsBuild.Generation.dll + <_Reqnroll_TaskAssembly>..\..\Reqnroll.Tools.MsBuild.Generation\bin\$(Configuration)\$(_Reqnroll_Needed_MSBuildGenerator)\tasks\Reqnroll.Tools.MsBuild.Generation.dll diff --git a/Plugins/Reqnroll.Verify/Reqnroll.Verify.ReqnrollPlugin/Reqnroll.Verify.ReqnrollPlugin.csproj b/Plugins/Reqnroll.Verify/Reqnroll.Verify.ReqnrollPlugin/Reqnroll.Verify.ReqnrollPlugin.csproj index 58723e760..a6aec9b9e 100644 --- a/Plugins/Reqnroll.Verify/Reqnroll.Verify.ReqnrollPlugin/Reqnroll.Verify.ReqnrollPlugin.csproj +++ b/Plugins/Reqnroll.Verify/Reqnroll.Verify.ReqnrollPlugin/Reqnroll.Verify.ReqnrollPlugin.csproj @@ -1,6 +1,6 @@ - + - $(Reqnroll_Generator_TFM) + net462;netstandard2.0 $(MSBuildThisFileDirectory)Reqnroll.Verify.nuspec $(Reqnroll_KeyFile) $(Reqnroll_EnableStrongNameSigning) diff --git a/Plugins/Reqnroll.Verify/Reqnroll.Verify.ReqnrollPlugin/Reqnroll.Verify.nuspec b/Plugins/Reqnroll.Verify/Reqnroll.Verify.ReqnrollPlugin/Reqnroll.Verify.nuspec index bf72de566..fb893ba03 100644 --- a/Plugins/Reqnroll.Verify/Reqnroll.Verify.ReqnrollPlugin/Reqnroll.Verify.nuspec +++ b/Plugins/Reqnroll.Verify/Reqnroll.Verify.ReqnrollPlugin/Reqnroll.Verify.nuspec @@ -20,11 +20,7 @@ - - - - - + @@ -35,11 +31,10 @@ - + - - + diff --git a/Plugins/Reqnroll.Verify/Reqnroll.Verify.ReqnrollPlugin/build/Reqnroll.Verify.targets b/Plugins/Reqnroll.Verify/Reqnroll.Verify.ReqnrollPlugin/build/Reqnroll.Verify.targets index dfa8e8105..1b5945aaf 100644 --- a/Plugins/Reqnroll.Verify/Reqnroll.Verify.ReqnrollPlugin/build/Reqnroll.Verify.targets +++ b/Plugins/Reqnroll.Verify/Reqnroll.Verify.ReqnrollPlugin/build/Reqnroll.Verify.targets @@ -1,6 +1,6 @@ - <_VerifyGeneratorPluginFramework Condition=" '$(MSBuildRuntimeType)' == 'Core'">netcoreapp3.1 + <_VerifyGeneratorPluginFramework Condition=" '$(MSBuildRuntimeType)' == 'Core'">netstandard2.0 <_VerifyGeneratorPluginFramework Condition=" '$(MSBuildRuntimeType)' != 'Core'">net462 <_VerifyGeneratorPluginPath>$(MSBuildThisFileDirectory)$(_VerifyGeneratorPluginFramework)\Reqnroll.Verify.ReqnrollPlugin.dll diff --git a/Plugins/Reqnroll.Windsor.ReqnrollPlugin/Reqnroll.Windsor.ReqnrollPlugin.csproj b/Plugins/Reqnroll.Windsor.ReqnrollPlugin/Reqnroll.Windsor.ReqnrollPlugin.csproj index 9ce8dfaf1..b9e35ec99 100644 --- a/Plugins/Reqnroll.Windsor.ReqnrollPlugin/Reqnroll.Windsor.ReqnrollPlugin.csproj +++ b/Plugins/Reqnroll.Windsor.ReqnrollPlugin/Reqnroll.Windsor.ReqnrollPlugin.csproj @@ -1,6 +1,6 @@ - $(Reqnroll_Runtime_TFM) + netstandard2.0 $(Reqnroll_KeyFile) $(Reqnroll_EnableStrongNameSigning) $(Reqnroll_PublicSign) diff --git a/Plugins/Reqnroll.Windsor.ReqnrollPlugin/Reqnroll.Windsor.nuspec b/Plugins/Reqnroll.Windsor.ReqnrollPlugin/Reqnroll.Windsor.nuspec index feb4de67a..bc9501d40 100644 --- a/Plugins/Reqnroll.Windsor.ReqnrollPlugin/Reqnroll.Windsor.nuspec +++ b/Plugins/Reqnroll.Windsor.ReqnrollPlugin/Reqnroll.Windsor.nuspec @@ -16,28 +16,17 @@ LICENSE reqnroll castle windsor di dependency injection - - - - - - - - - - - - - + + diff --git a/Plugins/Reqnroll.Windsor.ReqnrollPlugin/build/Reqnroll.Windsor.targets b/Plugins/Reqnroll.Windsor.ReqnrollPlugin/build/Reqnroll.Windsor.targets index 47de52b31..3f351247c 100644 --- a/Plugins/Reqnroll.Windsor.ReqnrollPlugin/build/Reqnroll.Windsor.targets +++ b/Plugins/Reqnroll.Windsor.ReqnrollPlugin/build/Reqnroll.Windsor.targets @@ -1,8 +1,6 @@ - - <_Reqnroll_WindsorPluginFramework Condition=" '$(TargetFrameworkIdentifier)' == '.NETCoreApp' ">netstandard2.0 - <_Reqnroll_WindsorPluginFramework Condition=" '$(TargetFrameworkIdentifier)' == '.NETFramework' ">net462 + <_Reqnroll_WindsorPluginFramework>netstandard2.0 <_Reqnroll_WindsorPluginPath>$(MSBuildThisFileDirectory)\..\lib\$(_Reqnroll_WindsorPluginFramework)\Reqnroll.Windsor.ReqnrollPlugin.dll diff --git a/Plugins/Reqnroll.xUnit.Generator.ReqnrollPlugin/Reqnroll.xUnit.Generator.ReqnrollPlugin.csproj b/Plugins/Reqnroll.xUnit.Generator.ReqnrollPlugin/Reqnroll.xUnit.Generator.ReqnrollPlugin.csproj index 66e6500e1..8a0e69ddb 100644 --- a/Plugins/Reqnroll.xUnit.Generator.ReqnrollPlugin/Reqnroll.xUnit.Generator.ReqnrollPlugin.csproj +++ b/Plugins/Reqnroll.xUnit.Generator.ReqnrollPlugin/Reqnroll.xUnit.Generator.ReqnrollPlugin.csproj @@ -1,6 +1,6 @@ - $(Reqnroll_Generator_TFM) + netstandard2.0 $(MSBuildThisFileDirectory)Reqnroll.xUnit.nuspec $(Reqnroll_KeyFile) $(Reqnroll_EnableStrongNameSigning) diff --git a/Plugins/Reqnroll.xUnit.Generator.ReqnrollPlugin/Reqnroll.xUnit.nuspec b/Plugins/Reqnroll.xUnit.Generator.ReqnrollPlugin/Reqnroll.xUnit.nuspec index 421d0d21e..1aa3e7975 100644 --- a/Plugins/Reqnroll.xUnit.Generator.ReqnrollPlugin/Reqnroll.xUnit.nuspec +++ b/Plugins/Reqnroll.xUnit.Generator.ReqnrollPlugin/Reqnroll.xUnit.nuspec @@ -16,39 +16,21 @@ reqnroll xUnit $copyright$ - - - - - - - - - - - - - - - - - + - - - - + + diff --git a/Plugins/Reqnroll.xUnit.Generator.ReqnrollPlugin/build/Reqnroll.xUnit.targets b/Plugins/Reqnroll.xUnit.Generator.ReqnrollPlugin/build/Reqnroll.xUnit.targets index 0a47457ae..60ad81ef6 100644 --- a/Plugins/Reqnroll.xUnit.Generator.ReqnrollPlugin/build/Reqnroll.xUnit.targets +++ b/Plugins/Reqnroll.xUnit.Generator.ReqnrollPlugin/build/Reqnroll.xUnit.targets @@ -16,12 +16,10 @@ - <_Reqnroll_xUnitGeneratorPlugin Condition=" '$(MSBuildRuntimeType)' == 'Core'" >netcoreapp3.1 - <_Reqnroll_xUnitGeneratorPlugin Condition=" '$(MSBuildRuntimeType)' != 'Core'" >net462 + <_Reqnroll_xUnitGeneratorPlugin>netstandard2.0 <_Reqnroll_xUnitGeneratorPluginPath>$(MSBuildThisFileDirectory)\$(_Reqnroll_xUnitGeneratorPlugin)\Reqnroll.xUnit.Generator.ReqnrollPlugin.dll - <_Reqnroll_xUnitRuntimePlugin Condition=" '$(TargetFrameworkIdentifier)' == '.NETCoreApp' ">netstandard2.0 - <_Reqnroll_xUnitRuntimePlugin Condition=" '$(TargetFrameworkIdentifier)' == '.NETFramework' ">net462 + <_Reqnroll_xUnitRuntimePlugin>netstandard2.0 <_Reqnroll_xUnitRuntimePluginPath>$(MSBuildThisFileDirectory)\..\lib\$(_Reqnroll_xUnitRuntimePlugin)\Reqnroll.xUnit.ReqnrollPlugin.dll $(MSBuildThisFileDirectory)xUnit.AssemblyHooks$(DefaultLanguageSourceExtension) diff --git a/Plugins/Reqnroll.xUnit.ReqnrollPlugin/Reqnroll.xUnit.ReqnrollPlugin.csproj b/Plugins/Reqnroll.xUnit.ReqnrollPlugin/Reqnroll.xUnit.ReqnrollPlugin.csproj index 0056c313d..386177533 100644 --- a/Plugins/Reqnroll.xUnit.ReqnrollPlugin/Reqnroll.xUnit.ReqnrollPlugin.csproj +++ b/Plugins/Reqnroll.xUnit.ReqnrollPlugin/Reqnroll.xUnit.ReqnrollPlugin.csproj @@ -1,6 +1,6 @@ - $(Reqnroll_Runtime_TFM) + netstandard2.0 $(Reqnroll_KeyFile) $(Reqnroll_EnableStrongNameSigning) $(Reqnroll_PublicSign) @@ -15,7 +15,9 @@ all runtime; build; native; contentfiles; analyzers + + diff --git a/Reqnroll.Generator/DefaultDependencyProvider.cs b/Reqnroll.Generator/DefaultDependencyProvider.cs index a3bde7d1c..cf9a95e90 100644 --- a/Reqnroll.Generator/DefaultDependencyProvider.cs +++ b/Reqnroll.Generator/DefaultDependencyProvider.cs @@ -6,6 +6,8 @@ using Reqnroll.Generator.Plugins; using Reqnroll.Generator.UnitTestConverter; using Reqnroll.Parser; +using Reqnroll.PlatformCompatibility; +using Reqnroll.Plugins; using Reqnroll.Tracing; using Reqnroll.Utils; @@ -25,6 +27,7 @@ public virtual void RegisterDefaults(ObjectContainer container) container.RegisterTypeAs(); container.RegisterTypeAs(); + PlatformHelper.RegisterPluginAssemblyLoader(container); container.RegisterTypeAs(); container.RegisterTypeAs(); diff --git a/Reqnroll.Generator/Plugins/GeneratorPluginLoader.cs b/Reqnroll.Generator/Plugins/GeneratorPluginLoader.cs index d3018e390..3d2e42994 100644 --- a/Reqnroll.Generator/Plugins/GeneratorPluginLoader.cs +++ b/Reqnroll.Generator/Plugins/GeneratorPluginLoader.cs @@ -1,28 +1,23 @@ using System; -using System.IO; using System.Reflection; using Reqnroll.Infrastructure; +using Reqnroll.PlatformCompatibility; using Reqnroll.Plugins; namespace Reqnroll.Generator.Plugins { - public class GeneratorPluginLoader : IGeneratorPluginLoader + public class GeneratorPluginLoader(IPluginAssemblyLoader _pluginAssemblyLoader) : IGeneratorPluginLoader { public IGeneratorPlugin LoadPlugin(PluginDescriptor pluginDescriptor) { Assembly pluginAssembly; try { - -#if NETCOREAPP - pluginAssembly = PluginAssemblyResolver.Load(pluginDescriptor.Path); -#else - pluginAssembly = Assembly.LoadFrom(pluginDescriptor.Path); -#endif + pluginAssembly = _pluginAssemblyLoader.LoadAssembly(pluginDescriptor.Path); } catch(Exception ex) { - throw new ReqnrollException($"Unable to load plugin assembly: {pluginDescriptor.Path}. Please check https://go.reqnroll.net/doc-plugins for details.", ex); + throw new ReqnrollException($"Unable to load plugin assembly: {pluginDescriptor.Path}. Please check https://go.reqnroll.net/doc-plugins for details. (Framework: {PlatformInformation.DotNetFrameworkDescription})", ex); } var pluginAttribute = (GeneratorPluginAttribute)Attribute.GetCustomAttribute(pluginAssembly, typeof(GeneratorPluginAttribute)); diff --git a/Reqnroll.Generator/Reqnroll.Generator.csproj b/Reqnroll.Generator/Reqnroll.Generator.csproj index 6b17cde00..4938fbb2d 100644 --- a/Reqnroll.Generator/Reqnroll.Generator.csproj +++ b/Reqnroll.Generator/Reqnroll.Generator.csproj @@ -1,6 +1,6 @@ - + - $(Reqnroll_Generator_TFM) + netstandard2.0 Reqnroll.Generator $(Reqnroll_KeyFile) $(Reqnroll_EnableStrongNameSigning) @@ -20,8 +20,6 @@ - - @@ -30,30 +28,4 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator.Cli/Reqnroll.TestProjectGenerator.Cli.csproj b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator.Cli/Reqnroll.TestProjectGenerator.Cli.csproj index 280b93c8a..eb2310446 100644 --- a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator.Cli/Reqnroll.TestProjectGenerator.Cli.csproj +++ b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator.Cli/Reqnroll.TestProjectGenerator.Cli.csproj @@ -2,7 +2,7 @@ Exe - net6.0 + net8.0 true reqnroll-tpg diff --git a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator.Tests/Reqnroll.TestProjectGenerator.Tests.csproj b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator.Tests/Reqnroll.TestProjectGenerator.Tests.csproj index af180213a..a20db0e0b 100644 --- a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator.Tests/Reqnroll.TestProjectGenerator.Tests.csproj +++ b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator.Tests/Reqnroll.TestProjectGenerator.Tests.csproj @@ -1,6 +1,6 @@ - net6.0 + net8.0 false diff --git a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator.csproj b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator.csproj index 9b6e8fcaf..66a584098 100644 --- a/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator.csproj +++ b/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator/Reqnroll.TestProjectGenerator.csproj @@ -21,13 +21,6 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - diff --git a/Reqnroll.Tools.MsBuild.Generation/Reqnroll.Tools.MsBuild.Generation.csproj b/Reqnroll.Tools.MsBuild.Generation/Reqnroll.Tools.MsBuild.Generation.csproj index 1c06a1bf3..fab42b65a 100644 --- a/Reqnroll.Tools.MsBuild.Generation/Reqnroll.Tools.MsBuild.Generation.csproj +++ b/Reqnroll.Tools.MsBuild.Generation/Reqnroll.Tools.MsBuild.Generation.csproj @@ -1,6 +1,6 @@ - + - $(Reqnroll_Tools_TFM) + $(Reqnroll_FullFramework_Tools_TFM);$(Reqnroll_Core_Tools_TFM) $(Reqnroll_KeyFile) $(Reqnroll_EnableStrongNameSigning) $(Reqnroll_PublicSign) @@ -56,19 +56,6 @@ - - - - - - - - - - - - - Microsoft.Build @@ -91,4 +78,19 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/Reqnroll.Tools.MsBuild.Generation/Reqnroll.Tools.MsBuild.Generation.nuspec b/Reqnroll.Tools.MsBuild.Generation/Reqnroll.Tools.MsBuild.Generation.nuspec index 562aabac5..b16bb720d 100644 --- a/Reqnroll.Tools.MsBuild.Generation/Reqnroll.Tools.MsBuild.Generation.nuspec +++ b/Reqnroll.Tools.MsBuild.Generation/Reqnroll.Tools.MsBuild.Generation.nuspec @@ -22,13 +22,13 @@ - - - - - - + + + + diff --git a/Reqnroll.Tools.MsBuild.Generation/build/Reqnroll.Tools.MsBuild.Generation.props b/Reqnroll.Tools.MsBuild.Generation/build/Reqnroll.Tools.MsBuild.Generation.props index b8b91dca9..7569d70ad 100644 --- a/Reqnroll.Tools.MsBuild.Generation/build/Reqnroll.Tools.MsBuild.Generation.props +++ b/Reqnroll.Tools.MsBuild.Generation/build/Reqnroll.Tools.MsBuild.Generation.props @@ -67,7 +67,7 @@ - <_Reqnroll_TaskFolder Condition=" '$(MSBuildRuntimeType)' == 'Core' And '$(_Reqnroll_TaskFolder)' == ''">netcoreapp3.1 + <_Reqnroll_TaskFolder Condition=" '$(MSBuildRuntimeType)' == 'Core' And '$(_Reqnroll_TaskFolder)' == ''">netstandard2.0 <_Reqnroll_TaskFolder Condition=" '$(MSBuildRuntimeType)' != 'Core' And '$(_Reqnroll_TaskFolder)' == ''">net462 <_Reqnroll_TaskAssembly Condition=" '$(_Reqnroll_TaskAssembly)' == '' ">..\tasks\$(_Reqnroll_TaskFolder)\Reqnroll.Tools.MsBuild.Generation.dll diff --git a/Reqnroll.sln.DotSettings b/Reqnroll.sln.DotSettings index 00558d182..00beadc2b 100644 --- a/Reqnroll.sln.DotSettings +++ b/Reqnroll.sln.DotSettings @@ -101,4 +101,5 @@ True True True - True \ No newline at end of file + True + True \ No newline at end of file diff --git a/Reqnroll/AssemblyAttributes.cs b/Reqnroll/AssemblyAttributes.cs index 44461642d..b14eb35dd 100644 --- a/Reqnroll/AssemblyAttributes.cs +++ b/Reqnroll/AssemblyAttributes.cs @@ -1,14 +1,5 @@ - using System.Runtime.CompilerServices; -//[assembly: AllowPartiallyTrustedCallers] - -#if REQNROLL_ENABLE_STRONG_NAME_SIGNING [assembly: InternalsVisibleTo("Reqnroll.RuntimeTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010059bb085601a8b65a8a7b00f34e6d85df230f1e4913d3c0eaadcd13c1fd9cd15c3f01567c49d5f41f617dbda6f544ea3e2d5b5a042f307a7c8d21a079254509ba543efaefce96fac977abd0a76206b721abc33f84ee45ae5383cf50eeb8e234595656fd4af916e1dcde644ce20bb9a68bd72bc7957b759560524c63ca294585ca")] [assembly: InternalsVisibleTo("Reqnroll.PluginTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010059bb085601a8b65a8a7b00f34e6d85df230f1e4913d3c0eaadcd13c1fd9cd15c3f01567c49d5f41f617dbda6f544ea3e2d5b5a042f307a7c8d21a079254509ba543efaefce96fac977abd0a76206b721abc33f84ee45ae5383cf50eeb8e234595656fd4af916e1dcde644ce20bb9a68bd72bc7957b759560524c63ca294585ca")] [assembly: InternalsVisibleTo("Reqnroll.SpecFlowCompatibility.ReqnrollPlugin, PublicKey=002400000480000094000000060200000024000052534131000400000100010059bb085601a8b65a8a7b00f34e6d85df230f1e4913d3c0eaadcd13c1fd9cd15c3f01567c49d5f41f617dbda6f544ea3e2d5b5a042f307a7c8d21a079254509ba543efaefce96fac977abd0a76206b721abc33f84ee45ae5383cf50eeb8e234595656fd4af916e1dcde644ce20bb9a68bd72bc7957b759560524c63ca294585ca")] -#else -[assembly: InternalsVisibleTo("Reqnroll.RuntimeTests")] -[assembly: InternalsVisibleTo("Reqnroll.PluginTests")] -[assembly: InternalsVisibleTo("Reqnroll.SpecFlowCompatibility.ReqnrollPlugin")] -#endif diff --git a/Reqnroll/Bindings/BindingInvoker.cs b/Reqnroll/Bindings/BindingInvoker.cs index 0fa34aba6..82273cabc 100644 --- a/Reqnroll/Bindings/BindingInvoker.cs +++ b/Reqnroll/Bindings/BindingInvoker.cs @@ -9,7 +9,6 @@ using System.Threading; using System.Threading.Tasks; using Reqnroll.Bindings.Reflection; -using Reqnroll.Compatibility; using Reqnroll.Configuration; using Reqnroll.ErrorHandling; using Reqnroll.Infrastructure; @@ -168,11 +167,7 @@ protected virtual Delegate CreateMethodDelegate(MethodInfo method) parameters.ToArray()); } -#if WINDOWS_PHONE - return ExpressionCompiler.ExpressionCompiler.Compile(lambda); -#else return lambda.Compile(); -#endif } protected virtual Expression GetBindingMethodCallExpression(Expression instance, MethodInfo method, Expression[] argumentsExpressions) diff --git a/Reqnroll/Bindings/Discovery/BindingSourceProcessor.cs b/Reqnroll/Bindings/Discovery/BindingSourceProcessor.cs index d6c88d9ac..147acb129 100644 --- a/Reqnroll/Bindings/Discovery/BindingSourceProcessor.cs +++ b/Reqnroll/Bindings/Discovery/BindingSourceProcessor.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Linq; using Reqnroll.Bindings.Reflection; -using Reqnroll.Compatibility; +using Reqnroll.PlatformCompatibility; namespace Reqnroll.Bindings.Discovery { diff --git a/Reqnroll/Bindings/Discovery/RuntimeBindingRegistryBuilder.cs b/Reqnroll/Bindings/Discovery/RuntimeBindingRegistryBuilder.cs index 63f239e0c..a1f475816 100644 --- a/Reqnroll/Bindings/Discovery/RuntimeBindingRegistryBuilder.cs +++ b/Reqnroll/Bindings/Discovery/RuntimeBindingRegistryBuilder.cs @@ -4,9 +4,9 @@ using System.Reflection; using Reqnroll.BoDi; using Reqnroll.Bindings.Reflection; -using Reqnroll.Compatibility; using Reqnroll.Configuration; using Reqnroll.Infrastructure; +using Reqnroll.PlatformCompatibility; namespace Reqnroll.Bindings.Discovery { diff --git a/Reqnroll/Bindings/StepContext.cs b/Reqnroll/Bindings/StepContext.cs index 7418becd0..c18425f12 100644 --- a/Reqnroll/Bindings/StepContext.cs +++ b/Reqnroll/Bindings/StepContext.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; using System.Globalization; -using Reqnroll.Compatibility; using Reqnroll.Configuration; +using Reqnroll.PlatformCompatibility; namespace Reqnroll.Bindings { diff --git a/Reqnroll/Configuration/ConfigurationLoader.cs b/Reqnroll/Configuration/ConfigurationLoader.cs index 1bd1f9dd4..469018523 100644 --- a/Reqnroll/Configuration/ConfigurationLoader.cs +++ b/Reqnroll/Configuration/ConfigurationLoader.cs @@ -4,8 +4,8 @@ using System.IO; using Reqnroll.BoDi; using Reqnroll.BindingSkeletons; -using Reqnroll.Compatibility; using Reqnroll.Configuration.JsonConfig; +using Reqnroll.PlatformCompatibility; using Reqnroll.Tracing; namespace Reqnroll.Configuration diff --git a/Reqnroll/Infrastructure/DefaultDependencyProvider.cs b/Reqnroll/Infrastructure/DefaultDependencyProvider.cs index 54b48f8bc..fc05fa24a 100644 --- a/Reqnroll/Infrastructure/DefaultDependencyProvider.cs +++ b/Reqnroll/Infrastructure/DefaultDependencyProvider.cs @@ -15,6 +15,7 @@ using Reqnroll.TestFramework; using Reqnroll.Time; using Reqnroll.Tracing; +using Reqnroll.PlatformCompatibility; namespace Reqnroll.Infrastructure { @@ -56,6 +57,7 @@ public virtual void RegisterGlobalContainerDefaults(ObjectContainer container) container.RegisterTypeAs(); container.RegisterTypeAs(); + PlatformHelper.RegisterPluginAssemblyLoader(container); container.RegisterTypeAs(); container.RegisterTypeAs(); container.RegisterTypeAs(); diff --git a/Reqnroll/Infrastructure/TestExecutionEngine.cs b/Reqnroll/Infrastructure/TestExecutionEngine.cs index e36fe9649..c0af86828 100644 --- a/Reqnroll/Infrastructure/TestExecutionEngine.cs +++ b/Reqnroll/Infrastructure/TestExecutionEngine.cs @@ -7,10 +7,10 @@ using Reqnroll.Analytics; using Reqnroll.Bindings; using Reqnroll.Bindings.Reflection; -using Reqnroll.Compatibility; using Reqnroll.Configuration; using Reqnroll.ErrorHandling; using Reqnroll.Events; +using Reqnroll.PlatformCompatibility; using Reqnroll.Plugins; using Reqnroll.Tracing; using Reqnroll.UnitTestProvider; diff --git a/Reqnroll/PlatformCompatibility/CultureInfoHelper.cs b/Reqnroll/PlatformCompatibility/CultureInfoHelper.cs index 9effe95b9..6c674a165 100644 --- a/Reqnroll/PlatformCompatibility/CultureInfoHelper.cs +++ b/Reqnroll/PlatformCompatibility/CultureInfoHelper.cs @@ -1,6 +1,6 @@ using System.Globalization; -namespace Reqnroll.Compatibility +namespace Reqnroll.PlatformCompatibility { internal static class CultureInfoHelper { diff --git a/Reqnroll/PlatformCompatibility/EnumHelper.cs b/Reqnroll/PlatformCompatibility/EnumHelper.cs index 43d9b34d6..4d0efcff3 100644 --- a/Reqnroll/PlatformCompatibility/EnumHelper.cs +++ b/Reqnroll/PlatformCompatibility/EnumHelper.cs @@ -1,6 +1,6 @@ using System; -namespace Reqnroll.Compatibility +namespace Reqnroll.PlatformCompatibility { internal static class EnumHelper { diff --git a/Reqnroll/PlatformCompatibility/ExceptionHelper.cs b/Reqnroll/PlatformCompatibility/ExceptionHelper.cs index 6ae159750..5dc90c12a 100644 --- a/Reqnroll/PlatformCompatibility/ExceptionHelper.cs +++ b/Reqnroll/PlatformCompatibility/ExceptionHelper.cs @@ -1,7 +1,7 @@ using System; using System.Reflection; -namespace Reqnroll.Compatibility +namespace Reqnroll.PlatformCompatibility { internal static class ExceptionHelper { diff --git a/Reqnroll/PlatformCompatibility/MonoHelper.cs b/Reqnroll/PlatformCompatibility/MonoHelper.cs index 08025054f..30d47b355 100644 --- a/Reqnroll/PlatformCompatibility/MonoHelper.cs +++ b/Reqnroll/PlatformCompatibility/MonoHelper.cs @@ -1,7 +1,7 @@ using System; using System.Reflection; -namespace Reqnroll.Compatibility +namespace Reqnroll.PlatformCompatibility { internal class MonoHelper { diff --git a/Reqnroll/PlatformCompatibility/PlatformHelper.cs b/Reqnroll/PlatformCompatibility/PlatformHelper.cs new file mode 100644 index 000000000..ca573b5ce --- /dev/null +++ b/Reqnroll/PlatformCompatibility/PlatformHelper.cs @@ -0,0 +1,14 @@ +using Reqnroll.BoDi; +using Reqnroll.Plugins; + +namespace Reqnroll.PlatformCompatibility; +public static class PlatformHelper +{ + public static void RegisterPluginAssemblyLoader(IObjectContainer container) + { + if (PlatformInformation.IsDotNetFramework) + container.RegisterTypeAs(); + else + container.RegisterTypeAs(); + } +} diff --git a/Reqnroll/PlatformCompatibility/PlatformInformation.cs b/Reqnroll/PlatformCompatibility/PlatformInformation.cs new file mode 100644 index 000000000..eb0eaad0c --- /dev/null +++ b/Reqnroll/PlatformCompatibility/PlatformInformation.cs @@ -0,0 +1,26 @@ +using System.Diagnostics; +using System.Runtime.InteropServices; +using System; +using System.Text.RegularExpressions; + +namespace Reqnroll.PlatformCompatibility; +public class PlatformInformation +{ + public static Architecture ProcessArchitecture => RuntimeInformation.ProcessArchitecture; + public static string DotNetFullVersion { get; } = FileVersionInfo.GetVersionInfo(typeof(Uri).Assembly.Location).ProductVersion; + public static string DotNetFrameworkDescription => RuntimeInformation.FrameworkDescription; + public static string DotNetFrameworkMainDescription { get; } = GetMainDotNetFrameworkDescription(); + public static bool IsDotNetFramework { get; } = GetIsDotNetFramework(); + + private static bool GetIsDotNetFramework() + { + string frameworkDescription = DotNetFrameworkDescription; + return frameworkDescription.StartsWith(".NET Framework", StringComparison.OrdinalIgnoreCase) || + frameworkDescription.StartsWith("Mono", StringComparison.OrdinalIgnoreCase); + } + + private static string GetMainDotNetFrameworkDescription() + { + return Regex.Replace(RuntimeInformation.FrameworkDescription, @"(?\d+\.\d+).*", "${mver}"); + } +} diff --git a/Reqnroll/Plugins/DotNetFrameworkPluginAssemblyLoader.cs b/Reqnroll/Plugins/DotNetFrameworkPluginAssemblyLoader.cs new file mode 100644 index 000000000..7ee805f63 --- /dev/null +++ b/Reqnroll/Plugins/DotNetFrameworkPluginAssemblyLoader.cs @@ -0,0 +1,8 @@ +using System.Reflection; + +namespace Reqnroll.Plugins; + +public class DotNetFrameworkPluginAssemblyLoader : IPluginAssemblyLoader +{ + public Assembly LoadAssembly(string assemblyName) => Assembly.LoadFrom(assemblyName); +} diff --git a/Reqnroll/Plugins/IPluginAssemblyLoader.cs b/Reqnroll/Plugins/IPluginAssemblyLoader.cs new file mode 100644 index 000000000..7d46da021 --- /dev/null +++ b/Reqnroll/Plugins/IPluginAssemblyLoader.cs @@ -0,0 +1,7 @@ +using System.Reflection; + +namespace Reqnroll.Plugins; +public interface IPluginAssemblyLoader +{ + Assembly LoadAssembly(string assemblyName); +} \ No newline at end of file diff --git a/Reqnroll/Plugins/PluginAssemblyLoader.cs b/Reqnroll/Plugins/PluginAssemblyLoader.cs new file mode 100644 index 000000000..9129d406f --- /dev/null +++ b/Reqnroll/Plugins/PluginAssemblyLoader.cs @@ -0,0 +1,8 @@ +using System.Reflection; + +namespace Reqnroll.Plugins; + +public class PluginAssemblyLoader : IPluginAssemblyLoader +{ + public Assembly LoadAssembly(string assemblyName) => PluginAssemblyResolver.Load(assemblyName); +} diff --git a/Reqnroll/Plugins/PluginAssemblyResolver.cs b/Reqnroll/Plugins/PluginAssemblyResolver.cs index 294ea8f4f..b7ef4817f 100644 --- a/Reqnroll/Plugins/PluginAssemblyResolver.cs +++ b/Reqnroll/Plugins/PluginAssemblyResolver.cs @@ -1,5 +1,3 @@ -#if NETSTANDARD - using System; using System.Collections.Generic; using System.IO; @@ -77,5 +75,3 @@ public static Assembly Load(string path) } } } - -#endif \ No newline at end of file diff --git a/Reqnroll/Plugins/RuntimePluginLoader.cs b/Reqnroll/Plugins/RuntimePluginLoader.cs index e0e4b187f..1d61da1f0 100644 --- a/Reqnroll/Plugins/RuntimePluginLoader.cs +++ b/Reqnroll/Plugins/RuntimePluginLoader.cs @@ -1,25 +1,22 @@ using System; using System.Reflection; +using Reqnroll.PlatformCompatibility; using Reqnroll.Tracing; namespace Reqnroll.Plugins { - public class RuntimePluginLoader : IRuntimePluginLoader + public class RuntimePluginLoader(IPluginAssemblyLoader _pluginAssemblyLoader) : IRuntimePluginLoader { public IRuntimePlugin LoadPlugin(string pluginAssemblyName, ITraceListener traceListener, bool traceMissingPluginAttribute) { Assembly assembly; try { -#if NETSTANDARD - assembly = PluginAssemblyResolver.Load(pluginAssemblyName); -#else - assembly = Assembly.LoadFrom(pluginAssemblyName); -#endif + assembly = _pluginAssemblyLoader.LoadAssembly(pluginAssemblyName); } catch (Exception ex) { - throw new ReqnrollException($"Unable to load plugin: {pluginAssemblyName}. Please check https://go.reqnroll.net/doc-plugins for details.", ex); + throw new ReqnrollException($"Unable to load plugin: {pluginAssemblyName}. Please check https://go.reqnroll.net/doc-plugins for details. (Framework: {PlatformInformation.DotNetFrameworkDescription})", ex); } var pluginAttribute = (RuntimePluginAttribute)Attribute.GetCustomAttribute(assembly, typeof(RuntimePluginAttribute)); diff --git a/Reqnroll/Plugins/RuntimePluginLocationMerger.cs b/Reqnroll/Plugins/RuntimePluginLocationMerger.cs index 6056e0ab2..6057a3e7e 100644 --- a/Reqnroll/Plugins/RuntimePluginLocationMerger.cs +++ b/Reqnroll/Plugins/RuntimePluginLocationMerger.cs @@ -8,12 +8,7 @@ public class RuntimePluginLocationMerger : IRuntimePluginLocationMerger public IReadOnlyList Merge(IReadOnlyList pluginPaths) { // Idea is to filter out the same assemblies stored on different paths. Shortcut: check if we even have duplicated assemblies - var hashset = new HashSet( -#if NETCOREAPP2_1_OR_GREATER - // initialize with expected size when available - pluginPaths.Count -#endif - ); + var hashset = new HashSet(); List modifiedList = null; for (var i = 0; i < pluginPaths.Count; i++) diff --git a/Reqnroll/Reqnroll.csproj b/Reqnroll/Reqnroll.csproj index 473f67ce8..023f1c76d 100644 --- a/Reqnroll/Reqnroll.csproj +++ b/Reqnroll/Reqnroll.csproj @@ -1,6 +1,6 @@  - $(Reqnroll_Runtime_TFM) + netstandard2.0 Reqnroll $(Reqnroll_KeyFile) $(Reqnroll_EnableStrongNameSigning) @@ -18,12 +18,13 @@ true + all runtime; build; native; contentfiles; analyzers - + @@ -31,29 +32,13 @@ - - - - - - - - - - - - - + - - $(DefineConstants);NETSTANDARD - - 3fd018ff-819d-4685-a6e1-6f09bc98d20b diff --git a/Reqnroll/Reqnroll.nuspec b/Reqnroll/Reqnroll.nuspec index 75c6b93ff..fd0d550d4 100644 --- a/Reqnroll/Reqnroll.nuspec +++ b/Reqnroll/Reqnroll.nuspec @@ -16,19 +16,12 @@ reqnroll bdd gherkin cucumber $copyright$ - + - - - - - - - @@ -37,14 +30,11 @@ - - - - - - - - + + + + + diff --git a/Reqnroll/Table.cs b/Reqnroll/Table.cs index 3584cf680..826f65d7f 100644 --- a/Reqnroll/Table.cs +++ b/Reqnroll/Table.cs @@ -10,9 +10,7 @@ namespace Reqnroll /// /// An alias for the class for backwards compatibility. /// -#if !BODI_LIMITEDRUNTIME [Serializable] -#endif public class Table { internal const string ERROR_NO_CELLS_TO_ADD = "No cells to add"; @@ -175,9 +173,7 @@ private void AddTableRow(StringBuilder builder, IEnumerable cells, int[] } } -#if !BODI_LIMITEDRUNTIME [Serializable] -#endif public class DataTableRows : IEnumerable { private readonly List _innerList = new(); @@ -207,9 +203,7 @@ internal string[][] ToArray() } } -#if !BODI_LIMITEDRUNTIME [Serializable] -#endif public class DataTableRow : IDictionary { private readonly Table _table; diff --git a/Reqnroll/Tracing/AnsiColor/AnsiColor.cs b/Reqnroll/Tracing/AnsiColor/AnsiColor.cs index c92487a24..e33e81746 100644 --- a/Reqnroll/Tracing/AnsiColor/AnsiColor.cs +++ b/Reqnroll/Tracing/AnsiColor/AnsiColor.cs @@ -23,9 +23,6 @@ public readonly struct AnsiColor public static AnsiColor Background(int r, int g, int b) => new(TerminalControlSequences.SetBackgroundColor, new TerminalRgbColor(r, g, b)); public static AnsiColor Composite(params AnsiColor[] codes) => new(codes); -#if NET3_0_OR_GREATER - [return: System.Diagnostics.CodeAnalysis.NotNullIfNotNull("text")] -#endif public static string? ColorizeText(string? text, AnsiColor code) { if (text == null) return null; @@ -70,7 +67,7 @@ public AnsiColor(TerminalControlSequences controlSequence, TerminalRgbColor rgbC private TerminalControlSequences GetResetSequence(TerminalControlSequences controlSequence) { - switch (ControlSequence) + switch (controlSequence) { case TerminalControlSequences.SetForegroundColor: return TerminalControlSequences.DefaultForegroundColor; case TerminalControlSequences.SetBackgroundColor: return TerminalControlSequences.DefaultBackgroundColor; diff --git a/Reqnroll/Tracing/LanguageHelper.cs b/Reqnroll/Tracing/LanguageHelper.cs index c22c5ab0a..dd9b0498f 100644 --- a/Reqnroll/Tracing/LanguageHelper.cs +++ b/Reqnroll/Tracing/LanguageHelper.cs @@ -4,9 +4,8 @@ using System.Linq; using Gherkin; using Reqnroll.Bindings; -using Reqnroll.Compatibility; using Reqnroll.Parser; - +using Reqnroll.PlatformCompatibility; namespace Reqnroll.Tracing { diff --git a/Tests/Reqnroll.GeneratorTests/Reqnroll.GeneratorTests.csproj b/Tests/Reqnroll.GeneratorTests/Reqnroll.GeneratorTests.csproj index 919c7e9be..e89529de3 100644 --- a/Tests/Reqnroll.GeneratorTests/Reqnroll.GeneratorTests.csproj +++ b/Tests/Reqnroll.GeneratorTests/Reqnroll.GeneratorTests.csproj @@ -40,20 +40,7 @@ all runtime; build; native; contentfiles; analyzers - - - - - - - - - - - - - diff --git a/Tests/Reqnroll.PluginTests/Generator/GeneratorPluginLoaderTests.cs b/Tests/Reqnroll.PluginTests/Generator/GeneratorPluginLoaderTests.cs index 4a57cb101..2c65090a2 100644 --- a/Tests/Reqnroll.PluginTests/Generator/GeneratorPluginLoaderTests.cs +++ b/Tests/Reqnroll.PluginTests/Generator/GeneratorPluginLoaderTests.cs @@ -1,6 +1,3 @@ -using System; -using System.IO; -using System.Reflection; using FluentAssertions; using Reqnroll.Generator.Plugins; using Reqnroll.Plugins; @@ -15,7 +12,7 @@ public class GeneratorPluginLoaderTests public void LoadPlugin_LoadXUnitSuccessfully() { //ARRANGE - var generatorPluginLoader = new GeneratorPluginLoader(); + var generatorPluginLoader = new GeneratorPluginLoader(new PluginAssemblyLoader()); //ACT var pluginDescriptor = new PluginDescriptor("Reqnroll.xUnit.Generator.ReqnrollPlugin", "Reqnroll.xUnit.Generator.ReqnrollPlugin.dll", PluginType.Generator, ""); diff --git a/Tests/Reqnroll.PluginTests/Infrastructure/WindsorPluginTests.cs b/Tests/Reqnroll.PluginTests/Infrastructure/WindsorPluginTests.cs index f6b0140df..3910edf0a 100644 --- a/Tests/Reqnroll.PluginTests/Infrastructure/WindsorPluginTests.cs +++ b/Tests/Reqnroll.PluginTests/Infrastructure/WindsorPluginTests.cs @@ -21,7 +21,7 @@ public class WindsorPluginTests [Fact] public void Can_load_Windsor_plugin() { - var loader = new RuntimePluginLoader(); + var loader = new RuntimePluginLoader(new PluginAssemblyLoader()); var listener = new Mock(); var plugin = loader.LoadPlugin("Reqnroll.Windsor.ReqnrollPlugin.dll", listener.Object, It.IsAny()); diff --git a/Tests/Reqnroll.RuntimeTests/Reqnroll.RuntimeTests.csproj b/Tests/Reqnroll.RuntimeTests/Reqnroll.RuntimeTests.csproj index de56b850a..f1ac8b1ef 100644 --- a/Tests/Reqnroll.RuntimeTests/Reqnroll.RuntimeTests.csproj +++ b/Tests/Reqnroll.RuntimeTests/Reqnroll.RuntimeTests.csproj @@ -34,21 +34,9 @@ all runtime; build; native; contentfiles; analyzers - - - - - - - - - - - - diff --git a/Tests/Reqnroll.Specs/Reqnroll.Specs.csproj b/Tests/Reqnroll.Specs/Reqnroll.Specs.csproj index c9201c45b..0298e7856 100644 --- a/Tests/Reqnroll.Specs/Reqnroll.Specs.csproj +++ b/Tests/Reqnroll.Specs/Reqnroll.Specs.csproj @@ -1,4 +1,4 @@ - + @@ -57,8 +57,8 @@ - <_Reqnroll_Needed_MSBuildGenerator Condition=" '$(MSBuildRuntimeType)' == 'Core'">net6.0 - <_Reqnroll_Needed_MSBuildGenerator Condition=" '$(MSBuildRuntimeType)' != 'Core'">$(Reqnroll_FullFramework_Generator_TFM) + <_Reqnroll_Needed_MSBuildGenerator Condition=" '$(MSBuildRuntimeType)' == 'Core'">$(Reqnroll_Core_Tools_TFM) + <_Reqnroll_Needed_MSBuildGenerator Condition=" '$(MSBuildRuntimeType)' != 'Core'">$(Reqnroll_FullFramework_Tools_TFM) @@ -73,15 +73,15 @@ - + - + - <_Reqnroll_TaskAssembly>..\..\Reqnroll.Tools.MsBuild.Generation\bin\$(Configuration)\$(_Reqnroll_Needed_MSBuildGenerator)\Reqnroll.Tools.MsBuild.Generation.dll + <_Reqnroll_TaskAssembly>..\..\Reqnroll.Tools.MsBuild.Generation\bin\$(Configuration)\$(_Reqnroll_Needed_MSBuildGenerator)\tasks\Reqnroll.Tools.MsBuild.Generation.dll diff --git a/docs/extend/plugins.md b/docs/extend/plugins.md index e72c66646..fe6f7a437 100644 --- a/docs/extend/plugins.md +++ b/docs/extend/plugins.md @@ -9,7 +9,9 @@ All types of plugins are created in a similar way. ## Runtime plugins -Runtime plugins need to target .NET Framework 4.6.2 and .NET Standard 2.0. +Runtime plugins should target .NET Standard 2.0 to be compatible with all .NET versions supported +by Reqnroll. Targetting a more specific version will limit the compatibility of your plugin. + Reqnroll searches for files that end with `.ReqnrollPlugin.dll` in the following locations: * The folder containing your `Reqnroll.dll` file @@ -44,7 +46,15 @@ Mandatory: ## Generator plugins -Generator plugins need to target .NET Framework 4.7.1 and .NET Core 3.1. +Runtime plugins should target .NET Standard 2.0 to be compatible with all scenarios supported by +Reqnroll. + +The generator plugins are invoked during build. They are usually invoked in a .NET environment +according to your .NET SDK (e.g. .NET 8.0), but in some cases (when built using MSBuild or in Visual Studio) +they might be invoked in a .NET 4.8 environment. Therefore, you have to make sure that your plugin +works in both environments. If necessary, you can multi-target your plugin, but using the right compiled +version of your plugin is the responsibility of the plugin itself. + The MSBuild task needs to know which generator plugins it should use. You therefore have to add your generator plugin to the `ReqnrollGeneratorPlugins` ItemGroup. This is passed to the MSBuild task as a parameter and later used to load the plugins. @@ -65,28 +75,31 @@ This is passed to the MSBuild task as a parameter and later used to load the plu ## Combined Package with both plugins -If you need to update generator and runtime plugins with a single NuGet package (as we are doing with the `Reqnroll.xUnit`, `Reqnroll.NUnit` and `Reqnroll.xUnit` packages), you can do so. +You can have a single NuGet package that contains both the runtime and generator plugins. +We use this approach for the `Reqnroll.xUnit`, `Reqnroll.NUnit` and `Reqnroll.xUnit` packages +for example. -As with the separate plugins, you need two projects. One for the runtime plugin, and one for the generator plugin. As you only want one NuGet package, the **NuSpec files must only be present in the generator project**. -This is because the generator plugin is built with a higher .NET Framework version (.NET 4.7.1), meaning you can add a dependency on the Runtime plugin (which is only .NET 4.6.1). This will not working the other way around. +The combined package can be built from a single project, or from two projects. The latter +allows you to have different dependencies for the runtime and generator plugins. -You can simply combine the contents of the `.targets` and `.props` file to a single one. +If you use two projects for the combined package, the **NuSpec files should only be present +in the generator project**. This is because the generators typically have more dependencies. +You can simply combine the contents of the `.targets` and `.props` file to a single one. ## Tips & Tricks ### Building Plugins on non-Windows machines -For building .NET 4.6.2 projects on non- Windows machines, the .NET Framework reference assemblies are needed. +For building .NET 4.6.2 projects on non-Windows machines, the .NET Framework reference assemblies are needed. You can add them with following PackageReference to your project: -``` xml +```xml all runtime; build; native; contentfiles; analyzers - ``` From eef03e4658333b01158ddea780441e1d691fc80f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1sp=C3=A1r=20Nagy?= Date: Tue, 21 May 2024 22:41:03 +0200 Subject: [PATCH 21/21] Fix #56 autofac ambiguous stepdef and hook required #127 issue (#139) * Refactor AutofacPlugin and add unit tests * Add extension method to add bindings from assembly * fix duplicate registrations bug * fix typo * Fix the fix and also #127 * Add CHANGELOG --- CHANGELOG.md | 2 + .../AutofacPlugin.cs | 274 ++++++++--------- .../ConfigurationMethodsProvider.cs | 15 + .../ContainerBuilderExtensions.cs | 19 +- .../ContainerBuilderFinder.cs | 120 ++++---- .../IConfigurationMethodsProvider.cs | 9 + .../IContainerBuilderFinder.cs | 2 +- .../Reqnroll.Autofac.ReqnrollPlugin.csproj | 2 + Reqnroll/BoDi/IObjectContainer.cs | 8 + .../Autofac/AutofacPluginTests.cs | 291 ++++++++++++++++++ .../Reqnroll.PluginTests.csproj | 1 + .../Reqnroll.RuntimeTests.csproj | 4 - docs/integrations/autofac.md | 47 +-- 13 files changed, 561 insertions(+), 233 deletions(-) create mode 100644 Plugins/Reqnroll.Autofac.ReqnrollPlugin/ConfigurationMethodsProvider.cs create mode 100644 Plugins/Reqnroll.Autofac.ReqnrollPlugin/IConfigurationMethodsProvider.cs create mode 100644 Tests/Reqnroll.PluginTests/Autofac/AutofacPluginTests.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 37652b5a7..78d557aa3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ * Allow creating single target (netstandard2.0) plugins * MsTest: Use ClassCleanupBehavior.EndOfClass instead of custom implementation (preparation for MsTest v4.0) * Fix: #71 StackOverflowException when using [StepArgumentTransformation] with same input and output type (for example string) +* Fix: Autofac without hook does not run GlobalDependencies (#127) +* Fix: Reqnroll.Autofac shows wrongly ambiguous step definition (#56) # v1.0.1 - 2024-02-16 diff --git a/Plugins/Reqnroll.Autofac.ReqnrollPlugin/AutofacPlugin.cs b/Plugins/Reqnroll.Autofac.ReqnrollPlugin/AutofacPlugin.cs index f78c88cee..eeb13ea9c 100644 --- a/Plugins/Reqnroll.Autofac.ReqnrollPlugin/AutofacPlugin.cs +++ b/Plugins/Reqnroll.Autofac.ReqnrollPlugin/AutofacPlugin.cs @@ -1,183 +1,183 @@ using System; using Autofac; +using Reqnroll.BoDi; using Reqnroll.Autofac; -using Reqnroll; using Reqnroll.Infrastructure; using Reqnroll.Plugins; using Reqnroll.UnitTestProvider; [assembly: RuntimePlugin(typeof(AutofacPlugin))] -namespace Reqnroll.Autofac -{ - using Reqnroll.BoDi; +namespace Reqnroll.Autofac; - using Reqnroll; +public class AutofacPlugin : IRuntimePlugin +{ + private static readonly object _registrationLock = new(); - public class AutofacPlugin : IRuntimePlugin + public void Initialize(RuntimePluginEvents runtimePluginEvents, RuntimePluginParameters runtimePluginParameters, UnitTestProviderConfiguration unitTestProviderConfiguration) { - private static readonly Object _registrationLock = new Object(); - - public void Initialize(RuntimePluginEvents runtimePluginEvents, RuntimePluginParameters runtimePluginParameters, UnitTestProviderConfiguration unitTestProviderConfiguration) + runtimePluginEvents.CustomizeGlobalDependencies += (_, args) => { - runtimePluginEvents.CustomizeGlobalDependencies += (sender, args) => + // temporary fix for CustomizeGlobalDependencies called multiple times + // see https://github.com/SpecFlowOSS/SpecFlow/issues/948 + if (!args.ObjectContainer.IsRegistered()) { - // temporary fix for CustomizeGlobalDependencies called multiple times - // see https://github.com/reqnroll/Reqnroll/issues/948 - if (!args.ObjectContainer.IsRegistered()) + // an extra lock to ensure that there are not two fast threads re-registering the same stuff + lock (_registrationLock) { - // an extra lock to ensure that there are not two super fast threads re-registering the same stuff - lock (_registrationLock) + if (!args.ObjectContainer.IsRegistered()) { - if (!args.ObjectContainer.IsRegistered()) + var testRunContainer = args.ObjectContainer; + RegisterTestRunPluginTypes(testRunContainer); + + var containerBuilderFinder = args.ObjectContainer.Resolve(); + var configureGlobalContainer = containerBuilderFinder.GetConfigureGlobalContainer(); + if (configureGlobalContainer != null) { - args.ObjectContainer.RegisterTypeAs(); - args.ObjectContainer.RegisterTypeAs(); - - var containerBuilderFinder = args.ObjectContainer.Resolve(); - var configureGlobalContainer = containerBuilderFinder.GetConfigureGlobalContainer(); - if (configureGlobalContainer != null) - { - var containerBuilder = configureGlobalContainer(new global::Autofac.ContainerBuilder()); - args.ObjectContainer.RegisterFactoryAs(() => containerBuilder.Build()); - } + var containerBuilder = configureGlobalContainer(new global::Autofac.ContainerBuilder()); + args.ObjectContainer.RegisterFactoryAs(() => containerBuilder.Build()); } } - - // workaround for parallel execution issue - this should be rather a feature in BoDi? - args.ObjectContainer.Resolve(); } - }; - runtimePluginEvents.CustomizeTestThreadDependencies += (sender, args) => - { - if (args.ObjectContainer.BaseContainer.IsRegistered()) - { - var container = args.ObjectContainer.BaseContainer.Resolve(); - args.ObjectContainer.RegisterFactoryAs(() => container.BeginLifetimeScope(nameof(TestThreadContext))); - } - }; + // workaround for parallel execution issue - this should be rather a feature in BoDi? + args.ObjectContainer.Resolve(); + } + }; - runtimePluginEvents.CustomizeFeatureDependencies += (sender, args) => + runtimePluginEvents.CustomizeTestThreadDependencies += (_, args) => + { + if (args.ObjectContainer.BaseContainer.IsRegistered()) { - var containerBuilderFinder = args.ObjectContainer.Resolve(); + var container = args.ObjectContainer.BaseContainer.Resolve(); + args.ObjectContainer.RegisterFactoryAs(() => container.BeginLifetimeScope(nameof(TestThreadContext))); + } + }; - var featureScopeFinder = containerBuilderFinder.GetFeatureLifetimeScope(); + runtimePluginEvents.CustomizeFeatureDependencies += (_, args) => + { + var containerBuilderFinder = args.ObjectContainer.Resolve(); - ILifetimeScope featureScope = null; + var featureScopeFinder = containerBuilderFinder.GetFeatureLifetimeScope(); - if (featureScopeFinder != null) - { - featureScope = featureScopeFinder(); - } - else if (args.ObjectContainer.BaseContainer.IsRegistered()) - { - var testThreadScope = args.ObjectContainer.BaseContainer.Resolve(); + ILifetimeScope featureScope = null; - featureScope = testThreadScope.BeginLifetimeScope(nameof(FeatureContext)); - } + if (featureScopeFinder != null) + { + // if the user provided a feature scope creation method, we use that + featureScope = featureScopeFinder(); + } + else if (args.ObjectContainer.BaseContainer.IsRegistered()) + { + // if the TestThread container has a lifetime scope, we make a child scope for the feature + var testThreadScope = args.ObjectContainer.BaseContainer.Resolve(); + featureScope = testThreadScope.BeginLifetimeScope(nameof(FeatureContext)); + } - if (featureScope != null) - { - args.ObjectContainer.RegisterInstanceAs(featureScope); - } - }; + if (featureScope != null) + { + args.ObjectContainer.RegisterInstanceAs(featureScope); + } + }; - runtimePluginEvents.CustomizeScenarioDependencies += (sender, args) => + runtimePluginEvents.CustomizeScenarioDependencies += (_, args) => + { + args.ObjectContainer.RegisterFactoryAs(() => { - args.ObjectContainer.RegisterFactoryAs(() => - { - var containerBuilderFinder = args.ObjectContainer.Resolve(); + var containerBuilderFinder = args.ObjectContainer.Resolve(); - var featureScope = GetFeatureScope(args.ObjectContainer, containerBuilderFinder); + var featureScope = GetFeatureScope(args.ObjectContainer, containerBuilderFinder); - if (featureScope != null) + if (featureScope != null) + { + return featureScope.BeginLifetimeScope(nameof(ScenarioContext), containerBuilder => { - return featureScope.BeginLifetimeScope(nameof(ScenarioContext), containerBuilder => + var configureScenarioContainer = containerBuilderFinder.GetConfigureScenarioContainer(); + + if (configureScenarioContainer != null) { - var configureScenarioContainer = containerBuilderFinder.GetConfigureScenarioContainer(); + containerBuilder = configureScenarioContainer(containerBuilder); + } - if (configureScenarioContainer != null) - { - containerBuilder = configureScenarioContainer(containerBuilder); - } + RegisterReqnrollDependencies(args.ObjectContainer, containerBuilder); + }); + } - RegisterReqnrollDependecies(args.ObjectContainer, containerBuilder); - }); - } + var legacyCreateScenarioContainerBuilder = containerBuilderFinder.GetLegacyCreateScenarioContainerBuilder(); + if (legacyCreateScenarioContainerBuilder == null) + { + throw new Exception("Unable to find scenario dependencies! Mark a static method that has a ContainerBuilder parameter and returns void with [ScenarioDependencies]!"); + } - var createScenarioContainerBuilder = containerBuilderFinder.GetCreateScenarioContainerBuilder(); - if (createScenarioContainerBuilder == null) - { - throw new Exception("Unable to find scenario dependencies! Mark a static method that has a ContainerBuilder parameter and returns void with [ScenarioDependencies]!"); - } + var containerBuilder = legacyCreateScenarioContainerBuilder(null); + RegisterReqnrollDependencies(args.ObjectContainer, containerBuilder); + args.ObjectContainer.RegisterFactoryAs(() => containerBuilder.Build()); + return args.ObjectContainer.Resolve().BeginLifetimeScope(nameof(ScenarioContext)); + }); + }; + } - var containerBuilder = createScenarioContainerBuilder(null); - RegisterReqnrollDependecies(args.ObjectContainer, containerBuilder); - args.ObjectContainer.RegisterFactoryAs(() => containerBuilder.Build()); - return args.ObjectContainer.Resolve().BeginLifetimeScope(nameof(ScenarioContext)); - }); - }; - } + protected virtual void RegisterTestRunPluginTypes(ObjectContainer testRunContainer) + { + testRunContainer.RegisterTypeAs(); + testRunContainer.RegisterTypeAs(); + testRunContainer.RegisterTypeAs(); + } - private static ILifetimeScope GetFeatureScope(ObjectContainer objectContainer, IContainerBuilderFinder containerBuilderFinder) + private static ILifetimeScope GetFeatureScope(ObjectContainer objectContainer, IContainerBuilderFinder containerBuilderFinder) + { + if (objectContainer.BaseContainer.IsRegistered()) { - if (objectContainer.BaseContainer.IsRegistered()) - { - return objectContainer.BaseContainer.Resolve(); - } - - var configureScenarioContainer = containerBuilderFinder.GetConfigureScenarioContainer(); - - if (configureScenarioContainer != null) - { - var containerBuilder = new global::Autofac.ContainerBuilder(); - objectContainer.RegisterFactoryAs(() => containerBuilder.Build()); - return objectContainer.Resolve(); - } - - return null; + return objectContainer.BaseContainer.Resolve(); } + var configureScenarioContainer = containerBuilderFinder.GetConfigureScenarioContainer(); - /// - /// Fix for https://github.com/gasparnagy/Reqnroll.Autofac/issues/11 Cannot resolve ScenarioInfo - /// Extracted from - /// https://github.com/reqnroll/Reqnroll/blob/master/Reqnroll/Infrastructure/ITestObjectResolver.cs - /// The test objects might be dependent on particular Reqnroll infrastructure, therefore the implemented - /// resolution logic should support resolving the following objects (from the provided Reqnroll container): - /// , , and - /// (to be able to resolve any other Reqnroll infrastucture). So basically - /// the resolution of these classes has to be forwarded to the original container. - /// - /// Reqnroll DI container. - /// Autofac ContainerBuilder. - private void RegisterReqnrollDependecies( - IObjectContainer objectContainer, - global::Autofac.ContainerBuilder containerBuilder) + if (configureScenarioContainer != null) { - containerBuilder.Register(ctx => objectContainer).As(); - containerBuilder.Register( - ctx => - { - var reqnrollContainer = ctx.Resolve(); - var scenarioContext = reqnrollContainer.Resolve(); - return scenarioContext; - }).As(); - containerBuilder.Register( - ctx => - { - var reqnrollContainer = ctx.Resolve(); - var scenarioContext = reqnrollContainer.Resolve(); - return scenarioContext; - }).As(); - containerBuilder.Register( - ctx => - { - var reqnrollContainer = ctx.Resolve(); - var scenarioContext = reqnrollContainer.Resolve(); - return scenarioContext; - }).As(); + var containerBuilder = new global::Autofac.ContainerBuilder(); + objectContainer.RegisterFactoryAs(() => containerBuilder.Build()); + return objectContainer.Resolve(); } + + return null; + } + + + /// + /// The test objects might be dependent on particular Reqnroll infrastructure, therefore the implemented + /// resolution logic should support resolving the following objects (from the provided Reqnroll container): + /// , , and + /// (to be able to resolve any other Reqnroll infrastructure). So basically + /// the resolution of these classes has to be forwarded to the original container. + /// + /// Reqnroll DI container. + /// Autofac ContainerBuilder. + private void RegisterReqnrollDependencies( + IObjectContainer objectContainer, + global::Autofac.ContainerBuilder containerBuilder) + { + containerBuilder.Register(_ => objectContainer).As(); + containerBuilder.Register( + ctx => + { + var reqnrollContainer = ctx.Resolve(); + var scenarioContext = reqnrollContainer.Resolve(); + return scenarioContext; + }).As(); + containerBuilder.Register( + ctx => + { + var reqnrollContainer = ctx.Resolve(); + var scenarioContext = reqnrollContainer.Resolve(); + return scenarioContext; + }).As(); + containerBuilder.Register( + ctx => + { + var reqnrollContainer = ctx.Resolve(); + var scenarioContext = reqnrollContainer.Resolve(); + return scenarioContext; + }).As(); } -} +} \ No newline at end of file diff --git a/Plugins/Reqnroll.Autofac.ReqnrollPlugin/ConfigurationMethodsProvider.cs b/Plugins/Reqnroll.Autofac.ReqnrollPlugin/ConfigurationMethodsProvider.cs new file mode 100644 index 000000000..5272c893a --- /dev/null +++ b/Plugins/Reqnroll.Autofac.ReqnrollPlugin/ConfigurationMethodsProvider.cs @@ -0,0 +1,15 @@ +using Reqnroll.Bindings; +using Reqnroll.Infrastructure; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Reqnroll.Autofac; +public class ConfigurationMethodsProvider(ITestAssemblyProvider _testAssemblyProvider) : IConfigurationMethodsProvider +{ + public IEnumerable GetConfigurationMethods() + { + return _testAssemblyProvider.TestAssembly.GetTypes() + .SelectMany(t => t.GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public)); + } +} diff --git a/Plugins/Reqnroll.Autofac.ReqnrollPlugin/ContainerBuilderExtensions.cs b/Plugins/Reqnroll.Autofac.ReqnrollPlugin/ContainerBuilderExtensions.cs index b03f9f0de..e66c734f3 100644 --- a/Plugins/Reqnroll.Autofac.ReqnrollPlugin/ContainerBuilderExtensions.cs +++ b/Plugins/Reqnroll.Autofac.ReqnrollPlugin/ContainerBuilderExtensions.cs @@ -1,7 +1,6 @@ using System; -using System.Linq; +using System.Reflection; using Autofac; -using Reqnroll; namespace Reqnroll.Autofac.ReqnrollPlugin { @@ -13,7 +12,7 @@ public static class ContainerBuilderExtensions /// /// Add Reqnroll binding for classes in the assembly where typeof TAssemblyType resides. /// - /// The type in an assembly to search for bindings. + /// Any type in an assembly to search for bindings. /// The builder. /// The builder. public static ContainerBuilder AddReqnrollBindings(this ContainerBuilder builder) => builder.AddReqnrollBindings(typeof(TAssemblyType)); @@ -22,12 +21,20 @@ public static class ContainerBuilderExtensions /// Add Reqnroll binding for classes in the assembly where the type resides. /// /// The builder. - /// The type in an assembly to search for bindings. + /// Any type in an assembly to search for bindings. /// The builder. - public static ContainerBuilder AddReqnrollBindings(this ContainerBuilder builder, Type type) + public static ContainerBuilder AddReqnrollBindings(this ContainerBuilder builder, Type type) => builder.AddReqnrollBindings(type.Assembly); + + /// + /// Add Reqnroll binding for classes in an assembly. + /// + /// The builder. + /// The assembly to search for bindings. + /// The builder. + public static ContainerBuilder AddReqnrollBindings(this ContainerBuilder builder, Assembly assembly) { builder - .RegisterAssemblyTypes(type.Assembly) + .RegisterAssemblyTypes(assembly) .Where(t => Attribute.IsDefined(t, typeof(BindingAttribute))) .SingleInstance(); return builder; diff --git a/Plugins/Reqnroll.Autofac.ReqnrollPlugin/ContainerBuilderFinder.cs b/Plugins/Reqnroll.Autofac.ReqnrollPlugin/ContainerBuilderFinder.cs index 42b80db27..c9834f5ee 100644 --- a/Plugins/Reqnroll.Autofac.ReqnrollPlugin/ContainerBuilderFinder.cs +++ b/Plugins/Reqnroll.Autofac.ReqnrollPlugin/ContainerBuilderFinder.cs @@ -3,85 +3,77 @@ using System.Reflection; using Autofac; using Reqnroll.Autofac.ReqnrollPlugin; -using Reqnroll.Bindings; -using Reqnroll.Bindings.Discovery; -using Reqnroll.Infrastructure; using ContainerBuilder = Autofac.ContainerBuilder; -namespace Reqnroll.Autofac +namespace Reqnroll.Autofac; + +public class ContainerBuilderFinder : IContainerBuilderFinder { - public class ContainerBuilderFinder : IContainerBuilderFinder + private readonly IConfigurationMethodsProvider _configurationMethodsProvider; + private readonly Lazy> _createConfigureGlobalContainer; + private readonly Lazy> _createConfigureScenarioContainer; + private readonly Lazy> _legacyCreateScenarioContainerBuilder; + private readonly Lazy> _getFeatureLifetimeScope; + + public ContainerBuilderFinder(IConfigurationMethodsProvider configurationMethodsProvider) { - private readonly IBindingRegistry bindingRegistry; - private readonly IRuntimeBindingRegistryBuilder bindingRegistryBuilder; - private readonly ITestAssemblyProvider testAssemblyProvider; - private readonly Lazy> createConfigureGlobalContainer; - private readonly Lazy> createConfigureScenarioContainer; - private readonly Lazy> createScenarioContainerBuilder; - private readonly Lazy> getFeatureLifetimeScope; + _configurationMethodsProvider = configurationMethodsProvider; - public ContainerBuilderFinder(IBindingRegistry bindingRegistry, IRuntimeBindingRegistryBuilder bindingRegistryBuilder, ITestAssemblyProvider testAssemblyProvider) + static ContainerBuilder InvokeVoidAndReturnBuilder(ContainerBuilder containerBuilder, MethodInfo methodInfo) { - this.bindingRegistry = bindingRegistry; - this.bindingRegistryBuilder = bindingRegistryBuilder; - this.testAssemblyProvider = testAssemblyProvider; - static ContainerBuilder invokeVoidAndReturnBuilder(ContainerBuilder containerBuilder, MethodInfo methodInfo) - { - methodInfo.Invoke(null, new[] { containerBuilder }); - return containerBuilder; - } - createConfigureGlobalContainer = new Lazy>(() => FindCreateScenarioContainerBuilder(typeof(GlobalDependenciesAttribute), typeof(void), invokeVoidAndReturnBuilder), true); - createConfigureScenarioContainer = new Lazy>(() => FindCreateScenarioContainerBuilder(typeof(ScenarioDependenciesAttribute), typeof(void), invokeVoidAndReturnBuilder), true); - createScenarioContainerBuilder = new Lazy>(() => FindCreateScenarioContainerBuilder(typeof(ScenarioDependenciesAttribute), typeof(ContainerBuilder), (c, m) => (ContainerBuilder)m.Invoke(null, null)), true); - getFeatureLifetimeScope = new Lazy>(() => FindLifetimeScope(typeof(FeatureDependenciesAttribute), typeof(ILifetimeScope), m => (ILifetimeScope)m.Invoke(null, null))); + methodInfo.Invoke(null, [ containerBuilder ]); + return containerBuilder; } + _createConfigureGlobalContainer = new Lazy>(() => FindCreateScenarioContainerBuilder(typeof(GlobalDependenciesAttribute), typeof(void), InvokeVoidAndReturnBuilder), true); + _createConfigureScenarioContainer = new Lazy>(() => FindCreateScenarioContainerBuilder(typeof(ScenarioDependenciesAttribute), typeof(void), InvokeVoidAndReturnBuilder), true); + _legacyCreateScenarioContainerBuilder = new Lazy>(() => FindCreateScenarioContainerBuilder(typeof(ScenarioDependenciesAttribute), typeof(ContainerBuilder), (_, m) => (ContainerBuilder)m.Invoke(null, null)), true); + _getFeatureLifetimeScope = new Lazy>(() => FindLifetimeScope(typeof(FeatureDependenciesAttribute), typeof(ILifetimeScope), m => (ILifetimeScope)m.Invoke(null, null))); + } - public Func GetConfigureGlobalContainer() - { - bindingRegistryBuilder.BuildBindingsFromAssembly(testAssemblyProvider.TestAssembly); - return createConfigureGlobalContainer.Value; - } + public Func GetConfigureGlobalContainer() + { + return _createConfigureGlobalContainer.Value; + } - public Func GetConfigureScenarioContainer() - { - return createConfigureScenarioContainer.Value; - } + public Func GetConfigureScenarioContainer() + { + return _createConfigureScenarioContainer.Value; + } - public Func GetCreateScenarioContainerBuilder() - { - return createScenarioContainerBuilder.Value; - } + // For legacy support: configuration methods that return a container builder. + // It is recommended to use the void methods that get a container builder as a parameter + public Func GetLegacyCreateScenarioContainerBuilder() + { + return _legacyCreateScenarioContainerBuilder.Value; + } - public Func GetFeatureLifetimeScope() - { - return getFeatureLifetimeScope.Value; - } + public Func GetFeatureLifetimeScope() + { + return _getFeatureLifetimeScope.Value; + } - protected virtual Func FindLifetimeScope(Type attributeType, Type returnType, Func invoke) - { - var method = GetMethod(attributeType, returnType); + protected virtual Func FindLifetimeScope(Type attributeType, Type returnType, Func invoke) + { + var method = GetMethod(attributeType, returnType); - return method == null - ? null - : () => invoke(method); - } + return method == null + ? null + : () => invoke(method); + } - protected virtual Func FindCreateScenarioContainerBuilder(Type attributeType, Type returnType, Func invoke) - { - var method = GetMethod(attributeType, returnType); + protected virtual Func FindCreateScenarioContainerBuilder(Type attributeType, Type returnType, Func invoke) + { + var method = GetMethod(attributeType, returnType); - return method == null - ? null - : containerBuilder => invoke(containerBuilder, method); - } + return method == null + ? null + : containerBuilder => invoke(containerBuilder, method); + } - private MethodInfo GetMethod(Type attributeType, Type returnType) - { - return bindingRegistry.GetBindingAssemblies() - .SelectMany(x => x.GetTypes()) - .SelectMany(x => x.GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public)) - .Where(x => x.ReturnType == returnType) - .FirstOrDefault(x => Attribute.IsDefined(x, attributeType)); - } + private MethodInfo GetMethod(Type attributeType, Type returnType) + { + return _configurationMethodsProvider.GetConfigurationMethods() + .Where(x => x.ReturnType == returnType) + .FirstOrDefault(x => Attribute.IsDefined(x, attributeType)); } } \ No newline at end of file diff --git a/Plugins/Reqnroll.Autofac.ReqnrollPlugin/IConfigurationMethodsProvider.cs b/Plugins/Reqnroll.Autofac.ReqnrollPlugin/IConfigurationMethodsProvider.cs new file mode 100644 index 000000000..92a5d3848 --- /dev/null +++ b/Plugins/Reqnroll.Autofac.ReqnrollPlugin/IConfigurationMethodsProvider.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; +using System.Reflection; + +namespace Reqnroll.Autofac; + +public interface IConfigurationMethodsProvider +{ + IEnumerable GetConfigurationMethods(); +} diff --git a/Plugins/Reqnroll.Autofac.ReqnrollPlugin/IContainerBuilderFinder.cs b/Plugins/Reqnroll.Autofac.ReqnrollPlugin/IContainerBuilderFinder.cs index c321b2d30..04e5d046a 100644 --- a/Plugins/Reqnroll.Autofac.ReqnrollPlugin/IContainerBuilderFinder.cs +++ b/Plugins/Reqnroll.Autofac.ReqnrollPlugin/IContainerBuilderFinder.cs @@ -9,7 +9,7 @@ public interface IContainerBuilderFinder Func GetConfigureGlobalContainer(); - Func GetCreateScenarioContainerBuilder(); + Func GetLegacyCreateScenarioContainerBuilder(); Func GetFeatureLifetimeScope(); } diff --git a/Plugins/Reqnroll.Autofac.ReqnrollPlugin/Reqnroll.Autofac.ReqnrollPlugin.csproj b/Plugins/Reqnroll.Autofac.ReqnrollPlugin/Reqnroll.Autofac.ReqnrollPlugin.csproj index a5fd3ba84..c46ff72dc 100644 --- a/Plugins/Reqnroll.Autofac.ReqnrollPlugin/Reqnroll.Autofac.ReqnrollPlugin.csproj +++ b/Plugins/Reqnroll.Autofac.ReqnrollPlugin/Reqnroll.Autofac.ReqnrollPlugin.csproj @@ -11,6 +11,8 @@ true true $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb + + Reqnroll.Autofac diff --git a/Reqnroll/BoDi/IObjectContainer.cs b/Reqnroll/BoDi/IObjectContainer.cs index 29e61c1c8..d906e0638 100644 --- a/Reqnroll/BoDi/IObjectContainer.cs +++ b/Reqnroll/BoDi/IObjectContainer.cs @@ -61,6 +61,14 @@ public interface IObjectContainer : IDisposable /// A name to resolve named instance, otherwise null. IStrategyRegistration RegisterFactoryAs(Func factoryDelegate, string name = null); + /// + /// Registers an instance produced by . The delegate will be called only once and the instance it returned will be returned in each resolution. + /// + /// Interface to register as. + /// The function to run to obtain the instance. + /// A name to resolve named instance, otherwise null. + IStrategyRegistration RegisterFactoryAs(Func factoryDelegate, string name = null); + /// /// Resolves an implementation object for an interface or type. /// diff --git a/Tests/Reqnroll.PluginTests/Autofac/AutofacPluginTests.cs b/Tests/Reqnroll.PluginTests/Autofac/AutofacPluginTests.cs new file mode 100644 index 000000000..a40ce4bab --- /dev/null +++ b/Tests/Reqnroll.PluginTests/Autofac/AutofacPluginTests.cs @@ -0,0 +1,291 @@ +using System; +using System.Collections.Specialized; +using System.Globalization; +using System.Linq; +using System.Reflection; +using Autofac; +using Autofac.Core.Registration; +using FluentAssertions; +using Moq; +using Reqnroll.Autofac; +using Reqnroll.Autofac.ReqnrollPlugin; +using Reqnroll.BoDi; +using Reqnroll.Configuration; +using Reqnroll.Infrastructure; +using Reqnroll.Plugins; +using Reqnroll.UnitTestProvider; +using Xunit; + +namespace Reqnroll.PluginTests.Autofac; +public class AutofacPluginTests +{ + class TestableAutofacPlugin : AutofacPlugin + { + private readonly IConfigurationMethodsProvider _configurationMethodsProvider; + + public TestableAutofacPlugin(Type configType, params string[] configMethodNames) + { + var configMethods = configType.GetMethods().AsEnumerable(); + if (configMethodNames.Any()) + { + configMethods = configMethods.Where(m => configMethodNames.Contains(m.Name)); + } + var configurationMethodsProviderMock = new Mock(); + configurationMethodsProviderMock.Setup(m => m.GetConfigurationMethods()).Returns(configMethods.ToArray()); + _configurationMethodsProvider = configurationMethodsProviderMock.Object; + } + + protected override void RegisterTestRunPluginTypes(ObjectContainer testRunContainer) + { + base.RegisterTestRunPluginTypes(testRunContainer); + testRunContainer.RegisterInstanceAs(_configurationMethodsProvider); + } + } + + interface IScenarioDependency1; + class ScenarioDependency1 : IScenarioDependency1; + class ScenarioDependency2(IGlobalDependency1 globalDependency1) : IScenarioDependency1 + { + public IGlobalDependency1 CurrentGlobalDependency1 { get; } = globalDependency1; + } + + interface IGlobalDependency1; + class GlobalDependency1 : IGlobalDependency1; + + public class ContainerSetup1 + { + [GlobalDependencies] + public static void SetupGlobalContainer(global::Autofac.ContainerBuilder containerBuilder) + { + containerBuilder + .RegisterType() + .As() + .SingleInstance(); + } + + [ScenarioDependencies] + public static void SetupScenarioContainer(global::Autofac.ContainerBuilder containerBuilder) + { + containerBuilder + .RegisterType() + .As() + .SingleInstance(); + } + + [ScenarioDependencies] + public static void SetupScenarioContainerWithGlobalDep(global::Autofac.ContainerBuilder containerBuilder) + { + containerBuilder + .RegisterType() + .As() + .SingleInstance(); + } + } + + public class ContainerSetup2 + { + private static readonly ILifetimeScope _existingGlobalScope; + + static ContainerSetup2() + { + var containerBuilder = new global::Autofac.ContainerBuilder(); + ContainerSetup1.SetupGlobalContainer(containerBuilder); + _existingGlobalScope = containerBuilder.Build(); + } + + [FeatureDependencies] + public static ILifetimeScope GetFeatureLifetimeScope() + { + return _existingGlobalScope.BeginLifetimeScope(); + } + + [ScenarioDependencies] + public static void SetupScenarioContainer(global::Autofac.ContainerBuilder containerBuilder) + { + ContainerSetup1.SetupScenarioContainer(containerBuilder); + } + } + +private readonly RuntimePluginEvents _runtimePluginEvents; + private readonly ObjectContainer _testRunContainer; + private readonly ObjectContainer _testThreadContainer; + private readonly ObjectContainer _featureContainer; + + public AutofacPluginTests() + { + _runtimePluginEvents = new RuntimePluginEvents(); + _testRunContainer = new ObjectContainer(); + var testAssemblyProviderMock = new Mock(); + testAssemblyProviderMock.SetupGet(tap => tap.TestAssembly).Returns(Assembly.GetExecutingAssembly()); + _testRunContainer.RegisterInstanceAs(testAssemblyProviderMock.Object); + + _testThreadContainer = new ObjectContainer(_testRunContainer); + _featureContainer = new ObjectContainer(_testThreadContainer); + } + + [Fact] + public void Should_register_AutofacTestObjectResolver() + { + // Arrange + var sut = new AutofacPlugin(); + + // Act + sut.Initialize(_runtimePluginEvents, new RuntimePluginParameters(), new UnitTestProviderConfiguration()); + var reqnrollConfiguration = ConfigurationLoader.GetDefault(); + _runtimePluginEvents.RaiseCustomizeGlobalDependencies(_testRunContainer, reqnrollConfiguration); + + // Assert + _testRunContainer.IsRegistered().Should().BeTrue(); + _testRunContainer.Resolve().Should().BeOfType(); + } + + [Fact] + public void Should_allow_global_registrations_to_TestThreadContainer() + { + // Arrange + var sut = new TestableAutofacPlugin(typeof(ContainerSetup1), nameof(ContainerSetup1.SetupGlobalContainer)); + + // Act + sut.Initialize(_runtimePluginEvents, new RuntimePluginParameters(), new UnitTestProviderConfiguration()); + var reqnrollConfiguration = ConfigurationLoader.GetDefault(); + _runtimePluginEvents.RaiseCustomizeGlobalDependencies(_testRunContainer, reqnrollConfiguration); + + var testThreadContainer = new ObjectContainer(_testRunContainer); + _runtimePluginEvents.RaiseCustomizeTestThreadDependencies(testThreadContainer); + + // Assert + var resolver = _testRunContainer.Resolve(); + var globalDep1 = resolver.ResolveBindingInstance(typeof(IGlobalDependency1), testThreadContainer); + globalDep1.Should().NotBeNull(); + } + + private ObjectContainer InitializeToScenarioContainer(TestableAutofacPlugin sut) + { + sut.Initialize(_runtimePluginEvents, new RuntimePluginParameters(), new UnitTestProviderConfiguration()); + var reqnrollConfiguration = ConfigurationLoader.GetDefault(); + _runtimePluginEvents.RaiseCustomizeGlobalDependencies(_testRunContainer, reqnrollConfiguration); + _runtimePluginEvents.RaiseCustomizeTestThreadDependencies(_testThreadContainer); + _runtimePluginEvents.RaiseCustomizeFeatureDependencies(_featureContainer); + + var scenarioContainer = new ObjectContainer(_featureContainer); + _runtimePluginEvents.RaiseCustomizeScenarioDependencies(scenarioContainer); + + return scenarioContainer; + } + + [Fact] + public void Should_allow_scenario_specific_registrations() + { + // Arrange + var sut = new TestableAutofacPlugin(typeof(ContainerSetup1), + nameof(ContainerSetup1.SetupGlobalContainer), + nameof(ContainerSetup1.SetupScenarioContainer)); + + // Act + var scenarioContainer = InitializeToScenarioContainer(sut); + + // Assert + var resolver = _testRunContainer.Resolve(); + var scenarioDep1 = resolver.ResolveBindingInstance(typeof(IScenarioDependency1), scenarioContainer); + scenarioDep1.Should().NotBeNull(); + + var globalDep1 = resolver.ResolveBindingInstance(typeof(IGlobalDependency1), scenarioContainer); + globalDep1.Should().NotBeNull(); + } + + [Fact] + public void Should_allow_scenario_specific_registrations_without_global_registrations() + { + // Arrange + var sut = new TestableAutofacPlugin(typeof(ContainerSetup1), + nameof(ContainerSetup1.SetupScenarioContainer)); + + // Act + var scenarioContainer = InitializeToScenarioContainer(sut); + + // Assert + var resolver = _testRunContainer.Resolve(); + var scenarioDep1 = resolver.ResolveBindingInstance(typeof(IScenarioDependency1), scenarioContainer); + scenarioDep1.Should().NotBeNull(); + + FluentActions.Invoking(() => resolver.ResolveBindingInstance(typeof(IGlobalDependency1), scenarioContainer)) + .Should() + .Throw(); + } + + + [Fact] + public void Should_allow_resolving_common_reqnroll_objects() + { + // Arrange + var sut = new TestableAutofacPlugin(typeof(ContainerSetup1), + nameof(ContainerSetup1.SetupGlobalContainer), + nameof(ContainerSetup1.SetupScenarioContainer)); + + // Act + var scenarioContainer = InitializeToScenarioContainer(sut); + var resolver = _testRunContainer.Resolve(); + var scenarioContext = new ScenarioContext(scenarioContainer, new ScenarioInfo("", "", Array.Empty(), new OrderedDictionary()), resolver); + scenarioContainer.RegisterInstanceAs(scenarioContext); + var featureContext = new FeatureContext(scenarioContainer, new FeatureInfo(CultureInfo.CurrentCulture, "", "", ""), ConfigurationLoader.GetDefault()); + _featureContainer.RegisterInstanceAs(featureContext); + var testThreadContext = new TestThreadContext(_testThreadContainer); + _testThreadContainer.RegisterInstanceAs(testThreadContext); + + // Assert + var resolvedContainer = resolver.ResolveBindingInstance(typeof(IObjectContainer), scenarioContainer); + resolvedContainer.Should().BeSameAs(scenarioContainer); + + var resolvedScenarioContext = resolver.ResolveBindingInstance(typeof(ScenarioContext), scenarioContainer); + resolvedScenarioContext.Should().BeSameAs(scenarioContext); + + var resolvedFeatureContext = resolver.ResolveBindingInstance(typeof(FeatureContext), scenarioContainer); + resolvedFeatureContext.Should().BeSameAs(featureContext); + + var resolvedTestThreadContext = resolver.ResolveBindingInstance(typeof(TestThreadContext), scenarioContainer); + resolvedTestThreadContext.Should().BeSameAs(testThreadContext); + } + + + [Fact] + public void Should_allow_having_scenario_dependency_that_depends_on_a_global() + { + // Arrange + var sut = new TestableAutofacPlugin(typeof(ContainerSetup1), + nameof(ContainerSetup1.SetupGlobalContainer), + nameof(ContainerSetup1.SetupScenarioContainerWithGlobalDep)); + + // Act + var scenarioContainer = InitializeToScenarioContainer(sut); + + // Assert + + + var resolver = _testRunContainer.Resolve(); + var scenarioDep1 = resolver.ResolveBindingInstance(typeof(IScenarioDependency1), scenarioContainer); + var dep2 = scenarioDep1.Should().BeOfType().Subject; + + var globalDep1 = resolver.ResolveBindingInstance(typeof(IGlobalDependency1), scenarioContainer); + globalDep1.Should().NotBeNull(); + + dep2.CurrentGlobalDependency1.Should().BeSameAs(globalDep1); + } + + [Fact] + public void Should_allow_using_existing_global_scope() + { + // Arrange + var sut = new TestableAutofacPlugin(typeof(ContainerSetup2)); + + // Act + var scenarioContainer = InitializeToScenarioContainer(sut); + + // Assert + var resolver = _testRunContainer.Resolve(); + var scenarioDep1 = resolver.ResolveBindingInstance(typeof(IScenarioDependency1), scenarioContainer); + scenarioDep1.Should().NotBeNull(); + + var globalDep1 = resolver.ResolveBindingInstance(typeof(IGlobalDependency1), scenarioContainer); + globalDep1.Should().NotBeNull(); + } +} diff --git a/Tests/Reqnroll.PluginTests/Reqnroll.PluginTests.csproj b/Tests/Reqnroll.PluginTests/Reqnroll.PluginTests.csproj index 856f60172..d1b454626 100644 --- a/Tests/Reqnroll.PluginTests/Reqnroll.PluginTests.csproj +++ b/Tests/Reqnroll.PluginTests/Reqnroll.PluginTests.csproj @@ -10,6 +10,7 @@ + diff --git a/Tests/Reqnroll.RuntimeTests/Reqnroll.RuntimeTests.csproj b/Tests/Reqnroll.RuntimeTests/Reqnroll.RuntimeTests.csproj index f1ac8b1ef..e1972e83a 100644 --- a/Tests/Reqnroll.RuntimeTests/Reqnroll.RuntimeTests.csproj +++ b/Tests/Reqnroll.RuntimeTests/Reqnroll.RuntimeTests.csproj @@ -41,10 +41,6 @@ - - - - PreserveNewest diff --git a/docs/integrations/autofac.md b/docs/integrations/autofac.md index 2fe5024ca..242cd1e31 100644 --- a/docs/integrations/autofac.md +++ b/docs/integrations/autofac.md @@ -48,41 +48,47 @@ using Reqnroll.Autofac.ReqnrollPlugin; ``` Then ```csharp -containerBuilder.AddReqnrollBindings(typeof(YourClassInTheReqnrollProject)) +containerBuilder.AddReqnrollBindings() ``` Or overload ```csharp -containerBuilder.AddReqnrollBindings() +containerBuilder.AddReqnrollBindings(Assembly.GetExecutingAssembly()) ``` Or manually register like so: ```csharp builder - .RegisterAssemblyTypes(typeof(TestDependencies).Assembly) + .RegisterAssemblyTypes(typeof(AnyClassInTheReqnrollProject).Assembly) .Where(t => Attribute.IsDefined(t, typeof(BindingAttribute))) .SingleInstance(); ``` ### 3. A typical dependency builder method for `[GlobalDependencies]` with `[ScenarioDependencies]` probably looks like this: ```csharp -[GlobalDependencies] -public static void CreateGlobalContainer(ContainerBuilder containerBuilder) +public class SetupTestDependencies { + [GlobalDependencies] + public static void SetupGlobalContainer(ContainerBuilder containerBuilder) + { // Register globally scoped runtime dependencies - Dependencies.RegisterGlobalDependencies(containerBuilder); - - //TODO: add Services that are shared globally. -} - -[ScenarioDependencies] -public static void CreateContainerBuilder(ContainerBuilder containerBuilder) -{ - // Register scenario scoped runtime dependencies - Dependencies.RegisterScenarioDependencies(containerBuilder); - - //TODO: add customizations, stubs required for testing - - containerBuilder.AddReqnrollBindings() + containerBuilder + .RegisterType() + .As() + .SingleInstance(); + } + + [ScenarioDependencies] + public static void SetupScenarioContainer(ContainerBuilder containerBuilder) + { + // Register scenario scoped runtime dependencies + containerBuilder + .RegisterType() + .As() + .SingleInstance(); + + // register binding classes + containerBuilder.AddReqnrollBindings(); + } } ``` @@ -109,8 +115,7 @@ public static ILifetimeScope GetFeatureLifetimeScope() public static void ConfigureContainerBuilder(ContainerBuilder containerBuilder) { //TODO: add customizations, stubs required for testing - - containerBuilder.AddReqnrollBindings(); + containerBuilder.AddReqnrollBindings(); } ```