using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using UnityEditor.IMGUI.Controls;
using UnityEditor.TestTools.TestRunner.Api;
using UnityEngine;
using TreeView = UnityEditor.IMGUI.Controls.TreeView<int>;
using TreeViewController = UnityEditor.IMGUI.Controls.TreeViewController<int>;
using TreeViewItem = UnityEditor.IMGUI.Controls.TreeViewItem<int>;
using LazyTreeViewDataSource = UnityEditor.IMGUI.Controls.LazyTreeViewDataSource<int>;
using TreeViewUtility = UnityEditor.IMGUI.Controls.TreeViewUtility<int>;
using TreeViewSelectState = UnityEditor.IMGUI.Controls.TreeViewSelectState<int>;
using ITreeViewGUI = UnityEditor.IMGUI.Controls.ITreeViewGUI<int>;
using ITreeViewDragging = UnityEditor.IMGUI.Controls.ITreeViewDragging<int>;
using ITreeViewDataSource = UnityEditor.IMGUI.Controls.ITreeViewDataSource<int>;
using TreeViewGUI = UnityEditor.IMGUI.Controls.TreeViewGUI<int>;
using TreeViewDragging = UnityEditor.IMGUI.Controls.TreeViewDragging<int>;
using TreeViewDataSource = UnityEditor.IMGUI.Controls.TreeViewDataSource<int>;
using TreeViewItemAlphaNumericSort = UnityEditor.IMGUI.Controls.TreeViewItemAlphaNumericSort<int>;
using RenameOverlay = UnityEditor.RenameOverlay<int>;
namespace UnityEditor.TestTools.TestRunner.GUI
{
internal sealed class TestTreeViewItem : TreeViewItem
{
public TestRunnerResult result;
internal ITestAdaptor m_Test;
public Type type;
public MethodInfo method;
private const int k_ResultTestMaxLength = 15000;
public bool IsGroupNode { get { return m_Test.IsSuite; } }
public string FullName { get { return m_Test.FullName; } }
public string UniqueName { get { return m_Test.UniqueName; } }
public override string displayName
{
get => $"{base.displayName}{(hasChildren ? $" ({TotalChildrenCount} tests) {(TotalSuccessChildrenCount > 0 ? $" {TotalSuccessChildrenCount} tests failed" : null)}" : null)}";
set => base.displayName = value;
}
public string GetAssemblyName()
{
var test = m_Test;
while (test != null)
{
if (test.IsTestAssembly)
{
return test.FullName;
}
test = test.Parent;
}
return null;
}
public IEnumerable<ITestAdaptor> GetMinimizedSelectedTree()
{
if (!m_Test.HasChildren)
{
yield return m_Test;
yield break;
}
var minimizedDescendants = children.OfType<TestTreeViewItem>().SelectMany(c => c.GetMinimizedSelectedTree()).ToArray();
var includeChildren = minimizedDescendants.Count(c => c.Parent == m_Test);
if (includeChildren == m_Test.Children.Count())
{
// All children are included in the filter, so we can just return the parent
yield return m_Test;
yield break;
}
foreach (var child in minimizedDescendants)
{
yield return child;
}
}
public TestTreeViewItem(ITestAdaptor test, int depth, TreeViewItem parent)
: base(GetId(test), depth, parent, test.Name)
{
m_Test = test;
if (test.TypeInfo != null)
{
type = test.TypeInfo.Type;
}
if (test.Method != null)
{
method = test.Method.MethodInfo;
}
displayName = test.Name.Replace("\n", "");
icon = Icons.s_UnknownImg;
}
public int TotalChildrenCount { get; set; }
public int TotalSuccessChildrenCount { get; set; }
private static int GetId(ITestAdaptor test)
{
return test.UniqueName.GetHashCode();
}
public void SetResult(TestRunnerResult testResult)
{
result = testResult;
result.SetResultChangedCallback(ResultUpdated);
ResultUpdated(result);
}
public string GetResultText()
{
if (result.resultStatus == TestRunnerResult.ResultStatus.NotRun)
{
if (result.ignoredOrSkipped)
{
return result.messages;
}
return string.Empty;
}
var durationString = String.Format("{0:0.000}", result.duration);
var sb = new StringBuilder(string.Format("{0} ({1}s)", displayName.Trim(), durationString));
if (!string.IsNullOrEmpty(result.description))
{
sb.AppendFormat("\n{0}", result.description);
}
if (!string.IsNullOrEmpty(result.messages))
{
sb.Append("\n---\n");
sb.Append(result.messages);
}
if (!string.IsNullOrEmpty(result.stacktrace))
{
sb.Append("\n---\n");
sb.Append(StacktraceWithHyperlinks(result.stacktrace));
}
if (!string.IsNullOrEmpty(result.output))
{
sb.Append("\n---\n");
sb.Append(result.output);
}
if (sb.Length > k_ResultTestMaxLength)
{
sb.Length = k_ResultTestMaxLength;
sb.AppendFormat("...\n\n---MESSAGE TRUNCATED AT {0} CHARACTERS---", k_ResultTestMaxLength);
}
return sb.ToString().Trim();
}
private void ResultUpdated(TestRunnerResult testResult)
{
switch (testResult.resultStatus)
{
case TestRunnerResult.ResultStatus.Passed:
icon = Icons.s_SuccessImg;
break;
case TestRunnerResult.ResultStatus.Failed:
icon = Icons.s_FailImg;
break;
case TestRunnerResult.ResultStatus.Inconclusive:
icon = Icons.s_InconclusiveImg;
break;
case TestRunnerResult.ResultStatus.Skipped:
icon = Icons.s_IgnoreImg;
break;
default:
if (testResult.ignoredOrSkipped)
{
icon = Icons.s_IgnoreImg;
}
else if (testResult.notRunnable)
{
icon = Icons.s_FailImg;
}
else
{
icon = Icons.s_UnknownImg;
}
break;
}
}
private static string StacktraceWithHyperlinks(string stacktraceText)
{
StringBuilder textWithHyperlinks = new StringBuilder();
var lines = stacktraceText.Split(new string[] { "\n" }, StringSplitOptions.None);
for (int i = 0; i < lines.Length; ++i)
{
string textBeforeFilePath = "] in ";
int filePathIndex = lines[i].IndexOf(textBeforeFilePath, StringComparison.Ordinal);
if (filePathIndex > 0)
{
filePathIndex += textBeforeFilePath.Length;
if (lines[i][filePathIndex] != '<') // sometimes no url is given, just an id between <>, we can't do an hyperlink
{
string filePathPart = lines[i].Substring(filePathIndex);
int lineIndex = filePathPart.LastIndexOf(":", StringComparison.Ordinal); // LastIndex because the url can contain ':' ex:"C:"
if (lineIndex > 0)
{
string lineString = filePathPart.Substring(lineIndex + 1);
string filePath = filePathPart.Substring(0, lineIndex);
#if UNITY_2021_3_OR_NEWER
var displayedPath = Path.GetRelativePath(Directory.GetCurrentDirectory(), filePath);
#else
var displayedPath = filePath;
#endif
textWithHyperlinks.Append($"{lines[i].Substring(0, filePathIndex)}<color=#40a0ff><a href=\"{filePath}\" line=\"{lineString}\">{displayedPath}:{lineString}</a></color>\n");
continue; // continue to evade the default case
}
}
}
// default case if no hyperlink : we just write the line
textWithHyperlinks.Append(lines[i] + "\n");
}
// Remove the last \n
if (textWithHyperlinks.Length > 0) // textWithHyperlinks always ends with \n if it is not empty
textWithHyperlinks.Remove(textWithHyperlinks.Length - 1, 1);
return textWithHyperlinks.ToString();
}
}
}