first commit
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.GUI
|
||||
{
|
||||
internal class AssetsDatabaseHelper : IAssetsDatabaseHelper
|
||||
{
|
||||
public void OpenAssetInItsDefaultExternalEditor(string assetPath, int line)
|
||||
{
|
||||
var asset = AssetDatabase.LoadMainAssetAtPath(assetPath);
|
||||
AssetDatabase.OpenAsset(asset, line);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 740b3785866edda4b8d1e1a05570a5f8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e88eef57957d4440c8a7ff2ef9dd3d97
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.GUI.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods for dealing with common bit operations.
|
||||
/// </summary>
|
||||
internal static class BitUtility
|
||||
{
|
||||
/// <summary>
|
||||
/// Evaluates the cardinality of an integer, treating the value as a bit set.
|
||||
/// Optimization based on http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel.
|
||||
/// </summary>
|
||||
/// <param name="integer">The input integer value.</param>
|
||||
/// <returns>The number of bits set in the provided input integer value.</returns>
|
||||
internal static int GetCardinality(int integer)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
integer = integer - ((integer >> 1) & 0x55555555);
|
||||
integer = (integer & 0x33333333) + ((integer >> 2) & 0x33333333);
|
||||
integer = (((integer + (integer >> 4)) & 0xF0F0F0F) * 0x1010101) >> 24;
|
||||
}
|
||||
|
||||
return integer;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 24a5f331fec74c9aa9e1e5d74b5a9589
|
||||
timeCreated: 1601747849
|
||||
@@ -0,0 +1,118 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.GUI.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// A flag enum content provider to be used with the <see cref="SelectionDropDown" /> control.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The flag enum type.</typeparam>
|
||||
internal class FlagEnumContentProvider<T> : ISelectionDropDownContentProvider where T : Enum
|
||||
{
|
||||
private readonly Action<T> m_ValueChangedCallback;
|
||||
private readonly T[] m_Values;
|
||||
internal Func<string, string> DisplayNameGenerator = ObjectNames.NicifyVariableName;
|
||||
private T m_CurrentValue;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="FlagEnumContentProvider{T}" /> class.
|
||||
/// </summary>
|
||||
/// <param name="initialValue">The initial selection value.</param>
|
||||
/// <param name="valueChangedCallback">The callback to be invoked on selection change.</param>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown if the generic enum parameter type is not integer based
|
||||
/// or if the initial selection value is empty.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentNullException">Thrown if the provided change callback is null.</exception>
|
||||
public FlagEnumContentProvider(T initialValue, Action<T> valueChangedCallback)
|
||||
{
|
||||
if (Enum.GetUnderlyingType(typeof(T)) != typeof(int))
|
||||
{
|
||||
throw new ArgumentException("Argument underlying type must be integer.");
|
||||
}
|
||||
|
||||
if ((int)(object)initialValue == 0)
|
||||
{
|
||||
throw new ArgumentException("The initial value must not be an empty set.", nameof(initialValue));
|
||||
}
|
||||
|
||||
if (valueChangedCallback == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(valueChangedCallback), "The value change callback must not be null.");
|
||||
}
|
||||
|
||||
m_CurrentValue = initialValue;
|
||||
m_Values = (T[])Enum.GetValues(typeof(T));
|
||||
m_ValueChangedCallback = valueChangedCallback;
|
||||
}
|
||||
|
||||
public int Count => m_Values.Length;
|
||||
public bool IsMultiSelection => true;
|
||||
|
||||
public string GetName(int index)
|
||||
{
|
||||
return ValidateIndexBounds(index) ? DisplayNameGenerator(m_Values[index].ToString()) : string.Empty;
|
||||
}
|
||||
|
||||
public int[] SeparatorIndices => new int[0];
|
||||
|
||||
public bool IsSelected(int index)
|
||||
{
|
||||
return ValidateIndexBounds(index) && IsSet(m_Values[index]);
|
||||
}
|
||||
|
||||
public void SelectItem(int index)
|
||||
{
|
||||
if (!ValidateIndexBounds(index))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (ChangeValue(m_Values[index]))
|
||||
{
|
||||
m_ValueChangedCallback(m_CurrentValue);
|
||||
}
|
||||
}
|
||||
|
||||
private bool ChangeValue(T flag)
|
||||
{
|
||||
var value = flag;
|
||||
var count = GetSetCount();
|
||||
|
||||
if (IsSet(value))
|
||||
{
|
||||
if (count == 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
m_CurrentValue = FlagEnumUtility.RemoveFlag(m_CurrentValue, flag);
|
||||
return true;
|
||||
}
|
||||
|
||||
m_CurrentValue = FlagEnumUtility.SetFlag(m_CurrentValue, flag);
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool IsSet(T flag)
|
||||
{
|
||||
return FlagEnumUtility.HasFlag(m_CurrentValue, flag);
|
||||
}
|
||||
|
||||
private int GetSetCount()
|
||||
{
|
||||
return BitUtility.GetCardinality((int)(object)m_CurrentValue);
|
||||
}
|
||||
|
||||
private bool ValidateIndexBounds(int index)
|
||||
{
|
||||
if (index < 0 || index >= Count)
|
||||
{
|
||||
Debug.LogError($"Requesting item index {index} from a collection of size {Count}");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d31403ec6b334194bdeb4c5ebad64097
|
||||
timeCreated: 1600072403
|
||||
@@ -0,0 +1,74 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.GUI.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods for dealing with common enumerator operations.
|
||||
/// </summary>
|
||||
internal static class FlagEnumUtility
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks for the presence of a flag in a flag enum value.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to check for the presence of the flag.</param>
|
||||
/// <param name="flag">The flag whose presence is to be checked.</param>
|
||||
/// <typeparam name="T">The flag enum type.</typeparam>
|
||||
/// <returns></returns>
|
||||
internal static bool HasFlag<T>(T value, T flag) where T : Enum
|
||||
{
|
||||
ValidateUnderlyingType<T>();
|
||||
|
||||
var intValue = (int)(object)value;
|
||||
var intFlag = (int)(object)flag;
|
||||
return (intValue & intFlag) == intFlag;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a flag in a flag enum value.
|
||||
/// </summary>
|
||||
/// <param name="value">The value where the flag should be set.</param>
|
||||
/// <param name="flag">The flag to be set.</param>
|
||||
/// <typeparam name="T">The flag enum type.</typeparam>
|
||||
/// <returns>The input value with the flag set.</returns>
|
||||
internal static T SetFlag<T>(T value, T flag) where T : Enum
|
||||
{
|
||||
ValidateUnderlyingType<T>();
|
||||
|
||||
var intValue = (int)(object)value;
|
||||
var intFlag = (int)(object)flag;
|
||||
var result = intValue | intFlag;
|
||||
return (T)Enum.ToObject(typeof(T), result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a flag in a flag enum value.
|
||||
/// </summary>
|
||||
/// <param name="value">The value where the flag should be removed.</param>
|
||||
/// <param name="flag">The flag to be removed.</param>
|
||||
/// <typeparam name="T">The flag enum type.</typeparam>
|
||||
/// <returns>The input value with the flag removed.</returns>
|
||||
internal static T RemoveFlag<T>(T value, T flag) where T : Enum
|
||||
{
|
||||
ValidateUnderlyingType<T>();
|
||||
|
||||
var intValue = (int)(object)value;
|
||||
var intFlag = (int)(object)flag;
|
||||
var result = intValue & ~intFlag;
|
||||
return (T)Enum.ToObject(typeof(T), result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates that the underlying type of an enum is integer.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The enum type.</typeparam>
|
||||
/// <exception cref="ArgumentException">Thrown if the underlying type of the enum type parameter is not integer.</exception>
|
||||
private static void ValidateUnderlyingType<T>() where T : Enum
|
||||
{
|
||||
if (Enum.GetUnderlyingType(typeof(T)) != typeof(int))
|
||||
{
|
||||
throw new ArgumentException("Argument underlying type must be integer.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ea6cc55375344f10834cf6fa65197525
|
||||
timeCreated: 1600072466
|
||||
@@ -0,0 +1,101 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.GUI.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// A generic type content provider to be used with the <see cref="SelectionDropDown" /> control.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of values represented by content elements.</typeparam>
|
||||
internal class GenericItemContentProvider<T> : ISelectionDropDownContentProvider where T : IEquatable<T>
|
||||
{
|
||||
private readonly ISelectableItem<T>[] m_Items;
|
||||
private readonly Action<T> m_ValueChangedCallback;
|
||||
private T m_CurrentValue;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="GenericItemContentProvider{T}" /> class.
|
||||
/// </summary>
|
||||
/// <param name="initialValue">The initial selection value.</param>
|
||||
/// <param name="items">The set of selectable items.</param>
|
||||
/// <param name="separatorIndices">The indices of items which should be followed by separator lines.</param>
|
||||
/// <param name="valueChangedCallback"></param>
|
||||
/// <exception cref="ArgumentNullException">Thrown if any of the provided arguments is null, except for the separator indices.</exception>
|
||||
/// <exception cref="ArgumentException">Thrown if the set of items is empty or does not contain the initial selection value.</exception>
|
||||
public GenericItemContentProvider(T initialValue, ISelectableItem<T>[] items, int[] separatorIndices, Action<T> valueChangedCallback)
|
||||
{
|
||||
if (initialValue == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(initialValue), "The initial selection value must not be null.");
|
||||
}
|
||||
|
||||
if (items == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(items), "The set of items must not be null.");
|
||||
}
|
||||
|
||||
if (valueChangedCallback == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(valueChangedCallback), "The value change callback must not be null.");
|
||||
}
|
||||
|
||||
if (items.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("The set of items must not be empty.", nameof(items));
|
||||
}
|
||||
|
||||
if (!items.Any(i => i.Value.Equals(initialValue)))
|
||||
{
|
||||
throw new ArgumentException("The initial selection value must be in the items set.", nameof(items));
|
||||
}
|
||||
|
||||
m_CurrentValue = initialValue;
|
||||
m_Items = items;
|
||||
SeparatorIndices = separatorIndices ?? new int[0];
|
||||
m_ValueChangedCallback = valueChangedCallback;
|
||||
}
|
||||
|
||||
public int Count => m_Items.Length;
|
||||
public bool IsMultiSelection => false;
|
||||
|
||||
public string GetName(int index)
|
||||
{
|
||||
return ValidateIndexBounds(index) ? m_Items[index].DisplayName : string.Empty;
|
||||
}
|
||||
|
||||
public int[] SeparatorIndices { get; }
|
||||
|
||||
public void SelectItem(int index)
|
||||
{
|
||||
if (!ValidateIndexBounds(index))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsSelected(index))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_CurrentValue = m_Items[index].Value;
|
||||
m_ValueChangedCallback(m_CurrentValue);
|
||||
}
|
||||
|
||||
public bool IsSelected(int index)
|
||||
{
|
||||
return ValidateIndexBounds(index) && m_Items[index].Value.Equals(m_CurrentValue);
|
||||
}
|
||||
|
||||
private bool ValidateIndexBounds(int index)
|
||||
{
|
||||
if (index < 0 || index >= Count)
|
||||
{
|
||||
Debug.LogError($"Requesting item index {index} from a collection of size {Count}");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7348ecf02a70497d9a09bd05ad2038ac
|
||||
timeCreated: 1600072422
|
||||
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.GUI.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a content element which can be used with the <see cref="GenericItemContentProvider{T}" /> content provider.
|
||||
/// </summary>
|
||||
internal interface ISelectableItem<out T>
|
||||
{
|
||||
/// <summary>
|
||||
/// The value represented by this item.
|
||||
/// </summary>
|
||||
T Value { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The name to be used when displaying this item.
|
||||
/// </summary>
|
||||
string DisplayName { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7b076e8f3150431baf926fe9ee030b1e
|
||||
timeCreated: 1600072411
|
||||
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.GUI.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a content provider that can be used with the <see cref="SelectionDropDown" /> control.
|
||||
/// </summary>
|
||||
internal interface ISelectionDropDownContentProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// The total number of items to display.
|
||||
/// </summary>
|
||||
int Count { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Multiple selection support.
|
||||
/// Multiple selection dropdowns don't get closed on selection change.
|
||||
/// </summary>
|
||||
bool IsMultiSelection { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The indices of items which should be followed by separator lines.
|
||||
/// </summary>
|
||||
int[] SeparatorIndices { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the display name of the item at the specified index.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the item whose display name is to be returned.</param>
|
||||
/// <returns>The display name of the item at the specified index.</returns>
|
||||
string GetName(int index);
|
||||
|
||||
/// <summary>
|
||||
/// Signals a request to select the item at the specified index.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the item to be selected.</param>
|
||||
void SelectItem(int index);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the selection status of the item at the specified index.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the item whose selection status is to be returned.</param>
|
||||
/// <returns><c>true</c> if the item is currently selected; otherwise, <c>false</c>. </returns>
|
||||
bool IsSelected(int index);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c2247a4dfeae4607949780b219a7d3c8
|
||||
timeCreated: 1600072397
|
||||
@@ -0,0 +1,82 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.GUI.Controls
|
||||
{
|
||||
internal class MultiValueContentProvider<T> : ISelectionDropDownContentProvider where T : IEquatable<T>
|
||||
{
|
||||
private T[] m_Values;
|
||||
private bool[] m_Selected;
|
||||
private Action<T[]> m_SelectionChangeCallback;
|
||||
|
||||
public MultiValueContentProvider(T[] values, T[] selectedValues, Action<T[]> selectionChangeCallback)
|
||||
{
|
||||
m_Values = values ?? throw new ArgumentNullException(nameof(values));
|
||||
if (selectedValues == null)
|
||||
{
|
||||
m_Selected = new bool[values.Length];
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Selected = values.Select(value => selectedValues.Contains(value)).ToArray();
|
||||
}
|
||||
m_SelectionChangeCallback = selectionChangeCallback;
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get { return m_Values.Length; }
|
||||
}
|
||||
|
||||
public bool IsMultiSelection
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
public int[] SeparatorIndices
|
||||
{
|
||||
get { return new int[0]; }
|
||||
}
|
||||
public string GetName(int index)
|
||||
{
|
||||
if (!ValidateIndexBounds(index))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return m_Values[index].ToString();
|
||||
}
|
||||
|
||||
public void SelectItem(int index)
|
||||
{
|
||||
if (!ValidateIndexBounds(index))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_Selected[index] = !m_Selected[index];
|
||||
m_SelectionChangeCallback.Invoke(m_Values.Where((v, i) => m_Selected[i]).ToArray());
|
||||
}
|
||||
|
||||
public bool IsSelected(int index)
|
||||
{
|
||||
if (!ValidateIndexBounds(index))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return m_Selected[index];
|
||||
}
|
||||
|
||||
private bool ValidateIndexBounds(int index)
|
||||
{
|
||||
if (index < 0 || index >= Count)
|
||||
{
|
||||
Debug.LogError($"Requesting item index {index} from a collection of size {Count}");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fb736ed47b6da4b499431bf9c6de5890
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.GUI.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// A default implementation of the <see cref="ISelectableItem{T}" /> interface.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the value represented by this content element.</typeparam>
|
||||
internal class SelectableItemContent<T> : ISelectableItem<T>
|
||||
{
|
||||
private readonly string m_DisplayName;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="SelectableItemContent{T}" /> class
|
||||
/// </summary>
|
||||
/// <param name="itemValue">The value represented by this item.</param>
|
||||
/// <param name="displayName">The display name of this item.</param>
|
||||
public SelectableItemContent(T itemValue, string displayName)
|
||||
{
|
||||
Value = itemValue;
|
||||
m_DisplayName = displayName;
|
||||
}
|
||||
|
||||
public T Value { get; }
|
||||
|
||||
public string DisplayName => m_DisplayName ?? string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f7d5e5417ccd4aa0bfa39228c674f142
|
||||
timeCreated: 1600072417
|
||||
@@ -0,0 +1,167 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.GUI.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// A DropDown editor control accepting <see cref="ISelectionDropDownContentProvider" />-based content providers.
|
||||
/// </summary>
|
||||
internal class SelectionDropDown : PopupWindowContent
|
||||
{
|
||||
private static readonly int k_ControlId = typeof(SelectionDropDown).GetHashCode();
|
||||
private readonly ISelectionDropDownContentProvider m_ContentProvider;
|
||||
private readonly Vector2 m_ContentSize;
|
||||
private Vector2 m_ScrollPosition = Vector2.zero;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="SelectionDropDown" /> editor control.
|
||||
/// </summary>
|
||||
/// <param name="contentProvider">The content provider to use.</param>
|
||||
public SelectionDropDown(ISelectionDropDownContentProvider contentProvider)
|
||||
{
|
||||
m_ContentProvider = contentProvider;
|
||||
var width = CalculateContentWidth();
|
||||
var height = CalculateContentHeight();
|
||||
m_ContentSize = new Vector2(width, height);
|
||||
}
|
||||
|
||||
public override void OnOpen()
|
||||
{
|
||||
base.OnOpen();
|
||||
editorWindow.wantsMouseMove = true;
|
||||
editorWindow.wantsMouseEnterLeaveWindow = true;
|
||||
}
|
||||
|
||||
public override void OnClose()
|
||||
{
|
||||
GUIUtility.hotControl = 0;
|
||||
base.OnClose();
|
||||
}
|
||||
|
||||
public override Vector2 GetWindowSize()
|
||||
{
|
||||
return m_ContentSize;
|
||||
}
|
||||
|
||||
public override void OnGUI(Rect rect)
|
||||
{
|
||||
var evt = Event.current;
|
||||
var contentRect = new Rect(Styles.TopMargin, 0, 1, m_ContentSize.y);
|
||||
m_ScrollPosition = UnityEngine.GUI.BeginScrollView(rect, m_ScrollPosition, contentRect);
|
||||
{
|
||||
var yPos = Styles.TopMargin;
|
||||
for (var i = 0; i < m_ContentProvider.Count; ++i)
|
||||
{
|
||||
var itemRect = new Rect(0, yPos, rect.width, Styles.LineHeight);
|
||||
var separatorOffset = 0f;
|
||||
|
||||
switch (evt.type)
|
||||
{
|
||||
case EventType.Repaint:
|
||||
var content = new GUIContent(m_ContentProvider.GetName(i));
|
||||
var hover = itemRect.Contains(evt.mousePosition);
|
||||
var on = m_ContentProvider.IsSelected(i);
|
||||
Styles.MenuItem.Draw(itemRect, content, hover, false, on, false);
|
||||
separatorOffset = DrawSeparator(i, itemRect);
|
||||
break;
|
||||
|
||||
case EventType.MouseDown:
|
||||
if (evt.button == 0 && itemRect.Contains(evt.mousePosition))
|
||||
{
|
||||
m_ContentProvider.SelectItem(i);
|
||||
if (!m_ContentProvider.IsMultiSelection)
|
||||
{
|
||||
editorWindow.Close();
|
||||
}
|
||||
|
||||
evt.Use();
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case EventType.MouseEnterWindow:
|
||||
GUIUtility.hotControl = k_ControlId;
|
||||
evt.Use();
|
||||
break;
|
||||
|
||||
case EventType.MouseLeaveWindow:
|
||||
GUIUtility.hotControl = 0;
|
||||
evt.Use();
|
||||
break;
|
||||
|
||||
case EventType.MouseUp:
|
||||
case EventType.MouseMove:
|
||||
evt.Use();
|
||||
break;
|
||||
}
|
||||
|
||||
yPos += Styles.LineHeight + separatorOffset;
|
||||
}
|
||||
}
|
||||
UnityEngine.GUI.EndScrollView();
|
||||
}
|
||||
|
||||
private float CalculateContentWidth()
|
||||
{
|
||||
var maxItemWidth = 0f;
|
||||
for (var i = 0; i < m_ContentProvider.Count; ++i)
|
||||
{
|
||||
var itemContent = new GUIContent(m_ContentProvider.GetName(i));
|
||||
var itemWidth = Styles.MenuItem.CalcSize(itemContent).x;
|
||||
maxItemWidth = Mathf.Max(itemWidth, maxItemWidth);
|
||||
}
|
||||
|
||||
return maxItemWidth;
|
||||
}
|
||||
|
||||
private float CalculateContentHeight()
|
||||
{
|
||||
return m_ContentProvider.Count * Styles.LineHeight
|
||||
+ m_ContentProvider.SeparatorIndices.Length * Styles.SeparatorHeight
|
||||
+ Styles.TopMargin + Styles.BottomMargin;
|
||||
}
|
||||
|
||||
private float DrawSeparator(int i, Rect itemRect)
|
||||
{
|
||||
if (Array.IndexOf(m_ContentProvider.SeparatorIndices, i) < 0)
|
||||
{
|
||||
return 0f;
|
||||
}
|
||||
|
||||
var separatorRect = GetSeparatorRect(itemRect);
|
||||
DrawRect(separatorRect, Styles.SeparatorColor);
|
||||
return Styles.SeparatorHeight;
|
||||
}
|
||||
|
||||
private static Rect GetSeparatorRect(Rect itemRect)
|
||||
{
|
||||
var x = itemRect.x + Styles.SeparatorMargin;
|
||||
var y = itemRect.y + itemRect.height + Styles.SeparatorHeight * 0.15f;
|
||||
var width = itemRect.width - 2 * Styles.SeparatorMargin;
|
||||
const float height = 1f;
|
||||
|
||||
return new Rect(x, y, width, height);
|
||||
}
|
||||
|
||||
private static void DrawRect(Rect rect, Color color)
|
||||
{
|
||||
var originalColor = UnityEngine.GUI.color;
|
||||
UnityEngine.GUI.color *= color;
|
||||
UnityEngine.GUI.DrawTexture(rect, EditorGUIUtility.whiteTexture);
|
||||
UnityEngine.GUI.color = originalColor;
|
||||
}
|
||||
|
||||
private static class Styles
|
||||
{
|
||||
public const float LineHeight = EditorGUI.kSingleLineHeight;
|
||||
public const float TopMargin = 3f;
|
||||
public const float BottomMargin = 1f;
|
||||
public const float SeparatorHeight = 4f;
|
||||
public const float SeparatorMargin = 3f;
|
||||
public static readonly GUIStyle MenuItem = "MenuItem";
|
||||
public static readonly Color SeparatorColor = EditorGUIUtility.isProSkin
|
||||
? new Color(0.32f, 0.32f, 0.32f, 1.333f)
|
||||
: new Color(0.6f, 0.6f, 0.6f, 1.333f);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f8f9b43d4e034c2d997f4eb6a0d85a96
|
||||
timeCreated: 1600072477
|
||||
@@ -0,0 +1,141 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using Unity.CodeEditor;
|
||||
using UnityEditor.Utils;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.GUI
|
||||
{
|
||||
internal class GuiHelper : IGuiHelper
|
||||
{
|
||||
public GuiHelper(IMonoCecilHelper monoCecilHelper, IAssetsDatabaseHelper assetsDatabaseHelper)
|
||||
{
|
||||
MonoCecilHelper = monoCecilHelper;
|
||||
AssetsDatabaseHelper = assetsDatabaseHelper;
|
||||
GetCSFiles = (dirPath, fileExtension) =>
|
||||
{
|
||||
return Directory.GetFiles(dirPath, $"*{fileExtension}", SearchOption.AllDirectories)
|
||||
.Select(Paths.UnifyDirectorySeparator);
|
||||
};
|
||||
}
|
||||
|
||||
internal Func<string, string, IEnumerable<string>> GetCSFiles;
|
||||
protected IMonoCecilHelper MonoCecilHelper { get; private set; }
|
||||
public IAssetsDatabaseHelper AssetsDatabaseHelper { get; private set; }
|
||||
public IExternalCodeEditor Editor { get; internal set; }
|
||||
private const string FileExtension = ".cs";
|
||||
|
||||
public void OpenScriptInExternalEditor(Type type, MethodInfo method)
|
||||
{
|
||||
var fileOpenInfo = GetFileOpenInfo(type, method);
|
||||
|
||||
if (string.IsNullOrEmpty(fileOpenInfo.FilePath))
|
||||
{
|
||||
Debug.LogWarning("Failed to open test method source code in external editor. Inconsistent filename and yield return operator in target method.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (fileOpenInfo.LineNumber == 1)
|
||||
{
|
||||
Debug.LogWarning("Failed to get a line number for unity test method. So please find it in opened file in external editor.");
|
||||
}
|
||||
|
||||
if (!fileOpenInfo.FilePath.Contains("Assets"))
|
||||
{
|
||||
(Editor ?? CodeEditor.CurrentEditor).OpenProject(fileOpenInfo.FilePath, fileOpenInfo.LineNumber, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
AssetsDatabaseHelper.OpenAssetInItsDefaultExternalEditor(fileOpenInfo.FilePath, fileOpenInfo.LineNumber);
|
||||
}
|
||||
}
|
||||
|
||||
public IFileOpenInfo GetFileOpenInfo(Type type, MethodInfo method)
|
||||
{
|
||||
var fileOpenInfo = MonoCecilHelper.TryGetCecilFileOpenInfo(type, method);
|
||||
if (string.IsNullOrEmpty(fileOpenInfo.FilePath))
|
||||
{
|
||||
var dirPath = Paths.UnifyDirectorySeparator(Application.dataPath);
|
||||
var allCsFiles = GetCSFiles(dirPath, FileExtension);
|
||||
|
||||
var fileName = allCsFiles.FirstOrDefault(x =>
|
||||
x.Split(Path.DirectorySeparatorChar).Last().Equals(string.Concat(GetTestFileName(type), FileExtension)));
|
||||
|
||||
fileOpenInfo.FilePath = fileName ?? string.Empty;
|
||||
}
|
||||
|
||||
if (!fileOpenInfo.FilePath.Contains("Assets"))
|
||||
{
|
||||
return fileOpenInfo;
|
||||
}
|
||||
fileOpenInfo.FilePath = FilePathToAssetsRelativeAndUnified(fileOpenInfo.FilePath);
|
||||
|
||||
return fileOpenInfo;
|
||||
}
|
||||
|
||||
internal static string GetTestFileName(Type type)
|
||||
{
|
||||
//This handles the case of a test in a nested class, getting the name of the base class
|
||||
if (type.FullName != null && type.Namespace != null && type.FullName.Contains("+"))
|
||||
{
|
||||
var removedNamespace = type.FullName.Substring(type.Namespace.Length + 1);
|
||||
return removedNamespace.Substring(0, removedNamespace.IndexOf("+", StringComparison.Ordinal));
|
||||
}
|
||||
return type.Name;
|
||||
}
|
||||
|
||||
public string FilePathToAssetsRelativeAndUnified(string filePath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filePath))
|
||||
return string.Empty;
|
||||
|
||||
#if UNITY_2021_3_OR_NEWER
|
||||
return Path.GetRelativePath(Directory.GetCurrentDirectory(), filePath);
|
||||
#else
|
||||
filePath = Paths.UnifyDirectorySeparator(filePath);
|
||||
var length = Paths.UnifyDirectorySeparator(Application.dataPath).Length - "Assets".Length;
|
||||
|
||||
return filePath.Substring(length);
|
||||
#endif
|
||||
}
|
||||
|
||||
public bool OpenScriptInExternalEditor(string stacktrace)
|
||||
{
|
||||
if (string.IsNullOrEmpty(stacktrace))
|
||||
return false;
|
||||
|
||||
var regex = new Regex("in (?<path>.*):{1}(?<line>[0-9]+)");
|
||||
|
||||
var matchingLines = stacktrace.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries).Where(x => regex.IsMatch(x)).ToList();
|
||||
if (!matchingLines.Any())
|
||||
return false;
|
||||
|
||||
var fileOpenInfos = matchingLines
|
||||
.Select(x => regex.Match(x))
|
||||
.Select(x =>
|
||||
new FileOpenInfo
|
||||
{
|
||||
FilePath = x.Groups["path"].Value,
|
||||
LineNumber = int.Parse(x.Groups["line"].Value)
|
||||
}).ToList();
|
||||
|
||||
var fileOpenInfo = fileOpenInfos
|
||||
.FirstOrDefault(openInfo => !string.IsNullOrEmpty(openInfo.FilePath) && File.Exists(openInfo.FilePath));
|
||||
|
||||
if (fileOpenInfo == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var filePath = FilePathToAssetsRelativeAndUnified(fileOpenInfo.FilePath);
|
||||
AssetsDatabaseHelper.OpenAssetInItsDefaultExternalEditor(filePath, fileOpenInfo.LineNumber);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d0138170d24533e47b8e6c250c6d7fbc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.GUI
|
||||
{
|
||||
internal interface IAssetsDatabaseHelper
|
||||
{
|
||||
void OpenAssetInItsDefaultExternalEditor(string assetPath, int line);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 208e46d59ff6e304db0318377d20f5a1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.GUI
|
||||
{
|
||||
internal interface IGuiHelper
|
||||
{
|
||||
bool OpenScriptInExternalEditor(string stacktrace);
|
||||
void OpenScriptInExternalEditor(Type type, MethodInfo method);
|
||||
IFileOpenInfo GetFileOpenInfo(Type type, MethodInfo method);
|
||||
string FilePathToAssetsRelativeAndUnified(string filePath);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fd57cf917f61bbb42b8f030436426ddd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 92e3104769da143888a712cdea27d950
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,24 @@
|
||||
namespace UnityEditor.TestTools.TestRunner.GUI.TestAssets
|
||||
{
|
||||
/// <inheritdoc />
|
||||
internal class ActiveFolderTemplateAssetCreator : IActiveFolderTemplateAssetCreator
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string GetActiveFolderPath()
|
||||
{
|
||||
return AssetDatabase.GetAssetPath(Selection.activeObject);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void CreateFolderWithTemplates(string defaultName, params string[] templateNames)
|
||||
{
|
||||
ProjectWindowUtil.CreateFolderWithTemplates(defaultName, templateNames);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void CreateScriptAssetFromTemplateFile(string defaultName, string templatePath)
|
||||
{
|
||||
ProjectWindowUtil.CreateScriptAssetFromTemplateFile(templatePath, defaultName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f47c66e80010e4020b6803b930eb432c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,78 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UnityEditor.Scripting.ScriptCompilation;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.GUI.TestAssets
|
||||
{
|
||||
/// <inheritdoc />
|
||||
internal class CustomScriptAssemblyMappingFinder : ICustomScriptAssemblyMappingFinder
|
||||
{
|
||||
/// <inheritdoc />
|
||||
/// <exception cref="ArgumentNullException">The provided <paramref name="folderPath" /> string argument is null.</exception>
|
||||
public ICustomScriptAssembly FindCustomScriptAssemblyFromFolderPath(string folderPath)
|
||||
{
|
||||
if (folderPath == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(folderPath));
|
||||
}
|
||||
|
||||
var scriptInFolderPath = Path.Combine(folderPath, "Foo.cs");
|
||||
var customScriptAssembly = FindCustomScriptAssemblyFromScriptPath(scriptInFolderPath);
|
||||
return customScriptAssembly;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the Custom Script Assembly associated with the provided script asset path.
|
||||
/// </summary>
|
||||
/// <param name="scriptPath">The script path to check.</param>
|
||||
/// <returns>The associated <see cref="ICustomScriptAssembly" />; null if none.</returns>
|
||||
private static ICustomScriptAssembly FindCustomScriptAssemblyFromScriptPath(string scriptPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
var customScriptAssembly = EditorCompilationInterface.Instance.FindCustomScriptAssemblyFromScriptPath(scriptPath);
|
||||
return new CustomScriptAssemblyWrapper(customScriptAssembly);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Custom Script Assembly wrapper.
|
||||
/// </summary>
|
||||
internal class CustomScriptAssemblyWrapper : ICustomScriptAssembly
|
||||
{
|
||||
internal readonly CustomScriptAssembly targetAssembly;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="CustomScriptAssemblyWrapper" /> class.
|
||||
/// </summary>
|
||||
/// <param name="assembly">The <see cref="CustomScriptAssembly" /> to be represented by the wrapper.</param>
|
||||
/// <exception cref="ArgumentNullException">The provided <paramref name="assembly" /> argument is null.</exception>
|
||||
internal CustomScriptAssemblyWrapper(CustomScriptAssembly assembly)
|
||||
{
|
||||
targetAssembly = assembly
|
||||
?? throw new ArgumentNullException(nameof(assembly), "The provided assembly must not be null.");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool HasPrecompiledReference(string libraryFilename)
|
||||
{
|
||||
var precompiledReferences = targetAssembly.PrecompiledReferences;
|
||||
var libraryReferenceExists = precompiledReferences != null
|
||||
&& precompiledReferences.Any(r => Path.GetFileName(r) == libraryFilename);
|
||||
return libraryReferenceExists;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool HasAssemblyFlag(AssemblyFlags flag)
|
||||
{
|
||||
var hasAssemblyFlag = (targetAssembly.AssemblyFlags & flag) == flag;
|
||||
return hasAssemblyFlag;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f03c073fcc564ab582ac38999beb4b6d
|
||||
timeCreated: 1603203112
|
||||
@@ -0,0 +1,93 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UnityEditor.Scripting.ScriptCompilation;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.GUI.TestAssets
|
||||
{
|
||||
/// <inheritdoc />
|
||||
internal class FolderPathTestCompilationContextProvider : IFolderPathTestCompilationContextProvider
|
||||
{
|
||||
internal const string nUnitLibraryFilename = "nunit.framework.dll";
|
||||
|
||||
private static ICustomScriptAssemblyMappingFinder s_CustomScriptAssemblyMappingFinder;
|
||||
|
||||
internal static ICustomScriptAssemblyMappingFinder CustomScriptAssemblyMappingFinder
|
||||
{
|
||||
private get => s_CustomScriptAssemblyMappingFinder ?? (s_CustomScriptAssemblyMappingFinder = new CustomScriptAssemblyMappingFinder());
|
||||
set => s_CustomScriptAssemblyMappingFinder = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the provided folder path belongs to a Custom Test Assembly.
|
||||
/// A Custom Test Assembly is defined by a valid reference to the precompiled NUnit library.
|
||||
/// </summary>
|
||||
/// <param name="folderPath">The folder path to check.</param>
|
||||
/// <returns>True if a custom test assembly associated with the provided folder can be found; false otherwise.</returns>
|
||||
/// <exception cref="ArgumentNullException">The <paramref name="folderPath" /> string argument is null.</exception>
|
||||
public bool FolderPathBelongsToCustomTestAssembly(string folderPath)
|
||||
{
|
||||
if (folderPath == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(folderPath));
|
||||
}
|
||||
|
||||
var customScriptAssembly = CustomScriptAssemblyMappingFinder.FindCustomScriptAssemblyFromFolderPath(folderPath);
|
||||
var assemblyIsCustomTestAssembly = customScriptAssembly != null
|
||||
&& customScriptAssembly.HasPrecompiledReference(nUnitLibraryFilename);
|
||||
return assemblyIsCustomTestAssembly;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the provided folder path belongs to an assembly capable of compiling Test Scripts.
|
||||
/// Unless the <see cref="PlayerSettings.playModeTestRunnerEnabled" /> setting is enabled,
|
||||
/// a Test Script can only be compiled in a Custom Test Assembly
|
||||
/// or an (implicit or explicit) <see cref="AssemblyFlags.EditorOnly" /> assembly.
|
||||
/// </summary>
|
||||
/// <param name="folderPath">The folder path to check.</param>
|
||||
/// <returns>True if Test Scripts can be successfully compiled when added to this folder path; false otherwise.</returns>
|
||||
/// <exception cref="ArgumentNullException">The <paramref name="folderPath" /> string argument is null.</exception>
|
||||
public bool TestScriptWillCompileInFolderPath(string folderPath)
|
||||
{
|
||||
if (folderPath == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(folderPath));
|
||||
}
|
||||
|
||||
if (PlayerSettings.playModeTestRunnerEnabled)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var customScriptAssembly = CustomScriptAssemblyMappingFinder.FindCustomScriptAssemblyFromFolderPath(folderPath);
|
||||
if (customScriptAssembly != null)
|
||||
{
|
||||
var assemblyCanCompileTestScripts = customScriptAssembly.HasPrecompiledReference(nUnitLibraryFilename)
|
||||
|| customScriptAssembly.HasAssemblyFlag(AssemblyFlags.EditorOnly);
|
||||
return assemblyCanCompileTestScripts;
|
||||
}
|
||||
|
||||
var isImplicitEditorAssembly = FolderPathBelongsToImplicitEditorAssembly(folderPath);
|
||||
return isImplicitEditorAssembly;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the provided folder path is a special editor path that belongs to an implicit editor assembly.
|
||||
/// </summary>
|
||||
/// <param name="folderPath">The folder path to check.</param>
|
||||
/// <returns>True if the folder path belongs to an implicit editor assembly; false otherwise.</returns>
|
||||
/// <exception cref="ArgumentNullException">The <paramref name="folderPath" /> string argument is null.</exception>
|
||||
internal static bool FolderPathBelongsToImplicitEditorAssembly(string folderPath)
|
||||
{
|
||||
if (folderPath == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(folderPath));
|
||||
}
|
||||
|
||||
const char unityPathSeparator = '/';
|
||||
var unityFormatPath = folderPath.Replace('\\', unityPathSeparator);
|
||||
var folderComponents = unityFormatPath.Split(unityPathSeparator);
|
||||
var folderComponentsIncludeEditorFolder = folderComponents.Any(n => n.ToLower().Equals("editor"));
|
||||
return folderComponentsIncludeEditorFolder;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3d92c578e28043ef95d4b703e008be64
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.GUI.TestAssets
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides basic utility methods for creating assets in the active Project Browser folder path.
|
||||
/// </summary>
|
||||
internal interface IActiveFolderTemplateAssetCreator
|
||||
{
|
||||
/// <summary>
|
||||
/// The active Project Browser folder path relative to the root project folder.
|
||||
/// </summary>
|
||||
/// <returns>The active folder path string.</returns>
|
||||
string GetActiveFolderPath();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new folder asset in the active folder path with assets defined by provided templates.
|
||||
/// </summary>
|
||||
/// <param name="defaultName">The default name of the folder.</param>
|
||||
/// <param name="templateNames">The names of templates to be used when creating embedded assets.</param>
|
||||
void CreateFolderWithTemplates(string defaultName, params string[] templateNames);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new script asset in the active folder path defined by the provided template.
|
||||
/// </summary>
|
||||
/// <param name="defaultName">The default name of the new script asset.</param>
|
||||
/// <param name="templatePath">The template to be used when creating the asset.</param>
|
||||
void CreateScriptAssetFromTemplateFile(string defaultName, string templatePath);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 409ee14a63784dc0ab5d002081211814
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using UnityEditor.Scripting.ScriptCompilation;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.GUI.TestAssets
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a wrapper for a Custom Script Assembly and exposes its basic properties.
|
||||
/// </summary>
|
||||
internal interface ICustomScriptAssembly
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if the Custom Script Assembly is referencing the provided precompiled library.
|
||||
/// </summary>
|
||||
/// <param name="libraryFilename">The name of the precompiled library reference to be checked.</param>
|
||||
/// <returns>True if the assembly references the provided precompiled library; false otherwise.</returns>
|
||||
bool HasPrecompiledReference(string libraryFilename);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the Custom Script Assembly has the provided <see cref="AssemblyFlags" /> value set.
|
||||
/// </summary>
|
||||
/// <param name="flag">The <see cref="AssemblyFlags" /> value to check against.</param>
|
||||
/// <returns>True if the provided <paramref name="flag" /> value is set; false otherwise.</returns>
|
||||
bool HasAssemblyFlag(AssemblyFlags flag);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 32829ea9e75c475295f73ff867e2f9d0
|
||||
timeCreated: 1603203107
|
||||
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.GUI.TestAssets
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides mapping information from folder paths to their corresponding Custom Script Assembly scope.
|
||||
/// </summary>
|
||||
internal interface ICustomScriptAssemblyMappingFinder
|
||||
{
|
||||
/// <summary>
|
||||
/// Finds the Custom Script Assembly associated with the provided folder path.
|
||||
/// </summary>
|
||||
/// <param name="folderPath">The folder path to check.</param>
|
||||
/// <returns>The associated <see cref="ICustomScriptAssembly" />; null if none.</returns>
|
||||
ICustomScriptAssembly FindCustomScriptAssemblyFromFolderPath(string folderPath);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0cd0deb81d984e58952ccd7e1dd6b2bb
|
||||
timeCreated: 1603203104
|
||||
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.GUI.TestAssets
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides Test Script compilation context associated with project folder paths.
|
||||
/// </summary>
|
||||
internal interface IFolderPathTestCompilationContextProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if the provided folder path belongs to a Custom Test Assembly.
|
||||
/// </summary>
|
||||
bool FolderPathBelongsToCustomTestAssembly(string folderPath);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the provided folder path belongs to an assembly capable of compiling Test Scripts.
|
||||
/// </summary>
|
||||
bool TestScriptWillCompileInFolderPath(string folderPath);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e5f4a89476c1448abc7e0a9719b13b36
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.GUI.TestAssets
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides an interface for creating test assets from templates.
|
||||
/// </summary>
|
||||
internal interface ITestScriptAssetsCreator
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new folder in the active folder path with an associated Test Script Assembly definition.
|
||||
/// </summary>
|
||||
/// <param name="isEditorOnly">Should the assembly definition be editor-only?</param>
|
||||
void AddNewFolderWithTestAssemblyDefinition(bool isEditorOnly = false);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the active folder path already contains a Test Script Assembly definition.
|
||||
/// </summary>
|
||||
/// <returns>True if the active folder path contains a Test Script Assembly; false otherwise.</returns>
|
||||
bool ActiveFolderContainsTestAssemblyDefinition();
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new Test Script asset in the active folder path.
|
||||
/// </summary>
|
||||
void AddNewTestScript();
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a Test Script asset can be compiled in the active folder path.
|
||||
/// </summary>
|
||||
/// <returns>True if a Test Script can be compiled in the active folder path; false otherwise.</returns>
|
||||
bool TestScriptWillCompileInActiveFolder();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e7f72702c2f04b999739380ef9c0de5f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.GUI.TestAssets
|
||||
{
|
||||
/// <summary>
|
||||
/// The set of Menu Items dedicated to creating test assets: Test Scripts and Custom Test Assemblies.
|
||||
/// </summary>
|
||||
internal static class TestScriptAssetMenuItems
|
||||
{
|
||||
internal const string addNewFolderWithTestAssemblyDefinitionMenuItem = "Assets/Create/Testing/Tests Assembly Folder";
|
||||
internal const string addNewTestScriptMenuItem = "Assets/Create/Testing/C# Test Script";
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new folder asset and an associated Custom Test Assembly in the active folder path.
|
||||
/// </summary>
|
||||
[MenuItem(addNewFolderWithTestAssemblyDefinitionMenuItem, false, 83)]
|
||||
public static void AddNewFolderWithTestAssemblyDefinition()
|
||||
{
|
||||
TestScriptAssetsCreator.Instance.AddNewFolderWithTestAssemblyDefinition();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if it is possible to add a new Custom Test Assembly inside the active folder path.
|
||||
/// </summary>
|
||||
/// <returns>False if the active folder path already contains a Custom Test Assembly; true otherwise.</returns>
|
||||
[MenuItem(addNewFolderWithTestAssemblyDefinitionMenuItem, true, 83)]
|
||||
public static bool CanAddNewFolderWithTestAssemblyDefinition()
|
||||
{
|
||||
var testAssemblyAlreadyExists = TestScriptAssetsCreator.Instance.ActiveFolderContainsTestAssemblyDefinition();
|
||||
return !testAssemblyAlreadyExists;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new Test Script asset in the active folder path.
|
||||
/// </summary>
|
||||
[MenuItem(addNewTestScriptMenuItem, false, 83)]
|
||||
public static void AddNewTestScript()
|
||||
{
|
||||
TestScriptAssetsCreator.Instance.AddNewTestScript();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if it is possible to add a new Test Script in the active folder path.
|
||||
/// </summary>
|
||||
/// <returns>True if a Test Script can be compiled in the active folder path; false otherwise.</returns>
|
||||
[MenuItem(addNewTestScriptMenuItem, true, 83)]
|
||||
public static bool CanAddNewTestScript()
|
||||
{
|
||||
var testScriptWillCompile = TestScriptAssetsCreator.Instance.TestScriptWillCompileInActiveFolder();
|
||||
return testScriptWillCompile;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3c702cb84a2a4576bf275a76bc17f8e8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,78 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.GUI.TestAssets
|
||||
{
|
||||
/// <inheritdoc />
|
||||
internal class TestScriptAssetsCreator : ITestScriptAssetsCreator
|
||||
{
|
||||
private const string k_AssemblyDefinitionEditModeTestTemplate = "92-Assembly Definition-NewEditModeTestAssembly.asmdef.txt";
|
||||
internal const string assemblyDefinitionTestTemplate = "92-Assembly Definition-NewTestAssembly.asmdef.txt";
|
||||
|
||||
internal const string resourcesTemplatePath = "Resources/ScriptTemplates";
|
||||
internal const string testScriptTemplate = "83-C# Script-NewTestScript.cs.txt";
|
||||
|
||||
internal const string defaultNewTestAssemblyFolderName = "Tests";
|
||||
internal const string defaultNewTestScriptName = "NewTestScript.cs";
|
||||
|
||||
private static IFolderPathTestCompilationContextProvider s_FolderPathCompilationContext;
|
||||
private static IActiveFolderTemplateAssetCreator s_ActiveFolderTemplateAssetCreator;
|
||||
private static ITestScriptAssetsCreator s_Instance;
|
||||
|
||||
internal static IFolderPathTestCompilationContextProvider FolderPathContext
|
||||
{
|
||||
private get => s_FolderPathCompilationContext ?? (s_FolderPathCompilationContext = new FolderPathTestCompilationContextProvider());
|
||||
set => s_FolderPathCompilationContext = value;
|
||||
}
|
||||
|
||||
internal static IActiveFolderTemplateAssetCreator ActiveFolderTemplateAssetCreator
|
||||
{
|
||||
private get => s_ActiveFolderTemplateAssetCreator ?? (s_ActiveFolderTemplateAssetCreator = new ActiveFolderTemplateAssetCreator());
|
||||
set => s_ActiveFolderTemplateAssetCreator = value;
|
||||
}
|
||||
|
||||
internal static ITestScriptAssetsCreator Instance => s_Instance ?? (s_Instance = new TestScriptAssetsCreator());
|
||||
|
||||
private static string ActiveFolderPath => ActiveFolderTemplateAssetCreator.GetActiveFolderPath();
|
||||
private static string ScriptTemplatesResourcesPath => Path.Combine(EditorApplication.applicationContentsPath, resourcesTemplatePath);
|
||||
|
||||
#if UNITY_2023_3_OR_NEWER
|
||||
private static string ScriptTemplatePath => Path.Combine(ScriptTemplatesResourcesPath, AssetsMenuUtility.GetScriptTemplatePath(ScriptTemplate.CSharp_NewTestScript));
|
||||
#else
|
||||
private static string ScriptTemplatePath => Path.Combine(ScriptTemplatesResourcesPath, testScriptTemplate);
|
||||
#endif
|
||||
|
||||
/// <inheritdoc />
|
||||
public void AddNewFolderWithTestAssemblyDefinition(bool isEditorOnly = false)
|
||||
{
|
||||
#if UNITY_2023_3_OR_NEWER
|
||||
var assemblyDefinitionTemplate =
|
||||
AssetsMenuUtility.GetScriptTemplatePath(isEditorOnly
|
||||
? ScriptTemplate.AsmDef_NewEditModeTestAssembly
|
||||
: ScriptTemplate.AsmDef_NewTestAssembly);
|
||||
#else
|
||||
var assemblyDefinitionTemplate = isEditorOnly ? k_AssemblyDefinitionEditModeTestTemplate : assemblyDefinitionTestTemplate;
|
||||
#endif
|
||||
ActiveFolderTemplateAssetCreator.CreateFolderWithTemplates(defaultNewTestAssemblyFolderName, assemblyDefinitionTemplate);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void AddNewTestScript()
|
||||
{
|
||||
var destPath = Path.Combine(ActiveFolderTemplateAssetCreator.GetActiveFolderPath(), defaultNewTestScriptName);
|
||||
ActiveFolderTemplateAssetCreator.CreateScriptAssetFromTemplateFile(destPath, ScriptTemplatePath);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ActiveFolderContainsTestAssemblyDefinition()
|
||||
{
|
||||
return FolderPathContext.FolderPathBelongsToCustomTestAssembly(ActiveFolderPath);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TestScriptWillCompileInActiveFolder()
|
||||
{
|
||||
return FolderPathContext.TestScriptWillCompileInFolderPath(ActiveFolderPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1bace19a170f47bb8d19645cfc580796
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 07ea0326ed848fb4489187cb58f96113
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,169 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor.IMGUI.Controls;
|
||||
using UnityEditor.TestTools.TestRunner.Api;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestRunner.NUnitExtensions.Filters;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.GUI
|
||||
{
|
||||
internal class TestTreeViewBuilder
|
||||
{
|
||||
internal struct TestCount
|
||||
{
|
||||
public int TotalTestCount;
|
||||
public int TotalFailedTestCount;
|
||||
}
|
||||
|
||||
public List<TestRunnerResult> results = new List<TestRunnerResult>();
|
||||
public readonly Dictionary<string, TestTreeViewItem> m_treeFiltered = new Dictionary<string, TestTreeViewItem>();
|
||||
private readonly Dictionary<string, TestRunnerResult> m_OldTestResults;
|
||||
private readonly TestRunnerUIFilter m_UIFilter;
|
||||
private readonly ITestAdaptor[] m_TestListRoots;
|
||||
private readonly Dictionary<string, List<TestRunnerResult>> m_ChildrenResults;
|
||||
private readonly bool m_runningOnPlatform;
|
||||
|
||||
private readonly List<string> m_AvailableCategories = new List<string>();
|
||||
|
||||
public string[] AvailableCategories
|
||||
{
|
||||
get { return m_AvailableCategories.Distinct().OrderBy(a => a).ToArray(); }
|
||||
}
|
||||
|
||||
public TestTreeViewBuilder(ITestAdaptor[] tests, Dictionary<string, TestRunnerResult> oldTestResultResults, TestRunnerUIFilter uiFilter, bool runningOnPlatform)
|
||||
{
|
||||
m_AvailableCategories.Add(CategoryFilterExtended.k_DefaultCategory);
|
||||
m_OldTestResults = oldTestResultResults;
|
||||
m_ChildrenResults = new Dictionary<string, List<TestRunnerResult>>();
|
||||
m_TestListRoots = tests;
|
||||
m_UIFilter = uiFilter;
|
||||
m_runningOnPlatform = runningOnPlatform;
|
||||
}
|
||||
|
||||
public TreeViewItem BuildTreeView()
|
||||
{
|
||||
m_treeFiltered.Clear();
|
||||
var rootItem = new TreeViewItem(int.MaxValue, 0, null, "Invisible Root Item");
|
||||
foreach (var testRoot in m_TestListRoots)
|
||||
{
|
||||
ParseTestTree(0, rootItem, testRoot);
|
||||
}
|
||||
|
||||
return rootItem;
|
||||
}
|
||||
|
||||
private bool IsFilteredOutByUIFilter(ITestAdaptor test, TestRunnerResult result)
|
||||
{
|
||||
if (m_UIFilter.PassedHidden && result.resultStatus == TestRunnerResult.ResultStatus.Passed)
|
||||
return true;
|
||||
if (m_UIFilter.FailedHidden && (result.resultStatus == TestRunnerResult.ResultStatus.Failed || result.resultStatus == TestRunnerResult.ResultStatus.Inconclusive))
|
||||
return true;
|
||||
if (m_UIFilter.NotRunHidden && (result.resultStatus == TestRunnerResult.ResultStatus.NotRun || result.resultStatus == TestRunnerResult.ResultStatus.Skipped))
|
||||
return true;
|
||||
if (!string.IsNullOrEmpty(m_UIFilter.m_SearchString) && result.FullName.IndexOf(m_UIFilter.m_SearchString, StringComparison.InvariantCultureIgnoreCase) < 0)
|
||||
return true;
|
||||
if (m_UIFilter.CategoryFilter.Length > 0)
|
||||
return !test.Categories.Any(category => m_UIFilter.CategoryFilter.Contains(category));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private TestCount ParseTestTree(int depth, TreeViewItem rootItem, ITestAdaptor testElement)
|
||||
{
|
||||
if (testElement == null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
var testCount = new TestCount();
|
||||
|
||||
m_AvailableCategories.AddRange(testElement.Categories);
|
||||
|
||||
var testElementId = testElement.UniqueName;
|
||||
if (!testElement.HasChildren)
|
||||
{
|
||||
m_OldTestResults.TryGetValue(testElementId, out var result);
|
||||
|
||||
if (result != null && !m_runningOnPlatform &&
|
||||
(result.ignoredOrSkipped
|
||||
|| result.notRunnable
|
||||
|| testElement.RunState == RunState.NotRunnable
|
||||
|| testElement.RunState == RunState.Ignored
|
||||
|| testElement.RunState == RunState.Skipped
|
||||
)
|
||||
)
|
||||
{
|
||||
// if the test was or becomes ignored or not runnable, we recreate the result in case it has changed
|
||||
// It does not apply if we are running on a platform, as evaluation of runstate needs to be evaluated on the player.
|
||||
result = null;
|
||||
}
|
||||
if (result == null)
|
||||
{
|
||||
result = new TestRunnerResult(testElement);
|
||||
}
|
||||
results.Add(result);
|
||||
|
||||
var test = new TestTreeViewItem(testElement, depth, rootItem);
|
||||
if (!IsFilteredOutByUIFilter(testElement, result))
|
||||
{
|
||||
rootItem.AddChild(test);
|
||||
if (!m_treeFiltered.ContainsKey(test.FullName))
|
||||
m_treeFiltered.Add(test.FullName, test);
|
||||
}
|
||||
else
|
||||
{
|
||||
return testCount;
|
||||
}
|
||||
test.SetResult(result);
|
||||
testCount.TotalTestCount = 1;
|
||||
testCount.TotalFailedTestCount = result.resultStatus == TestRunnerResult.ResultStatus.Failed ? 1 : 0;
|
||||
if (m_ChildrenResults != null && testElement.Parent != null)
|
||||
{
|
||||
m_ChildrenResults.TryGetValue(testElement.ParentUniqueName, out var resultList);
|
||||
if (resultList != null)
|
||||
{
|
||||
resultList.Add(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
resultList = new List<TestRunnerResult> {result};
|
||||
m_ChildrenResults.Add(testElement.ParentUniqueName, resultList);
|
||||
}
|
||||
}
|
||||
|
||||
return testCount;
|
||||
}
|
||||
|
||||
var groupResult = new TestRunnerResult(testElement);
|
||||
results.Add(groupResult);
|
||||
var group = new TestTreeViewItem(testElement, depth, rootItem);
|
||||
|
||||
depth++;
|
||||
|
||||
foreach (var child in testElement.Children)
|
||||
{
|
||||
var childTestCount = ParseTestTree(depth, group, child);
|
||||
|
||||
testCount.TotalTestCount += childTestCount.TotalTestCount;
|
||||
testCount.TotalFailedTestCount += childTestCount.TotalFailedTestCount;
|
||||
}
|
||||
|
||||
|
||||
if (testElement.IsTestAssembly && !testElement.HasChildren)
|
||||
{
|
||||
return testCount;
|
||||
}
|
||||
|
||||
if (group.hasChildren)
|
||||
rootItem.AddChild(group);
|
||||
|
||||
group.TotalChildrenCount = testCount.TotalTestCount;
|
||||
group.TotalSuccessChildrenCount = testCount.TotalFailedTestCount;
|
||||
groupResult.CalculateParentResult(testElementId, m_ChildrenResults);
|
||||
group.SetResult(groupResult);
|
||||
|
||||
return testCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e17c88b021c2a4c409b3f15b0d80ac62
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 68cb547af0187634aad591a09c01cd5b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.GUI
|
||||
{
|
||||
internal static class Icons
|
||||
{
|
||||
public static readonly Texture2D s_FailImg;
|
||||
public static readonly Texture2D s_IgnoreImg;
|
||||
public static readonly Texture2D s_SuccessImg;
|
||||
public static readonly Texture2D s_UnknownImg;
|
||||
public static readonly Texture2D s_InconclusiveImg;
|
||||
public static readonly Texture2D s_StopwatchImg;
|
||||
|
||||
static Icons()
|
||||
{
|
||||
s_FailImg = EditorGUIUtility.IconContent("TestFailed").image as Texture2D;
|
||||
s_IgnoreImg = EditorGUIUtility.IconContent("TestIgnored").image as Texture2D;
|
||||
s_SuccessImg = EditorGUIUtility.IconContent("TestPassed").image as Texture2D;
|
||||
s_UnknownImg = EditorGUIUtility.IconContent("TestNormal").image as Texture2D;
|
||||
s_InconclusiveImg = EditorGUIUtility.IconContent("TestInconclusive").image as Texture2D;
|
||||
s_StopwatchImg = EditorGUIUtility.IconContent("TestStopwatch").image as Texture2D;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 27769e9b00b038d47aefe306a4d20bec
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor.IMGUI.Controls;
|
||||
using UnityEditor.TestTools.TestRunner.Api;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.GUI
|
||||
{
|
||||
internal class TestListTreeViewDataSource : TreeViewDataSource
|
||||
{
|
||||
private bool m_ExpandTreeOnCreation;
|
||||
private readonly TestListGUI m_TestListGUI;
|
||||
private ITestAdaptor[] m_RootTests;
|
||||
|
||||
public TestListTreeViewDataSource(TreeViewController testListTree, TestListGUI testListGUI, ITestAdaptor[] rootTests) : base(testListTree)
|
||||
{
|
||||
showRootItem = false;
|
||||
rootIsCollapsable = false;
|
||||
m_TestListGUI = testListGUI;
|
||||
m_RootTests = rootTests;
|
||||
}
|
||||
|
||||
public void UpdateRootTest(ITestAdaptor[] rootTests)
|
||||
{
|
||||
m_RootTests = rootTests;
|
||||
}
|
||||
|
||||
public override void FetchData()
|
||||
{
|
||||
var testListBuilder = new TestTreeViewBuilder(m_RootTests, m_TestListGUI.ResultsByKey, m_TestListGUI.m_TestRunnerUIFilter, m_TestListGUI.m_RunOnPlatform);
|
||||
|
||||
m_RootItem = testListBuilder.BuildTreeView();
|
||||
SetExpanded(m_RootItem, true);
|
||||
if (m_RootItem.hasChildren && m_RootItem.children.Count == 1)
|
||||
SetExpanded(m_RootItem.children[0], true);
|
||||
|
||||
if (m_ExpandTreeOnCreation)
|
||||
SetExpandedWithChildren(m_RootItem, true);
|
||||
|
||||
m_TestListGUI.newResultList = new List<TestRunnerResult>(testListBuilder.results);
|
||||
m_TestListGUI.filteredTree = testListBuilder.m_treeFiltered;
|
||||
m_TestListGUI.m_TestRunnerUIFilter.availableCategories = testListBuilder.AvailableCategories;
|
||||
m_NeedRefreshRows = true;
|
||||
}
|
||||
|
||||
public override bool IsRenamingItemAllowed(TreeViewItem item)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public void ExpandTreeOnCreation()
|
||||
{
|
||||
m_ExpandTreeOnCreation = true;
|
||||
}
|
||||
|
||||
public override bool IsExpandable(TreeViewItem item)
|
||||
{
|
||||
if (item is TestTreeViewItem)
|
||||
return ((TestTreeViewItem)item).IsGroupNode;
|
||||
return base.IsExpandable(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ce87c287371edde43a4b5fcfdee7b9ef
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using UnityEditor.IMGUI.Controls;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.GUI
|
||||
{
|
||||
internal class TestListTreeViewGUI : TreeViewGUI
|
||||
{
|
||||
public TestListTreeViewGUI(TreeViewController testListTree) : base(testListTree)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 52c907c81459f324497af504b84fd557
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,220 @@
|
||||
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;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ce567ddbf30368344bc7b80e20cac36e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using UnityEditor.TestTools.TestRunner.GUI.Controls;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.GUI
|
||||
{
|
||||
internal static class TestRunnerGUI
|
||||
{
|
||||
private static Styles s_Styles;
|
||||
private static Styles Style => s_Styles ?? (s_Styles = new Styles());
|
||||
|
||||
internal static void TestPlatformSelectionDropDown(ISelectionDropDownContentProvider contentProvider)
|
||||
{
|
||||
var text = Style.TestPlatformButtonString;
|
||||
for (int i = 0; i < contentProvider.Count; i++)
|
||||
{
|
||||
if (contentProvider.IsSelected(i))
|
||||
{
|
||||
text += " " + contentProvider.GetName(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var content = new GUIContent(text);
|
||||
SelectionDropDown(contentProvider, content, GUILayout.Width(EditorStyles.toolbarDropDown.CalcSize(content).x));
|
||||
}
|
||||
|
||||
internal static void CategorySelectionDropDown(ISelectionDropDownContentProvider contentProvider)
|
||||
{
|
||||
SelectionDropDown(contentProvider, Style.CategoryButtonContent, GUILayout.Width(Style.CategoryButtonWidth));
|
||||
}
|
||||
|
||||
private static void SelectionDropDown(ISelectionDropDownContentProvider listContentProvider, GUIContent buttonContent,
|
||||
params GUILayoutOption[] options)
|
||||
{
|
||||
var rect = EditorGUILayout.GetControlRect(false, EditorGUI.kSingleLineHeight, Styles.DropdownButton, options);
|
||||
if (!EditorGUI.DropdownButton(rect, buttonContent, FocusType.Passive, Styles.DropdownButton))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var selectionDropDown = new SelectionDropDown(listContentProvider);
|
||||
PopupWindow.Show(rect, selectionDropDown);
|
||||
}
|
||||
|
||||
private class Styles
|
||||
{
|
||||
public static readonly GUIStyle DropdownButton = EditorStyles.toolbarDropDown;
|
||||
public readonly string TestPlatformButtonString = "Run Location:";
|
||||
public readonly GUIContent CategoryButtonContent = new GUIContent("Category");
|
||||
public readonly float CategoryButtonWidth;
|
||||
|
||||
public Styles()
|
||||
{
|
||||
CategoryButtonWidth = DropdownButton.CalcSize(CategoryButtonContent).x;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5a0dfda606a24736913c00edd76e55f6
|
||||
timeCreated: 1600071499
|
||||
@@ -0,0 +1,185 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor.TestTools.TestRunner.Api;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.GUI
|
||||
{
|
||||
[Serializable]
|
||||
internal class TestRunnerResult : UITestRunnerFilter.IClearableResult
|
||||
{
|
||||
public string id;
|
||||
public string uniqueId;
|
||||
public string name;
|
||||
public string fullName;
|
||||
public ResultStatus resultStatus = ResultStatus.NotRun;
|
||||
public float duration;
|
||||
public string messages;
|
||||
public string output;
|
||||
public string stacktrace;
|
||||
public bool notRunnable;
|
||||
public bool ignoredOrSkipped;
|
||||
public string description;
|
||||
public bool isSuite;
|
||||
public List<string> categories;
|
||||
public string parentId;
|
||||
public string parentUniqueId;
|
||||
|
||||
//This field is suppose to mark results from before domain reload
|
||||
//Such result is outdated because the code might haev changed
|
||||
//This field will get reset every time a domain reload happens
|
||||
[NonSerialized]
|
||||
public bool notOutdated;
|
||||
|
||||
protected Action<TestRunnerResult> m_OnResultUpdate;
|
||||
|
||||
internal TestRunnerResult(ITestAdaptor test)
|
||||
{
|
||||
id = test.Id;
|
||||
uniqueId = test.UniqueName;
|
||||
|
||||
fullName = test.FullName;
|
||||
name = test.Name;
|
||||
description = test.Description;
|
||||
isSuite = test.IsSuite;
|
||||
|
||||
ignoredOrSkipped = test.RunState == RunState.Ignored || test.RunState == RunState.Skipped;
|
||||
notRunnable = test.RunState == RunState.NotRunnable;
|
||||
|
||||
if (ignoredOrSkipped)
|
||||
{
|
||||
resultStatus = ResultStatus.Skipped;
|
||||
messages = test.SkipReason;
|
||||
}
|
||||
if (notRunnable)
|
||||
{
|
||||
resultStatus = ResultStatus.Failed;
|
||||
messages = test.SkipReason;
|
||||
}
|
||||
categories = test.Categories.ToList();
|
||||
parentId = test.ParentId;
|
||||
parentUniqueId = test.ParentUniqueName;
|
||||
}
|
||||
|
||||
internal TestRunnerResult(ITestResultAdaptor testResult) : this(testResult.Test)
|
||||
{
|
||||
notOutdated = true;
|
||||
|
||||
messages = testResult.Message;
|
||||
output = testResult.Output;
|
||||
stacktrace = testResult.StackTrace;
|
||||
duration = (float)testResult.Duration;
|
||||
if (testResult.Test.IsSuite && testResult.ResultState == "Ignored")
|
||||
{
|
||||
resultStatus = ResultStatus.Passed;
|
||||
}
|
||||
else
|
||||
{
|
||||
resultStatus = ParseNUnitResultStatus(testResult.TestStatus);
|
||||
}
|
||||
}
|
||||
|
||||
public void CalculateParentResult(string parentId, IDictionary<string, List<TestRunnerResult>> results)
|
||||
{
|
||||
if (results == null) return;
|
||||
results.TryGetValue(parentId , out var childrenResult);
|
||||
if (childrenResult == null) return;
|
||||
if (childrenResult.TrueForAll(x => x.resultStatus == ResultStatus.Passed)) resultStatus = ResultStatus.Passed;
|
||||
if (childrenResult.TrueForAll(x => x.resultStatus == ResultStatus.Skipped)) resultStatus = ResultStatus.Skipped;
|
||||
else if (childrenResult.Any(x => x.resultStatus == ResultStatus.Skipped))
|
||||
{
|
||||
resultStatus = ResultStatus.Passed;
|
||||
}
|
||||
if (childrenResult.Any(x => x.resultStatus == ResultStatus.Inconclusive)) resultStatus = ResultStatus.Inconclusive;
|
||||
if (childrenResult.Any(x => x.resultStatus == ResultStatus.Failed)) resultStatus = ResultStatus.Failed;
|
||||
if (childrenResult.Any(x => x.resultStatus == ResultStatus.NotRun)) resultStatus = ResultStatus.NotRun;
|
||||
UpdateParentResult(results);
|
||||
}
|
||||
|
||||
private void UpdateParentResult(IDictionary<string, List<TestRunnerResult>> results)
|
||||
{
|
||||
if (string.IsNullOrEmpty(parentUniqueId)) return;
|
||||
results.TryGetValue(parentUniqueId, out var parentResultList);
|
||||
if (parentResultList != null && parentResultList.Count > 0)
|
||||
{
|
||||
parentResultList.Add(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
results.Add(parentUniqueId, new List<TestRunnerResult> {this});
|
||||
}
|
||||
}
|
||||
|
||||
public void Update(TestRunnerResult result)
|
||||
{
|
||||
if (ReferenceEquals(result, null))
|
||||
return;
|
||||
resultStatus = result.resultStatus;
|
||||
duration = result.duration;
|
||||
messages = result.messages;
|
||||
output = result.output;
|
||||
stacktrace = result.stacktrace;
|
||||
ignoredOrSkipped = result.ignoredOrSkipped;
|
||||
notRunnable = result.notRunnable;
|
||||
description = result.description;
|
||||
notOutdated = result.notOutdated;
|
||||
if (m_OnResultUpdate != null)
|
||||
{
|
||||
m_OnResultUpdate(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetResultChangedCallback(Action<TestRunnerResult> resultUpdated)
|
||||
{
|
||||
m_OnResultUpdate = resultUpdated;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
internal enum ResultStatus
|
||||
{
|
||||
NotRun,
|
||||
Passed,
|
||||
Failed,
|
||||
Inconclusive,
|
||||
Skipped
|
||||
}
|
||||
|
||||
private static ResultStatus ParseNUnitResultStatus(TestStatus status)
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case TestStatus.Passed:
|
||||
return ResultStatus.Passed;
|
||||
case TestStatus.Failed:
|
||||
return ResultStatus.Failed;
|
||||
case TestStatus.Inconclusive:
|
||||
return ResultStatus.Inconclusive;
|
||||
case TestStatus.Skipped:
|
||||
return ResultStatus.Skipped;
|
||||
default:
|
||||
return ResultStatus.NotRun;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("{0} ({1})", name, fullName);
|
||||
}
|
||||
|
||||
public string Id { get { return uniqueId; } }
|
||||
public string FullName { get { return fullName; } }
|
||||
public string ParentId { get { return parentUniqueId; } }
|
||||
public bool IsSuite { get { return isSuite; } }
|
||||
public List<string> Categories { get { return categories; } }
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
resultStatus = ResultStatus.NotRun;
|
||||
stacktrace = string.Empty;
|
||||
duration = 0.0f;
|
||||
if (m_OnResultUpdate != null)
|
||||
m_OnResultUpdate(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a04a45bbed9e1714f9902fc9443669b9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,187 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor.TestTools.TestRunner.Api;
|
||||
using UnityEditor.TestTools.TestRunner.GUI.Controls;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.GUI
|
||||
{
|
||||
[Serializable]
|
||||
internal class TestRunnerUIFilter
|
||||
{
|
||||
private int m_PassedCount;
|
||||
private int m_FailedCount;
|
||||
private int m_NotRunCount;
|
||||
private int m_InconclusiveCount;
|
||||
private int m_SkippedCount;
|
||||
|
||||
public int PassedCount { get { return m_PassedCount; } }
|
||||
public int FailedCount { get { return m_FailedCount + m_InconclusiveCount; } }
|
||||
public int NotRunCount { get { return m_NotRunCount + m_SkippedCount; } }
|
||||
|
||||
[SerializeField]
|
||||
public bool PassedHidden;
|
||||
[SerializeField]
|
||||
public bool FailedHidden;
|
||||
[SerializeField]
|
||||
public bool NotRunHidden;
|
||||
|
||||
[SerializeField]
|
||||
public string m_SearchString;
|
||||
[SerializeField]
|
||||
private string[] selectedCategories = new string[0];
|
||||
|
||||
public string[] availableCategories = new string[0];
|
||||
|
||||
|
||||
private GUIContent m_SucceededBtn;
|
||||
private GUIContent m_FailedBtn;
|
||||
private GUIContent m_NotRunBtn;
|
||||
|
||||
public Action RebuildTestList;
|
||||
public Action UpdateTestTreeRoots;
|
||||
public Action<string> SearchStringChanged;
|
||||
public Action SearchStringCleared;
|
||||
public bool IsFiltering
|
||||
{
|
||||
get
|
||||
{
|
||||
return !string.IsNullOrEmpty(m_SearchString) || PassedHidden || FailedHidden || NotRunHidden ||
|
||||
(selectedCategories != null && selectedCategories.Length > 0);
|
||||
}
|
||||
}
|
||||
|
||||
public string[] CategoryFilter
|
||||
{
|
||||
get { return selectedCategories; }
|
||||
}
|
||||
|
||||
public void UpdateCounters(List<TestRunnerResult> resultList, Dictionary<string, TestTreeViewItem> filteredTree)
|
||||
{
|
||||
m_PassedCount = m_FailedCount = m_NotRunCount = m_InconclusiveCount = m_SkippedCount = 0;
|
||||
foreach (var result in resultList)
|
||||
{
|
||||
if (result.isSuite)
|
||||
continue;
|
||||
if (filteredTree != null && !filteredTree.ContainsKey(result.fullName))
|
||||
continue;
|
||||
switch (result.resultStatus)
|
||||
{
|
||||
case TestRunnerResult.ResultStatus.Passed:
|
||||
m_PassedCount++;
|
||||
break;
|
||||
case TestRunnerResult.ResultStatus.Failed:
|
||||
m_FailedCount++;
|
||||
break;
|
||||
case TestRunnerResult.ResultStatus.Inconclusive:
|
||||
m_InconclusiveCount++;
|
||||
break;
|
||||
case TestRunnerResult.ResultStatus.Skipped:
|
||||
m_SkippedCount++;
|
||||
break;
|
||||
case TestRunnerResult.ResultStatus.NotRun:
|
||||
default:
|
||||
m_NotRunCount++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var succeededTooltip = string.Format("Show tests that succeeded\n{0} succeeded", m_PassedCount);
|
||||
m_SucceededBtn = new GUIContent(PassedCount.ToString(), Icons.s_SuccessImg, succeededTooltip);
|
||||
var failedTooltip = string.Format("Show tests that failed\n{0} failed\n{1} inconclusive", m_FailedCount, m_InconclusiveCount);
|
||||
m_FailedBtn = new GUIContent(FailedCount.ToString(), Icons.s_FailImg, failedTooltip);
|
||||
var notRunTooltip = string.Format("Show tests that didn't run\n{0} didn't run\n{1} skipped or ignored", m_NotRunCount, m_SkippedCount);
|
||||
m_NotRunBtn = new GUIContent(NotRunCount.ToString(), Icons.s_UnknownImg, notRunTooltip);
|
||||
}
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
if (m_SearchString == null)
|
||||
{
|
||||
m_SearchString = "";
|
||||
}
|
||||
m_SearchString = EditorGUILayout.ToolbarSearchField(m_SearchString);
|
||||
if (EditorGUI.EndChangeCheck() && SearchStringChanged != null)
|
||||
{
|
||||
SearchStringChanged(m_SearchString);
|
||||
if (String.IsNullOrEmpty(m_SearchString))
|
||||
SearchStringCleared();
|
||||
}
|
||||
|
||||
if (availableCategories != null && availableCategories.Any())
|
||||
{
|
||||
TestRunnerGUI.CategorySelectionDropDown(BuildCategorySelectionProvider());
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.Popup(0, new[] { "<No categories available>" }, EditorStyles.toolbarDropDown, GUILayout.MaxWidth(150));
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
if (m_SucceededBtn != null)
|
||||
{
|
||||
PassedHidden = !GUILayout.Toggle(!PassedHidden, m_SucceededBtn, EditorStyles.toolbarButton, GUILayout.MaxWidth(GetMaxWidth(PassedCount)));
|
||||
}
|
||||
if (m_FailedBtn != null)
|
||||
{
|
||||
FailedHidden = !GUILayout.Toggle(!FailedHidden, m_FailedBtn, EditorStyles.toolbarButton, GUILayout.MaxWidth(GetMaxWidth(FailedCount)));
|
||||
}
|
||||
if (m_NotRunBtn != null)
|
||||
{
|
||||
NotRunHidden = !GUILayout.Toggle(!NotRunHidden, m_NotRunBtn, EditorStyles.toolbarButton, GUILayout.MaxWidth(GetMaxWidth(NotRunCount)));
|
||||
}
|
||||
|
||||
if (EditorGUI.EndChangeCheck() && RebuildTestList != null)
|
||||
{
|
||||
RebuildTestList();
|
||||
}
|
||||
}
|
||||
|
||||
public void OnModeGUI()
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
|
||||
{
|
||||
// TODO: Tabs for editmode, playmode and player
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
private ISelectionDropDownContentProvider BuildCategorySelectionProvider()
|
||||
{
|
||||
var itemProvider = new MultiValueContentProvider<string>(availableCategories, selectedCategories,
|
||||
categories =>
|
||||
{
|
||||
selectedCategories = categories;
|
||||
UpdateTestTreeRoots();
|
||||
});
|
||||
|
||||
return itemProvider;
|
||||
}
|
||||
|
||||
private static int GetMaxWidth(int count)
|
||||
{
|
||||
if (count < 10)
|
||||
return 33;
|
||||
return count < 100 ? 40 : 47;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
PassedHidden = false;
|
||||
FailedHidden = false;
|
||||
NotRunHidden = false;
|
||||
selectedCategories = new string[0];
|
||||
m_SearchString = "";
|
||||
if (SearchStringChanged != null)
|
||||
{
|
||||
SearchStringChanged(m_SearchString);
|
||||
}
|
||||
if (SearchStringCleared != null)
|
||||
{
|
||||
SearchStringCleared();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 15f870c6975ad6449b5b52514b90dc2b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,152 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.GUI
|
||||
{
|
||||
[Serializable]
|
||||
internal class UITestRunnerFilter
|
||||
{
|
||||
#pragma warning disable 649
|
||||
public string[] assemblyNames;
|
||||
public string[] groupNames;
|
||||
public string[] categoryNames;
|
||||
public string[] testNames;
|
||||
public bool synchronousOnly;
|
||||
|
||||
public static string AssemblyNameFromPath(string path)
|
||||
{
|
||||
string output = Path.GetFileName(path);
|
||||
if (output != null && output.EndsWith(".dll", StringComparison.OrdinalIgnoreCase))
|
||||
return output.Substring(0, output.Length - 4);
|
||||
return output;
|
||||
}
|
||||
|
||||
private bool CategoryMatches(IEnumerable<string> categories)
|
||||
{
|
||||
if (categoryNames == null || categoryNames.Length == 0)
|
||||
return true;
|
||||
|
||||
foreach (string category in categories)
|
||||
{
|
||||
if (categoryNames.Contains(category))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool IDMatchesAssembly(string id)
|
||||
{
|
||||
if (AreOptionalFiltersEmpty())
|
||||
return true;
|
||||
|
||||
if (assemblyNames == null || assemblyNames.Length == 0)
|
||||
return true;
|
||||
|
||||
int openingBracket = id.IndexOf('[');
|
||||
int closingBracket = id.IndexOf(']');
|
||||
if (openingBracket >= 0 && openingBracket < id.Length && closingBracket > openingBracket &&
|
||||
openingBracket < id.Length)
|
||||
{
|
||||
//Some assemblies are absolute and explicitly part of the test ID e.g.
|
||||
//"[/path/to/assembly-name.dll][rest of ID ...]"
|
||||
//While some are minimal assembly names e.g.
|
||||
//"[assembly-name][rest of ID ...]"
|
||||
//Strip them down to just the assembly name
|
||||
string assemblyNameFromID =
|
||||
AssemblyNameFromPath(id.Substring(openingBracket + 1, closingBracket - openingBracket - 1));
|
||||
foreach (string assemblyName in assemblyNames)
|
||||
{
|
||||
if (assemblyName.Equals(assemblyNameFromID, StringComparison.OrdinalIgnoreCase))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool NameMatches(string name)
|
||||
{
|
||||
if (AreOptionalFiltersEmpty())
|
||||
return true;
|
||||
|
||||
if (groupNames == null || groupNames.Length == 0)
|
||||
return true;
|
||||
|
||||
foreach (var nameFromFilter in groupNames)
|
||||
{
|
||||
//Strict regex match for test group name on its own
|
||||
if (Regex.IsMatch(name, nameFromFilter))
|
||||
return true;
|
||||
//Match test names that end with Parameterized test values and full nunit generated test names that have . separators
|
||||
var regex = nameFromFilter.TrimEnd('$') + @"[\.|\(.*\)]";
|
||||
if (Regex.IsMatch(name, regex))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool AreOptionalFiltersEmpty()
|
||||
{
|
||||
if (assemblyNames != null && assemblyNames.Length != 0)
|
||||
return false;
|
||||
if (groupNames != null && groupNames.Length != 0)
|
||||
return false;
|
||||
if (testNames != null && testNames.Length != 0)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool NameMatchesExactly(string name, HashSet<string> nameLookup)
|
||||
{
|
||||
if (AreOptionalFiltersEmpty())
|
||||
return true;
|
||||
|
||||
if (testNames == null || testNames.Length == 0)
|
||||
return true;
|
||||
|
||||
return nameLookup.Contains(name);
|
||||
}
|
||||
|
||||
private static void ClearAncestors(Dictionary<string, IClearableResult> newResultList, string parentID)
|
||||
{
|
||||
while (!string.IsNullOrEmpty(parentID) && newResultList.TryGetValue(parentID, out var parent))
|
||||
{
|
||||
parent.Clear();
|
||||
parentID = parent.ParentId;
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearResults(Dictionary<string, IClearableResult> newResultList)
|
||||
{
|
||||
var nameLookup = new HashSet<string>(testNames ?? new string[0]);
|
||||
foreach (var kvp in newResultList)
|
||||
{
|
||||
var result = kvp.Value;
|
||||
if (!result.IsSuite && CategoryMatches(result.Categories))
|
||||
{
|
||||
if (IDMatchesAssembly(result.Id) && NameMatches(result.FullName) &&
|
||||
NameMatchesExactly(result.FullName, nameLookup))
|
||||
{
|
||||
result.Clear();
|
||||
ClearAncestors(newResultList, result.ParentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal interface IClearableResult
|
||||
{
|
||||
string Id { get; }
|
||||
string FullName { get; }
|
||||
string ParentId { get; }
|
||||
bool IsSuite { get; }
|
||||
List<string> Categories { get; }
|
||||
void Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8069e1fc631e461ababf11f19a9c0df3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c5535d742ea2e4941850b421f9c70a1f
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,846 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UnityEditor.IMGUI.Controls;
|
||||
using UnityEditor.TestTools.TestRunner.Api;
|
||||
using UnityEditor.TestTools.TestRunner.GUI.TestAssets;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.GUI
|
||||
{
|
||||
[Serializable]
|
||||
internal class TestListGUI
|
||||
{
|
||||
private static readonly GUIContent s_GUIRunSelectedTests = EditorGUIUtility.TrTextContent("Run Selected", "Run selected test(s)");
|
||||
private static readonly GUIContent s_GUIRunAllTests = EditorGUIUtility.TrTextContent("Run All", "Run all tests");
|
||||
private static readonly GUIContent s_GUIRerunFailedTests = EditorGUIUtility.TrTextContent("Rerun Failed", "Rerun all failed tests");
|
||||
private static readonly GUIContent s_GUIRun = EditorGUIUtility.TrTextContent("Run");
|
||||
private static readonly GUIContent s_GUIRunUntilFailed = EditorGUIUtility.TrTextContent("Run Until Failed");
|
||||
private static readonly GUIContent s_GUIRun100Times = EditorGUIUtility.TrTextContent("Run 100 times");
|
||||
private static readonly GUIContent s_GUIOpenTest = EditorGUIUtility.TrTextContent("Open source code");
|
||||
private static readonly GUIContent s_GUIOpenErrorLine = EditorGUIUtility.TrTextContent("Open error line");
|
||||
private static readonly GUIContent s_GUIClearResults = EditorGUIUtility.TrTextContent("Clear Results", "Clear all test results");
|
||||
private static readonly GUIContent s_SaveResults = EditorGUIUtility.TrTextContent("Export Results", "Save the latest test results to a file");
|
||||
private static readonly GUIContent s_GUICancelRun = EditorGUIUtility.TrTextContent("Cancel Run");
|
||||
|
||||
[SerializeField]
|
||||
private TestRunnerWindow m_Window;
|
||||
[NonSerialized]
|
||||
private TestRunnerApi m_TestRunnerApi;
|
||||
|
||||
[NonSerialized]
|
||||
internal TestMode m_TestMode;
|
||||
|
||||
[NonSerialized]
|
||||
internal bool m_RunOnPlatform;
|
||||
|
||||
[NonSerialized]
|
||||
internal bool m_buildOnly;
|
||||
|
||||
[SerializeField]
|
||||
private TestRunProgress runProgress;
|
||||
public Dictionary<string, TestTreeViewItem> filteredTree { get; set; }
|
||||
|
||||
public List<TestRunnerResult> newResultList
|
||||
{
|
||||
get { return m_NewResultList; }
|
||||
set
|
||||
{
|
||||
m_NewResultList = value;
|
||||
m_ResultByKey = null;
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
private List<TestRunnerResult> m_NewResultList = new List<TestRunnerResult>();
|
||||
|
||||
private Dictionary<string, TestRunnerResult> m_ResultByKey;
|
||||
internal Dictionary<string, TestRunnerResult> ResultsByKey
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_ResultByKey == null)
|
||||
{
|
||||
m_ResultByKey = new Dictionary<string, TestRunnerResult>();
|
||||
foreach (var result in newResultList)
|
||||
{
|
||||
if (m_ResultByKey.ContainsKey(result.uniqueId))
|
||||
{
|
||||
Debug.LogWarning($"Multiple tests has the same unique id '{result.uniqueId}', their results will be overwritten.");
|
||||
continue;
|
||||
}
|
||||
m_ResultByKey.Add(result.uniqueId, result);
|
||||
}
|
||||
}
|
||||
|
||||
return m_ResultByKey;
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
private string m_ResultText;
|
||||
[SerializeField]
|
||||
private string m_ResultStacktrace;
|
||||
|
||||
private TreeViewController m_TestListTree;
|
||||
[SerializeField]
|
||||
internal TreeViewState m_TestListState;
|
||||
[SerializeField]
|
||||
internal TestRunnerUIFilter m_TestRunnerUIFilter = new TestRunnerUIFilter();
|
||||
|
||||
private Vector2 m_TestInfoScroll, m_TestListScroll;
|
||||
private List<TestRunnerResult> m_QueuedResults = new List<TestRunnerResult>();
|
||||
private ITestResultAdaptor m_LatestTestResults;
|
||||
|
||||
public TestListGUI()
|
||||
{
|
||||
MonoCecilHelper = new MonoCecilHelper();
|
||||
AssetsDatabaseHelper = new AssetsDatabaseHelper();
|
||||
|
||||
GuiHelper = new GuiHelper(MonoCecilHelper, AssetsDatabaseHelper);
|
||||
TestRunnerApi.runProgressChanged.AddListener(UpdateProgressStatus);
|
||||
}
|
||||
|
||||
private void UpdateProgressStatus(TestRunProgress progress)
|
||||
{
|
||||
runProgress = progress;
|
||||
TestRunnerWindow.s_Instance.Repaint();
|
||||
}
|
||||
|
||||
private IMonoCecilHelper MonoCecilHelper { get; set; }
|
||||
private IAssetsDatabaseHelper AssetsDatabaseHelper { get; set; }
|
||||
private IGuiHelper GuiHelper { get; set; }
|
||||
|
||||
private struct PlayerMenuItem
|
||||
{
|
||||
public GUIContent name;
|
||||
public bool filterSelectedTestsOnly;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
private int m_SelectedOption;
|
||||
|
||||
public void PrintHeadPanel()
|
||||
{
|
||||
if (m_RunOnPlatform)
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
|
||||
GUILayout.Label("Running on " + EditorUserBuildSettings.activeBuildTarget);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
using (new EditorGUI.DisabledScope(m_TestListTree == null || IsBusy()))
|
||||
{
|
||||
m_TestRunnerUIFilter.OnModeGUI();
|
||||
DrawFilters();
|
||||
}
|
||||
}
|
||||
|
||||
public void PrintProgressBar(Rect rect)
|
||||
{
|
||||
if (runProgress == null || runProgress.HasFinished || string.IsNullOrEmpty(runProgress.RunGuid))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
|
||||
|
||||
EditorGUI.ProgressBar(EditorGUILayout.GetControlRect(), runProgress.Progress, runProgress.CurrentStepName);
|
||||
if (GUILayout.Button(s_GUICancelRun, GUILayout.Width(100)))
|
||||
{
|
||||
TestRunnerApi.CancelTestRun(runProgress.RunGuid);
|
||||
}
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
public void PrintBottomPanel()
|
||||
{
|
||||
using (new EditorGUI.DisabledScope(m_TestListTree == null || IsBusy()))
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
|
||||
{
|
||||
using (new EditorGUI.DisabledScope(m_LatestTestResults == null))
|
||||
{
|
||||
if (GUILayout.Button(s_SaveResults))
|
||||
{
|
||||
var filePath = EditorUtility.SaveFilePanel(s_SaveResults.text, "",
|
||||
$"TestResults_{DateTime.Now:yyyyMMdd_HHmmss}.xml", "xml");
|
||||
if (!string.IsNullOrEmpty(filePath))
|
||||
{
|
||||
TestRunnerApi.SaveResultToFile(m_LatestTestResults, filePath);
|
||||
}
|
||||
|
||||
GUIUtility.ExitGUI();
|
||||
}
|
||||
}
|
||||
|
||||
if (GUILayout.Button(s_GUIClearResults))
|
||||
{
|
||||
foreach (var result in newResultList)
|
||||
{
|
||||
result.Clear();
|
||||
}
|
||||
|
||||
m_TestRunnerUIFilter.UpdateCounters(newResultList, filteredTree);
|
||||
Reload();
|
||||
GUIUtility.ExitGUI();
|
||||
}
|
||||
|
||||
GUILayout.FlexibleSpace();
|
||||
|
||||
using (new EditorGUI.DisabledScope(m_TestRunnerUIFilter.FailedCount == 0))
|
||||
{
|
||||
if (GUILayout.Button(s_GUIRerunFailedTests))
|
||||
{
|
||||
RunTests(RunFilterType.RunFailed);
|
||||
GUIUtility.ExitGUI();
|
||||
}
|
||||
}
|
||||
|
||||
using (new EditorGUI.DisabledScope(m_TestListTree == null || !m_TestListTree.HasSelection()))
|
||||
{
|
||||
if (GUILayout.Button(s_GUIRunSelectedTests))
|
||||
{
|
||||
RunTests(RunFilterType.RunSelected);
|
||||
GUIUtility.ExitGUI();
|
||||
}
|
||||
}
|
||||
|
||||
if (GUILayout.Button(s_GUIRunAllTests))
|
||||
{
|
||||
RunTests(RunFilterType.RunAll);
|
||||
GUIUtility.ExitGUI();
|
||||
}
|
||||
|
||||
if (m_TestMode == TestMode.PlayMode && m_RunOnPlatform)
|
||||
{
|
||||
PlayerMenuItem[] menuItems;
|
||||
|
||||
if (EditorUserBuildSettings.installInBuildFolder)
|
||||
{
|
||||
// Note: We select here m_buildOnly = false, so build location dialog won't show up
|
||||
// The player won't actually be ran when using together with EditorUserBuildSettings.installInBuildFolder
|
||||
m_buildOnly = false;
|
||||
|
||||
menuItems = new []
|
||||
{
|
||||
new PlayerMenuItem()
|
||||
{
|
||||
name = new GUIContent("Install All Tests In Build Folder"), filterSelectedTestsOnly = false
|
||||
},
|
||||
new PlayerMenuItem()
|
||||
{
|
||||
name = new GUIContent("Install Selected Tests In Build Folder"), filterSelectedTestsOnly = true
|
||||
}
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
m_buildOnly = true;
|
||||
|
||||
menuItems = new []
|
||||
{
|
||||
new PlayerMenuItem()
|
||||
{
|
||||
name = new GUIContent($"{GetBuildText()} All Tests"), filterSelectedTestsOnly = false
|
||||
},
|
||||
new PlayerMenuItem()
|
||||
{
|
||||
name = new GUIContent($"{GetBuildText()} Selected Tests"), filterSelectedTestsOnly = true
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (GUILayout.Button(GUIContent.none, EditorStyles.toolbarDropDown))
|
||||
{
|
||||
Vector2 mousePos = Event.current.mousePosition;
|
||||
|
||||
EditorUtility.DisplayCustomMenu(new Rect(mousePos.x, mousePos.y, 0, 0),
|
||||
menuItems.Select(m => m.name).ToArray(),
|
||||
-1,
|
||||
(object userData, string[] options, int selected) => RunTests(menuItems[selected].filterSelectedTestsOnly ? RunFilterType.BuildSelected : RunFilterType.BuildAll), menuItems);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
}
|
||||
|
||||
private string GetBuildText()
|
||||
{
|
||||
switch (EditorUserBuildSettings.activeBuildTarget)
|
||||
{
|
||||
case BuildTarget.Android:
|
||||
if (EditorUserBuildSettings.exportAsGoogleAndroidProject)
|
||||
return "Export";
|
||||
break;
|
||||
case BuildTarget.iOS:
|
||||
return "Export";
|
||||
}
|
||||
return "Build";
|
||||
}
|
||||
|
||||
private string PickBuildLocation()
|
||||
{
|
||||
var target = EditorUserBuildSettings.activeBuildTarget;
|
||||
var targetGroup = BuildPipeline.GetBuildTargetGroup(target);
|
||||
var lastLocation = EditorUserBuildSettings.GetBuildLocation(target);
|
||||
var extension = PostprocessBuildPlayer.GetExtensionForBuildTarget(targetGroup, target, BuildOptions.None);
|
||||
var defaultName = FileUtil.GetLastPathNameComponent(lastLocation);
|
||||
lastLocation = string.IsNullOrEmpty(lastLocation) ? string.Empty : Path.GetDirectoryName(lastLocation);
|
||||
bool updateExistingBuild;
|
||||
var location = EditorUtility.SaveBuildPanel(target, $"{GetBuildText()} {target}", lastLocation, defaultName, extension,
|
||||
out updateExistingBuild);
|
||||
if (!string.IsNullOrEmpty(location))
|
||||
EditorUserBuildSettings.SetBuildLocation(target, location);
|
||||
return location;
|
||||
}
|
||||
|
||||
private void DrawFilters()
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
|
||||
m_TestRunnerUIFilter.Draw();
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
public bool HasTreeData()
|
||||
{
|
||||
return m_TestListTree != null;
|
||||
}
|
||||
|
||||
public void RenderTestList()
|
||||
{
|
||||
if (m_TestListTree == null)
|
||||
{
|
||||
GUILayout.Label("Loading...");
|
||||
return;
|
||||
}
|
||||
|
||||
m_TestListScroll = EditorGUILayout.BeginScrollView(m_TestListScroll,
|
||||
GUILayout.ExpandWidth(true),
|
||||
GUILayout.MaxWidth(2000));
|
||||
|
||||
if (m_TestListTree.data.root == null || m_TestListTree.data.rowCount == 0 || (!m_TestListTree.isSearching && !m_TestListTree.data.GetItem(0).hasChildren))
|
||||
{
|
||||
if (m_TestRunnerUIFilter.IsFiltering)
|
||||
{
|
||||
var notMatchFoundStyle = new GUIStyle("label");
|
||||
notMatchFoundStyle.normal.textColor = Color.red;
|
||||
notMatchFoundStyle.alignment = TextAnchor.MiddleCenter;
|
||||
GUILayout.Label("No match found", notMatchFoundStyle);
|
||||
if (GUILayout.Button("Clear filters"))
|
||||
{
|
||||
m_TestRunnerUIFilter.Clear();
|
||||
UpdateTestTree();
|
||||
m_Window.Repaint();
|
||||
}
|
||||
}
|
||||
RenderNoTestsInfo();
|
||||
}
|
||||
else
|
||||
{
|
||||
var treeRect = EditorGUILayout.GetControlRect(GUILayout.ExpandHeight(true), GUILayout.ExpandWidth(true));
|
||||
var treeViewKeyboardControlId = GUIUtility.GetControlID(FocusType.Keyboard);
|
||||
|
||||
m_TestListTree.OnGUI(treeRect, treeViewKeyboardControlId);
|
||||
}
|
||||
|
||||
m_TestRunnerUIFilter.UpdateCounters(newResultList, filteredTree);
|
||||
EditorGUILayout.EndScrollView();
|
||||
}
|
||||
|
||||
private void RenderNoTestsInfo()
|
||||
{
|
||||
var testScriptAssetsCreator = new TestScriptAssetsCreator();
|
||||
if (!testScriptAssetsCreator.ActiveFolderContainsTestAssemblyDefinition())
|
||||
{
|
||||
var noTestsText = "No tests to show.";
|
||||
|
||||
if (!PlayerSettings.playModeTestRunnerEnabled)
|
||||
{
|
||||
const string testsMustLiveInCustomTestAssemblies =
|
||||
"Test scripts can be added to assemblies referencing the \"nunit.framework.dll\" library " +
|
||||
"or folders with Assembly Definition References targeting \"UnityEngine.TestRunner\" or \"UnityEditor.TestRunner\".";
|
||||
|
||||
noTestsText += Environment.NewLine + testsMustLiveInCustomTestAssemblies;
|
||||
}
|
||||
|
||||
EditorGUILayout.HelpBox(noTestsText, MessageType.Info);
|
||||
if (GUILayout.Button("Create a new Test Assembly Folder in the active path."))
|
||||
{
|
||||
testScriptAssetsCreator.AddNewFolderWithTestAssemblyDefinition(m_TestMode == TestMode.EditMode);
|
||||
}
|
||||
}
|
||||
|
||||
const string notTestAssembly = "Test Scripts can only be created inside test assemblies.";
|
||||
const string createTestScriptInCurrentFolder = "Create a new Test Script in the active path.";
|
||||
var canAddTestScriptAndItWillCompile = testScriptAssetsCreator.TestScriptWillCompileInActiveFolder();
|
||||
|
||||
using (new EditorGUI.DisabledScope(!canAddTestScriptAndItWillCompile))
|
||||
{
|
||||
var createTestScriptInCurrentFolderGUI = !canAddTestScriptAndItWillCompile
|
||||
? new GUIContent(createTestScriptInCurrentFolder, notTestAssembly)
|
||||
: new GUIContent(createTestScriptInCurrentFolder);
|
||||
|
||||
if (GUILayout.Button(createTestScriptInCurrentFolderGUI))
|
||||
{
|
||||
testScriptAssetsCreator.AddNewTestScript();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RenderDetails(float width)
|
||||
{
|
||||
m_TestInfoScroll = EditorGUILayout.BeginScrollView(m_TestInfoScroll, GUILayout.ExpandWidth(true));
|
||||
var resultTextHeight = TestRunnerWindow.Styles.info.CalcHeight(new GUIContent(m_ResultText), width);
|
||||
EditorGUILayout.SelectableLabel(m_ResultText, TestRunnerWindow.Styles.info,
|
||||
GUILayout.ExpandHeight(true),
|
||||
GUILayout.ExpandWidth(true),
|
||||
GUILayout.MinHeight(resultTextHeight));
|
||||
EditorGUILayout.EndScrollView();
|
||||
}
|
||||
|
||||
public void Reload()
|
||||
{
|
||||
if (m_TestListTree != null)
|
||||
{
|
||||
m_TestListTree.ReloadData();
|
||||
UpdateQueuedResults();
|
||||
}
|
||||
}
|
||||
|
||||
public void Repaint()
|
||||
{
|
||||
if (m_TestListTree == null || m_TestListTree.data.root == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_TestListTree.Repaint();
|
||||
if (m_TestListTree.data.rowCount == 0)
|
||||
m_TestListTree.SetSelection(new int[0], false);
|
||||
TestSelectionCallback(m_TestListState.selectedIDs.ToArray());
|
||||
}
|
||||
|
||||
public void Init(TestRunnerWindow window, ITestAdaptor rootTest)
|
||||
{
|
||||
Init(window, new[] {rootTest});
|
||||
}
|
||||
|
||||
private void Init(TestRunnerWindow window, ITestAdaptor[] rootTests)
|
||||
{
|
||||
if (m_Window == null)
|
||||
{
|
||||
m_Window = window;
|
||||
}
|
||||
|
||||
if (m_TestListTree == null)
|
||||
{
|
||||
if (m_TestListState == null)
|
||||
{
|
||||
m_TestListState = new TreeViewState();
|
||||
}
|
||||
if (m_TestListTree == null)
|
||||
m_TestListTree = new TreeViewController(m_Window, m_TestListState);
|
||||
|
||||
m_TestListTree.deselectOnUnhandledMouseDown = false;
|
||||
|
||||
m_TestListTree.selectionChangedCallback += TestSelectionCallback;
|
||||
m_TestListTree.itemDoubleClickedCallback += TestDoubleClickCallback;
|
||||
m_TestListTree.contextClickItemCallback += TestContextClickCallback;
|
||||
|
||||
var testListTreeViewDataSource = new TestListTreeViewDataSource(m_TestListTree, this, rootTests);
|
||||
|
||||
m_TestListTree.Init(new Rect(),
|
||||
testListTreeViewDataSource,
|
||||
new TestListTreeViewGUI(m_TestListTree),
|
||||
null);
|
||||
}
|
||||
|
||||
m_TestRunnerUIFilter.UpdateCounters(newResultList, filteredTree);
|
||||
m_TestRunnerUIFilter.RebuildTestList = () => m_TestListTree.ReloadData();
|
||||
m_TestRunnerUIFilter.UpdateTestTreeRoots = UpdateTestTree;
|
||||
m_TestRunnerUIFilter.SearchStringChanged = s => m_TestListTree.ReloadData();
|
||||
m_TestRunnerUIFilter.SearchStringCleared = () => FrameSelection();
|
||||
}
|
||||
|
||||
public void UpdateResult(TestRunnerResult result)
|
||||
{
|
||||
if (!HasTreeData())
|
||||
{
|
||||
m_QueuedResults.Add(result);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (!ResultsByKey.TryGetValue(result.uniqueId, out var testRunnerResult))
|
||||
{
|
||||
// Add missing result due to e.g. changes in code for uniqueId due to change of package version.
|
||||
m_NewResultList.Add(result);
|
||||
ResultsByKey[result.uniqueId] = result;
|
||||
testRunnerResult = result;
|
||||
}
|
||||
|
||||
testRunnerResult.Update(result);
|
||||
Repaint();
|
||||
m_Window.Repaint();
|
||||
}
|
||||
|
||||
public void RunFinished(ITestResultAdaptor results)
|
||||
{
|
||||
m_LatestTestResults = results;
|
||||
UpdateTestTree();
|
||||
}
|
||||
|
||||
private void UpdateTestTree()
|
||||
{
|
||||
var testList = this;
|
||||
if (m_TestRunnerApi == null)
|
||||
{
|
||||
m_TestRunnerApi= ScriptableObject.CreateInstance<TestRunnerApi>();
|
||||
}
|
||||
|
||||
m_TestRunnerApi.RetrieveTestList(GetExecutionSettings(), rootTest =>
|
||||
{
|
||||
testList.UpdateTestTree(new[] { rootTest });
|
||||
testList.Reload();
|
||||
});
|
||||
}
|
||||
|
||||
public void UpdateTestTree(ITestAdaptor[] tests)
|
||||
{
|
||||
if (!HasTreeData())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
(m_TestListTree.data as TestListTreeViewDataSource).UpdateRootTest(tests);
|
||||
|
||||
m_TestListTree.ReloadData();
|
||||
Repaint();
|
||||
m_Window.Repaint();
|
||||
}
|
||||
|
||||
private void UpdateQueuedResults()
|
||||
{
|
||||
foreach (var testRunnerResult in m_QueuedResults)
|
||||
{
|
||||
if (ResultsByKey.TryGetValue(testRunnerResult.uniqueId, out var existingResult))
|
||||
{
|
||||
existingResult.Update(testRunnerResult);
|
||||
}
|
||||
}
|
||||
m_QueuedResults.Clear();
|
||||
TestSelectionCallback(m_TestListState.selectedIDs.ToArray());
|
||||
m_TestRunnerUIFilter.UpdateCounters(newResultList, filteredTree);
|
||||
Repaint();
|
||||
m_Window.Repaint();
|
||||
}
|
||||
|
||||
internal void TestSelectionCallback(int[] selected)
|
||||
{
|
||||
if (m_TestListTree != null && selected.Length > 0)
|
||||
{
|
||||
if (m_TestListTree != null)
|
||||
{
|
||||
var node = m_TestListTree.FindItem(selected[0]);
|
||||
if (node is TestTreeViewItem)
|
||||
{
|
||||
var test = node as TestTreeViewItem;
|
||||
m_ResultText = test.GetResultText();
|
||||
m_ResultStacktrace = test.result.stacktrace;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (selected.Length == 0)
|
||||
{
|
||||
m_ResultText = "";
|
||||
}
|
||||
}
|
||||
|
||||
private void TestDoubleClickCallback(int id)
|
||||
{
|
||||
if (IsBusy())
|
||||
return;
|
||||
|
||||
RunTests(RunFilterType.RunSpecific, id);
|
||||
GUIUtility.ExitGUI();
|
||||
}
|
||||
|
||||
private void RunTests(RunFilterType runFilter, params int[] specificTests)
|
||||
{
|
||||
if (EditorUtility.scriptCompilationFailed)
|
||||
{
|
||||
Debug.LogError("Fix compilation issues before running tests");
|
||||
return;
|
||||
}
|
||||
|
||||
var filters = ConstructFilter(runFilter, specificTests);
|
||||
if (filters == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var filter in filters)
|
||||
{
|
||||
filter.ClearResults(newResultList.OfType<UITestRunnerFilter.IClearableResult>().ToDictionary(result => result.Id));
|
||||
}
|
||||
|
||||
var testFilters = filters.Select(filter => new Filter
|
||||
{
|
||||
testMode = m_TestMode,
|
||||
assemblyNames = filter.assemblyNames,
|
||||
categoryNames = filter.categoryNames,
|
||||
groupNames = filter.groupNames,
|
||||
testNames = filter.testNames,
|
||||
}).ToArray();
|
||||
|
||||
#if UNITY_2022_2_OR_NEWER
|
||||
var executionSettings =
|
||||
CreateExecutionSettings(m_RunOnPlatform ? EditorUserBuildSettings.activeBuildTarget : null,
|
||||
testFilters);
|
||||
#else
|
||||
var executionSettings =
|
||||
CreateExecutionSettings(m_RunOnPlatform ? EditorUserBuildSettings.activeBuildTarget : (BuildTarget?)null,
|
||||
testFilters);
|
||||
#endif
|
||||
if (runFilter == RunFilterType.BuildAll || runFilter == RunFilterType.BuildSelected)
|
||||
{
|
||||
var runSettings = new PlayerLauncherTestRunSettings();
|
||||
runSettings.buildOnly = m_buildOnly;
|
||||
|
||||
if (runSettings.buildOnly)
|
||||
{
|
||||
runSettings.buildOnlyLocationPath = PickBuildLocation();
|
||||
if (string.IsNullOrEmpty(runSettings.buildOnlyLocationPath))
|
||||
{
|
||||
Debug.LogWarning("Aborting, build selection was canceled.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
executionSettings.overloadTestRunSettings = runSettings;
|
||||
|
||||
Debug.Log(executionSettings.ToString());
|
||||
}
|
||||
|
||||
if (m_TestRunnerApi == null)
|
||||
{
|
||||
m_TestRunnerApi = ScriptableObject.CreateInstance<TestRunnerApi>();
|
||||
}
|
||||
|
||||
m_TestRunnerApi.Execute(executionSettings);
|
||||
|
||||
if (executionSettings.targetPlatform != null && !(runFilter == RunFilterType.BuildAll || runFilter == RunFilterType.BuildSelected))
|
||||
{
|
||||
GUIUtility.ExitGUI();
|
||||
}
|
||||
}
|
||||
|
||||
private void TestContextClickCallback(int id)
|
||||
{
|
||||
if (id == 0)
|
||||
return;
|
||||
|
||||
var m = new GenericMenu();
|
||||
var multilineSelection = m_TestListState.selectedIDs.Count > 1;
|
||||
|
||||
if (!multilineSelection)
|
||||
{
|
||||
var testNode = GetSelectedTest();
|
||||
var isNotSuite = !testNode.IsGroupNode;
|
||||
if (isNotSuite)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(m_ResultStacktrace))
|
||||
{
|
||||
m.AddItem(s_GUIOpenErrorLine,
|
||||
false,
|
||||
data =>
|
||||
{
|
||||
if (!GuiHelper.OpenScriptInExternalEditor(m_ResultStacktrace))
|
||||
{
|
||||
GuiHelper.OpenScriptInExternalEditor(testNode.type, testNode.method);
|
||||
}
|
||||
},
|
||||
"");
|
||||
}
|
||||
|
||||
m.AddItem(s_GUIOpenTest,
|
||||
false,
|
||||
data => GuiHelper.OpenScriptInExternalEditor(testNode.type, testNode.method),
|
||||
"");
|
||||
m.AddSeparator("");
|
||||
}
|
||||
}
|
||||
|
||||
if (!IsBusy())
|
||||
{
|
||||
m.AddItem(multilineSelection ? s_GUIRunSelectedTests : s_GUIRun,
|
||||
false,
|
||||
data => RunTests(RunFilterType.RunSelected),
|
||||
"");
|
||||
}
|
||||
else
|
||||
m.AddDisabledItem(multilineSelection ? s_GUIRunSelectedTests : s_GUIRun, false);
|
||||
|
||||
m.ShowAsContext();
|
||||
}
|
||||
|
||||
private enum RunFilterType
|
||||
{
|
||||
RunAll,
|
||||
RunSelected,
|
||||
RunFailed,
|
||||
RunSpecific,
|
||||
BuildAll,
|
||||
BuildSelected
|
||||
}
|
||||
|
||||
private struct FilterConstructionStep
|
||||
{
|
||||
public int Id;
|
||||
public TreeViewItem Item;
|
||||
}
|
||||
|
||||
private UITestRunnerFilter[] ConstructFilter(RunFilterType runFilter, int[] specificTests = null)
|
||||
{
|
||||
if ((runFilter == RunFilterType.RunAll || runFilter == RunFilterType.BuildAll) && !m_TestRunnerUIFilter.IsFiltering)
|
||||
{
|
||||
// Shortcut for RunAll, which will not trigger any explicit tests
|
||||
return new[] {new UITestRunnerFilter()};
|
||||
}
|
||||
|
||||
var includedIds = GetIdsIncludedInRunFilter(runFilter, specificTests);
|
||||
var testsInFilter = includedIds.Select(id => m_TestListTree.FindItem(id)).Cast<TestTreeViewItem>()
|
||||
.SelectMany(item => item.GetMinimizedSelectedTree()).Distinct().ToArray();
|
||||
|
||||
if (testsInFilter.Length == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (testsInFilter.Any(test => test.Parent == null))
|
||||
{
|
||||
// The root element is included in the minified list, which means we are running all tests
|
||||
// It should however trigger explicit tests, which is done by a groupNames filter matching all groups
|
||||
return new[]
|
||||
{
|
||||
new UITestRunnerFilter()
|
||||
{
|
||||
groupNames = new[] {".*"}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var assemblies = testsInFilter.Where(test => test.IsTestAssembly).ToArray();
|
||||
var tests = testsInFilter.Where(test => !test.IsTestAssembly).ToArray();
|
||||
|
||||
var filters = new List<UITestRunnerFilter>();
|
||||
if (tests.Length > 0)
|
||||
{
|
||||
filters.Add(new UITestRunnerFilter
|
||||
{
|
||||
testNames = tests.Select(test => test.FullName).ToArray()
|
||||
});
|
||||
}
|
||||
|
||||
if (assemblies.Length > 0)
|
||||
{
|
||||
filters.Add(new UITestRunnerFilter
|
||||
{
|
||||
assemblyNames = assemblies.Select(test =>
|
||||
{
|
||||
// remove .dll from the end of the name
|
||||
var name = test.Name;
|
||||
if (name.EndsWith(".dll"))
|
||||
name = name.Substring(0, name.Length - 4);
|
||||
return name;
|
||||
}).ToArray()
|
||||
});
|
||||
}
|
||||
|
||||
return filters.ToArray();
|
||||
}
|
||||
|
||||
private IEnumerable<int> GetIdsIncludedInRunFilter(RunFilterType runFilter, int[] specificTests)
|
||||
{
|
||||
switch (runFilter)
|
||||
{
|
||||
case RunFilterType.RunSelected:
|
||||
case RunFilterType.BuildSelected:
|
||||
return m_TestListState.selectedIDs;
|
||||
case RunFilterType.RunSpecific:
|
||||
if (specificTests == null)
|
||||
{
|
||||
throw new ArgumentNullException(
|
||||
$"For {nameof(RunFilterType.RunSpecific)}, the {nameof(specificTests)} argument must not be null.");
|
||||
}
|
||||
|
||||
return specificTests;
|
||||
default:
|
||||
return m_TestListTree.GetRowIDs();
|
||||
}
|
||||
}
|
||||
|
||||
private TestTreeViewItem GetSelectedTest()
|
||||
{
|
||||
foreach (var lineId in m_TestListState.selectedIDs)
|
||||
{
|
||||
var line = m_TestListTree.FindItem(lineId);
|
||||
if (line is TestTreeViewItem)
|
||||
{
|
||||
return line as TestTreeViewItem;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void FrameSelection()
|
||||
{
|
||||
if (m_TestListTree.HasSelection())
|
||||
{
|
||||
var firstClickedID = m_TestListState.selectedIDs.First() == m_TestListState.lastClickedID ? m_TestListState.selectedIDs.Last() : m_TestListState.selectedIDs.First();
|
||||
m_TestListTree.Frame(firstClickedID, true, false);
|
||||
}
|
||||
}
|
||||
|
||||
public void RebuildUIFilter()
|
||||
{
|
||||
m_TestRunnerUIFilter.UpdateCounters(newResultList, filteredTree);
|
||||
if (m_TestRunnerUIFilter.IsFiltering)
|
||||
{
|
||||
m_TestListTree.ReloadData();
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsBusy()
|
||||
{
|
||||
return TestRunnerApi.IsRunActive() || EditorApplication.isCompiling || EditorApplication.isPlaying;
|
||||
}
|
||||
|
||||
public ExecutionSettings GetExecutionSettings()
|
||||
{
|
||||
var filter = new Filter
|
||||
{
|
||||
testMode = m_TestMode
|
||||
};
|
||||
|
||||
#if UNITY_2022_2_OR_NEWER
|
||||
return CreateExecutionSettings(m_RunOnPlatform ? EditorUserBuildSettings.activeBuildTarget : null, filter);
|
||||
#else
|
||||
return CreateExecutionSettings(m_RunOnPlatform ? EditorUserBuildSettings.activeBuildTarget : (BuildTarget?)null, filter);
|
||||
#endif
|
||||
}
|
||||
|
||||
private static ExecutionSettings CreateExecutionSettings(BuildTarget? buildTarget, params Filter[] filters)
|
||||
{
|
||||
return new ExecutionSettings(filters)
|
||||
{
|
||||
targetPlatform = buildTarget,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b8abb41ceb6f62c45a00197ae59224c1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user