暫無描述
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.

TestCompilerAttribute.cs 45KB

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