No Description
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 41KB

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