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

FilePathMetaInfo.cs 9.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Globalization;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Text;
  7. using NUnit.Framework.Interfaces;
  8. using UnityEditor.TestTools.TestRunner.GUI;
  9. using UnityEngine;
  10. namespace UnityEditor.TestRunner.TestLaunchers
  11. {
  12. internal static class FilePathMetaInfo
  13. {
  14. [Serializable]
  15. private struct FileReference
  16. {
  17. public string FilePath;
  18. public int LineNumber;
  19. }
  20. private enum PathType
  21. {
  22. ProjectRepositoryPath,
  23. ProjectPath,
  24. }
  25. public static void TryCreateFile(ITest runnerLoadedTest, BuildPlayerOptions playerBuildOptions)
  26. {
  27. try
  28. {
  29. var metaFileDestinationPath = GetMetaDestinationPath(playerBuildOptions);
  30. var repositoryPath = GetPathFromArgs(PathType.ProjectRepositoryPath);
  31. // if no path is given, early out so we do not pollute the build player folder with the file path file.
  32. if (string.IsNullOrEmpty(repositoryPath))
  33. {
  34. return;
  35. }
  36. // Create a dictionary for the test names and their file paths
  37. var testFilePaths = new Dictionary<string, FileReference>();
  38. RecursivelyPopulateFileReferences(runnerLoadedTest, testFilePaths, repositoryPath, new GuiHelper(new MonoCecilHelper(), new AssetsDatabaseHelper()));
  39. SaveToJsonFile(testFilePaths, metaFileDestinationPath);
  40. }
  41. catch (Exception e)
  42. {
  43. Debug.LogWarning("Saving test file path meta info failed: " + e.Message);
  44. }
  45. }
  46. // This function serializes dictionary to json file, all the logic would not be necessary if Unity was able to serialize Dictionaries, or if we could use Newtonsoft.Json.
  47. // This function could be changed later on, or we can use different data structure than Dictionary.
  48. private static void SaveToJsonFile(Dictionary<string, FileReference> testFilePaths, string metaFileDestinationPath)
  49. {
  50. using (var fileStream = File.CreateText(Path.Combine(metaFileDestinationPath, "TestFileReferences.json")))
  51. {
  52. fileStream.WriteLine("{");
  53. foreach (var testFilePath in testFilePaths)
  54. {
  55. fileStream.WriteLine($" \"{JavaScriptStringEncode(testFilePath.Key)}\": {{");
  56. fileStream.WriteLine($" \"filePath\": \"{JavaScriptStringEncode(testFilePath.Value.FilePath)}\",");
  57. fileStream.WriteLine($" \"lineNumber\": {testFilePath.Value.LineNumber}");
  58. // check if it is the last element in the dictionary
  59. if (testFilePath.Key != testFilePaths.Keys.Last())
  60. {
  61. fileStream.WriteLine(" },");
  62. }
  63. else
  64. {
  65. fileStream.WriteLine(" }");
  66. }
  67. }
  68. fileStream.WriteLine("}");
  69. }
  70. }
  71. private static string GetMetaDestinationPath(BuildPlayerOptions playerBuildOptions)
  72. {
  73. // If we are Auto-Running the player, use project path instead of player build path because it will be wiped out after successful run.
  74. if ((playerBuildOptions.options & BuildOptions.AutoRunPlayer) != 0)
  75. {
  76. return Path.Combine(GetPathFromArgs(PathType.ProjectPath));
  77. }
  78. // if the buildOutputPath is for a file, then get the directory of it
  79. return File.Exists(playerBuildOptions.locationPathName) ? Path.GetDirectoryName(playerBuildOptions.locationPathName) : playerBuildOptions.locationPathName;
  80. }
  81. private static void RecursivelyPopulateFileReferences(ITest test, Dictionary<string, FileReference> testFilePaths, string repositoryPath, IGuiHelper guiHelper)
  82. {
  83. if (test.HasChildren)
  84. {
  85. foreach (var child in test.Tests)
  86. {
  87. RecursivelyPopulateFileReferences(child, testFilePaths, repositoryPath, guiHelper);
  88. }
  89. return;
  90. }
  91. var testMethod = test.Method;
  92. if (testMethod == null)
  93. {
  94. testMethod = test.Parent.Method;
  95. if (testMethod == null)
  96. {
  97. return;
  98. }
  99. }
  100. var methodInfo = test.Method.MethodInfo;
  101. var type = test.TypeInfo.Type;
  102. var fileOpenInfo = guiHelper.GetFileOpenInfo(type, methodInfo);
  103. var filePathString = Path.Combine(repositoryPath, fileOpenInfo.FilePath);
  104. var lineNumber = fileOpenInfo.LineNumber;
  105. var fileReference = new FileReference
  106. {
  107. FilePath = filePathString,
  108. LineNumber = lineNumber
  109. };
  110. // Cannot be simplified with .TryAdd because Unity 2020.3 and below does not have it.
  111. if (!testFilePaths.ContainsKey(test.FullName))
  112. {
  113. testFilePaths.Add(test.FullName, fileReference);
  114. }
  115. }
  116. private static string GetPathFromArgs(PathType type)
  117. {
  118. var commandLineArgs = Environment.GetCommandLineArgs();
  119. string lookFor;
  120. switch (type)
  121. {
  122. case PathType.ProjectRepositoryPath:
  123. lookFor = "-projectRepositoryPath";
  124. break;
  125. case PathType.ProjectPath:
  126. lookFor = "-projectPath";
  127. break;
  128. default:
  129. throw new ArgumentException("Invalid PathType");
  130. }
  131. for (var i = 0; i < commandLineArgs.Length; i++)
  132. {
  133. if (commandLineArgs[i].Equals(lookFor))
  134. {
  135. return commandLineArgs[i + 1];
  136. }
  137. }
  138. return string.Empty;
  139. }
  140. // Below implementation is copy-paste from HttpUtility.JavaScriptStringEncode
  141. private static string JavaScriptStringEncode(string value) {
  142. if (String.IsNullOrEmpty(value)) {
  143. return String.Empty;
  144. }
  145. StringBuilder b = null;
  146. int startIndex = 0;
  147. int count = 0;
  148. for (int i = 0; i < value.Length; i++) {
  149. char c = value[i];
  150. // Append the unhandled characters (that do not require special treament)
  151. // to the string builder when special characters are detected.
  152. if (CharRequiresJavaScriptEncoding(c)) {
  153. if (b == null) {
  154. b = new StringBuilder(value.Length + 5);
  155. }
  156. if (count > 0) {
  157. b.Append(value, startIndex, count);
  158. }
  159. startIndex = i + 1;
  160. count = 0;
  161. }
  162. switch (c) {
  163. case '\r':
  164. b.Append("\\r");
  165. break;
  166. case '\t':
  167. b.Append("\\t");
  168. break;
  169. case '\"':
  170. b.Append("\\\"");
  171. break;
  172. case '\\':
  173. b.Append("\\\\");
  174. break;
  175. case '\n':
  176. b.Append("\\n");
  177. break;
  178. case '\b':
  179. b.Append("\\b");
  180. break;
  181. case '\f':
  182. b.Append("\\f");
  183. break;
  184. default:
  185. if (CharRequiresJavaScriptEncoding(c)) {
  186. AppendCharAsUnicodeJavaScript(b, c);
  187. }
  188. else {
  189. count++;
  190. }
  191. break;
  192. }
  193. }
  194. if (b == null) {
  195. return value;
  196. }
  197. if (count > 0) {
  198. b.Append(value, startIndex, count);
  199. }
  200. return b.ToString();
  201. }
  202. private static bool CharRequiresJavaScriptEncoding(char c) {
  203. return c < 0x20 // control chars always have to be encoded
  204. || c == '\"' // chars which must be encoded per JSON spec
  205. || c == '\\'
  206. || c == '\'' // HTML-sensitive chars encoded for safety
  207. || c == '<'
  208. || c == '>'
  209. || c == '&'
  210. || c == '\u0085' // newline chars (see Unicode 6.2, Table 5-1 [http://www.unicode.org/versions/Unicode6.2.0/ch05.pdf]) have to be encoded (DevDiv #663531)
  211. || c == '\u2028'
  212. || c == '\u2029';
  213. }
  214. private static void AppendCharAsUnicodeJavaScript(StringBuilder builder, char c) {
  215. builder.Append("\\u");
  216. builder.Append(((int)c).ToString("x4", CultureInfo.InvariantCulture));
  217. }
  218. }
  219. }