// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Xunit.Sdk;
namespace SixLabors.ImageSharp.Tests
{
///
/// Base class for Theory Data attributes which pass an instance of to the test case.
///
public abstract class ImageDataAttributeBase : DataAttribute
{
protected readonly object[] AdditionalParameters;
protected readonly PixelTypes PixelTypes;
///
/// Initializes a new instance of the class.
///
///
///
///
protected ImageDataAttributeBase(string memberName, PixelTypes pixelTypes, object[] additionalParameters)
{
this.PixelTypes = pixelTypes;
this.AdditionalParameters = additionalParameters;
this.MemberName = memberName;
}
///
/// Gets the member name
///
public string MemberName { get; }
///
/// Gets the member type
///
public Type MemberType { get; set; }
/// Returns the data to be used to test the theory.
/// The method that is being tested
/// One or more sets of theory data. Each invocation of the test method
/// is represented by a single object array.
public override IEnumerable GetData(MethodInfo testMethod)
{
IEnumerable addedRows = Enumerable.Empty().ToArray();
if (!string.IsNullOrWhiteSpace(this.MemberName))
{
Type type = this.MemberType ?? testMethod.DeclaringType;
Func accessor = this.GetPropertyAccessor(type, this.MemberName) ?? this.GetFieldAccessor(type, this.MemberName);
if (accessor != null)
{
object obj = accessor();
if (obj is IEnumerable memberItems)
{
addedRows = memberItems.Select(x => x as object[]);
if (addedRows.Any(x => x == null))
{
throw new ArgumentException($"Property {this.MemberName} on {this.MemberType ?? testMethod.DeclaringType} yielded an item that is not an object[]");
}
}
}
}
if (!addedRows.Any())
{
addedRows = new[] { new object[0] };
}
bool firstIsprovider = this.FirstIsProvider(testMethod);
if (firstIsprovider)
{
return this.InnerGetData(testMethod, addedRows);
}
return addedRows.Select(x => x.Concat(this.AdditionalParameters).ToArray());
}
///
/// Returns a value indicating whether the first parameter of the method is a test provider.
///
///
///
private bool FirstIsProvider(MethodInfo testMethod)
{
TypeInfo dataType = testMethod.GetParameters().First().ParameterType.GetTypeInfo();
return dataType.IsGenericType && dataType.GetGenericTypeDefinition() == typeof(TestImageProvider<>);
}
private IEnumerable InnerGetData(MethodInfo testMethod, IEnumerable memberData)
{
foreach (KeyValuePair kv in this.PixelTypes.ExpandAllTypes())
{
Type factoryType = typeof(TestImageProvider<>).MakeGenericType(kv.Value);
foreach (object[] originalFacoryMethodArgs in this.GetAllFactoryMethodArgs(testMethod, factoryType))
{
foreach (object[] row in memberData)
{
object[] actualFactoryMethodArgs = new object[originalFacoryMethodArgs.Length + 2];
Array.Copy(originalFacoryMethodArgs, actualFactoryMethodArgs, originalFacoryMethodArgs.Length);
actualFactoryMethodArgs[actualFactoryMethodArgs.Length - 2] = testMethod;
actualFactoryMethodArgs[actualFactoryMethodArgs.Length - 1] = kv.Key;
object factory = factoryType.GetMethod(this.GetFactoryMethodName(testMethod))
.Invoke(null, actualFactoryMethodArgs);
object[] result = new object[this.AdditionalParameters.Length + 1 + row.Length];
result[0] = factory;
Array.Copy(row, 0, result, 1, row.Length);
Array.Copy(this.AdditionalParameters, 0, result, 1 + row.Length, this.AdditionalParameters.Length);
yield return result;
}
}
}
}
///
/// Generates the collection of method arguments from the given test as a generic enumerable.
///
/// The test method
/// The test image provider factory type
/// The
protected virtual IEnumerable GetAllFactoryMethodArgs(MethodInfo testMethod, Type factoryType)
{
object[] args = this.GetFactoryMethodArgs(testMethod, factoryType);
return Enumerable.Repeat(args, 1);
}
///
/// Generates the collection of method arguments from the given test.
///
/// The test method
/// The test image provider factory type
/// The
protected virtual object[] GetFactoryMethodArgs(MethodInfo testMethod, Type factoryType)
{
throw new InvalidOperationException("Semi-abstract method");
}
///
/// Generates the method name from the given test method.
///
/// The test method
/// The
protected abstract string GetFactoryMethodName(MethodInfo testMethod);
///
/// Gets the field accessor for the given type.
///
protected Func GetFieldAccessor(Type type, string memberName)
{
FieldInfo fieldInfo = null;
for (Type reflectionType = type; reflectionType != null; reflectionType = reflectionType.GetTypeInfo().BaseType)
{
fieldInfo = reflectionType.GetRuntimeField(memberName);
if (fieldInfo != null)
break;
}
if (fieldInfo == null || !fieldInfo.IsStatic)
return null;
return () => fieldInfo.GetValue(null);
}
///
/// Gets the property accessor for the given type.
///
protected Func GetPropertyAccessor(Type type, string memberName)
{
PropertyInfo propInfo = null;
for (Type reflectionType = type; reflectionType != null; reflectionType = reflectionType.GetTypeInfo().BaseType)
{
propInfo = reflectionType.GetRuntimeProperty(memberName);
if (propInfo != null)
{
break;
}
}
if (propInfo?.GetMethod == null || !propInfo.GetMethod.IsStatic)
{
return null;
}
return () => propInfo.GetValue(null, null);
}
}
}