CppMatchingHelper.cs 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. // ***********************************************************************
  2. // Copyright (c) 2017 Unity Technologies. All rights reserved.
  3. //
  4. // Licensed under the ##LICENSENAME##.
  5. // See LICENSE.md file in the project root for full license information.
  6. // ***********************************************************************
  7. using NUnit.Framework;
  8. using Autodesk.Fbx;
  9. using System.Collections.Generic;
  10. namespace Autodesk.Fbx.UnitTests
  11. {
  12. static class CppMatchingHelper
  13. {
  14. public delegate T Factory<T>(double[] expected);
  15. public delegate T TestCommand<T>(T a, T b);
  16. public delegate bool AreSimilar<T>(T a, T b);
  17. /**
  18. * Help tests like FbxVector4Test verify that the FbxSharp
  19. * implementation of an arithmetic type matches the C++ FBX SDK
  20. * implementation.
  21. *
  22. * E.g. test that the C# and C++ FbxVector4 implementations match.
  23. *
  24. * The scheme is that the build system compiles and runs a C++ test
  25. * program (e.g. Vectors.cpp), which outputs lines in a format:
  26. * context:command:double,double,double...
  27. *
  28. * Context and command are strings with no colons in them.
  29. *
  30. * Each double is encoded as
  31. * description/exact
  32. * where 'description' is a human-readable double (e.g. 1.002452) and
  33. * exact is a 64-bit hex string that has the exact bits of a double.
  34. *
  35. * Each line is the result of running a unary or binary operation (the
  36. * command) on two variables 'a' and 'b'. The command "a" should be
  37. * issued first, followed by the command "b". These initialize the two
  38. * variables.
  39. *
  40. * Callers must provide:
  41. * - the file that the C++ test program creates
  42. * - the 'context' that matches; we ignore lines for other contexts
  43. * - a factory to convert an array of doubles to the type you are
  44. * testing (e.g. FbxVector4)
  45. * - a map from 'command' values to lambda functions that implement that command
  46. * (except for commands "a" and "b")
  47. * - optionally, a map from 'command' values to lambda functions that
  48. * compare results (to allow inexact comparison, e.g. for differences
  49. * in rounding).
  50. *
  51. * The C++ test must call every command.
  52. */
  53. public static void MatchingTest<T>(
  54. string filename,
  55. string test_context,
  56. Factory<T> factory,
  57. Dictionary<string, TestCommand<T>> commands,
  58. Dictionary<string, AreSimilar<T>> custom_comparators = null
  59. ) where T : new()
  60. {
  61. var a = new T();
  62. var b = new T();
  63. var commands_used = new HashSet<string>();
  64. using (var file = new System.IO.StreamReader(filename)) {
  65. string line;
  66. while ( null != (line = file.ReadLine()) ) {
  67. string context;
  68. string command;
  69. double [] expectedArray;
  70. ParseLine(line, out context, out command, out expectedArray);
  71. if (context != test_context) {
  72. continue;
  73. }
  74. var expected = factory(expectedArray);
  75. // Perform the command, depending what it is.
  76. T actualValue;
  77. if (command == "a") {
  78. actualValue = a = expected;
  79. } else if (command == "b") {
  80. actualValue = b = expected;
  81. } else {
  82. commands_used.Add(command);
  83. Assert.IsTrue(commands.ContainsKey(command), "unknown command " + command);
  84. actualValue = commands[command](a, b);
  85. }
  86. // Make sure we got the expected result.
  87. if (custom_comparators != null && custom_comparators.ContainsKey(command)) {
  88. var comp = custom_comparators[command];
  89. Assert.IsTrue(comp(expected, actualValue), command);
  90. } else {
  91. Assert.AreEqual(expected, actualValue, command);
  92. }
  93. }
  94. }
  95. // Make sure we actually called all those commands.
  96. Assert.That(commands_used, Is.EquivalentTo(commands.Keys));
  97. }
  98. #if ENABLE_COVERAGE_TEST
  99. /**
  100. * The coverage tester won't track the calls that MatchingTests makes
  101. * to the lambda functions. Call this function in your static
  102. * constructor to register the calls.
  103. *
  104. * This is accurate because MatchingTests makes sure that every command
  105. * was actually issued.
  106. */
  107. public static void RegisterLambdaCalls<T>(
  108. System.Reflection.MethodInfo caller,
  109. Dictionary<string, TestCommand<T>> commands)
  110. {
  111. foreach(var lambda in commands.Values) {
  112. CoverageTester.RegisterReflectionCall(caller, lambda.Method);
  113. }
  114. }
  115. #endif
  116. // Parse one line in the file.
  117. static void ParseLine(string line,
  118. out string out_context,
  119. out string out_command,
  120. out double [] out_expected)
  121. {
  122. // Parse the whole colon-separated line:
  123. // file.cpp:5:a + 2:6.71089e+07/0x419000000c000000,6.7...
  124. var items = line.Split(':');
  125. Assert.AreEqual(items.Length, 3);
  126. out_context = items[0];
  127. out_command = items[1];
  128. // now parse the comma-separated doubles:
  129. // 6.71089e+07/0x419000000c000000,6.71089e+07/0x4190000010000000,...
  130. var doubles = items[2];
  131. items = doubles.Split(',');
  132. out_expected = new double[items.Length];
  133. for(int i = 0, n = items.Length; i < n; ++i) {
  134. // parse one double: 6.71089e+07/0x419000000c000000
  135. // we ignore the printed double, just take its exact 64-bit representation.
  136. var pair = items[i].Split('/');
  137. Assert.AreEqual(2, pair.Length);
  138. var asInt = System.Convert.ToInt64(pair[1], 16);
  139. var asDouble = System.BitConverter.Int64BitsToDouble(asInt);
  140. out_expected[i] = asDouble;
  141. }
  142. }
  143. }
  144. }