using System; using System.Collections.Generic; using UnitTestSharp; using System.Reflection; using UnitTestSharp.Extensions; using Azimuth.DenseLinearAlgebra; namespace Azimuth.UnitTests { public interface ITestFixture { MethodInfo MethodInfo { get; } } [IgnoreFixture] public abstract class BlasBoundsFixtureBase : UnitTestSharp.TestFixture { [System.AttributeUsage(System.AttributeTargets.Method, AllowMultiple = false, Inherited = false)] public class AAttribute : System.Attribute { } [System.AttributeUsage(System.AttributeTargets.Method, AllowMultiple = false, Inherited = false)] public class BAttribute : System.Attribute { } [System.AttributeUsage(System.AttributeTargets.Method, AllowMultiple = false, Inherited = false)] public class CAttribute : System.Attribute { } [System.AttributeUsage(System.AttributeTargets.Method, AllowMultiple = false, Inherited = false)] public class DAttribute : System.Attribute { } [System.AttributeUsage(System.AttributeTargets.Method, AllowMultiple = false, Inherited = false)] public class VectorAUnchangedAttribute : AAttribute { } [System.AttributeUsage(System.AttributeTargets.Method, AllowMultiple = false, Inherited = false)] public class VectorBUnchangedAttribute : BAttribute { } [System.AttributeUsage(System.AttributeTargets.Method, AllowMultiple = false, Inherited = false)] public class VectorCUnchangedAttribute : CAttribute { } [System.AttributeUsage(System.AttributeTargets.Method, AllowMultiple = false, Inherited = false)] public class VectorDUnchangedAttribute : DAttribute { } [System.AttributeUsage(System.AttributeTargets.Method, AllowMultiple = false, Inherited = false)] public class InstanceAttribute : System.Attribute { } public class Arguments where VectorType : class, IVector { public object[] Parameters { get; private set; } private int LengthIndex => ParameterDefinitions.Length - 1 - extraParametersAfterLength; private ParameterInfo[] ParameterDefinitions { get; set; } public int OtherVectorsCount { get; private set; } public VectorType Instance { get; private set; } public int InstanceOperationStride { get; private set; } public int VectorAOperationStride { get; private set; } public int VectorBOperationStride { get; private set; } public int VectorCOperationStride { get; private set; } public int VectorDOperationStride { get; private set; } private int OffsetsStartIndex { get; set; } public Func VectorFactory { get; private set; } public bool IsStatic => originatorOffsetCount == 0; private int originatorOffsetCount; private int vectorsStart; private int extraParametersAfterLength; private Dictionary> nonVectorTypeFactories; public VectorType A { get => (VectorType)Parameters[vectorsStart + 0]; set => Parameters[vectorsStart + 0] = value; } public VectorType B { get => (VectorType)Parameters[vectorsStart + 1]; set => Parameters[vectorsStart + 1] = value; } public VectorType C { get => (VectorType)Parameters[vectorsStart + 2]; set => Parameters[vectorsStart + 2] = value; } public VectorType D { get => (VectorType)Parameters[vectorsStart + 3]; set => Parameters[vectorsStart + 3] = value; } public int Offset { get { if (OffsetsStartIndex >= 0) return (int)Parameters[OffsetsStartIndex]; return 0; } set { if (OffsetsStartIndex >= 0) Parameters[OffsetsStartIndex] = value; } } public int OffsetA { get => (int)Parameters[OffsetsStartIndex + originatorOffsetCount + 0]; set => Parameters[OffsetsStartIndex + originatorOffsetCount + 0] = value; } public int OffsetB { get => (int)Parameters[OffsetsStartIndex + originatorOffsetCount + 1]; set => Parameters[OffsetsStartIndex + originatorOffsetCount + 1] = value; } public int OffsetC { get => (int)Parameters[OffsetsStartIndex + originatorOffsetCount + 2]; set => Parameters[OffsetsStartIndex + originatorOffsetCount + 2] = value; } public int OffsetD { get => (int)Parameters[OffsetsStartIndex + originatorOffsetCount + 3]; set => Parameters[OffsetsStartIndex + originatorOffsetCount + 3] = value; } public int Length { get => (int)(int?)Parameters[LengthIndex]; set => Parameters[LengthIndex] = (int?)value; } public static Type GetType(ParameterInfo parameter) { var parameterType = parameter.ParameterType; if (parameterType.IsByRef) { return parameterType.GetElementType(); } return parameterType; } public Arguments(MethodInfo methodInfo, Func vectorFactory, Dictionary> nonVectorTypeFactories, int instanceOperationStride, int vectorAOperationStride, int vectorBOperationStride, int vectorCOperationStride, int vectorDOperationStride) { this.nonVectorTypeFactories = nonVectorTypeFactories; InstanceOperationStride = instanceOperationStride; VectorAOperationStride = vectorAOperationStride; VectorBOperationStride = vectorBOperationStride; VectorCOperationStride = vectorCOperationStride; VectorDOperationStride = vectorDOperationStride; VectorFactory = vectorFactory; ParameterDefinitions = methodInfo.GetParameters(); Parameters = new object[ParameterDefinitions.Length]; originatorOffsetCount = methodInfo.IsStatic ? 0 : 1; extraParametersAfterLength = 0; for (int i = ParameterDefinitions.Length - 1; i >= 0; --i) { if (ParameterDefinitions[i].ParameterType == typeof(int?)) { break; } extraParametersAfterLength++; } OtherVectorsCount = 0; OffsetsStartIndex = -1; vectorsStart = -1; for (int i = 0; i < ParameterDefinitions.Length; ++i) { var parameter = ParameterDefinitions[i]; if (parameter.HasDefaultValue) { OffsetsStartIndex = i; break; } if (GetType(parameter) == typeof(VectorType)) { if (vectorsStart < 0) { vectorsStart = i; } ++OtherVectorsCount; } } if (OtherVectorsCount >= 5) { throw new NotImplementedException(); } for (int i = OffsetsStartIndex; i < OffsetsStartIndex + OtherVectorsCount + originatorOffsetCount; ++i) { if (ParameterDefinitions[i].ParameterType != typeof(int)) { throw new InvalidOperationException( $"The parameter at index {i} was expected to be an int but was a " + $"{ParameterDefinitions[i].ParameterType}"); } } if (ParameterDefinitions[LengthIndex].ParameterType != typeof(int?)) { throw new InvalidOperationException( $"The last parameter was expected to be an int? but was a " + $"{ParameterDefinitions[LengthIndex].ParameterType}"); } for (int i = LengthIndex + 1; i < LengthIndex + extraParametersAfterLength; ++i) { if (!ParameterDefinitions[i].HasDefaultValue) { throw new InvalidOperationException( $"Parameters after {ParameterDefinitions[LengthIndex]} should have default values."); } } } public void SetDefaults() { Length = int.MaxValue; if (originatorOffsetCount > 0) { Instance = VectorFactory(new Scalar[] { 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, }); Length = Math.Min(Length, Instance.Length / InstanceOperationStride); } else { Instance = null; } Offset = 0; for (int i = LengthIndex + 1; i <= LengthIndex + extraParametersAfterLength; ++i) { Parameters[i] = ParameterDefinitions[i].DefaultValue; } for (int i = 0; i < OffsetsStartIndex; ++i) { // Set default values for the non-vector arguments. if (GetType(ParameterDefinitions[i]) is Type type && type != typeof(VectorType)) { if (nonVectorTypeFactories?.TryGetValue(type, out var factory) == true) { Parameters[i] = factory(); } else { Parameters[i] = Activator.CreateInstance(type); } } } if (OtherVectorsCount >= 1) { A = VectorFactory(new Scalar[] { 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24}); OffsetA = 0; Length = Math.Min(Length, A.Length / VectorAOperationStride); } if (OtherVectorsCount >= 2) { B = VectorFactory(new Scalar[] { 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25}); OffsetB = 0; Length = Math.Min(Length, B.Length / VectorBOperationStride); } if (OtherVectorsCount >= 3) { C = VectorFactory(new Scalar[] { 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26}); OffsetC = 0; Length = Math.Min(Length, C.Length / VectorCOperationStride); } if (OtherVectorsCount >= 4) { D = VectorFactory(new Scalar[] { 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27}); OffsetD = 0; Length = Math.Min(Length, D.Length / VectorDOperationStride); } } public void SetZeroLengths() { Length = 0; if (originatorOffsetCount > 0) { Instance = VectorFactory(new Scalar[0]); } else { Instance = null; } if (OtherVectorsCount >= 1) { A = VectorFactory(new Scalar[0]); } if (OtherVectorsCount >= 2) { B = VectorFactory(new Scalar[0]); } if (OtherVectorsCount >= 3) { C = VectorFactory(new Scalar[0]); } if (OtherVectorsCount >= 4) { D = VectorFactory(new Scalar[0]); } } } } [IgnoreFixture] public abstract class BlasBoundsFixtureGeneric : BlasBoundsFixtureBase, ITestFixture where VectorType : class, IVector { public virtual string MethodName => throw new NotImplementedException(); public virtual MethodInfo MethodInfo => typeof(VectorType).GetMethod(MethodName); public virtual bool VectorAChanged => false; public virtual bool VectorBChanged => false; public virtual bool VectorCChanged => false; public virtual bool VectorDChanged => false; public virtual int OperationStride => 1; public virtual int InstanceOperationStride => OperationStride; public virtual int VectorAOperationStride => OperationStride; public virtual int VectorBOperationStride => OperationStride; public virtual int VectorCOperationStride => OperationStride; public virtual int VectorDOperationStride => OperationStride; public virtual bool AllowsZeroLength => true; public virtual Dictionary> TypeFactory => null; public Arguments Arguments { get; private set; } public BlasBoundsFixtureGeneric(Func vectorFactory) { Arguments = new Arguments(MethodInfo, vectorFactory, TypeFactory, InstanceOperationStride, VectorAOperationStride, VectorBOperationStride, VectorCOperationStride, VectorDOperationStride); } public override IList FindTestableMethods(ref int ignored) { var tests = new List(base.FindTestableMethods(ref ignored)); if (Arguments.OtherVectorsCount <= 3) { tests.RemoveAll(test => test.HasAttribute(typeof(DAttribute))); } if (Arguments.OtherVectorsCount <= 2) { tests.RemoveAll(test => test.HasAttribute(typeof(CAttribute))); } if (Arguments.OtherVectorsCount <= 1) { tests.RemoveAll(test => test.HasAttribute(typeof(BAttribute))); } if (Arguments.OtherVectorsCount <= 0) { tests.RemoveAll(test => test.HasAttribute(typeof(AAttribute))); } if (MethodInfo.IsStatic) { tests.RemoveAll(test => test.HasAttribute(typeof(InstanceAttribute))); } if (VectorAChanged) { tests.RemoveAll(test => test.HasAttribute(typeof(VectorAUnchangedAttribute))); } if (VectorBChanged) { tests.RemoveAll(test => test.HasAttribute(typeof(VectorBUnchangedAttribute))); } if (VectorCChanged) { tests.RemoveAll(test => test.HasAttribute(typeof(VectorCUnchangedAttribute))); } if (VectorDChanged) { tests.RemoveAll(test => test.HasAttribute(typeof(VectorDUnchangedAttribute))); } return tests.AsReadOnly(); } [IgnoreTest] public void CallMethodUnderTest() { MethodInfo.Invoke(Arguments.Instance, Arguments.Parameters); } public override void TestSetup() { Arguments.SetDefaults(); } public virtual void BaselinePasses() { CallMethodUnderTest(); } public virtual void ZeroLength() { Arguments.SetZeroLengths(); if (!AllowsZeroLength) { CheckThrow(typeof(Exception)); } CallMethodUnderTest(); if (AllowsZeroLength) { var expected = Arguments.VectorFactory(new Scalar[0]); if (!Arguments.IsStatic) { CheckEqual(expected, Arguments.Instance); } if (Arguments.OtherVectorsCount >= 1) { CheckEqual(expected, Arguments.A); } if (Arguments.OtherVectorsCount >= 2) { CheckEqual(expected, Arguments.B); } if (Arguments.OtherVectorsCount >= 3) { CheckEqual(expected, Arguments.C); } if (Arguments.OtherVectorsCount >= 4) { CheckEqual(expected, Arguments.D); } } } [Instance] public virtual void Offset_Negative() { Arguments.Length = 0; Arguments.Offset = -1; CheckThrow(typeof(Exception)); CallMethodUnderTest(); } [Instance] public virtual void Offset_TooLarge() { Arguments.Length = 0; Arguments.Offset = Arguments.Instance.Length + InstanceOperationStride; CheckThrow(typeof(Exception)); CallMethodUnderTest(); } [A] public virtual void OffsetA_Negative() { Arguments.Length = 0; Arguments.OffsetA = -1; CheckThrow(typeof(Exception)); CallMethodUnderTest(); } [A] public virtual void OffsetA_TooLarge() { Arguments.Length = 0; Arguments.OffsetA = Arguments.A.Length + VectorAOperationStride; CheckThrow(typeof(Exception)); CallMethodUnderTest(); } [B] public virtual void OffsetB_Negative() { Arguments.Length = 0; Arguments.OffsetB = -1; CheckThrow(typeof(Exception)); CallMethodUnderTest(); } [B] public virtual void OffsetB_TooLarge() { Arguments.Length = 0; Arguments.OffsetB = Arguments.B.Length + VectorBOperationStride; CheckThrow(typeof(Exception)); CallMethodUnderTest(); } [C] public virtual void OffsetC_Negative() { Arguments.Length = 0; Arguments.OffsetC = -1; CheckThrow(typeof(Exception)); CallMethodUnderTest(); } [C] public virtual void OffsetC_TooLarge() { Arguments.Length = 0; Arguments.OffsetC = Arguments.C.Length + VectorCOperationStride; CheckThrow(typeof(Exception)); CallMethodUnderTest(); } [D] public virtual void OffsetD_Negative() { Arguments.Length = 0; Arguments.OffsetD = -1; CheckThrow(typeof(Exception)); CallMethodUnderTest(); } [D] public virtual void OffsetD_TooLarge() { Arguments.Length = 0; Arguments.OffsetD = Arguments.D.Length + VectorDOperationStride; CheckThrow(typeof(Exception)); CallMethodUnderTest(); } public virtual void Length_Negative() { Arguments.Length = -1; CheckThrow(typeof(Exception)); CallMethodUnderTest(); } public virtual void Length_TooLarge() { ++Arguments.Length; CheckThrow(typeof(Exception)); CallMethodUnderTest(); } [VectorAUnchanged] public virtual void A_Unchanged() { var expected = Arguments.VectorFactory(new Scalar[] { 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24 }); CallMethodUnderTest(); CheckEqual(expected, Arguments.A); } [VectorBUnchanged] public virtual void B_Unchanged() { var expected = Arguments.VectorFactory(new Scalar[] { 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 }); CallMethodUnderTest(); CheckEqual(expected, Arguments.B); } [VectorCUnchanged] public virtual void C_Unchanged() { var expected = Arguments.VectorFactory(new Scalar[] { 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26 }); CallMethodUnderTest(); CheckEqual(expected, Arguments.C); } [VectorDUnchanged] public virtual void D_Unchanged() { var expected = Arguments.VectorFactory(new Scalar[] { 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, }); CallMethodUnderTest(); CheckEqual(expected, Arguments.D); } } /// /// Does common testing for DenseVector BLAS methods such as MADD. /// [IgnoreFixture] public abstract class BlasBoundsFixture : BlasBoundsFixtureGeneric { public BlasBoundsFixture() : base(array => new DenseVector(array)) { } } }