123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969 |
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Linq;
- using System.Reflection;
- using System.Runtime.InteropServices;
- using Burst.Compiler.IL.Tests.Helpers;
- using NUnit.Framework;
- using NUnit.Framework.Interfaces;
- using NUnit.Framework.Internal;
- using NUnit.Framework.Internal.Builders;
- using NUnit.Framework.Internal.Commands;
- using Unity.Burst;
- using Unity.Collections;
- using Unity.Mathematics;
- using UnityBenchShared;
- #if !BURST_TESTS_ONLY
- using ExecutionContext = NUnit.Framework.Internal.ITestExecutionContext;
- #else
- using ExecutionContext = NUnit.Framework.Internal.TestExecutionContext;
- #endif
-
- namespace Burst.Compiler.IL.Tests
- {
- /// <summary>
- /// When used as a type in TestCompiler arguments signifies that the corresponding parameter is a pointer output.
- /// </summary>
- internal struct ReturnBox { }
-
-
- /// <summary>
- /// Interface used for initialize function pointers.
- /// </summary>
- internal interface IFunctionPointerProvider
- {
- object FromIntPtr(IntPtr ptr);
- }
-
- /// <summary>
- /// Used to implement custom testing behaviour
- /// </summary>
- internal interface TestCompilerBaseExtensions
- {
- (bool shouldSkip, string skipReason) SkipTest(MethodInfo method);
- Type FetchAlternateDelegate(out bool isInRegistry, out Func<object, object[], object> caller);
- object[] ProcessNativeArgsForDelegateCaller(object[] nativeArgs, MethodInfo methodInfo);
- }
-
- [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)]
- internal abstract class TestCompilerAttributeBase : TestCaseAttribute, ITestBuilder, IWrapTestMethod
- {
- private readonly NUnitTestCaseBuilder _builder = new NUnitTestCaseBuilder();
-
- public const string GoldFolder = "gold/x86";
- public const string GoldFolderArm = "gold/arm";
- public const string GeneratedFolder = "generated/x86";
- public const string GeneratedFolderArm = "generated/arm";
-
- public TestCompilerAttributeBase(params object[] arguments) : base(arguments)
- {
- }
-
- /// <summary>
- /// Whether the test should only be compiled and not run. Useful for having tests that would produce infinitely running code which could ICE the compiler.
- /// </summary>
- public bool CompileOnly { get; set; }
-
- /// <summary>
- /// The type of exception the test expects to be thrown.
- /// </summary>
- public Type ExpectedException { get; set; }
-
- /// <summary>
- /// Whether the test is expected to throw a compiler exception or not.
- /// </summary>
- public bool ExpectCompilerException { get; set; }
-
- public bool DisableGold { get; set; }
-
- public DiagnosticId ExpectedDiagnosticId
- {
- get => throw new InvalidOperationException();
- set => ExpectedDiagnosticIds = new DiagnosticId[] { value };
- }
-
- public DiagnosticId[] ExpectedDiagnosticIds { get; set; } = new DiagnosticId[0];
-
- public bool FastMath { get; set; }
-
- /// <summary>
- /// Use this property when the JIT calculation is wrong (e.g when using float)
- /// </summary>
- public object OverrideResultOnMono { get; set; }
-
- /// <summary>
- /// Use this property to set the result of the managed method and skip running it completely (for example when there is no reference managed implementation)
- /// </summary>
- public object OverrideManagedResult { get; set; }
-
- /// <summary>
- /// Use this when a pointer is used in a sizeof computation, since on a 32bit target the result will differ versus our 64bit managed results.
- /// </summary>
- public object OverrideOn32BitNative { get; set; }
-
- /// <summary>
- /// Use this and specify a TargetPlatform (Host) to have the test ignored when running on that host. Mostly used by WASM at present.
- /// </summary>
- public object IgnoreOnPlatform { get; set; }
-
- public bool IgnoreOnNetFramework { get; set; }
- public bool IgnoreOnNetCore { get; set; }
-
- public bool? IsDeterministic { get; set; }
-
- public bool SkipForILInterpreter { get; set; }
-
- protected virtual bool SupportException => true;
-
- public bool IgnoreExceptionMessages { get; set; }
-
- public bool DisableStringInterpolationInExceptionMessages { get; set; }
-
- IEnumerable<TestMethod> ITestBuilder.BuildFrom(IMethodInfo method, Test suite)
- {
- // If the system doesn't support exceptions (Unity editor for delegates) we should not test with exceptions
- bool skipTest = (ExpectCompilerException || ExpectedException != null) && !SupportException;
- var expectResult = !method.ReturnType.IsType(typeof(void));
- var arguments = new List<object>(this.Arguments);
-
- // Expand arguments with IntRangeAttributes if we have them
- foreach (var param in method.GetParameters())
- {
- var attrs = param.GetCustomAttributes<IntRangeAttribute>(false);
- if (attrs == null || attrs.Length != 1)
- continue;
-
- arguments.Add(attrs[0]);
- }
-
- IEnumerable<object[]> permutations = CreatePermutation(0, arguments.ToArray(), method.GetParameters());
-
- // TODO: Workaround for a scalability bug with R# or Rider
- // Run only one testcase if not running from the commandline
- if (!IsCommandLine())
- {
- permutations = permutations.Take(1);
- }
-
- foreach (var newArguments in permutations)
- {
- var caseParameters = new TestCaseParameters(newArguments);
- if (expectResult)
- {
- caseParameters.ExpectedResult = true;
- if (OverrideResultOnMono != null && IsMono())
- {
- caseParameters.Properties.Set(nameof(OverrideResultOnMono), OverrideResultOnMono);
- }
- if (OverrideManagedResult != null)
- {
- caseParameters.Properties.Set(nameof(OverrideManagedResult), OverrideManagedResult);
- }
- if (OverrideOn32BitNative != null)
- {
- caseParameters.Properties.Set(nameof(OverrideOn32BitNative), OverrideOn32BitNative);
- }
- }
-
- // Transfer FastMath parameter to the compiler
- caseParameters.Properties.Set(nameof(FastMath), FastMath);
-
- var test = _builder.BuildTestMethod(method, suite, caseParameters);
- if (skipTest)
- {
- test.RunState = RunState.Skipped;
- test.Properties.Add(PropertyNames.SkipReason, "Exceptions are not supported");
- }
-
- yield return test;
- }
- }
-
- private static IEnumerable<object[]> CreatePermutation(int index, object[] args, IParameterInfo[] parameters)
- {
- if (index >= args.Length)
- {
- yield return args;
- yield break;
- }
- var copyArgs = (object[])args.Clone();
- bool hasRange = false;
- for (; index < args.Length; index++)
- {
- var arg = copyArgs[index];
- if (arg is DataRange)
- {
- var range = (DataRange)arg;
- // TEMP: Disable NaN test for now
- //range = range & ~(DataRange.NaN);
- foreach (var value in range.ExpandRange(parameters[index].ParameterType, index))
- {
- copyArgs[index] = value;
- foreach (var subPermutation in CreatePermutation(index + 1, copyArgs, parameters))
- {
- hasRange = true;
- yield return subPermutation;
- }
- }
- }
- else if (arg is IntRangeAttribute)
- {
- var ir = (IntRangeAttribute)arg;
- if (ir != null)
- {
- for (int x = ir.Lo; x <= ir.Hi; ++x)
- {
- copyArgs[index] = x;
-
- foreach (var subPermutation in CreatePermutation(index + 1, copyArgs, parameters))
- {
- hasRange = true;
- yield return subPermutation;
- }
- }
- }
- }
- }
- if (!hasRange)
- {
- yield return copyArgs;
- }
- }
-
- TestCommand ICommandWrapper.Wrap(TestCommand command)
- {
- var testMethod = (TestMethod)command.Test;
- return GetTestCommand(this, testMethod, testMethod);
- }
-
- protected abstract bool IsCommandLine();
-
- protected abstract bool IsMono();
-
- protected abstract TestCompilerCommandBase GetTestCommand(TestCompilerAttributeBase attribute, Test test, TestMethod originalMethod);
- }
-
- internal abstract class TestCompilerCommandBase : TestCommand
- {
- protected readonly TestMethod _originalMethod;
- private readonly bool _compileOnly;
- private readonly Type _expectedException;
- private readonly bool _ignoreExceptionMessages;
- private readonly bool _disableStringInterpolationInExceptionMessages;
- protected readonly bool _expectCompilerException;
- private readonly DiagnosticId[] _expectedDiagnosticIds;
-
- protected virtual bool TestInterpreter => false;
-
- protected TestCompilerCommandBase(TestCompilerAttributeBase attribute, Test test, TestMethod originalMethod) : base(test)
- {
- _originalMethod = originalMethod;
- Attribute = attribute;
- _compileOnly = Attribute.CompileOnly;
- _expectedException = Attribute.ExpectedException;
- _ignoreExceptionMessages = Attribute.IgnoreExceptionMessages;
- _disableStringInterpolationInExceptionMessages = Attribute.DisableStringInterpolationInExceptionMessages;
- _expectCompilerException = Attribute.ExpectCompilerException;
- _expectedDiagnosticIds = Attribute.ExpectedDiagnosticIds;
- }
-
- public TestCompilerAttributeBase Attribute { get; }
-
- public override TestResult Execute(ExecutionContext context)
- {
- TestResult lastResult = null;
- for (int i = 0; i < GetRunCount(); i++)
- {
- lastResult = ExecuteMethod(context);
- }
-
- return lastResult;
- }
-
- protected virtual Type CreateNativeDelegateType(Type returnType, Type[] arguments, out bool isInRegistry, out Func<object, object[], object> caller)
- {
- if (GetExtension() != null)
- {
- Type type = GetExtension().FetchAlternateDelegate(out isInRegistry, out caller);
- if (type != null)
- {
- return type;
- }
- }
-
- isInRegistry = false;
- StaticDelegateCallback staticDelegate;
- if (StaticDelegateRegistry.TryFind(returnType, arguments, out staticDelegate))
- {
- isInRegistry = true;
- caller = staticDelegate.Caller;
- return staticDelegate.DelegateType;
- }
- else
- {
- #if BURST_TESTS_ONLY
- // Else we try to do it with a dynamic call
- var type = DelegateHelper.NewDelegateType(returnType, arguments);
- caller = StaticDynamicDelegateCaller;
- return type;
- #else
- throw new Exception("Couldn't find delegate in static registry and not able to use a dynamic call.");
- #endif
- }
- }
-
- private static Func<object, object[], object> StaticDynamicDelegateCaller = new Func<object, object[], object>((del, arguments) => ((Delegate)del).DynamicInvoke(arguments));
-
- private static readonly int MaxReturnBoxSize = 512;
-
- protected bool RunManagedBeforeNative { get; set; }
-
- protected static readonly Dictionary<string, string> BailedTests = new Dictionary<string, string>();
-
- private string ExpectedExceptionMessage = null;
-
- private unsafe void ZeroMemory(byte* ptr, int size)
- {
- for (int i = 0; i < size; i++)
- {
- *(ptr + i) = 0;
- }
- }
-
- private unsafe TestResult ExecuteMethod(ExecutionContext context)
- {
- byte* returnBox = stackalloc byte[MaxReturnBoxSize];
- Setup();
- var methodInfo = _originalMethod.Method.MethodInfo;
-
- var runTest = TestOnCurrentHostEnvironment(methodInfo);
-
- if (runTest)
- {
- var arguments = GetArgumentsArray(_originalMethod);
-
- // We can't skip tests during BuildFrom that rely on specific options (e.g. current platform)
- // So we handle the remaining cases here via extensions
- if (GetExtension() != null)
- {
- var skip = GetExtension().SkipTest(methodInfo);
- if (skip.shouldSkip)
- {
- // For now, mark the tests as passed rather than skipped, to avoid the log spam
- //On wasm this log spam accounts for 33minutes of test execution time!!
- //context.CurrentResult.SetResult(ResultState.Skipped, skip.skipReason);
- context.CurrentResult.SetResult(ResultState.Success);
- return context.CurrentResult;
- }
- }
-
- // If we expect a compiler exception, then we need to allow argument transformation to fail,
- // because this may be the actual thing that we're trying to test.
- object[] nativeArgs = null;
- Type[] nativeArgTypes = null;
- Type returnBoxType = null;
- var transformedArguments = false;
- if (_expectCompilerException)
- {
- var expectedExceptionResult = TryExpectedException(
- context,
- () => TransformArguments(_originalMethod.Method.MethodInfo, arguments, out nativeArgs, out nativeArgTypes, returnBox, out returnBoxType),
- "Transforming arguments",
- type => true,
- "Any exception",
- false,
- false);
- if (expectedExceptionResult != TryExpectedExceptionResult.DidNotThrowException)
- {
- return context.CurrentResult;
- }
- transformedArguments = true;
- }
-
- if (!transformedArguments)
- {
- TransformArguments(_originalMethod.Method.MethodInfo, arguments, out nativeArgs, out nativeArgTypes, returnBox, out returnBoxType);
- }
-
- bool isInRegistry = false;
- Func<object, object[], object> nativeDelegateCaller;
- var delegateType = CreateNativeDelegateType(_originalMethod.Method.MethodInfo.ReturnType, nativeArgTypes, out isInRegistry, out nativeDelegateCaller);
- if (!isInRegistry)
- {
- TestContext.Out.WriteLine($"Warning, the delegate for the method `{_originalMethod.Method}` has not been generated");
- }
-
- Delegate compiledFunction;
- Delegate interpretDelegate;
- try
- {
- compiledFunction = CompileDelegate(context, methodInfo, delegateType, returnBox, out _, out interpretDelegate);
- }
- catch (Exception ex) when (_expectedException != null && ex.GetType() == _expectedException)
- {
- context.CurrentResult.SetResult(ResultState.Success);
- return context.CurrentResult;
- }
-
- Assert.IsTrue(returnBoxType == null || Marshal.SizeOf(returnBoxType) <= MaxReturnBoxSize);
-
- if (TestInterpreter)
- {
- compiledFunction = interpretDelegate;
- }
-
- if (compiledFunction == null)
- {
- return context.CurrentResult;
- }
-
- if (_compileOnly) // If the test only wants to compile the code, bail now.
- {
- context.CurrentResult.SetResult(ResultState.Success);
- return context.CurrentResult;
- }
- else if (_expectedException != null) // Special case if we have an expected exception
- {
- if (TryExpectedException(context, () => _originalMethod.Method.Invoke(context.TestObject, arguments), ".NET", type => type == _expectedException, _expectedException.FullName, false) != TryExpectedExceptionResult.ThrewExpectedException)
- {
- return context.CurrentResult;
- }
-
- if (TryExpectedException(context, () => RunNativeCode(nativeDelegateCaller,compiledFunction, nativeArgs), "Native", type => type == _expectedException, _expectedException.FullName, true) != TryExpectedExceptionResult.ThrewExpectedException)
- {
- return context.CurrentResult;
- }
- }
- else
- {
- object resultNative = null;
-
- // We are forced to run native before managed, because on IL2CPP, if a parameter
- // is a ref, it will keep the same memory location for both managed and burst
- // while in .NET CLR we have a different behavior
- // The result is that on functions expecting the same input value through the ref
- // it won't be anymore true because the managed could have modified the value before
- // burst
-
- // ------------------------------------------------------------------
- // Run Native (Before)
- // ------------------------------------------------------------------
- if (!RunManagedBeforeNative && !TestInterpreter)
- {
- if (GetExtension() != null)
- {
- nativeArgs = GetExtension().ProcessNativeArgsForDelegateCaller(nativeArgs, methodInfo);
- }
- resultNative = RunNativeCode(nativeDelegateCaller,compiledFunction, nativeArgs);
- if (returnBoxType != null)
- {
- resultNative = Marshal.PtrToStructure((IntPtr)returnBox, returnBoxType);
- }
- }
-
- // ------------------------------------------------------------------
- // Run Interpreter
- // ------------------------------------------------------------------
- object resultInterpreter = null;
-
- if (TestInterpreter)
- {
- ZeroMemory(returnBox, MaxReturnBoxSize);
- var name = methodInfo.DeclaringType.FullName + "." + methodInfo.Name;
- if (!InterpretMethod(interpretDelegate, methodInfo, nativeArgs, methodInfo.ReturnType,
- out var reason, out resultInterpreter))
- {
- lock (BailedTests)
- {
- BailedTests[name] = reason;
- }
- }
- else
- {
- if (returnBoxType != null)
- {
- resultInterpreter = Marshal.PtrToStructure((IntPtr)returnBox, returnBoxType);
- }
- }
- }
-
-
- // ------------------------------------------------------------------
- // Run Managed
- // ------------------------------------------------------------------
- object resultClr;
- // This option skips running the managed version completely
- var overrideManagedResult = _originalMethod.Properties.Get("OverrideManagedResult");
- if (overrideManagedResult != null)
- {
- TestContext.Out.WriteLine($"Using OverrideManagedResult: `{overrideManagedResult}` to compare to burst `{resultNative}`, managed version not run");
- resultClr = overrideManagedResult;
- }
- else
- {
- ZeroMemory(returnBox, MaxReturnBoxSize);
- resultClr = _originalMethod.Method.Invoke(context.TestObject, arguments);
-
- if (returnBoxType != null)
- {
- resultClr = Marshal.PtrToStructure((IntPtr)returnBox, returnBoxType);
- }
- }
-
- var overrideResultOnMono = _originalMethod.Properties.Get("OverrideResultOnMono");
- if (overrideResultOnMono != null)
- {
- TestContext.Out.WriteLine($"Using OverrideResultOnMono: `{overrideResultOnMono}` instead of `{resultClr}` compare to burst `{resultNative}`");
- resultClr = overrideResultOnMono;
- }
-
- var overrideOn32BitNative = _originalMethod.Properties.Get("OverrideOn32BitNative");
- if (overrideOn32BitNative != null && TargetIs32Bit())
- {
- TestContext.Out.WriteLine($"Using OverrideOn32BitNative: '{overrideOn32BitNative}' instead of '{resultClr}' compare to burst '{resultNative}' due to 32bit native runtime");
- resultClr = overrideOn32BitNative;
- }
-
- // ------------------------------------------------------------------
- // Run Native (After)
- // ------------------------------------------------------------------
- if (RunManagedBeforeNative && !TestInterpreter)
- {
- ZeroMemory(returnBox, MaxReturnBoxSize);
- resultNative = RunNativeCode(nativeDelegateCaller, compiledFunction, nativeArgs);
- if (returnBoxType != null)
- {
- resultNative = Marshal.PtrToStructure((IntPtr)returnBox, returnBoxType);
- }
- }
-
- if (!TestInterpreter)
- {
- // Use our own version (for handling correctly float precision)
- AssertHelper.AreEqual(resultClr, resultNative, GetULP());
- }
- else if (resultInterpreter != null)
- {
- AssertHelper.AreEqual(resultClr, resultInterpreter, GetULP());
- }
-
- // Validate deterministic outputs - Disabled for now
- //RunDeterminismValidation(_originalMethod, resultNative);
-
- // Allow to process native result
- ProcessNativeResult(_originalMethod, resultNative);
-
- context.CurrentResult.SetResult(ResultState.Success);
-
- PostAssert(context);
- }
-
- // Check that the method is actually in the registry
- Assert.True(isInRegistry, "The test method is not in the registry, recompile the project with the updated StaticDelegateRegistry.generated.cs");
-
- // Make an attempt to clean up arguments (to reduce wasted native heap memory)
- DisposeObjects(arguments);
- DisposeObjects(nativeArgs);
- }
-
- // Compile the method once again, this time for Arm CPU to check against gold asm images
- GoldFileTestForOtherPlatforms(methodInfo, runTest);
-
- CompleteTest(context);
-
- return context.CurrentResult;
- }
-
- protected virtual object RunNativeCode(Func<object,object[],object> nativeDelegateCaller, Delegate compiledFunction, object[] nativeArgs)
- {
- return nativeDelegateCaller(compiledFunction, nativeArgs);
- }
-
- protected virtual void PostAssert(ExecutionContext context)
- {
- }
-
- protected virtual void ProcessNativeResult(TestMethod method, object result)
- {
- }
- protected virtual string GetLinesFromDeterminismLog()
- {
- return "";
- }
- protected virtual bool IsDeterministicTest(TestMethod method)
- {
- return false;
- }
-
- private static void DisposeObjects(object[] arguments)
- {
- foreach (object o in arguments)
- {
- IDisposable disp = o as IDisposable;
- disp?.Dispose();
- }
- }
-
- private object[] CloneArguments(object[] arguments)
- {
- var newArguments = new object[arguments.Length];
- for (int i = 0; i < arguments.Length; i++)
- {
- newArguments[i] = arguments[i];
- }
- return newArguments;
- }
-
- protected unsafe void TransformArguments(MethodInfo method, object[] args, out object[] nativeArgs, out Type[] nativeArgTypes, byte* returnBox, out Type returnBoxType)
- {
- returnBoxType = null;
-
- // Transform Arguments if necessary
- nativeArgs = (object[])args.Clone();
-
- for (var i = 0; i < nativeArgs.Length; i++)
- {
- var arg = args[i];
- if (arg == null)
- {
- throw new AssertionException($"Argument number `{i}` for method `{method}` cannot be null");
- }
-
- if (arg.GetType() == typeof(float[]))
- {
- args[i] = ConvertToNativeArray((float[])arg);
- }
- else if (arg.GetType() == typeof(int[]))
- {
- args[i] = ConvertToNativeArray((int[])arg);
- }
- else if (arg.GetType() == typeof(float3[]))
- {
- args[i] = ConvertToNativeArray((float3[])arg);
- }
- else if (arg is Type)
- {
- var attrType = (Type)arg;
- if (typeof(IArgumentProvider).IsAssignableFrom(attrType))
- {
- var argumentProvider = (IArgumentProvider)Activator.CreateInstance(attrType);
- // Duplicate the input for C#/Burst in case the code is modifying the data
- args[i] = argumentProvider.Value;
- nativeArgs[i] = argumentProvider.Value;
- }
- else if (typeof(ReturnBox).IsAssignableFrom(attrType))
- {
- args[i] = (IntPtr)returnBox;
- nativeArgs[i] = (IntPtr)returnBox;
- }
- }
- }
-
- var parameters = method.GetParameters();
- nativeArgTypes = new Type[nativeArgs.Length];
- for (var i = 0; i < parameters.Length; i++)
- {
- var expectedArgType = parameters[i].ParameterType;
- var actualArgType = args[i].GetType();
- var actualNativeArgType = nativeArgs[i].GetType();
-
- if (typeof(IFunctionPointerProvider).IsAssignableFrom(expectedArgType) || (expectedArgType.IsByRef && typeof(IFunctionPointerProvider).IsAssignableFrom(expectedArgType.GetElementType())) && actualNativeArgType == typeof(string))
- {
- var methodName = (string)args[i];
- var candidates =
- _originalMethod.Method.MethodInfo.DeclaringType?
- .GetMethods()
- .Where(x => x.IsStatic && x.Name.Equals(methodName))
- .ToArray();
-
- if (candidates == null || candidates.Length != 1)
- {
- throw new ArgumentException($"Could not resolve an unambigoues static method from name {methodName}.");
- }
-
- var functionPointer = CompileFunctionPointer(candidates[0], expectedArgType.IsByRef ? expectedArgType.GetElementType() : expectedArgType);
- nativeArgs[i] = functionPointer;
- args[i] = functionPointer;
- actualNativeArgType = expectedArgType;
- actualArgType = expectedArgType;
- }
- else
- {
- // If the expected parameter for the native is a reference, we need to specify it here
- if (expectedArgType.IsByRef)
- {
- actualArgType = actualArgType.MakeByRefType();
- actualNativeArgType = actualNativeArgType.MakeByRefType();
- }
- if (expectedArgType == typeof(IntPtr))
- {
- if (actualNativeArgType == typeof(int))
- {
- nativeArgs[i] = new IntPtr((int)args[i]);
- args[i] = new IntPtr((int)args[i]);
- actualNativeArgType = typeof(IntPtr);
- actualArgType = typeof(IntPtr);
- }
- else if (actualNativeArgType == typeof(long))
- {
- nativeArgs[i] = new IntPtr((long)args[i]);
- args[i] = new IntPtr((long)args[i]);
- actualNativeArgType = typeof(IntPtr);
- actualArgType = typeof(IntPtr);
- }
- }
- if (expectedArgType == typeof(UIntPtr))
- {
- if (actualNativeArgType == typeof(uint))
- {
- nativeArgs[i] = new UIntPtr((uint)args[i]);
- args[i] = new UIntPtr((uint)args[i]);
- actualNativeArgType = typeof(UIntPtr);
- actualArgType = typeof(UIntPtr);
- }
- else if (actualNativeArgType == typeof(ulong))
- {
- nativeArgs[i] = new UIntPtr((ulong)args[i]);
- args[i] = new UIntPtr((ulong)args[i]);
- actualNativeArgType = typeof(UIntPtr);
- actualArgType = typeof(UIntPtr);
- }
- }
- if (expectedArgType.IsPointer && actualNativeArgType == typeof(IntPtr))
- {
- if ((IntPtr)args[i] == (IntPtr)returnBox)
- {
- if (returnBoxType != null)
- {
- throw new ArgumentException($"Only one ReturnBox allowed");
- }
- returnBoxType = expectedArgType.GetElementType();
- }
-
- nativeArgs[i] = args[i];
- actualNativeArgType = expectedArgType;
- actualArgType = expectedArgType;
- }
- }
-
- nativeArgTypes[i] = actualNativeArgType;
-
- if (expectedArgType != actualArgType)
- {
- throw new ArgumentException($"Type mismatch in parameter {i} passed to {method.Name}: expected {expectedArgType}, got {actualArgType}.");
- }
- }
- }
-
- private static NativeArray<T> ConvertToNativeArray<T>(T[] array) where T : struct
- {
- var nativeArray = new NativeArray<T>(array.Length, Allocator.Persistent);
- for (var j = 0; j < array.Length; j++)
- nativeArray[j] = array[j];
- return nativeArray;
- }
-
- protected enum TryExpectedExceptionResult
- {
- ThrewExpectedException,
- ThrewUnexpectedException,
- DidNotThrowException,
- }
-
- protected TryExpectedExceptionResult TryExpectedException(ExecutionContext context, Action action, string contextName, Func<Type, bool> expectedException, string expectedExceptionName, bool isTargetException, bool requireException = true)
- {
- Type caughtType = null;
-
- Exception caughtException = null;
- try
- {
- action();
- }
- catch (Exception ex)
- {
- if (isTargetException && ex is TargetInvocationException)
- {
- ex = ((TargetInvocationException)ex).InnerException;
- }
- if (ex is NUnitException)
- ex = ex.InnerException;
- caughtException = ex;
- if (caughtException != null)
- {
- caughtType = caughtException.GetType();
-
- if (ExpectedExceptionMessage == null)
- ExpectedExceptionMessage = caughtException.Message;
- }
- }
-
- if (caughtException == null && !requireException)
- {
- return TryExpectedExceptionResult.DidNotThrowException;
- }
-
- if (caughtType != null && expectedException(caughtType))
- {
- if (!CheckExpectedDiagnostics(context, contextName))
- {
- return TryExpectedExceptionResult.ThrewUnexpectedException;
- }
- else if (caughtException.Message != ExpectedExceptionMessage && !_ignoreExceptionMessages && !_disableStringInterpolationInExceptionMessages)
- {
- context.CurrentResult.SetResult(ResultState.Failure, $"In {contextName} code, expected exception message {ExpectedExceptionMessage} but got {caughtException.Message}");
- return TryExpectedExceptionResult.ThrewUnexpectedException;
- }
- else if (caughtException.Message == ExpectedExceptionMessage && _disableStringInterpolationInExceptionMessages && !_ignoreExceptionMessages && contextName == "Native")
- {
- context.CurrentResult.SetResult(ResultState.Failure, $"In {contextName} code, expected exception message {caughtException.Message} to not be interpolated.");
- return TryExpectedExceptionResult.ThrewUnexpectedException;
- }
- else
- {
- context.CurrentResult.SetResult(ResultState.Success);
- return TryExpectedExceptionResult.ThrewExpectedException;
- }
- }
- else if (caughtType != null)
- {
- context.CurrentResult.SetResult(ResultState.Failure, $"In {contextName} code, expected {expectedExceptionName} but got {caughtType.Name}. Exception: {caughtException}");
- return TryExpectedExceptionResult.ThrewUnexpectedException;
- }
- else
- {
- context.CurrentResult.SetResult(ResultState.Failure, $"In {contextName} code, expected {expectedExceptionName} but no exception was thrown");
- return TryExpectedExceptionResult.ThrewUnexpectedException;
- }
- }
-
- private static string GetDiagnosticIds(IEnumerable<DiagnosticId> diagnosticIds)
- {
- if (diagnosticIds.Count() == 0)
- {
- return "None";
- }
- else
- {
- return string.Join(",", diagnosticIds);
- }
- }
-
- public static void ReportBailedTests(TextWriter writer = null)
- {
- writer = writer ?? Console.Out;
- lock (BailedTests)
- {
- foreach (var bailedTest in BailedTests.OrderBy(kv => kv.Key))
- {
- writer.WriteLine($"{bailedTest.Key}: {bailedTest.Value}");
- }
- }
- }
-
- protected bool CheckExpectedDiagnostics(ExecutionContext context, string contextName)
- {
- var loggedDiagnosticIds = GetLoggedDiagnosticIds().OrderBy(x => x);
- var expectedDiagnosticIds = _expectedDiagnosticIds.OrderBy(x => x);
-
- if (!loggedDiagnosticIds.SequenceEqual(expectedDiagnosticIds))
- {
- context.CurrentResult.SetResult(ResultState.Failure, $"In {contextName} code, expecting diagnostic(s) to be logged with IDs {GetDiagnosticIds(_expectedDiagnosticIds)} but instead the following diagnostic(s) were logged: {GetDiagnosticIds(loggedDiagnosticIds)}");
- return false;
- }
- return true;
- }
-
- protected virtual IEnumerable<DiagnosticId> GetLoggedDiagnosticIds() => Array.Empty<DiagnosticId>();
- protected virtual IEnumerable<DiagnosticId> GetExpectedDiagnosticIds() => _expectedDiagnosticIds;
-
- protected void RunDeterminismValidation(TestMethod method, object resultNative)
- {
- // GetLines first as this will allow us to ignore these tests if the log file is missing
- //which occurs when running the "trunk" package tests, since they use their own project file
- //a possible workaround for this is to embed the log into a .cs file and stick that in the tests
- //folder, then we don't need the resource folder version.
- var lines = GetLinesFromDeterminismLog();
-
- // If the log is not found, this will also return false
- if (!IsDeterministicTest(method))
- return;
-
- var allLines = lines.Split(new char[] { '\r', '\n' });
- string matchName = $"{method.FullName}:";
- foreach (var line in allLines)
- {
- if (line.StartsWith(matchName))
- {
- if (resultNative.GetType() == typeof(Single))
- {
- unsafe
- {
- var val = (float)resultNative;
- int intvalue = *((int*)&val);
- var resStr = $"0x{intvalue:X4}";
-
- if (!line.EndsWith(resStr))
- {
- Assert.Fail($"Deterministic mismatch '{method.FullName}: {resStr}' but expected '{line}'");
- }
- }
- }
- else
- {
- Assert.That(resultNative.GetType() == typeof(Double));
- unsafe
- {
- var val = (double)resultNative;
- long longvalue = *((long*)&val);
- var resStr = $"0x{longvalue:X8}";
-
- if (!line.EndsWith(resStr))
- {
- Assert.Fail($"Deterministic mismatch '{method.FullName}: {resStr}' but expected '{line}'");
- }
- }
- }
- return;
- }
- }
- Assert.Fail($"Deterministic mismatch test not present : '{method.FullName}'");
- }
-
- protected abstract int GetRunCount();
-
- protected abstract void CompleteTest(ExecutionContext context);
-
- protected abstract int GetULP();
-
- protected abstract object[] GetArgumentsArray(TestMethod method);
-
- protected abstract unsafe Delegate CompileDelegate(ExecutionContext context, MethodInfo methodInfo, Type delegateType, byte* returnBox, out Type returnBoxType, out Delegate interpretDelegate);
-
- protected abstract bool InterpretMethod(Delegate interpretDelegate, MethodInfo methodInfo, object[] args, Type returnType, out string reason, out object result);
-
- protected abstract void GoldFileTestForOtherPlatforms(MethodInfo methodInfo, bool testWasRun);
-
- protected abstract bool TestOnCurrentHostEnvironment(MethodInfo methodInfo);
-
- protected abstract object CompileFunctionPointer(MethodInfo methodInfo, Type functionType);
-
- protected abstract void Setup();
-
- protected abstract TestResult HandleCompilerException(ExecutionContext context, MethodInfo methodInfo);
-
- protected abstract TestCompilerBaseExtensions GetExtension();
-
- protected abstract bool TargetIs32Bit();
- }
-
- [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
- internal sealed class IntRangeAttribute : Attribute
- {
- public readonly int Lo;
- public readonly int Hi;
-
- public IntRangeAttribute(int hi) { Hi = hi; }
- public IntRangeAttribute(int lo, int hi) { Lo = lo; Hi = hi; }
- }
- }
|