GuiHelper.cs 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Reflection;
  7. using System.Text.RegularExpressions;
  8. using Unity.CodeEditor;
  9. using UnityEditor.Utils;
  10. using UnityEngine;
  11. namespace UnityEditor.TestTools.TestRunner.GUI
  12. {
  13. internal class GuiHelper : IGuiHelper
  14. {
  15. public GuiHelper(IMonoCecilHelper monoCecilHelper, IAssetsDatabaseHelper assetsDatabaseHelper)
  16. {
  17. MonoCecilHelper = monoCecilHelper;
  18. AssetsDatabaseHelper = assetsDatabaseHelper;
  19. Editor = new DefaultExternalCodeEditor();
  20. GetCSFiles = (dirPath, fileExtension) =>
  21. {
  22. return Directory.GetFiles(dirPath, $"*{fileExtension}", SearchOption.AllDirectories)
  23. .Select(Paths.UnifyDirectorySeparator);
  24. };
  25. }
  26. internal Func<string, string, IEnumerable<string>> GetCSFiles;
  27. protected IMonoCecilHelper MonoCecilHelper { get; private set; }
  28. public IAssetsDatabaseHelper AssetsDatabaseHelper { get; private set; }
  29. public IExternalCodeEditor Editor { get; internal set; }
  30. private const string FileExtension = ".cs";
  31. public void OpenScriptInExternalEditor(Type type, MethodInfo method)
  32. {
  33. var fileOpenInfo = GetFileOpenInfo(type, method);
  34. if (string.IsNullOrEmpty(fileOpenInfo.FilePath))
  35. {
  36. Debug.LogWarning("Failed to open test method source code in external editor. Inconsistent filename and yield return operator in target method.");
  37. return;
  38. }
  39. if (fileOpenInfo.LineNumber == 1)
  40. {
  41. Debug.LogWarning("Failed to get a line number for unity test method. So please find it in opened file in external editor.");
  42. }
  43. if (!fileOpenInfo.FilePath.Contains("Assets"))
  44. {
  45. Editor.OpenProject(fileOpenInfo.FilePath, fileOpenInfo.LineNumber, 1);
  46. }
  47. else
  48. {
  49. AssetsDatabaseHelper.OpenAssetInItsDefaultExternalEditor(fileOpenInfo.FilePath, fileOpenInfo.LineNumber);
  50. }
  51. }
  52. public IFileOpenInfo GetFileOpenInfo(Type type, MethodInfo method)
  53. {
  54. var fileOpenInfo = MonoCecilHelper.TryGetCecilFileOpenInfo(type, method);
  55. if (string.IsNullOrEmpty(fileOpenInfo.FilePath))
  56. {
  57. var dirPath = Paths.UnifyDirectorySeparator(Application.dataPath);
  58. var allCsFiles = GetCSFiles(dirPath, FileExtension);
  59. var fileName = allCsFiles.FirstOrDefault(x =>
  60. x.Split(Path.DirectorySeparatorChar).Last().Equals(string.Concat(GetTestFileName(type), FileExtension)));
  61. fileOpenInfo.FilePath = fileName ?? string.Empty;
  62. }
  63. if (!fileOpenInfo.FilePath.Contains("Assets"))
  64. {
  65. return fileOpenInfo;
  66. }
  67. fileOpenInfo.FilePath = FilePathToAssetsRelativeAndUnified(fileOpenInfo.FilePath);
  68. return fileOpenInfo;
  69. }
  70. internal static string GetTestFileName(Type type)
  71. {
  72. //This handles the case of a test in a nested class, getting the name of the base class
  73. if (type.FullName != null && type.Namespace!=null && type.FullName.Contains("+"))
  74. {
  75. var removedNamespace = type.FullName.Substring(type.Namespace.Length+1);
  76. return removedNamespace.Substring(0,removedNamespace.IndexOf("+", StringComparison.Ordinal));
  77. }
  78. return type.Name;
  79. }
  80. public string FilePathToAssetsRelativeAndUnified(string filePath)
  81. {
  82. if (string.IsNullOrEmpty(filePath))
  83. return string.Empty;
  84. filePath = Paths.UnifyDirectorySeparator(filePath);
  85. var length = Paths.UnifyDirectorySeparator(Application.dataPath).Length - "Assets".Length;
  86. return filePath.Substring(length);
  87. }
  88. public bool OpenScriptInExternalEditor(string stacktrace)
  89. {
  90. if (string.IsNullOrEmpty(stacktrace))
  91. return false;
  92. var regex = new Regex("in (?<path>.*):{1}(?<line>[0-9]+)");
  93. var matchingLines = stacktrace.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries).Where(x => regex.IsMatch(x)).ToList();
  94. if (!matchingLines.Any())
  95. return false;
  96. var fileOpenInfos = matchingLines
  97. .Select(x => regex.Match(x))
  98. .Select(x =>
  99. new FileOpenInfo
  100. {
  101. FilePath = x.Groups["path"].Value,
  102. LineNumber = int.Parse(x.Groups["line"].Value)
  103. }).ToList();
  104. var fileOpenInfo = fileOpenInfos
  105. .FirstOrDefault(openInfo => !string.IsNullOrEmpty(openInfo.FilePath) && File.Exists(openInfo.FilePath));
  106. if (fileOpenInfo == null)
  107. {
  108. return false;
  109. }
  110. var filePath = FilePathToAssetsRelativeAndUnified(fileOpenInfo.FilePath);
  111. AssetsDatabaseHelper.OpenAssetInItsDefaultExternalEditor(filePath, fileOpenInfo.LineNumber);
  112. return true;
  113. }
  114. }
  115. }