csharp/42skillz/Diverse/Diverse/Types/TypeFuzzer.cs

TypeFuzzer.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace Diverse
{
    /// 
    /// Fuzz instance of Types.
    /// 
    internal clast TypeFuzzer : IFuzzTypes
    {
        private const int MaxRecursionAllowedWhileFuzzing = 125;
        private const int MaxCountToFuzzInLists = 5;
        private readonly IFuzz _fuzzer;

        /// 
        /// Instantiates a .
        /// 
        /// Instance of  to use.
        public TypeFuzzer(IFuzz fuzzer)
        {
            _fuzzer = fuzzer;
        }

        /// 
        /// Generates an instance of a type T.
        /// 
        /// An instance of type T with some fuzzed properties.
        public T GenerateInstanceOf()
        {
            var instance = GenerateInstanceOf(0);

            return instance;
        }

        private T GenerateInstanceOf(int recursionLevel)
        {
            recursionLevel++;
            if (recursionLevel > MaxRecursionAllowedWhileFuzzing)
            {
                return default(T);
            }

            var type = typeof(T);

            var constructor = type.GetConstructorWithBiggestNumberOfParameters();
            if (constructor == null || type.IsCoveredByAFuzzer()) // the case with some BCL types
            {
                var instance = FuzzAnyDotNetType(Type.GetTypeCode(type), type, recursionLevel);
                return (T)instance;
            }

            if (constructor.IsEmpty())
            {
                var instance = InstantiateAndFuzzViaPropertiesWhenTheyHaveSetters(constructor, recursionLevel, type);
                return instance;
            }
            else
            {
                try
                {
                    return InstantiateAndFuzzViaConstructorWithBiggestNumberOfParameters(constructor, recursionLevel);
                }
                catch (Exception)
                {
                    return InstantiateAndFuzzViaOtherConstructorIteratingOnAllThemUntilItWorks(recursionLevel, type);
                }
            }
        }

        private T InstantiateAndFuzzViaConstructorWithBiggestNumberOfParameters(ConstructorInfo constructor, int recursionLevel)
        {
            var constructorParameters = PrepareFuzzedParametersForThisConstructor(constructor, recursionLevel);
            var instance = constructor.Invoke(constructorParameters);
            return (T)instance;
        }

        private T InstantiateAndFuzzViaPropertiesWhenTheyHaveSetters(ConstructorInfo constructor, int recursionLevel,
            Type genericType, object instance = null)
        {
            if (instance == null)
            {
                instance = constructor.Invoke(new object[0]);
            }

            var propertyInfos = genericType.GetProperties().Where(prop => prop.CanWrite);
            foreach (var propertyInfo in propertyInfos)
            {
                var propertyType = propertyInfo.PropertyType;
                var propertyValue = FuzzAnyDotNetType(Type.GetTypeCode(propertyType), propertyType, recursionLevel);

                propertyInfo.SetValue(instance, propertyValue);
            }

            return (T)instance;
        }

        private object[] PrepareFuzzedParametersForThisConstructor(ConstructorInfo constructor, int recursionLevel)
        {
            var parameters = new List();
            var parameterInfos = constructor.GetParameters();
            foreach (var parameterInfo in parameterInfos)
            {
                var type = parameterInfo.ParameterType;

                // Default .NET types
                parameters.Add(FuzzAnyDotNetType(Type.GetTypeCode(type), type, recursionLevel));
            }

            return parameters.ToArray();
        }

        private object FuzzAnyDotNetType(TypeCode typeCode, Type type, int recursionLevel)
        {
            if (type.IsEnum)
            {
                return FuzzEnumValue(type);
            }

            if (type.IsEnumerable())
            {
                return GenerateListOf(type, recursionLevel);
            }

            object result;
            switch (typeCode)
            {
                case TypeCode.Boolean:
                    result = _fuzzer.HeadsOrTails();
                    break;

                case TypeCode.Int32:
                    result = _fuzzer.GenerateInteger();
                    break;

                case TypeCode.Int64:
                    result = _fuzzer.GenerateLong();
                    break;

                case TypeCode.Decimal:
                    result = _fuzzer.GeneratePositiveDecimal();
                    break;

                case TypeCode.String:
                    result = _fuzzer.GenerateFirstName();
                    break;

                case TypeCode.DateTime:
                    result = _fuzzer.GenerateDateTime();
                    break;

                default:
                    // is it an IEnumerable?
                    result = GenerateInstanceOf(type, recursionLevel);
                    break;
            }

            return result;
        }

        private T InstantiateAndFuzzViaOtherConstructorIteratingOnAllThemUntilItWorks(int recursionLevel, Type type)
        {
            T instance;
            // Some constructor are complicated to use (e.g. those accepting abstract clastes as input)
            // Try other constructors until it works
            var constructors = type.GetConstructorsOrderedByNumberOfParametersDesc().Skip(1);
            foreach (var constructorInfo in constructors)
            {
                try
                {
                    instance = InstantiateAndFuzzViaConstructorWithBiggestNumberOfParameters(constructorInfo, recursionLevel);
                    return instance;
                }
                catch (Exception)
                {
                    continue;
                }
            }

            // We couldn't use any of its Constructor. Let's return a default instance (degraded mode)
            instance = default(T);

            return instance;
        }

        private IEnumerable GenerateListOf(Type type, int recursionLevel)
        {
            var typeGenericTypeArguments = type.GenericTypeArguments;

            var listType = typeof(List);
            var constructedListType = listType.MakeGenericType(typeGenericTypeArguments);

            // Instantiates a collection of ...
            var list = Activator.CreateInstance(constructedListType) as IList;

            // Add 5 elements of this type
            for (var i = 0; i < MaxCountToFuzzInLists; i++)
            {
                list.Add(GenerateInstanceOf(typeGenericTypeArguments.Single(), recursionLevel));
            }

            return list;
        }

        private object GenerateInstanceOf(Type type, int recursionLevel)
        {
            return CallPrivateGenericMethod(type, nameof(GenerateInstanceOf), new object[] { recursionLevel });
        }

        private object CallPrivateGenericMethod(Type typeOfT, string privateMethodName, object[] parameters)
        {
            var methodInfo = ((TypeInfo) typeof(TypeFuzzer)).DeclaredMethods.Single(m =>
                m.IsGenericMethod && m.IsPrivate && m.Name.Contains(privateMethodName));
            var generic = methodInfo.MakeGenericMethod(typeOfT);
            
            // private T GenerateInstanceOf(int recursionLevel)
            var result = generic.Invoke(this, parameters);
            
            return result;
        }

        /// 
        /// Generates an instance of an  type.
        /// 
        /// Type of the 
        /// An random value of the specified  type.
        public T GenerateEnum()
        {
            var enumValues = Enum.GetValues(typeof(T));
            return (T)enumValues.GetValue(_fuzzer.Random.Next(0, enumValues.Length));
        }

        private object FuzzEnumValue(Type enumType)
        {
            var enumValues = Enum.GetValues(enumType);
            return enumValues.GetValue(_fuzzer.Random.Next(0, enumValues.Length));
        }
    }
}