暫無描述
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

TestCompilerAttributeBase.cs 38KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Reflection;
  6. using System.Runtime.InteropServices;
  7. using Burst.Compiler.IL.Tests.Helpers;
  8. using NUnit.Framework;
  9. using NUnit.Framework.Interfaces;
  10. using NUnit.Framework.Internal;
  11. using NUnit.Framework.Internal.Builders;
  12. using NUnit.Framework.Internal.Commands;
  13. using Unity.Burst;
  14. using Unity.Collections;
  15. using Unity.Mathematics;
  16. using UnityBenchShared;
  17. #if !BURST_TESTS_ONLY
  18. using ExecutionContext = NUnit.Framework.Internal.ITestExecutionContext;
  19. #else
  20. using ExecutionContext = NUnit.Framework.Internal.TestExecutionContext;
  21. #endif
  22. namespace Burst.Compiler.IL.Tests
  23. {
  24. /// <summary>
  25. /// When used as a type in TestCompiler arguments signifies that the corresponding parameter is a pointer output.
  26. /// </summary>
  27. internal struct ReturnBox { }
  28. /// <summary>
  29. /// Interface used for initialize function pointers.
  30. /// </summary>
  31. internal interface IFunctionPointerProvider
  32. {
  33. object FromIntPtr(IntPtr ptr);
  34. }
  35. /// <summary>
  36. /// Used to implement custom testing behaviour
  37. /// </summary>
  38. internal interface TestCompilerBaseExtensions
  39. {
  40. (bool shouldSkip, string skipReason) SkipTest(MethodInfo method);
  41. Type FetchAlternateDelegate(out bool isInRegistry, out Func<object, object[], object> caller);
  42. object[] ProcessNativeArgsForDelegateCaller(object[] nativeArgs, MethodInfo methodInfo);
  43. }
  44. [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)]
  45. internal abstract class TestCompilerAttributeBase : TestCaseAttribute, ITestBuilder, IWrapTestMethod
  46. {
  47. private readonly NUnitTestCaseBuilder _builder = new NUnitTestCaseBuilder();
  48. public const string GoldFolder = "gold/x86";
  49. public const string GoldFolderArm = "gold/arm";
  50. public const string GeneratedFolder = "generated/x86";
  51. public const string GeneratedFolderArm = "generated/arm";
  52. public TestCompilerAttributeBase(params object[] arguments) : base(arguments)
  53. {
  54. }
  55. /// <summary>
  56. /// 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.
  57. /// </summary>
  58. public bool CompileOnly { get; set; }
  59. /// <summary>
  60. /// The type of exception the test expects to be thrown.
  61. /// </summary>
  62. public Type ExpectedException { get; set; }
  63. /// <summary>
  64. /// Whether the test is expected to throw a compiler exception or not.
  65. /// </summary>
  66. public bool ExpectCompilerException { get; set; }
  67. public bool DisableGold { get; set; }
  68. public DiagnosticId ExpectedDiagnosticId
  69. {
  70. get => throw new InvalidOperationException();
  71. set => ExpectedDiagnosticIds = new DiagnosticId[] { value };
  72. }
  73. public DiagnosticId[] ExpectedDiagnosticIds { get; set; } = new DiagnosticId[0];
  74. public bool FastMath { get; set; }
  75. /// <summary>
  76. /// Use this property when the JIT calculation is wrong (e.g when using float)
  77. /// </summary>
  78. public object OverrideResultOnMono { get; set; }
  79. /// <summary>
  80. /// 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)
  81. /// </summary>
  82. public object OverrideManagedResult { get; set; }
  83. /// <summary>
  84. /// 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.
  85. /// </summary>
  86. public object OverrideOn32BitNative { get; set; }
  87. /// <summary>
  88. /// Use this and specify a TargetPlatform (Host) to have the test ignored when running on that host. Mostly used by WASM at present.
  89. /// </summary>
  90. public object IgnoreOnPlatform { get; set; }
  91. public bool? IsDeterministic { get; set; }
  92. protected virtual bool SupportException => true;
  93. IEnumerable<TestMethod> ITestBuilder.BuildFrom(IMethodInfo method, Test suite)
  94. {
  95. // If the system doesn't support exceptions (Unity editor for delegates) we should not test with exceptions
  96. bool skipTest = (ExpectCompilerException || ExpectedException != null) && !SupportException;
  97. var expectResult = !method.ReturnType.IsType(typeof(void));
  98. var arguments = new List<object>(this.Arguments);
  99. // Expand arguments with IntRangeAttributes if we have them
  100. foreach (var param in method.GetParameters())
  101. {
  102. var attrs = param.GetCustomAttributes<IntRangeAttribute>(false);
  103. if (attrs == null || attrs.Length != 1)
  104. continue;
  105. arguments.Add(attrs[0]);
  106. }
  107. IEnumerable<object[]> permutations = CreatePermutation(0, arguments.ToArray(), method.GetParameters());
  108. // TODO: Workaround for a scalability bug with R# or Rider
  109. // Run only one testcase if not running from the commandline
  110. if (!IsCommandLine())
  111. {
  112. permutations = permutations.Take(1);
  113. }
  114. foreach (var newArguments in permutations)
  115. {
  116. var caseParameters = new TestCaseParameters(newArguments);
  117. if (expectResult)
  118. {
  119. caseParameters.ExpectedResult = true;
  120. if (OverrideResultOnMono != null && IsMono())
  121. {
  122. caseParameters.Properties.Set(nameof(OverrideResultOnMono), OverrideResultOnMono);
  123. }
  124. if (OverrideManagedResult != null)
  125. {
  126. caseParameters.Properties.Set(nameof(OverrideManagedResult), OverrideManagedResult);
  127. }
  128. if (OverrideOn32BitNative != null)
  129. {
  130. caseParameters.Properties.Set(nameof(OverrideOn32BitNative), OverrideOn32BitNative);
  131. }
  132. }
  133. // Transfer FastMath parameter to the compiler
  134. caseParameters.Properties.Set(nameof(FastMath), FastMath);
  135. var test = _builder.BuildTestMethod(method, suite, caseParameters);
  136. if (skipTest)
  137. {
  138. test.RunState = RunState.Skipped;
  139. test.Properties.Add(PropertyNames.SkipReason, "Exceptions are not supported");
  140. }
  141. yield return test;
  142. }
  143. }
  144. private static IEnumerable<object[]> CreatePermutation(int index, object[] args, IParameterInfo[] parameters)
  145. {
  146. if (index >= args.Length)
  147. {
  148. yield return args;
  149. yield break;
  150. }
  151. var copyArgs = (object[])args.Clone();
  152. bool hasRange = false;
  153. for (; index < args.Length; index++)
  154. {
  155. var arg = copyArgs[index];
  156. if (arg is DataRange)
  157. {
  158. var range = (DataRange)arg;
  159. // TEMP: Disable NaN test for now
  160. //range = range & ~(DataRange.NaN);
  161. foreach (var value in range.ExpandRange(parameters[index].ParameterType, index))
  162. {
  163. copyArgs[index] = value;
  164. foreach (var subPermutation in CreatePermutation(index + 1, copyArgs, parameters))
  165. {
  166. hasRange = true;
  167. yield return subPermutation;
  168. }
  169. }
  170. }
  171. else if (arg is IntRangeAttribute)
  172. {
  173. var ir = (IntRangeAttribute)arg;
  174. if (ir != null)
  175. {
  176. for (int x = ir.Lo; x <= ir.Hi; ++x)
  177. {
  178. copyArgs[index] = x;
  179. foreach (var subPermutation in CreatePermutation(index + 1, copyArgs, parameters))
  180. {
  181. hasRange = true;
  182. yield return subPermutation;
  183. }
  184. }
  185. }
  186. }
  187. }
  188. if (!hasRange)
  189. {
  190. yield return copyArgs;
  191. }
  192. }
  193. TestCommand ICommandWrapper.Wrap(TestCommand command)
  194. {
  195. var testMethod = (TestMethod)command.Test;
  196. return GetTestCommand(this, testMethod, testMethod);
  197. }
  198. protected abstract bool IsCommandLine();
  199. protected abstract bool IsMono();
  200. protected abstract TestCompilerCommandBase GetTestCommand(TestCompilerAttributeBase attribute, Test test, TestMethod originalMethod);
  201. }
  202. internal abstract class TestCompilerCommandBase : TestCommand
  203. {
  204. protected readonly TestMethod _originalMethod;
  205. private readonly bool _compileOnly;
  206. private readonly Type _expectedException;
  207. protected readonly bool _expectCompilerException;
  208. protected readonly DiagnosticId[] _expectedDiagnosticIds;
  209. protected virtual bool TestInterpreter => false;
  210. protected TestCompilerCommandBase(TestCompilerAttributeBase attribute, Test test, TestMethod originalMethod) : base(test)
  211. {
  212. _originalMethod = originalMethod;
  213. Attribute = attribute;
  214. _compileOnly = Attribute.CompileOnly;
  215. _expectedException = Attribute.ExpectedException;
  216. _expectCompilerException = Attribute.ExpectCompilerException;
  217. _expectedDiagnosticIds = Attribute.ExpectedDiagnosticIds;
  218. }
  219. public TestCompilerAttributeBase Attribute { get; }
  220. public override TestResult Execute(ExecutionContext context)
  221. {
  222. TestResult lastResult = null;
  223. for (int i = 0; i < GetRunCount(); i++)
  224. {
  225. lastResult = ExecuteMethod(context);
  226. }
  227. return lastResult;
  228. }
  229. protected virtual Type CreateNativeDelegateType(Type returnType, Type[] arguments, out bool isInRegistry, out Func<object, object[], object> caller)
  230. {
  231. if (GetExtension() != null)
  232. {
  233. Type type = GetExtension().FetchAlternateDelegate(out isInRegistry, out caller);
  234. if (type != null)
  235. {
  236. return type;
  237. }
  238. }
  239. isInRegistry = false;
  240. StaticDelegateCallback staticDelegate;
  241. if (StaticDelegateRegistry.TryFind(returnType, arguments, out staticDelegate))
  242. {
  243. isInRegistry = true;
  244. caller = staticDelegate.Caller;
  245. return staticDelegate.DelegateType;
  246. }
  247. else
  248. {
  249. #if BURST_TESTS_ONLY
  250. // Else we try to do it with a dynamic call
  251. var type = DelegateHelper.NewDelegateType(returnType, arguments);
  252. caller = StaticDynamicDelegateCaller;
  253. return type;
  254. #else
  255. throw new Exception("Couldn't find delegate in static registry and not able to use a dynamic call.");
  256. #endif
  257. }
  258. }
  259. private static Func<object, object[], object> StaticDynamicDelegateCaller = new Func<object, object[], object>((del, arguments) => ((Delegate)del).DynamicInvoke(arguments));
  260. private static readonly int MaxReturnBoxSize = 512;
  261. protected bool RunManagedBeforeNative { get; set; }
  262. protected static readonly Dictionary<string, string> BailedTests = new Dictionary<string, string>();
  263. private unsafe void ZeroMemory(byte* ptr, int size)
  264. {
  265. for (int i = 0; i < size; i++)
  266. {
  267. *(ptr + i) = 0;
  268. }
  269. }
  270. private unsafe TestResult ExecuteMethod(ExecutionContext context)
  271. {
  272. byte* returnBox = stackalloc byte[MaxReturnBoxSize];
  273. Setup();
  274. var methodInfo = _originalMethod.Method.MethodInfo;
  275. var runTest = TestOnCurrentHostEnvironment(methodInfo);
  276. if (runTest)
  277. {
  278. var arguments = GetArgumentsArray(_originalMethod);
  279. // We can't skip tests during BuildFrom that rely on specific options (e.g. current platform)
  280. // So we handle the remaining cases here via extensions
  281. if (GetExtension() != null)
  282. {
  283. var skip = GetExtension().SkipTest(methodInfo);
  284. if (skip.shouldSkip)
  285. {
  286. // For now, mark the tests as passed rather than skipped, to avoid the log spam
  287. //On wasm this log spam accounts for 33minutes of test execution time!!
  288. //context.CurrentResult.SetResult(ResultState.Skipped, skip.skipReason);
  289. context.CurrentResult.SetResult(ResultState.Success);
  290. return context.CurrentResult;
  291. }
  292. }
  293. // Special case for Compiler Exceptions when IL can't be translated
  294. if (_expectCompilerException)
  295. {
  296. // We still need to transform arguments here in case there's a function pointer that we expect to fail compilation.
  297. var expectedExceptionResult = TryExpectedException(
  298. context,
  299. () => TransformArguments(_originalMethod.Method.MethodInfo, arguments, out _, out _, returnBox, out _),
  300. "Transforming arguments",
  301. type => true,
  302. "Any exception",
  303. false,
  304. false);
  305. if (expectedExceptionResult != TryExpectedExceptionResult.DidNotThrowException)
  306. {
  307. return context.CurrentResult;
  308. }
  309. return HandleCompilerException(context, methodInfo);
  310. }
  311. object[] nativeArgs;
  312. Type[] nativeArgTypes;
  313. TransformArguments(_originalMethod.Method.MethodInfo, arguments, out nativeArgs, out nativeArgTypes, returnBox, out Type returnBoxType);
  314. bool isInRegistry = false;
  315. Func<object, object[], object> nativeDelegateCaller;
  316. var delegateType = CreateNativeDelegateType(_originalMethod.Method.MethodInfo.ReturnType, nativeArgTypes, out isInRegistry, out nativeDelegateCaller);
  317. if (!isInRegistry)
  318. {
  319. TestContext.Out.WriteLine($"Warning, the delegate for the method `{_originalMethod.Method}` has not been generated");
  320. }
  321. Delegate compiledFunction;
  322. Delegate interpretDelegate;
  323. try
  324. {
  325. compiledFunction = CompileDelegate(context, methodInfo, delegateType, returnBox, out _, out interpretDelegate);
  326. }
  327. catch (Exception ex) when (_expectedException != null && ex.GetType() == _expectedException)
  328. {
  329. context.CurrentResult.SetResult(ResultState.Success);
  330. return context.CurrentResult;
  331. }
  332. Assert.IsTrue(returnBoxType == null || Marshal.SizeOf(returnBoxType) <= MaxReturnBoxSize);
  333. if (compiledFunction == null)
  334. return context.CurrentResult;
  335. if (_compileOnly) // If the test only wants to compile the code, bail now.
  336. {
  337. return context.CurrentResult;
  338. }
  339. else if (_expectedException != null) // Special case if we have an expected exception
  340. {
  341. if (TryExpectedException(context, () => _originalMethod.Method.Invoke(context.TestObject, arguments), ".NET", type => type == _expectedException, _expectedException.FullName, false) != TryExpectedExceptionResult.ThrewExpectedException)
  342. {
  343. return context.CurrentResult;
  344. }
  345. if (TryExpectedException(context, () => nativeDelegateCaller(compiledFunction, nativeArgs), "Native", type => type == _expectedException, _expectedException.FullName, true) != TryExpectedExceptionResult.ThrewExpectedException)
  346. {
  347. return context.CurrentResult;
  348. }
  349. }
  350. else
  351. {
  352. object resultNative = null;
  353. // We are forced to run native before managed, because on IL2CPP, if a parameter
  354. // is a ref, it will keep the same memory location for both managed and burst
  355. // while in .NET CLR we have a different behavior
  356. // The result is that on functions expecting the same input value through the ref
  357. // it won't be anymore true because the managed could have modified the value before
  358. // burst
  359. // ------------------------------------------------------------------
  360. // Run Native (Before)
  361. // ------------------------------------------------------------------
  362. if (!RunManagedBeforeNative && !TestInterpreter)
  363. {
  364. if (GetExtension() != null)
  365. {
  366. nativeArgs = GetExtension().ProcessNativeArgsForDelegateCaller(nativeArgs, methodInfo);
  367. }
  368. resultNative = nativeDelegateCaller(compiledFunction, nativeArgs);
  369. if (returnBoxType != null)
  370. {
  371. resultNative = Marshal.PtrToStructure((IntPtr)returnBox, returnBoxType);
  372. }
  373. }
  374. // ------------------------------------------------------------------
  375. // Run Interpreter
  376. // ------------------------------------------------------------------
  377. object resultInterpreter = null;
  378. if (TestInterpreter)
  379. {
  380. ZeroMemory(returnBox, MaxReturnBoxSize);
  381. var name = methodInfo.DeclaringType.FullName + "." + methodInfo.Name;
  382. if (!InterpretMethod(interpretDelegate, methodInfo, nativeArgs, methodInfo.ReturnType,
  383. out var reason, out resultInterpreter))
  384. {
  385. lock (BailedTests)
  386. {
  387. BailedTests[name] = reason;
  388. }
  389. }
  390. else
  391. {
  392. if (returnBoxType != null)
  393. {
  394. resultInterpreter = Marshal.PtrToStructure((IntPtr)returnBox, returnBoxType);
  395. }
  396. }
  397. }
  398. // ------------------------------------------------------------------
  399. // Run Managed
  400. // ------------------------------------------------------------------
  401. object resultClr;
  402. // This option skips running the managed version completely
  403. var overrideManagedResult = _originalMethod.Properties.Get("OverrideManagedResult");
  404. if (overrideManagedResult != null)
  405. {
  406. TestContext.Out.WriteLine($"Using OverrideManagedResult: `{overrideManagedResult}` to compare to burst `{resultNative}`, managed version not run");
  407. resultClr = overrideManagedResult;
  408. }
  409. else
  410. {
  411. ZeroMemory(returnBox, MaxReturnBoxSize);
  412. resultClr = _originalMethod.Method.Invoke(context.TestObject, arguments);
  413. if (returnBoxType != null)
  414. {
  415. resultClr = Marshal.PtrToStructure((IntPtr)returnBox, returnBoxType);
  416. }
  417. }
  418. var overrideResultOnMono = _originalMethod.Properties.Get("OverrideResultOnMono");
  419. if (overrideResultOnMono != null)
  420. {
  421. TestContext.Out.WriteLine($"Using OverrideResultOnMono: `{overrideResultOnMono}` instead of `{resultClr}` compare to burst `{resultNative}`");
  422. resultClr = overrideResultOnMono;
  423. }
  424. var overrideOn32BitNative = _originalMethod.Properties.Get("OverrideOn32BitNative");
  425. if (overrideOn32BitNative != null && TargetIs32Bit())
  426. {
  427. TestContext.Out.WriteLine($"Using OverrideOn32BitNative: '{overrideOn32BitNative}' instead of '{resultClr}' compare to burst '{resultNative}' due to 32bit native runtime");
  428. resultClr = overrideOn32BitNative;
  429. }
  430. // ------------------------------------------------------------------
  431. // Run Native (After)
  432. // ------------------------------------------------------------------
  433. if (RunManagedBeforeNative && !TestInterpreter)
  434. {
  435. ZeroMemory(returnBox, MaxReturnBoxSize);
  436. resultNative = nativeDelegateCaller(compiledFunction, nativeArgs);
  437. if (returnBoxType != null)
  438. {
  439. resultNative = Marshal.PtrToStructure((IntPtr)returnBox, returnBoxType);
  440. }
  441. }
  442. if (!TestInterpreter)
  443. {
  444. // Use our own version (for handling correctly float precision)
  445. AssertHelper.AreEqual(resultClr, resultNative, GetULP());
  446. }
  447. else if (resultInterpreter != null)
  448. {
  449. AssertHelper.AreEqual(resultClr, resultInterpreter, GetULP());
  450. }
  451. // Validate deterministic outputs - Disabled for now
  452. //RunDeterminismValidation(_originalMethod, resultNative);
  453. // Allow to process native result
  454. ProcessNativeResult(_originalMethod, resultNative);
  455. context.CurrentResult.SetResult(ResultState.Success);
  456. PostAssert(context);
  457. }
  458. // Check that the method is actually in the registry
  459. Assert.True(isInRegistry, "The test method is not in the registry, recompile the project with the updated StaticDelegateRegistry.generated.cs");
  460. // Make an attempt to clean up arguments (to reduce wasted native heap memory)
  461. DisposeObjects(arguments);
  462. DisposeObjects(nativeArgs);
  463. }
  464. // Compile the method once again, this time for Arm CPU to check against gold asm images
  465. GoldFileTestForOtherPlatforms(methodInfo, runTest);
  466. CompleteTest(context);
  467. return context.CurrentResult;
  468. }
  469. protected virtual void PostAssert(ExecutionContext context)
  470. {
  471. }
  472. protected virtual void ProcessNativeResult(TestMethod method, object result)
  473. {
  474. }
  475. protected virtual string GetLinesFromDeterminismLog()
  476. {
  477. return "";
  478. }
  479. protected virtual bool IsDeterministicTest(TestMethod method)
  480. {
  481. return false;
  482. }
  483. private static void DisposeObjects(object[] arguments)
  484. {
  485. foreach (object o in arguments)
  486. {
  487. IDisposable disp = o as IDisposable;
  488. disp?.Dispose();
  489. }
  490. }
  491. private object[] CloneArguments(object[] arguments)
  492. {
  493. var newArguments = new object[arguments.Length];
  494. for (int i = 0; i < arguments.Length; i++)
  495. {
  496. newArguments[i] = arguments[i];
  497. }
  498. return newArguments;
  499. }
  500. protected unsafe void TransformArguments(MethodInfo method, object[] args, out object[] nativeArgs, out Type[] nativeArgTypes, byte* returnBox, out Type returnBoxType)
  501. {
  502. returnBoxType = null;
  503. // Transform Arguments if necessary
  504. nativeArgs = (object[])args.Clone();
  505. for (var i = 0; i < nativeArgs.Length; i++)
  506. {
  507. var arg = args[i];
  508. if (arg == null)
  509. {
  510. throw new AssertionException($"Argument number `{i}` for method `{method}` cannot be null");
  511. }
  512. if (arg.GetType() == typeof(float[]))
  513. {
  514. args[i] = ConvertToNativeArray((float[])arg);
  515. }
  516. else if (arg.GetType() == typeof(int[]))
  517. {
  518. args[i] = ConvertToNativeArray((int[])arg);
  519. }
  520. else if (arg.GetType() == typeof(float3[]))
  521. {
  522. args[i] = ConvertToNativeArray((float3[])arg);
  523. }
  524. else if (arg is Type)
  525. {
  526. var attrType = (Type)arg;
  527. if (typeof(IArgumentProvider).IsAssignableFrom(attrType))
  528. {
  529. var argumentProvider = (IArgumentProvider)Activator.CreateInstance(attrType);
  530. // Duplicate the input for C#/Burst in case the code is modifying the data
  531. args[i] = argumentProvider.Value;
  532. nativeArgs[i] = argumentProvider.Value;
  533. }
  534. else if (typeof(ReturnBox).IsAssignableFrom(attrType))
  535. {
  536. args[i] = (IntPtr)returnBox;
  537. nativeArgs[i] = (IntPtr)returnBox;
  538. }
  539. }
  540. }
  541. var parameters = method.GetParameters();
  542. nativeArgTypes = new Type[nativeArgs.Length];
  543. for (var i = 0; i < parameters.Length; i++)
  544. {
  545. var expectedArgType = parameters[i].ParameterType;
  546. var actualArgType = args[i].GetType();
  547. var actualNativeArgType = nativeArgs[i].GetType();
  548. if (typeof(IFunctionPointerProvider).IsAssignableFrom(expectedArgType) || (expectedArgType.IsByRef && typeof(IFunctionPointerProvider).IsAssignableFrom(expectedArgType.GetElementType())) && actualNativeArgType == typeof(string))
  549. {
  550. var methodName = (string)args[i];
  551. var candidates =
  552. _originalMethod.Method.MethodInfo.DeclaringType?
  553. .GetMethods()
  554. .Where(x => x.IsStatic && x.Name.Equals(methodName))
  555. .ToArray();
  556. if (candidates == null || candidates.Length != 1)
  557. {
  558. throw new ArgumentException($"Could not resolve an unambigoues static method from name {methodName}.");
  559. }
  560. var functionPointer = CompileFunctionPointer(candidates[0], expectedArgType.IsByRef ? expectedArgType.GetElementType() : expectedArgType);
  561. nativeArgs[i] = functionPointer;
  562. args[i] = functionPointer;
  563. actualNativeArgType = expectedArgType;
  564. actualArgType = expectedArgType;
  565. }
  566. else
  567. {
  568. // If the expected parameter for the native is a reference, we need to specify it here
  569. if (expectedArgType.IsByRef)
  570. {
  571. actualArgType = actualArgType.MakeByRefType();
  572. actualNativeArgType = actualNativeArgType.MakeByRefType();
  573. }
  574. if (expectedArgType == typeof(IntPtr) && actualNativeArgType == typeof(int))
  575. {
  576. nativeArgs[i] = new IntPtr((int)args[i]);
  577. args[i] = new IntPtr((int)args[i]);
  578. actualNativeArgType = typeof(IntPtr);
  579. actualArgType = typeof(IntPtr);
  580. }
  581. if (expectedArgType == typeof(UIntPtr) && actualNativeArgType == typeof(uint))
  582. {
  583. nativeArgs[i] = new UIntPtr((uint)args[i]);
  584. args[i] = new UIntPtr((uint)args[i]);
  585. actualNativeArgType = typeof(UIntPtr);
  586. actualArgType = typeof(UIntPtr);
  587. }
  588. if (expectedArgType.IsPointer && actualNativeArgType == typeof(IntPtr))
  589. {
  590. if ((IntPtr)args[i] == (IntPtr)returnBox)
  591. {
  592. if (returnBoxType != null)
  593. {
  594. throw new ArgumentException($"Only one ReturnBox allowed");
  595. }
  596. returnBoxType = expectedArgType.GetElementType();
  597. }
  598. nativeArgs[i] = args[i];
  599. actualNativeArgType = expectedArgType;
  600. actualArgType = expectedArgType;
  601. }
  602. }
  603. nativeArgTypes[i] = actualNativeArgType;
  604. if (expectedArgType != actualArgType)
  605. {
  606. throw new ArgumentException($"Type mismatch in parameter {i} passed to {method.Name}: expected {expectedArgType}, got {actualArgType}.");
  607. }
  608. }
  609. }
  610. private static NativeArray<T> ConvertToNativeArray<T>(T[] array) where T : struct
  611. {
  612. var nativeArray = new NativeArray<T>(array.Length, Allocator.Persistent);
  613. for (var j = 0; j < array.Length; j++)
  614. nativeArray[j] = array[j];
  615. return nativeArray;
  616. }
  617. protected enum TryExpectedExceptionResult
  618. {
  619. ThrewExpectedException,
  620. ThrewUnexpectedException,
  621. DidNotThrowException,
  622. }
  623. protected TryExpectedExceptionResult TryExpectedException(ExecutionContext context, Action action, string contextName, Func<Type, bool> expectedException, string expectedExceptionName, bool isTargetException, bool requireException = true)
  624. {
  625. Type caughtType = null;
  626. Exception caughtException = null;
  627. try
  628. {
  629. action();
  630. }
  631. catch (Exception ex)
  632. {
  633. if (isTargetException && ex is TargetInvocationException)
  634. {
  635. ex = ((TargetInvocationException)ex).InnerException;
  636. }
  637. if (ex is NUnitException)
  638. ex = ex.InnerException;
  639. caughtException = ex;
  640. if (caughtException != null)
  641. {
  642. caughtType = caughtException.GetType();
  643. }
  644. }
  645. if (caughtException == null && !requireException)
  646. {
  647. return TryExpectedExceptionResult.DidNotThrowException;
  648. }
  649. if (caughtType != null && expectedException(caughtType))
  650. {
  651. if (!CheckExpectedDiagnostics(context, contextName))
  652. {
  653. return TryExpectedExceptionResult.ThrewUnexpectedException;
  654. }
  655. else
  656. {
  657. context.CurrentResult.SetResult(ResultState.Success);
  658. return TryExpectedExceptionResult.ThrewExpectedException;
  659. }
  660. }
  661. else if (caughtType != null)
  662. {
  663. context.CurrentResult.SetResult(ResultState.Failure, $"In {contextName} code, expected {expectedExceptionName} but got {caughtType.Name}. Exception: {caughtException}");
  664. return TryExpectedExceptionResult.ThrewUnexpectedException;
  665. }
  666. else
  667. {
  668. context.CurrentResult.SetResult(ResultState.Failure, $"In {contextName} code, expected {expectedExceptionName} but no exception was thrown");
  669. return TryExpectedExceptionResult.ThrewUnexpectedException;
  670. }
  671. }
  672. private static string GetDiagnosticIds(IEnumerable<DiagnosticId> diagnosticIds)
  673. {
  674. if (diagnosticIds.Count() == 0)
  675. {
  676. return "None";
  677. }
  678. else
  679. {
  680. return string.Join(",", diagnosticIds);
  681. }
  682. }
  683. public static void ReportBailedTests(TextWriter writer = null)
  684. {
  685. writer = writer ?? Console.Out;
  686. lock (BailedTests)
  687. {
  688. foreach (var bailedTest in BailedTests.OrderBy(kv => kv.Key))
  689. {
  690. writer.WriteLine($"{bailedTest.Key}: {bailedTest.Value}");
  691. }
  692. }
  693. }
  694. protected bool CheckExpectedDiagnostics(ExecutionContext context, string contextName)
  695. {
  696. var loggedDiagnosticIds = GetLoggedDiagnosticIds().OrderBy(x => x);
  697. var expectedDiagnosticIds = _expectedDiagnosticIds.OrderBy(x => x);
  698. if (!loggedDiagnosticIds.SequenceEqual(expectedDiagnosticIds))
  699. {
  700. 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)}");
  701. return false;
  702. }
  703. return true;
  704. }
  705. protected virtual IEnumerable<DiagnosticId> GetLoggedDiagnosticIds() => Array.Empty<DiagnosticId>();
  706. protected virtual IEnumerable<DiagnosticId> GetExpectedDiagnosticIds() => _expectedDiagnosticIds;
  707. protected void RunDeterminismValidation(TestMethod method, object resultNative)
  708. {
  709. // GetLines first as this will allow us to ignore these tests if the log file is missing
  710. //which occurs when running the "trunk" package tests, since they use their own project file
  711. //a possible workaround for this is to embed the log into a .cs file and stick that in the tests
  712. //folder, then we don't need the resource folder version.
  713. var lines = GetLinesFromDeterminismLog();
  714. // If the log is not found, this will also return false
  715. if (!IsDeterministicTest(method))
  716. return;
  717. var allLines = lines.Split(new char[] { '\r', '\n' });
  718. string matchName = $"{method.FullName}:";
  719. foreach (var line in allLines)
  720. {
  721. if (line.StartsWith(matchName))
  722. {
  723. if (resultNative.GetType() == typeof(Single))
  724. {
  725. unsafe
  726. {
  727. var val = (float)resultNative;
  728. int intvalue = *((int*)&val);
  729. var resStr = $"0x{intvalue:X4}";
  730. if (!line.EndsWith(resStr))
  731. {
  732. Assert.Fail($"Deterministic mismatch '{method.FullName}: {resStr}' but expected '{line}'");
  733. }
  734. }
  735. }
  736. else
  737. {
  738. Assert.That(resultNative.GetType() == typeof(Double));
  739. unsafe
  740. {
  741. var val = (double)resultNative;
  742. long longvalue = *((long*)&val);
  743. var resStr = $"0x{longvalue:X8}";
  744. if (!line.EndsWith(resStr))
  745. {
  746. Assert.Fail($"Deterministic mismatch '{method.FullName}: {resStr}' but expected '{line}'");
  747. }
  748. }
  749. }
  750. return;
  751. }
  752. }
  753. Assert.Fail($"Deterministic mismatch test not present : '{method.FullName}'");
  754. }
  755. protected abstract int GetRunCount();
  756. protected abstract void CompleteTest(ExecutionContext context);
  757. protected abstract int GetULP();
  758. protected abstract object[] GetArgumentsArray(TestMethod method);
  759. protected abstract unsafe Delegate CompileDelegate(ExecutionContext context, MethodInfo methodInfo, Type delegateType, byte* returnBox, out Type returnBoxType, out Delegate interpretDelegate);
  760. protected abstract bool InterpretMethod(Delegate interpretDelegate, MethodInfo methodInfo, object[] args, Type returnType, out string reason, out object result);
  761. protected abstract void GoldFileTestForOtherPlatforms(MethodInfo methodInfo, bool testWasRun);
  762. protected abstract bool TestOnCurrentHostEnvironment(MethodInfo methodInfo);
  763. protected abstract object CompileFunctionPointer(MethodInfo methodInfo, Type functionType);
  764. protected abstract void Setup();
  765. protected abstract TestResult HandleCompilerException(ExecutionContext context, MethodInfo methodInfo);
  766. protected abstract TestCompilerBaseExtensions GetExtension();
  767. protected abstract bool TargetIs32Bit();
  768. }
  769. [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
  770. internal sealed class IntRangeAttribute : Attribute
  771. {
  772. public readonly int Lo;
  773. public readonly int Hi;
  774. public IntRangeAttribute(int hi) { Hi = hi; }
  775. public IntRangeAttribute(int lo, int hi) { Lo = lo; Hi = hi; }
  776. }
  777. }