diff --git a/CHANGELOG.md b/CHANGELOG.md
index 58df120..a115d92 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,9 @@
# Changelog
+## [1.4.1] - 2023-11-27
+- Multiple improvements to the UI, including better dropdowns, filtering, and a new test list view for Player.
+- Fixed uncategorized UI tests filtering for parameterized tests (DSTR-219).
+- In async tests, any failing logs will now first be evaluated after the async method has completed. (DSTR-839)
+
## [1.4.0] - 2023-11-10
- Added api for saving test results to a file.
- Added a button for saving the test results of the latest run.
diff --git a/Documentation~/reference-async-tests.md b/Documentation~/reference-async-tests.md
index b72f2ad..fe24723 100644
--- a/Documentation~/reference-async-tests.md
+++ b/Documentation~/reference-async-tests.md
@@ -2,6 +2,8 @@
You can use the dotnet Task asynchronous programming model to write asynchronous tests. If you're new to asynchronous programming and its applications, see the [Microsoft documentation](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/) for a comprehensive guide. See also the documentation for NUnit [Test](https://docs.nunit.org/articles/nunit/writing-tests/attributes/test.html), which explains the requirements for an async test. Async code is run on the main thread and Unity Test Framework will `await` it by checking if the task is done on each [update](https://docs.unity3d.com/ScriptReference/PlayerLoop.Update.html) for Play Mode or on each [EditorApplication.update](https://docs.unity3d.com/ScriptReference/EditorApplication-update.html) outside Play Mode.
+Note that any failing log messages will first be evaluated after the async test has completed. This means that if you have a failing log message in an async test, it will not be reported until the test has completed.
+
The following code snippet demonstrates an async test based on Microsoft's making breakfast example. Note that the test method is marked with the `async` keyword and has return type `Task`. We set up a list of Tasks corresponding to asynchronous methods representing parts of the breakfast making process. We use `await` to start these tasks in a non-blocking way, write to the log when each one completes, and write again to the log when all are completed.
```
diff --git a/UnityEditor.TestRunner/GUI/Controls.meta b/UnityEditor.TestRunner/GUI/Controls.meta
new file mode 100644
index 0000000..46c93bc
--- /dev/null
+++ b/UnityEditor.TestRunner/GUI/Controls.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: e88eef57957d4440c8a7ff2ef9dd3d97
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/UnityEditor.TestRunner/GUI/Controls/BitUtility.cs b/UnityEditor.TestRunner/GUI/Controls/BitUtility.cs
new file mode 100644
index 0000000..fad44fb
--- /dev/null
+++ b/UnityEditor.TestRunner/GUI/Controls/BitUtility.cs
@@ -0,0 +1,28 @@
+using System;
+
+namespace UnityEditor.TestTools.TestRunner.GUI.Controls
+{
+ ///
+ /// Provides methods for dealing with common bit operations.
+ ///
+ internal static class BitUtility
+ {
+ ///
+ /// Evaluates the cardinality of an integer, treating the value as a bit set.
+ /// Optimization based on http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel.
+ ///
+ /// The input integer value.
+ /// The number of bits set in the provided input integer value.
+ 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;
+ }
+ }
+}
diff --git a/UnityEditor.TestRunner/GUI/Controls/BitUtility.cs.meta b/UnityEditor.TestRunner/GUI/Controls/BitUtility.cs.meta
new file mode 100644
index 0000000..e8ab9da
--- /dev/null
+++ b/UnityEditor.TestRunner/GUI/Controls/BitUtility.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 24a5f331fec74c9aa9e1e5d74b5a9589
+timeCreated: 1601747849
\ No newline at end of file
diff --git a/UnityEditor.TestRunner/GUI/Controls/FlagEnumContentProvider.cs b/UnityEditor.TestRunner/GUI/Controls/FlagEnumContentProvider.cs
new file mode 100644
index 0000000..ea352fa
--- /dev/null
+++ b/UnityEditor.TestRunner/GUI/Controls/FlagEnumContentProvider.cs
@@ -0,0 +1,118 @@
+using System;
+using UnityEngine;
+
+namespace UnityEditor.TestTools.TestRunner.GUI.Controls
+{
+ ///
+ /// A flag enum content provider to be used with the control.
+ ///
+ /// The flag enum type.
+ internal class FlagEnumContentProvider : ISelectionDropDownContentProvider where T : Enum
+ {
+ private readonly Action m_ValueChangedCallback;
+ private readonly T[] m_Values;
+ internal Func DisplayNameGenerator = ObjectNames.NicifyVariableName;
+ private T m_CurrentValue;
+
+ ///
+ /// Creates a new instance of the class.
+ ///
+ /// The initial selection value.
+ /// The callback to be invoked on selection change.
+ ///
+ /// Thrown if the generic enum parameter type is not integer based
+ /// or if the initial selection value is empty.
+ ///
+ /// Thrown if the provided change callback is null.
+ public FlagEnumContentProvider(T initialValue, Action 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;
+ }
+ }
+}
diff --git a/UnityEditor.TestRunner/GUI/Controls/FlagEnumContentProvider.cs.meta b/UnityEditor.TestRunner/GUI/Controls/FlagEnumContentProvider.cs.meta
new file mode 100644
index 0000000..cf0b821
--- /dev/null
+++ b/UnityEditor.TestRunner/GUI/Controls/FlagEnumContentProvider.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: d31403ec6b334194bdeb4c5ebad64097
+timeCreated: 1600072403
\ No newline at end of file
diff --git a/UnityEditor.TestRunner/GUI/Controls/FlagEnumUtility.cs b/UnityEditor.TestRunner/GUI/Controls/FlagEnumUtility.cs
new file mode 100644
index 0000000..aded730
--- /dev/null
+++ b/UnityEditor.TestRunner/GUI/Controls/FlagEnumUtility.cs
@@ -0,0 +1,74 @@
+using System;
+using UnityEngine;
+
+namespace UnityEditor.TestTools.TestRunner.GUI.Controls
+{
+ ///
+ /// Provides methods for dealing with common enumerator operations.
+ ///
+ internal static class FlagEnumUtility
+ {
+ ///
+ /// Checks for the presence of a flag in a flag enum value.
+ ///
+ /// The value to check for the presence of the flag.
+ /// The flag whose presence is to be checked.
+ /// The flag enum type.
+ ///
+ internal static bool HasFlag(T value, T flag) where T : Enum
+ {
+ ValidateUnderlyingType();
+
+ var intValue = (int)(object)value;
+ var intFlag = (int)(object)flag;
+ return (intValue & intFlag) == intFlag;
+ }
+
+ ///
+ /// Sets a flag in a flag enum value.
+ ///
+ /// The value where the flag should be set.
+ /// The flag to be set.
+ /// The flag enum type.
+ /// The input value with the flag set.
+ internal static T SetFlag(T value, T flag) where T : Enum
+ {
+ ValidateUnderlyingType();
+
+ var intValue = (int)(object)value;
+ var intFlag = (int)(object)flag;
+ var result = intValue | intFlag;
+ return (T)Enum.ToObject(typeof(T), result);
+ }
+
+ ///
+ /// Removes a flag in a flag enum value.
+ ///
+ /// The value where the flag should be removed.
+ /// The flag to be removed.
+ /// The flag enum type.
+ /// The input value with the flag removed.
+ internal static T RemoveFlag(T value, T flag) where T : Enum
+ {
+ ValidateUnderlyingType();
+
+ var intValue = (int)(object)value;
+ var intFlag = (int)(object)flag;
+ var result = intValue & ~intFlag;
+ return (T)Enum.ToObject(typeof(T), result);
+ }
+
+ ///
+ /// Validates that the underlying type of an enum is integer.
+ ///
+ /// The enum type.
+ /// Thrown if the underlying type of the enum type parameter is not integer.
+ private static void ValidateUnderlyingType() where T : Enum
+ {
+ if (Enum.GetUnderlyingType(typeof(T)) != typeof(int))
+ {
+ throw new ArgumentException("Argument underlying type must be integer.");
+ }
+ }
+ }
+}
diff --git a/UnityEditor.TestRunner/GUI/Controls/FlagEnumUtility.cs.meta b/UnityEditor.TestRunner/GUI/Controls/FlagEnumUtility.cs.meta
new file mode 100644
index 0000000..700c377
--- /dev/null
+++ b/UnityEditor.TestRunner/GUI/Controls/FlagEnumUtility.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: ea6cc55375344f10834cf6fa65197525
+timeCreated: 1600072466
\ No newline at end of file
diff --git a/UnityEditor.TestRunner/GUI/Controls/GenericItemContentProvider.cs b/UnityEditor.TestRunner/GUI/Controls/GenericItemContentProvider.cs
new file mode 100644
index 0000000..c8a9628
--- /dev/null
+++ b/UnityEditor.TestRunner/GUI/Controls/GenericItemContentProvider.cs
@@ -0,0 +1,101 @@
+using System;
+using System.Linq;
+using UnityEngine;
+
+namespace UnityEditor.TestTools.TestRunner.GUI.Controls
+{
+ ///
+ /// A generic type content provider to be used with the control.
+ ///
+ /// The type of values represented by content elements.
+ internal class GenericItemContentProvider : ISelectionDropDownContentProvider where T : IEquatable
+ {
+ private readonly ISelectableItem[] m_Items;
+ private readonly Action m_ValueChangedCallback;
+ private T m_CurrentValue;
+
+ ///
+ /// Creates a new instance of the class.
+ ///
+ /// The initial selection value.
+ /// The set of selectable items.
+ /// The indices of items which should be followed by separator lines.
+ ///
+ /// Thrown if any of the provided arguments is null, except for the separator indices.
+ /// Thrown if the set of items is empty or does not contain the initial selection value.
+ public GenericItemContentProvider(T initialValue, ISelectableItem[] items, int[] separatorIndices, Action 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;
+ }
+ }
+}
diff --git a/UnityEditor.TestRunner/GUI/Controls/GenericItemContentProvider.cs.meta b/UnityEditor.TestRunner/GUI/Controls/GenericItemContentProvider.cs.meta
new file mode 100644
index 0000000..c88886d
--- /dev/null
+++ b/UnityEditor.TestRunner/GUI/Controls/GenericItemContentProvider.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 7348ecf02a70497d9a09bd05ad2038ac
+timeCreated: 1600072422
\ No newline at end of file
diff --git a/UnityEditor.TestRunner/GUI/Controls/ISelectableItem.cs b/UnityEditor.TestRunner/GUI/Controls/ISelectableItem.cs
new file mode 100644
index 0000000..4b41b2e
--- /dev/null
+++ b/UnityEditor.TestRunner/GUI/Controls/ISelectableItem.cs
@@ -0,0 +1,20 @@
+using System;
+
+namespace UnityEditor.TestTools.TestRunner.GUI.Controls
+{
+ ///
+ /// Defines a content element which can be used with the content provider.
+ ///
+ internal interface ISelectableItem
+ {
+ ///
+ /// The value represented by this item.
+ ///
+ T Value { get; }
+
+ ///
+ /// The name to be used when displaying this item.
+ ///
+ string DisplayName { get; }
+ }
+}
diff --git a/UnityEditor.TestRunner/GUI/Controls/ISelectableItem.cs.meta b/UnityEditor.TestRunner/GUI/Controls/ISelectableItem.cs.meta
new file mode 100644
index 0000000..737274f
--- /dev/null
+++ b/UnityEditor.TestRunner/GUI/Controls/ISelectableItem.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 7b076e8f3150431baf926fe9ee030b1e
+timeCreated: 1600072411
\ No newline at end of file
diff --git a/UnityEditor.TestRunner/GUI/Controls/ISelectionDropDownContentProvider.cs b/UnityEditor.TestRunner/GUI/Controls/ISelectionDropDownContentProvider.cs
new file mode 100644
index 0000000..3b5a002
--- /dev/null
+++ b/UnityEditor.TestRunner/GUI/Controls/ISelectionDropDownContentProvider.cs
@@ -0,0 +1,46 @@
+using System;
+
+namespace UnityEditor.TestTools.TestRunner.GUI.Controls
+{
+ ///
+ /// Defines a content provider that can be used with the control.
+ ///
+ internal interface ISelectionDropDownContentProvider
+ {
+ ///
+ /// The total number of items to display.
+ ///
+ int Count { get; }
+
+ ///
+ /// Multiple selection support.
+ /// Multiple selection dropdowns don't get closed on selection change.
+ ///
+ bool IsMultiSelection { get; }
+
+ ///
+ /// The indices of items which should be followed by separator lines.
+ ///
+ int[] SeparatorIndices { get; }
+
+ ///
+ /// Returns the display name of the item at the specified index.
+ ///
+ /// The index of the item whose display name is to be returned.
+ /// The display name of the item at the specified index.
+ string GetName(int index);
+
+ ///
+ /// Signals a request to select the item at the specified index.
+ ///
+ /// The index of the item to be selected.
+ void SelectItem(int index);
+
+ ///
+ /// Returns the selection status of the item at the specified index.
+ ///
+ /// The index of the item whose selection status is to be returned.
+ /// true if the item is currently selected; otherwise, false.
+ bool IsSelected(int index);
+ }
+}
diff --git a/UnityEditor.TestRunner/GUI/Controls/ISelectionDropDownContentProvider.cs.meta b/UnityEditor.TestRunner/GUI/Controls/ISelectionDropDownContentProvider.cs.meta
new file mode 100644
index 0000000..bebc56f
--- /dev/null
+++ b/UnityEditor.TestRunner/GUI/Controls/ISelectionDropDownContentProvider.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: c2247a4dfeae4607949780b219a7d3c8
+timeCreated: 1600072397
\ No newline at end of file
diff --git a/UnityEditor.TestRunner/GUI/Controls/MultiValueContentProvider.cs b/UnityEditor.TestRunner/GUI/Controls/MultiValueContentProvider.cs
new file mode 100644
index 0000000..3c036b9
--- /dev/null
+++ b/UnityEditor.TestRunner/GUI/Controls/MultiValueContentProvider.cs
@@ -0,0 +1,82 @@
+using System;
+using System.Linq;
+using UnityEngine;
+
+namespace UnityEditor.TestTools.TestRunner.GUI.Controls
+{
+ internal class MultiValueContentProvider : ISelectionDropDownContentProvider where T : IEquatable
+ {
+ private T[] m_Values;
+ private bool[] m_Selected;
+ private Action m_SelectionChangeCallback;
+
+ public MultiValueContentProvider(T[] values, T[] selectedValues, Action 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;
+ }
+ }
+}
diff --git a/UnityEditor.TestRunner/GUI/TestListBuilder/TestFilterSettings.cs.meta b/UnityEditor.TestRunner/GUI/Controls/MultiValueContentProvider.cs.meta
similarity index 80%
rename from UnityEditor.TestRunner/GUI/TestListBuilder/TestFilterSettings.cs.meta
rename to UnityEditor.TestRunner/GUI/Controls/MultiValueContentProvider.cs.meta
index 8d26c30..736f93a 100644
--- a/UnityEditor.TestRunner/GUI/TestListBuilder/TestFilterSettings.cs.meta
+++ b/UnityEditor.TestRunner/GUI/Controls/MultiValueContentProvider.cs.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: 046c3854296c5ec48bac50da6ca248ec
+guid: fb736ed47b6da4b499431bf9c6de5890
MonoImporter:
externalObjects: {}
serializedVersion: 2
diff --git a/UnityEditor.TestRunner/GUI/Controls/SelectableItemContent.cs b/UnityEditor.TestRunner/GUI/Controls/SelectableItemContent.cs
new file mode 100644
index 0000000..8d49832
--- /dev/null
+++ b/UnityEditor.TestRunner/GUI/Controls/SelectableItemContent.cs
@@ -0,0 +1,28 @@
+using System;
+
+namespace UnityEditor.TestTools.TestRunner.GUI.Controls
+{
+ ///
+ /// A default implementation of the interface.
+ ///
+ /// The type of the value represented by this content element.
+ internal class SelectableItemContent : ISelectableItem
+ {
+ private readonly string m_DisplayName;
+
+ ///
+ /// Creates a new instance of the class
+ ///
+ /// The value represented by this item.
+ /// The display name of this item.
+ public SelectableItemContent(T itemValue, string displayName)
+ {
+ Value = itemValue;
+ m_DisplayName = displayName;
+ }
+
+ public T Value { get; }
+
+ public string DisplayName => m_DisplayName ?? string.Empty;
+ }
+}
diff --git a/UnityEditor.TestRunner/GUI/Controls/SelectableItemContent.cs.meta b/UnityEditor.TestRunner/GUI/Controls/SelectableItemContent.cs.meta
new file mode 100644
index 0000000..f52b195
--- /dev/null
+++ b/UnityEditor.TestRunner/GUI/Controls/SelectableItemContent.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: f7d5e5417ccd4aa0bfa39228c674f142
+timeCreated: 1600072417
\ No newline at end of file
diff --git a/UnityEditor.TestRunner/GUI/Controls/SelectionDropDown.cs b/UnityEditor.TestRunner/GUI/Controls/SelectionDropDown.cs
new file mode 100644
index 0000000..9e83c45
--- /dev/null
+++ b/UnityEditor.TestRunner/GUI/Controls/SelectionDropDown.cs
@@ -0,0 +1,167 @@
+using System;
+using UnityEngine;
+
+namespace UnityEditor.TestTools.TestRunner.GUI.Controls
+{
+ ///
+ /// A DropDown editor control accepting -based content providers.
+ ///
+ 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;
+
+ ///
+ /// Creates a new instance of the editor control.
+ ///
+ /// The content provider to use.
+ 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);
+ }
+ }
+}
diff --git a/UnityEditor.TestRunner/GUI/Controls/SelectionDropDown.cs.meta b/UnityEditor.TestRunner/GUI/Controls/SelectionDropDown.cs.meta
new file mode 100644
index 0000000..1b57998
--- /dev/null
+++ b/UnityEditor.TestRunner/GUI/Controls/SelectionDropDown.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: f8f9b43d4e034c2d997f4eb6a0d85a96
+timeCreated: 1600072477
\ No newline at end of file
diff --git a/UnityEditor.TestRunner/GUI/GuiHelper.cs b/UnityEditor.TestRunner/GUI/GuiHelper.cs
index b35446c..e940473 100644
--- a/UnityEditor.TestRunner/GUI/GuiHelper.cs
+++ b/UnityEditor.TestRunner/GUI/GuiHelper.cs
@@ -94,14 +94,14 @@ 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;
- const string assetsFolder = "Assets";
- var assetsFolderIndex = filePath.IndexOf(assetsFolder);
- if (assetsFolderIndex < 0)
- return filePath;
-
- return filePath.Substring(assetsFolderIndex, filePath.Length - assetsFolderIndex);
+ return filePath.Substring(length);
+#endif
}
public bool OpenScriptInExternalEditor(string stacktrace)
diff --git a/UnityEditor.TestRunner/GUI/TestAssets.meta b/UnityEditor.TestRunner/GUI/TestAssets.meta
new file mode 100644
index 0000000..1bea759
--- /dev/null
+++ b/UnityEditor.TestRunner/GUI/TestAssets.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 92e3104769da143888a712cdea27d950
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/UnityEditor.TestRunner/GUI/TestAssets/ActiveFolderTemplateAssetCreator.cs b/UnityEditor.TestRunner/GUI/TestAssets/ActiveFolderTemplateAssetCreator.cs
new file mode 100644
index 0000000..0d475f3
--- /dev/null
+++ b/UnityEditor.TestRunner/GUI/TestAssets/ActiveFolderTemplateAssetCreator.cs
@@ -0,0 +1,26 @@
+using System;
+
+namespace UnityEditor.TestTools.TestRunner.GUI.TestAssets
+{
+ ///
+ internal class ActiveFolderTemplateAssetCreator : IActiveFolderTemplateAssetCreator
+ {
+ ///
+ public string GetActiveFolderPath()
+ {
+ return ProjectWindowUtil.GetActiveFolderPath();
+ }
+
+ ///
+ public void CreateFolderWithTemplates(string defaultName, params string[] templateNames)
+ {
+ ProjectWindowUtil.CreateFolderWithTemplates(defaultName, templateNames);
+ }
+
+ ///
+ public void CreateScriptAssetFromTemplateFile(string defaultName, string templatePath)
+ {
+ ProjectWindowUtil.CreateScriptAssetFromTemplateFile(templatePath, defaultName);
+ }
+ }
+}
diff --git a/UnityEditor.TestRunner/GUI/TestListBuilder/RenderingOptions.cs.meta b/UnityEditor.TestRunner/GUI/TestAssets/ActiveFolderTemplateAssetCreator.cs.meta
similarity index 80%
rename from UnityEditor.TestRunner/GUI/TestListBuilder/RenderingOptions.cs.meta
rename to UnityEditor.TestRunner/GUI/TestAssets/ActiveFolderTemplateAssetCreator.cs.meta
index a566ec8..a1c48e1 100644
--- a/UnityEditor.TestRunner/GUI/TestListBuilder/RenderingOptions.cs.meta
+++ b/UnityEditor.TestRunner/GUI/TestAssets/ActiveFolderTemplateAssetCreator.cs.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: 87357ff0dec4ef348a295235835c6ee4
+guid: f47c66e80010e4020b6803b930eb432c
MonoImporter:
externalObjects: {}
serializedVersion: 2
diff --git a/UnityEditor.TestRunner/GUI/TestAssets/CustomScriptAssemblyMappingFinder.cs b/UnityEditor.TestRunner/GUI/TestAssets/CustomScriptAssemblyMappingFinder.cs
new file mode 100644
index 0000000..3062bde
--- /dev/null
+++ b/UnityEditor.TestRunner/GUI/TestAssets/CustomScriptAssemblyMappingFinder.cs
@@ -0,0 +1,78 @@
+using System;
+using System.IO;
+using System.Linq;
+using UnityEditor.Scripting.ScriptCompilation;
+
+namespace UnityEditor.TestTools.TestRunner.GUI.TestAssets
+{
+ ///
+ internal class CustomScriptAssemblyMappingFinder : ICustomScriptAssemblyMappingFinder
+ {
+ ///
+ /// The provided string argument is null.
+ 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;
+ }
+
+ ///
+ /// Finds the Custom Script Assembly associated with the provided script asset path.
+ ///
+ /// The script path to check.
+ /// The associated ; null if none.
+ private static ICustomScriptAssembly FindCustomScriptAssemblyFromScriptPath(string scriptPath)
+ {
+ try
+ {
+ var customScriptAssembly = EditorCompilationInterface.Instance.FindCustomScriptAssemblyFromScriptPath(scriptPath);
+ return new CustomScriptAssemblyWrapper(customScriptAssembly);
+ }
+ catch (Exception)
+ {
+ return null;
+ }
+ }
+
+ ///
+ /// Custom Script Assembly wrapper.
+ ///
+ internal class CustomScriptAssemblyWrapper : ICustomScriptAssembly
+ {
+ internal readonly CustomScriptAssembly targetAssembly;
+
+ ///
+ /// Creates a new instance of the class.
+ ///
+ /// The to be represented by the wrapper.
+ /// The provided argument is null.
+ internal CustomScriptAssemblyWrapper(CustomScriptAssembly assembly)
+ {
+ targetAssembly = assembly
+ ?? throw new ArgumentNullException(nameof(assembly), "The provided assembly must not be null.");
+ }
+
+ ///
+ public bool HasPrecompiledReference(string libraryFilename)
+ {
+ var precompiledReferences = targetAssembly.PrecompiledReferences;
+ var libraryReferenceExists = precompiledReferences != null
+ && precompiledReferences.Any(r => Path.GetFileName(r) == libraryFilename);
+ return libraryReferenceExists;
+ }
+
+ ///
+ public bool HasAssemblyFlag(AssemblyFlags flag)
+ {
+ var hasAssemblyFlag = (targetAssembly.AssemblyFlags & flag) == flag;
+ return hasAssemblyFlag;
+ }
+ }
+ }
+}
diff --git a/UnityEditor.TestRunner/GUI/TestAssets/CustomScriptAssemblyMappingFinder.cs.meta b/UnityEditor.TestRunner/GUI/TestAssets/CustomScriptAssemblyMappingFinder.cs.meta
new file mode 100644
index 0000000..e836d80
--- /dev/null
+++ b/UnityEditor.TestRunner/GUI/TestAssets/CustomScriptAssemblyMappingFinder.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: f03c073fcc564ab582ac38999beb4b6d
+timeCreated: 1603203112
\ No newline at end of file
diff --git a/UnityEditor.TestRunner/GUI/TestAssets/FolderPathTestCompilationContextProvider.cs b/UnityEditor.TestRunner/GUI/TestAssets/FolderPathTestCompilationContextProvider.cs
new file mode 100644
index 0000000..bc1fcd2
--- /dev/null
+++ b/UnityEditor.TestRunner/GUI/TestAssets/FolderPathTestCompilationContextProvider.cs
@@ -0,0 +1,93 @@
+using System;
+using System.Linq;
+using UnityEditor.Scripting.ScriptCompilation;
+
+namespace UnityEditor.TestTools.TestRunner.GUI.TestAssets
+{
+ ///
+ 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;
+ }
+
+ ///
+ /// 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.
+ ///
+ /// The folder path to check.
+ /// True if a custom test assembly associated with the provided folder can be found; false otherwise.
+ /// The string argument is null.
+ 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;
+ }
+
+ ///
+ /// Checks if the provided folder path belongs to an assembly capable of compiling Test Scripts.
+ /// Unless the setting is enabled,
+ /// a Test Script can only be compiled in a Custom Test Assembly
+ /// or an (implicit or explicit) assembly.
+ ///
+ /// The folder path to check.
+ /// True if Test Scripts can be successfully compiled when added to this folder path; false otherwise.
+ /// The string argument is null.
+ 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;
+ }
+
+ ///
+ /// Checks if the provided folder path is a special editor path that belongs to an implicit editor assembly.
+ ///
+ /// The folder path to check.
+ /// True if the folder path belongs to an implicit editor assembly; false otherwise.
+ /// The string argument is null.
+ 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;
+ }
+ }
+}
diff --git a/UnityEditor.TestRunner/GUI/TestListGuiHelper.cs.meta b/UnityEditor.TestRunner/GUI/TestAssets/FolderPathTestCompilationContextProvider.cs.meta
similarity index 80%
rename from UnityEditor.TestRunner/GUI/TestListGuiHelper.cs.meta
rename to UnityEditor.TestRunner/GUI/TestAssets/FolderPathTestCompilationContextProvider.cs.meta
index b60d7fe..1a76bca 100644
--- a/UnityEditor.TestRunner/GUI/TestListGuiHelper.cs.meta
+++ b/UnityEditor.TestRunner/GUI/TestAssets/FolderPathTestCompilationContextProvider.cs.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: 97a05971510726f438153cd4987526fb
+guid: 3d92c578e28043ef95d4b703e008be64
MonoImporter:
externalObjects: {}
serializedVersion: 2
diff --git a/UnityEditor.TestRunner/GUI/TestAssets/IActiveFolderTemplateAssetCreator.cs b/UnityEditor.TestRunner/GUI/TestAssets/IActiveFolderTemplateAssetCreator.cs
new file mode 100644
index 0000000..dea6874
--- /dev/null
+++ b/UnityEditor.TestRunner/GUI/TestAssets/IActiveFolderTemplateAssetCreator.cs
@@ -0,0 +1,30 @@
+using System;
+
+namespace UnityEditor.TestTools.TestRunner.GUI.TestAssets
+{
+ ///
+ /// Provides basic utility methods for creating assets in the active Project Browser folder path.
+ ///
+ internal interface IActiveFolderTemplateAssetCreator
+ {
+ ///
+ /// The active Project Browser folder path relative to the root project folder.
+ ///
+ /// The active folder path string.
+ string GetActiveFolderPath();
+
+ ///
+ /// Creates a new folder asset in the active folder path with assets defined by provided templates.
+ ///
+ /// The default name of the folder.
+ /// The names of templates to be used when creating embedded assets.
+ void CreateFolderWithTemplates(string defaultName, params string[] templateNames);
+
+ ///
+ /// Creates a new script asset in the active folder path defined by the provided template.
+ ///
+ /// The default name of the new script asset.
+ /// The template to be used when creating the asset.
+ void CreateScriptAssetFromTemplateFile(string defaultName, string templatePath);
+ }
+}
diff --git a/UnityEditor.TestRunner/GUI/TestListBuilder/ResultSummarizer.cs.meta b/UnityEditor.TestRunner/GUI/TestAssets/IActiveFolderTemplateAssetCreator.cs.meta
similarity index 80%
rename from UnityEditor.TestRunner/GUI/TestListBuilder/ResultSummarizer.cs.meta
rename to UnityEditor.TestRunner/GUI/TestAssets/IActiveFolderTemplateAssetCreator.cs.meta
index 197b321..5928983 100644
--- a/UnityEditor.TestRunner/GUI/TestListBuilder/ResultSummarizer.cs.meta
+++ b/UnityEditor.TestRunner/GUI/TestAssets/IActiveFolderTemplateAssetCreator.cs.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: 95a2914724952ef40bb590d0607fc878
+guid: 409ee14a63784dc0ab5d002081211814
MonoImporter:
externalObjects: {}
serializedVersion: 2
diff --git a/UnityEditor.TestRunner/GUI/TestAssets/ICustomScriptAssembly.cs b/UnityEditor.TestRunner/GUI/TestAssets/ICustomScriptAssembly.cs
new file mode 100644
index 0000000..f9dccfd
--- /dev/null
+++ b/UnityEditor.TestRunner/GUI/TestAssets/ICustomScriptAssembly.cs
@@ -0,0 +1,25 @@
+using System;
+using UnityEditor.Scripting.ScriptCompilation;
+
+namespace UnityEditor.TestTools.TestRunner.GUI.TestAssets
+{
+ ///
+ /// Provides a wrapper for a Custom Script Assembly and exposes its basic properties.
+ ///
+ internal interface ICustomScriptAssembly
+ {
+ ///
+ /// Checks if the Custom Script Assembly is referencing the provided precompiled library.
+ ///
+ /// The name of the precompiled library reference to be checked.
+ /// True if the assembly references the provided precompiled library; false otherwise.
+ bool HasPrecompiledReference(string libraryFilename);
+
+ ///
+ /// Checks if the Custom Script Assembly has the provided value set.
+ ///
+ /// The value to check against.
+ /// True if the provided value is set; false otherwise.
+ bool HasAssemblyFlag(AssemblyFlags flag);
+ }
+}
diff --git a/UnityEditor.TestRunner/GUI/TestAssets/ICustomScriptAssembly.cs.meta b/UnityEditor.TestRunner/GUI/TestAssets/ICustomScriptAssembly.cs.meta
new file mode 100644
index 0000000..1471fa1
--- /dev/null
+++ b/UnityEditor.TestRunner/GUI/TestAssets/ICustomScriptAssembly.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 32829ea9e75c475295f73ff867e2f9d0
+timeCreated: 1603203107
\ No newline at end of file
diff --git a/UnityEditor.TestRunner/GUI/TestAssets/ICustomScriptAssemblyMappingFinder.cs b/UnityEditor.TestRunner/GUI/TestAssets/ICustomScriptAssemblyMappingFinder.cs
new file mode 100644
index 0000000..70bf98e
--- /dev/null
+++ b/UnityEditor.TestRunner/GUI/TestAssets/ICustomScriptAssemblyMappingFinder.cs
@@ -0,0 +1,17 @@
+using System;
+
+namespace UnityEditor.TestTools.TestRunner.GUI.TestAssets
+{
+ ///
+ /// Provides mapping information from folder paths to their corresponding Custom Script Assembly scope.
+ ///
+ internal interface ICustomScriptAssemblyMappingFinder
+ {
+ ///
+ /// Finds the Custom Script Assembly associated with the provided folder path.
+ ///
+ /// The folder path to check.
+ /// The associated ; null if none.
+ ICustomScriptAssembly FindCustomScriptAssemblyFromFolderPath(string folderPath);
+ }
+}
diff --git a/UnityEditor.TestRunner/GUI/TestAssets/ICustomScriptAssemblyMappingFinder.cs.meta b/UnityEditor.TestRunner/GUI/TestAssets/ICustomScriptAssemblyMappingFinder.cs.meta
new file mode 100644
index 0000000..a1d7270
--- /dev/null
+++ b/UnityEditor.TestRunner/GUI/TestAssets/ICustomScriptAssemblyMappingFinder.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 0cd0deb81d984e58952ccd7e1dd6b2bb
+timeCreated: 1603203104
\ No newline at end of file
diff --git a/UnityEditor.TestRunner/GUI/TestAssets/IFolderPathTestCompilationContextProvider.cs b/UnityEditor.TestRunner/GUI/TestAssets/IFolderPathTestCompilationContextProvider.cs
new file mode 100644
index 0000000..dff8392
--- /dev/null
+++ b/UnityEditor.TestRunner/GUI/TestAssets/IFolderPathTestCompilationContextProvider.cs
@@ -0,0 +1,20 @@
+using System;
+
+namespace UnityEditor.TestTools.TestRunner.GUI.TestAssets
+{
+ ///
+ /// Provides Test Script compilation context associated with project folder paths.
+ ///
+ internal interface IFolderPathTestCompilationContextProvider
+ {
+ ///
+ /// Checks if the provided folder path belongs to a Custom Test Assembly.
+ ///
+ bool FolderPathBelongsToCustomTestAssembly(string folderPath);
+
+ ///
+ /// Checks if the provided folder path belongs to an assembly capable of compiling Test Scripts.
+ ///
+ bool TestScriptWillCompileInFolderPath(string folderPath);
+ }
+}
diff --git a/UnityEditor.TestRunner/GUI/TestAssets/IFolderPathTestCompilationContextProvider.cs.meta b/UnityEditor.TestRunner/GUI/TestAssets/IFolderPathTestCompilationContextProvider.cs.meta
new file mode 100644
index 0000000..e32dfd1
--- /dev/null
+++ b/UnityEditor.TestRunner/GUI/TestAssets/IFolderPathTestCompilationContextProvider.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: e5f4a89476c1448abc7e0a9719b13b36
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/UnityEditor.TestRunner/GUI/TestAssets/ITestScriptAssetsCreator.cs b/UnityEditor.TestRunner/GUI/TestAssets/ITestScriptAssetsCreator.cs
new file mode 100644
index 0000000..2ffad16
--- /dev/null
+++ b/UnityEditor.TestRunner/GUI/TestAssets/ITestScriptAssetsCreator.cs
@@ -0,0 +1,33 @@
+using System;
+
+namespace UnityEditor.TestTools.TestRunner.GUI.TestAssets
+{
+ ///
+ /// Provides an interface for creating test assets from templates.
+ ///
+ internal interface ITestScriptAssetsCreator
+ {
+ ///
+ /// Creates a new folder in the active folder path with an associated Test Script Assembly definition.
+ ///
+ /// Should the assembly definition be editor-only?
+ void AddNewFolderWithTestAssemblyDefinition(bool isEditorOnly = false);
+
+ ///
+ /// Checks if the active folder path already contains a Test Script Assembly definition.
+ ///
+ /// True if the active folder path contains a Test Script Assembly; false otherwise.
+ bool ActiveFolderContainsTestAssemblyDefinition();
+
+ ///
+ /// Adds a new Test Script asset in the active folder path.
+ ///
+ void AddNewTestScript();
+
+ ///
+ /// Checks if a Test Script asset can be compiled in the active folder path.
+ ///
+ /// True if a Test Script can be compiled in the active folder path; false otherwise.
+ bool TestScriptWillCompileInActiveFolder();
+ }
+}
diff --git a/UnityEditor.TestRunner/GUI/TestAssets/ITestScriptAssetsCreator.cs.meta b/UnityEditor.TestRunner/GUI/TestAssets/ITestScriptAssetsCreator.cs.meta
new file mode 100644
index 0000000..6451483
--- /dev/null
+++ b/UnityEditor.TestRunner/GUI/TestAssets/ITestScriptAssetsCreator.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: e7f72702c2f04b999739380ef9c0de5f
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/UnityEditor.TestRunner/GUI/TestAssets/TestScriptAssetMenuItems.cs b/UnityEditor.TestRunner/GUI/TestAssets/TestScriptAssetMenuItems.cs
new file mode 100644
index 0000000..05f4479
--- /dev/null
+++ b/UnityEditor.TestRunner/GUI/TestAssets/TestScriptAssetMenuItems.cs
@@ -0,0 +1,53 @@
+using System;
+
+namespace UnityEditor.TestTools.TestRunner.GUI.TestAssets
+{
+ ///
+ /// The set of Menu Items dedicated to creating test assets: Test Scripts and Custom Test Assemblies.
+ ///
+ internal static class TestScriptAssetMenuItems
+ {
+ internal const string addNewFolderWithTestAssemblyDefinitionMenuItem = "Assets/Create/Testing/Tests Assembly Folder";
+ internal const string addNewTestScriptMenuItem = "Assets/Create/Testing/C# Test Script";
+
+ ///
+ /// Adds a new folder asset and an associated Custom Test Assembly in the active folder path.
+ ///
+ [MenuItem(addNewFolderWithTestAssemblyDefinitionMenuItem, false, 83)]
+ public static void AddNewFolderWithTestAssemblyDefinition()
+ {
+ TestScriptAssetsCreator.Instance.AddNewFolderWithTestAssemblyDefinition();
+ }
+
+ ///
+ /// Checks if it is possible to add a new Custom Test Assembly inside the active folder path.
+ ///
+ /// False if the active folder path already contains a Custom Test Assembly; true otherwise.
+ [MenuItem(addNewFolderWithTestAssemblyDefinitionMenuItem, true, 83)]
+ public static bool CanAddNewFolderWithTestAssemblyDefinition()
+ {
+ var testAssemblyAlreadyExists = TestScriptAssetsCreator.Instance.ActiveFolderContainsTestAssemblyDefinition();
+ return !testAssemblyAlreadyExists;
+ }
+
+ ///
+ /// Adds a new Test Script asset in the active folder path.
+ ///
+ [MenuItem(addNewTestScriptMenuItem, false, 83)]
+ public static void AddNewTestScript()
+ {
+ TestScriptAssetsCreator.Instance.AddNewTestScript();
+ }
+
+ ///
+ /// Checks if it is possible to add a new Test Script in the active folder path.
+ ///
+ /// True if a Test Script can be compiled in the active folder path; false otherwise.
+ [MenuItem(addNewTestScriptMenuItem, true, 83)]
+ public static bool CanAddNewTestScript()
+ {
+ var testScriptWillCompile = TestScriptAssetsCreator.Instance.TestScriptWillCompileInActiveFolder();
+ return testScriptWillCompile;
+ }
+ }
+}
diff --git a/UnityEditor.TestRunner/GUI/TestAssets/TestScriptAssetMenuItems.cs.meta b/UnityEditor.TestRunner/GUI/TestAssets/TestScriptAssetMenuItems.cs.meta
new file mode 100644
index 0000000..9d61fd7
--- /dev/null
+++ b/UnityEditor.TestRunner/GUI/TestAssets/TestScriptAssetMenuItems.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 3c702cb84a2a4576bf275a76bc17f8e8
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/UnityEditor.TestRunner/GUI/TestAssets/TestScriptAssetsCreator.cs b/UnityEditor.TestRunner/GUI/TestAssets/TestScriptAssetsCreator.cs
new file mode 100644
index 0000000..b1c2ad3
--- /dev/null
+++ b/UnityEditor.TestRunner/GUI/TestAssets/TestScriptAssetsCreator.cs
@@ -0,0 +1,66 @@
+using System;
+using System.IO;
+
+namespace UnityEditor.TestTools.TestRunner.GUI.TestAssets
+{
+ ///
+ 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);
+ private static string ScriptTemplatePath => Path.Combine(ScriptTemplatesResourcesPath, testScriptTemplate);
+
+ ///
+ public void AddNewFolderWithTestAssemblyDefinition(bool isEditorOnly = false)
+ {
+ var assemblyDefinitionTemplate = isEditorOnly ? k_AssemblyDefinitionEditModeTestTemplate : assemblyDefinitionTestTemplate;
+ ActiveFolderTemplateAssetCreator.CreateFolderWithTemplates(defaultNewTestAssemblyFolderName, assemblyDefinitionTemplate);
+ }
+
+ ///
+ public void AddNewTestScript()
+ {
+ var destPath = Path.Combine(ActiveFolderTemplateAssetCreator.GetActiveFolderPath(), defaultNewTestScriptName);
+ ActiveFolderTemplateAssetCreator.CreateScriptAssetFromTemplateFile(destPath, ScriptTemplatePath);
+ }
+
+ ///
+ public bool ActiveFolderContainsTestAssemblyDefinition()
+ {
+ return FolderPathContext.FolderPathBelongsToCustomTestAssembly(ActiveFolderPath);
+ }
+
+ ///
+ public bool TestScriptWillCompileInActiveFolder()
+ {
+ return FolderPathContext.TestScriptWillCompileInFolderPath(ActiveFolderPath);
+ }
+ }
+}
diff --git a/UnityEditor.TestRunner/GUI/TestAssets/TestScriptAssetsCreator.cs.meta b/UnityEditor.TestRunner/GUI/TestAssets/TestScriptAssetsCreator.cs.meta
new file mode 100644
index 0000000..90b9931
--- /dev/null
+++ b/UnityEditor.TestRunner/GUI/TestAssets/TestScriptAssetsCreator.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 1bace19a170f47bb8d19645cfc580796
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/UnityEditor.TestRunner/GUI/TestListBuilder/RenderingOptions.cs b/UnityEditor.TestRunner/GUI/TestListBuilder/RenderingOptions.cs
deleted file mode 100644
index 0f1e6e0..0000000
--- a/UnityEditor.TestRunner/GUI/TestListBuilder/RenderingOptions.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using System;
-
-namespace UnityEditor.TestTools.TestRunner.GUI
-{
- internal class RenderingOptions
- {
- public string nameFilter;
- public bool showSucceeded;
- public bool showFailed;
- public bool showIgnored;
- public bool showNotRunned;
- public string[] categories;
- }
-}
diff --git a/UnityEditor.TestRunner/GUI/TestListBuilder/ResultSummarizer.cs b/UnityEditor.TestRunner/GUI/TestListBuilder/ResultSummarizer.cs
deleted file mode 100644
index f81990f..0000000
--- a/UnityEditor.TestRunner/GUI/TestListBuilder/ResultSummarizer.cs
+++ /dev/null
@@ -1,174 +0,0 @@
-// ****************************************************************
-// Based on nUnit 2.6.2 (http://www.nunit.org/)
-// ****************************************************************
-
-using System;
-using System.Collections.Generic;
-
-namespace UnityEditor.TestTools.TestRunner.GUI
-{
- ///
- /// Summary description for ResultSummarizer.
- ///
- internal class ResultSummarizer
- {
- private int m_ErrorCount = -1;
- private int m_FailureCount;
- private int m_IgnoreCount = -1;
- private int m_InconclusiveCount = -1;
- private int m_NotRunnable = -1;
- private int m_ResultCount;
- private int m_SkipCount;
- private int m_SuccessCount;
- private int m_TestsRun;
-
- private TimeSpan m_Duration = TimeSpan.FromSeconds(0);
-
- public ResultSummarizer(IEnumerable results)
- {
- foreach (var result in results)
- Summarize(result);
- }
-
- public bool success
- {
- get { return m_FailureCount == 0; }
- }
-
- ///
- /// Returns the number of test cases for which results
- /// have been summarized. Any tests excluded by use of
- /// Category or Explicit attributes are not counted.
- ///
- public int ResultCount
- {
- get { return m_ResultCount; }
- }
-
- ///
- /// Returns the number of test cases actually run, which
- /// is the same as ResultCount, less any Skipped, Ignored
- /// or NonRunnable tests.
- ///
- public int TestsRun
- {
- get { return m_TestsRun; }
- }
-
- ///
- /// Returns the number of tests that passed
- ///
- public int Passed
- {
- get { return m_SuccessCount; }
- }
-
- ///
- /// Returns the number of test cases that had an error.
- ///
- public int errors
- {
- get { return m_ErrorCount; }
- }
-
- ///
- /// Returns the number of test cases that failed.
- ///
- public int failures
- {
- get { return m_FailureCount; }
- }
-
- ///
- /// Returns the number of test cases that failed.
- ///
- public int inconclusive
- {
- get { return m_InconclusiveCount; }
- }
-
- ///
- /// Returns the number of test cases that were not runnable
- /// due to errors in the signature of the class or method.
- /// Such tests are also counted as Errors.
- ///
- public int notRunnable
- {
- get { return m_NotRunnable; }
- }
-
- ///
- /// Returns the number of test cases that were skipped.
- ///
- public int Skipped
- {
- get { return m_SkipCount; }
- }
-
- public int ignored
- {
- get { return m_IgnoreCount; }
- }
-
- public double duration
- {
- get { return m_Duration.TotalSeconds; }
- }
-
- public int testsNotRun
- {
- get { return m_SkipCount + m_IgnoreCount + m_NotRunnable; }
- }
-
- public void Summarize(TestRunnerResult result)
- {
- m_Duration += TimeSpan.FromSeconds(result.duration);
- m_ResultCount++;
-
- if (result.resultStatus != TestRunnerResult.ResultStatus.NotRun)
- {
- //TODO implement missing features
- // if(result.IsIgnored)
- // {
- // m_IgnoreCount++;
- // return;
- // }
-
- m_SkipCount++;
- return;
- }
-
- switch (result.resultStatus)
- {
- case TestRunnerResult.ResultStatus.Passed:
- m_SuccessCount++;
- m_TestsRun++;
- break;
- case TestRunnerResult.ResultStatus.Failed:
- m_FailureCount++;
- m_TestsRun++;
- break;
- //TODO implement missing features
- // case TestResultState.Error:
- // case TestResultState.Cancelled:
- // m_ErrorCount++;
- // m_TestsRun++;
- // break;
- // case TestResultState.Inconclusive:
- // m_InconclusiveCount++;
- // m_TestsRun++;
- // break;
- // case TestResultState.NotRunnable:
- // m_NotRunnable++;
- // // errorCount++;
- // break;
- // case TestResultState.Ignored:
- // m_IgnoreCount++;
- // break;
- default:
- m_SkipCount++;
- break;
- }
- }
- }
-}
diff --git a/UnityEditor.TestRunner/GUI/TestListBuilder/TestFilterSettings.cs b/UnityEditor.TestRunner/GUI/TestListBuilder/TestFilterSettings.cs
deleted file mode 100644
index fd33c4e..0000000
--- a/UnityEditor.TestRunner/GUI/TestListBuilder/TestFilterSettings.cs
+++ /dev/null
@@ -1,105 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using UnityEngine;
-
-namespace UnityEditor.TestTools.TestRunner.GUI
-{
- internal class TestFilterSettings
- {
- public bool showSucceeded;
- public bool showFailed;
- public bool showIgnored;
- public bool showNotRun;
-
- public string filterByName;
- public int filterByCategory;
-
- private GUIContent m_SucceededBtn;
- private GUIContent m_FailedBtn;
- private GUIContent m_IgnoredBtn;
- private GUIContent m_NotRunBtn;
-
- public string[] availableCategories;
-
- private readonly string m_PrefsKey;
-
- public TestFilterSettings(string prefsKey)
- {
- availableCategories = null;
- m_PrefsKey = prefsKey;
- Load();
- UpdateCounters(Enumerable.Empty());
- }
-
- public void Load()
- {
- showSucceeded = EditorPrefs.GetBool(m_PrefsKey + ".ShowSucceeded", true);
- showFailed = EditorPrefs.GetBool(m_PrefsKey + ".ShowFailed", true);
- showIgnored = EditorPrefs.GetBool(m_PrefsKey + ".ShowIgnored", true);
- showNotRun = EditorPrefs.GetBool(m_PrefsKey + ".ShowNotRun", true);
- filterByName = EditorPrefs.GetString(m_PrefsKey + ".FilterByName", string.Empty);
- filterByCategory = EditorPrefs.GetInt(m_PrefsKey + ".FilterByCategory", 0);
- }
-
- public void Save()
- {
- EditorPrefs.SetBool(m_PrefsKey + ".ShowSucceeded", showSucceeded);
- EditorPrefs.SetBool(m_PrefsKey + ".ShowFailed", showFailed);
- EditorPrefs.SetBool(m_PrefsKey + ".ShowIgnored", showIgnored);
- EditorPrefs.SetBool(m_PrefsKey + ".ShowNotRun", showNotRun);
- EditorPrefs.SetString(m_PrefsKey + ".FilterByName", filterByName);
- EditorPrefs.SetInt(m_PrefsKey + ".FilterByCategory", filterByCategory);
- }
-
- public void UpdateCounters(IEnumerable results)
- {
- var summary = new ResultSummarizer(results);
-
- m_SucceededBtn = new GUIContent(summary.Passed.ToString(), Icons.s_SuccessImg, "Show tests that succeeded");
- m_FailedBtn = new GUIContent((summary.errors + summary.failures + summary.inconclusive).ToString(), Icons.s_FailImg, "Show tests that failed");
- m_IgnoredBtn = new GUIContent((summary.ignored + summary.notRunnable).ToString(), Icons.s_IgnoreImg, "Show tests that are ignored");
- m_NotRunBtn = new GUIContent((summary.testsNotRun - summary.ignored - summary.notRunnable).ToString(), Icons.s_UnknownImg, "Show tests that didn't run");
- }
-
- public string[] GetSelectedCategories()
- {
- if (availableCategories == null)
- return new string[0];
-
- return availableCategories.Where((c, i) => (filterByCategory & (1 << i)) != 0).ToArray();
- }
-
- public void OnGUI()
- {
- EditorGUI.BeginChangeCheck();
-
- filterByName = GUILayout.TextField(filterByName, "ToolbarSeachTextField", GUILayout.MinWidth(100), GUILayout.MaxWidth(250), GUILayout.ExpandWidth(true));
- if (GUILayout.Button(GUIContent.none, string.IsNullOrEmpty(filterByName) ? "ToolbarSeachCancelButtonEmpty" : "ToolbarSeachCancelButton"))
- filterByName = string.Empty;
-
- if (availableCategories != null && availableCategories.Length > 0)
- filterByCategory = EditorGUILayout.MaskField(filterByCategory, availableCategories, EditorStyles.toolbarDropDown, GUILayout.MaxWidth(90));
-
- showSucceeded = GUILayout.Toggle(showSucceeded, m_SucceededBtn, EditorStyles.toolbarButton);
- showFailed = GUILayout.Toggle(showFailed, m_FailedBtn, EditorStyles.toolbarButton);
- showIgnored = GUILayout.Toggle(showIgnored, m_IgnoredBtn, EditorStyles.toolbarButton);
- showNotRun = GUILayout.Toggle(showNotRun, m_NotRunBtn, EditorStyles.toolbarButton);
-
- if (EditorGUI.EndChangeCheck())
- Save();
- }
-
- public RenderingOptions BuildRenderingOptions()
- {
- var options = new RenderingOptions();
- options.showSucceeded = showSucceeded;
- options.showFailed = showFailed;
- options.showIgnored = showIgnored;
- options.showNotRunned = showNotRun;
- options.nameFilter = filterByName;
- options.categories = GetSelectedCategories();
- return options;
- }
- }
-}
diff --git a/UnityEditor.TestRunner/GUI/TestListBuilder/TestTreeViewBuilder.cs b/UnityEditor.TestRunner/GUI/TestListBuilder/TestTreeViewBuilder.cs
index b10bb5d..e904cde 100644
--- a/UnityEditor.TestRunner/GUI/TestListBuilder/TestTreeViewBuilder.cs
+++ b/UnityEditor.TestRunner/GUI/TestListBuilder/TestTreeViewBuilder.cs
@@ -3,16 +3,25 @@
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 results = new List();
+ public readonly Dictionary m_treeFiltered = new Dictionary();
private readonly Dictionary m_OldTestResults;
private readonly TestRunnerUIFilter m_UIFilter;
- private readonly ITestAdaptor m_TestListRoot;
+ private readonly ITestAdaptor[] m_TestListRoots;
+ private readonly Dictionary> m_ChildrenResults;
private readonly List m_AvailableCategories = new List();
@@ -21,18 +30,24 @@ public string[] AvailableCategories
get { return m_AvailableCategories.Distinct().OrderBy(a => a).ToArray(); }
}
- public TestTreeViewBuilder(ITestAdaptor tests, Dictionary oldTestResultResults, TestRunnerUIFilter uiFilter)
+ public TestTreeViewBuilder(ITestAdaptor[] tests, Dictionary oldTestResultResults, TestRunnerUIFilter uiFilter)
{
m_AvailableCategories.Add(CategoryFilterExtended.k_DefaultCategory);
m_OldTestResults = oldTestResultResults;
- m_TestListRoot = tests;
+ m_ChildrenResults = new Dictionary>();
+ m_TestListRoots = tests;
m_UIFilter = uiFilter;
}
- public TreeViewItem BuildTreeView(TestFilterSettings settings, bool sceneBased, string sceneName)
+ public TreeViewItem BuildTreeView()
{
+ m_treeFiltered.Clear();
var rootItem = new TreeViewItem(int.MaxValue, 0, null, "Invisible Root Item");
- ParseTestTree(0, rootItem, m_TestListRoot);
+ foreach (var testRoot in m_TestListRoots)
+ {
+ ParseTestTree(0, rootItem, testRoot);
+ }
+
return rootItem;
}
@@ -44,13 +59,23 @@ private bool IsFilteredOutByUIFilter(ITestAdaptor test, TestRunnerResult result)
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 void ParseTestTree(int depth, TreeViewItem rootItem, ITestAdaptor testElement)
+ 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;
@@ -78,33 +103,64 @@ private void ParseTestTree(int depth, TreeViewItem rootItem, ITestAdaptor testEl
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);
- return;
- }
+ 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 {result};
+ m_ChildrenResults.Add(testElement.ParentUniqueName, resultList);
+ }
+ }
- m_OldTestResults.TryGetValue(testElementId, out var groupResult);
- if (groupResult == null)
- {
- groupResult = new TestRunnerResult(testElement);
+ return testCount;
}
+ var groupResult = new TestRunnerResult(testElement);
results.Add(groupResult);
var group = new TestTreeViewItem(testElement, depth, rootItem);
- group.SetResult(groupResult);
depth++;
+
foreach (var child in testElement.Children)
{
- ParseTestTree(depth, group, child);
+ var childTestCount = ParseTestTree(depth, group, child);
+
+ testCount.TotalTestCount += childTestCount.TotalTestCount;
+ testCount.TotalFailedTestCount += childTestCount.TotalFailedTestCount;
}
if (testElement.IsTestAssembly && !testElement.HasChildren)
- return;
+ {
+ 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;
}
}
}
diff --git a/UnityEditor.TestRunner/GUI/TestListGuiHelper.cs b/UnityEditor.TestRunner/GUI/TestListGuiHelper.cs
deleted file mode 100644
index ca88ccb..0000000
--- a/UnityEditor.TestRunner/GUI/TestListGuiHelper.cs
+++ /dev/null
@@ -1,140 +0,0 @@
-using System;
-using System.IO;
-using System.Linq;
-using UnityEditor.ProjectWindowCallback;
-using UnityEditor.Scripting.ScriptCompilation;
-using UnityEngine;
-using Object = UnityEngine.Object;
-
-namespace UnityEditor.TestTools.TestRunner.GUI
-{
- internal class TestListGUIHelper
- {
- private const string kResourcesTemplatePath = "Resources/ScriptTemplates";
- private const string kAssemblyDefinitionTestTemplate = "92-Assembly Definition-NewTestAssembly.asmdef.txt";
-
- private const string kAssemblyDefinitionEditModeTestTemplate =
- "92-Assembly Definition-NewEditModeTestAssembly.asmdef.txt";
-
- private const string kTestScriptTemplate = "83-C# Script-NewTestScript.cs.txt";
- private const string kNewTestScriptName = "NewTestScript.cs";
- private const string kNunit = "nunit.framework.dll";
-
- [MenuItem("Assets/Create/Testing/Tests Assembly Folder", false, 83)]
- public static void MenuItemAddFolderAndAsmDefForTesting()
- {
- AddFolderAndAsmDefForTesting();
- }
-
- [MenuItem("Assets/Create/Testing/Tests Assembly Folder", true, 83)]
- public static bool MenuItemAddFolderAndAsmDefForTestingWithValidation()
- {
- return !SelectedFolderContainsTestAssembly();
- }
-
- public static void AddFolderAndAsmDefForTesting(bool isEditorOnly = false)
- {
- ProjectWindowUtil.CreateFolderWithTemplates("Tests",
- isEditorOnly ? kAssemblyDefinitionEditModeTestTemplate : kAssemblyDefinitionTestTemplate);
- }
-
- public static bool SelectedFolderContainsTestAssembly()
- {
- var theNearestCustomScriptAssembly = GetTheNearestCustomScriptAssembly();
- if (theNearestCustomScriptAssembly != null)
- {
- return theNearestCustomScriptAssembly.PrecompiledReferences != null && theNearestCustomScriptAssembly.PrecompiledReferences.Any(x => Path.GetFileName(x) == kNunit);
- }
-
- return false;
- }
-
- [MenuItem("Assets/Create/Testing/C# Test Script", false, 83)]
- public static void AddTest()
- {
- var basePath = Path.Combine(EditorApplication.applicationContentsPath, kResourcesTemplatePath);
- var destPath = Path.Combine(GetActiveFolderPath(), kNewTestScriptName);
-#if UNITY_2023_3_OR_NEWER
- var templatePath = Path.Combine(basePath, AssetsMenuUtility.GetScriptTemplatePath(ScriptTemplate.CSharp_NewTestScript));
-#else
- var templatePath = Path.Combine(basePath, kTestScriptTemplate);
-#endif
- var icon = EditorGUIUtility.IconContent("cs Script Icon").image as Texture2D;
- ProjectWindowUtil.StartNameEditingIfProjectWindowExists(0,
- ScriptableObject.CreateInstance(), destPath, icon, templatePath);
-
- AssetDatabase.Refresh();
- }
-
- [MenuItem("Assets/Create/Testing/C# Test Script", true, 83)]
- public static bool CanAddScriptAndItWillCompile()
- {
- return CanAddEditModeTestScriptAndItWillCompile() || CanAddPlayModeTestScriptAndItWillCompile();
- }
-
- public static bool CanAddEditModeTestScriptAndItWillCompile()
- {
- var theNearestCustomScriptAssembly = GetTheNearestCustomScriptAssembly();
- if (theNearestCustomScriptAssembly != null)
- {
- return (theNearestCustomScriptAssembly.AssemblyFlags & AssemblyFlags.EditorOnly) ==
- AssemblyFlags.EditorOnly;
- }
-
- var activeFolderPath = GetActiveFolderPath();
- return activeFolderPath.ToLower().Contains("/editor");
- }
-
- public static bool CanAddPlayModeTestScriptAndItWillCompile()
- {
- if (PlayerSettings.playModeTestRunnerEnabled)
- {
- return true;
- }
-
- var theNearestCustomScriptAssembly = GetTheNearestCustomScriptAssembly();
-
- if (theNearestCustomScriptAssembly == null)
- {
- return false;
- }
-
- var hasTestAssemblyFlag = theNearestCustomScriptAssembly.PrecompiledReferences != null && theNearestCustomScriptAssembly.PrecompiledReferences.Any(x => Path.GetFileName(x) == kNunit);;
- var editorOnlyAssembly = (theNearestCustomScriptAssembly.AssemblyFlags & AssemblyFlags.EditorOnly) != 0;
-
- return hasTestAssemblyFlag && !editorOnlyAssembly;
- }
-
- public static string GetActiveFolderPath()
- {
- var path = "Assets";
-
- foreach (var obj in Selection.GetFiltered(typeof(Object), SelectionMode.Assets))
- {
- path = AssetDatabase.GetAssetPath(obj);
- if (!string.IsNullOrEmpty(path) && File.Exists(path))
- {
- path = Path.GetDirectoryName(path);
- break;
- }
- }
- return path;
- }
-
- private static CustomScriptAssembly GetTheNearestCustomScriptAssembly()
- {
- CustomScriptAssembly findCustomScriptAssemblyFromScriptPath;
- try
- {
- findCustomScriptAssemblyFromScriptPath =
- EditorCompilationInterface.Instance.FindCustomScriptAssemblyFromScriptPath(
- Path.Combine(GetActiveFolderPath(), "Foo.cs"));
- }
- catch (Exception)
- {
- return null;
- }
- return findCustomScriptAssemblyFromScriptPath;
- }
- }
-}
diff --git a/UnityEditor.TestRunner/GUI/TestListTreeView/TestListTreeViewDataSource.cs b/UnityEditor.TestRunner/GUI/TestListTreeView/TestListTreeViewDataSource.cs
index 076b1aa..2dd914e 100644
--- a/UnityEditor.TestRunner/GUI/TestListTreeView/TestListTreeViewDataSource.cs
+++ b/UnityEditor.TestRunner/GUI/TestListTreeView/TestListTreeViewDataSource.cs
@@ -2,8 +2,6 @@
using System.Collections.Generic;
using UnityEditor.IMGUI.Controls;
using UnityEditor.TestTools.TestRunner.Api;
-using UnityEngine.SceneManagement;
-using UnityEngine.TestTools.TestRunner;
namespace UnityEditor.TestTools.TestRunner.GUI
{
@@ -11,30 +9,26 @@ internal class TestListTreeViewDataSource : TreeViewDataSource
{
private bool m_ExpandTreeOnCreation;
private readonly TestListGUI m_TestListGUI;
- private ITestAdaptor m_RootTest;
+ private ITestAdaptor[] m_RootTests;
- public TestListTreeViewDataSource(TreeViewController testListTree, TestListGUI testListGUI, ITestAdaptor rootTest) : base(testListTree)
+ public TestListTreeViewDataSource(TreeViewController testListTree, TestListGUI testListGUI, ITestAdaptor[] rootTests) : base(testListTree)
{
showRootItem = false;
rootIsCollapsable = false;
m_TestListGUI = testListGUI;
- m_RootTest = rootTest;
+ m_RootTests = rootTests;
}
- public void UpdateRootTest(ITestAdaptor rootTest)
+ public void UpdateRootTest(ITestAdaptor[] rootTests)
{
- m_RootTest = rootTest;
+ m_RootTests = rootTests;
}
public override void FetchData()
{
- var sceneName = SceneManager.GetActiveScene().name;
- if (sceneName.StartsWith("InitTestScene"))
- sceneName = PlaymodeTestsController.GetController().settings.originalScene;
+ var testListBuilder = new TestTreeViewBuilder(m_RootTests, m_TestListGUI.ResultsByKey, m_TestListGUI.m_TestRunnerUIFilter);
- var testListBuilder = new TestTreeViewBuilder(m_RootTest, m_TestListGUI.ResultsByKey, m_TestListGUI.m_TestRunnerUIFilter);
-
- m_RootItem = testListBuilder.BuildTreeView(null, false, sceneName);
+ m_RootItem = testListBuilder.BuildTreeView();
SetExpanded(m_RootItem, true);
if (m_RootItem.hasChildren && m_RootItem.children.Count == 1)
SetExpanded(m_RootItem.children[0], true);
@@ -43,6 +37,7 @@ public override void FetchData()
SetExpandedWithChildren(m_RootItem, true);
m_TestListGUI.newResultList = new List(testListBuilder.results);
+ m_TestListGUI.filteredTree = testListBuilder.m_treeFiltered;
m_TestListGUI.m_TestRunnerUIFilter.availableCategories = testListBuilder.AvailableCategories;
m_NeedRefreshRows = true;
}
@@ -63,36 +58,5 @@ public override bool IsExpandable(TreeViewItem item)
return ((TestTreeViewItem)item).IsGroupNode;
return base.IsExpandable(item);
}
-
- protected override List Search(TreeViewItem rootItem, string search)
- {
- var result = new List();
-
- if (rootItem.hasChildren)
- {
- foreach (var child in rootItem.children)
- {
- SearchTestTree(child, search, result);
- }
- }
- return result;
- }
-
- protected void SearchTestTree(TreeViewItem item, string search, IList searchResult)
- {
- var testItem = item as TestTreeViewItem;
- if (!testItem.IsGroupNode)
- {
- if (testItem.FullName.ToLower().Contains(search))
- {
- searchResult.Add(item);
- }
- }
- else if (item.children != null)
- {
- foreach (var child in item.children)
- SearchTestTree(child, search, searchResult);
- }
- }
}
}
diff --git a/UnityEditor.TestRunner/GUI/TestListTreeView/TestTreeViewItem.cs b/UnityEditor.TestRunner/GUI/TestListTreeView/TestTreeViewItem.cs
index 4226e31..8b4dc16 100644
--- a/UnityEditor.TestRunner/GUI/TestListTreeView/TestTreeViewItem.cs
+++ b/UnityEditor.TestRunner/GUI/TestListTreeView/TestTreeViewItem.cs
@@ -19,7 +19,14 @@ internal sealed class TestTreeViewItem : TreeViewItem
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;
@@ -54,6 +61,9 @@ public TestTreeViewItem(ITestAdaptor test, int depth, TreeViewItem parent)
icon = Icons.s_UnknownImg;
}
+ public int TotalChildrenCount { get; set; }
+ public int TotalSuccessChildrenCount { get; set; }
+
private static int GetId(ITestAdaptor test)
{
return test.UniqueName.GetHashCode();
diff --git a/UnityEditor.TestRunner/GUI/TestRunnerGUI.cs b/UnityEditor.TestRunner/GUI/TestRunnerGUI.cs
new file mode 100644
index 0000000..87df1c1
--- /dev/null
+++ b/UnityEditor.TestRunner/GUI/TestRunnerGUI.cs
@@ -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;
+ }
+ }
+ }
+}
diff --git a/UnityEditor.TestRunner/GUI/TestRunnerGUI.cs.meta b/UnityEditor.TestRunner/GUI/TestRunnerGUI.cs.meta
new file mode 100644
index 0000000..988a456
--- /dev/null
+++ b/UnityEditor.TestRunner/GUI/TestRunnerGUI.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 5a0dfda606a24736913c00edd76e55f6
+timeCreated: 1600071499
\ No newline at end of file
diff --git a/UnityEditor.TestRunner/GUI/TestRunnerResult.cs b/UnityEditor.TestRunner/GUI/TestRunnerResult.cs
index ddda847..fb2f4fa 100644
--- a/UnityEditor.TestRunner/GUI/TestRunnerResult.cs
+++ b/UnityEditor.TestRunner/GUI/TestRunnerResult.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using UnityEditor.TestTools.TestRunner.Api;
+using UnityEngine;
namespace UnityEditor.TestTools.TestRunner.GUI
{
@@ -78,6 +79,36 @@ internal TestRunnerResult(ITestResultAdaptor testResult) : this(testResult.Test)
}
}
+ public void CalculateParentResult(string parentId, IDictionary> 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;
+ UpdateParentResult(results);
+ }
+
+ private void UpdateParentResult(IDictionary> 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 {this});
+ }
+ }
+
public void Update(TestRunnerResult result)
{
if (ReferenceEquals(result, null))
@@ -141,6 +172,8 @@ public override string ToString()
public void Clear()
{
resultStatus = ResultStatus.NotRun;
+ stacktrace = string.Empty;
+ duration = 0.0f;
if (m_OnResultUpdate != null)
m_OnResultUpdate(this);
}
diff --git a/UnityEditor.TestRunner/GUI/TestRunnerUIFilter.cs b/UnityEditor.TestRunner/GUI/TestRunnerUIFilter.cs
index 2c86604..fbf0048 100644
--- a/UnityEditor.TestRunner/GUI/TestRunnerUIFilter.cs
+++ b/UnityEditor.TestRunner/GUI/TestRunnerUIFilter.cs
@@ -1,6 +1,8 @@
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
@@ -26,9 +28,9 @@ internal class TestRunnerUIFilter
public bool NotRunHidden;
[SerializeField]
- private string m_SearchString;
+ public string m_SearchString;
[SerializeField]
- private int selectedCategoryMask;
+ private string[] selectedCategories = new string[0];
public string[] availableCategories = new string[0];
@@ -38,6 +40,7 @@ internal class TestRunnerUIFilter
private GUIContent m_NotRunBtn;
public Action RebuildTestList;
+ public Action UpdateTestTreeRoots;
public Action SearchStringChanged;
public Action SearchStringCleared;
public bool IsFiltering
@@ -45,33 +48,24 @@ public bool IsFiltering
get
{
return !string.IsNullOrEmpty(m_SearchString) || PassedHidden || FailedHidden || NotRunHidden ||
- selectedCategoryMask != 0;
+ (selectedCategories != null && selectedCategories.Length > 0);
}
}
public string[] CategoryFilter
{
- get
- {
- var list = new List();
- for (int i = 0; i < availableCategories.Length; i++)
- {
- if ((selectedCategoryMask & (1 << i)) != 0)
- {
- list.Add(availableCategories[i]);
- }
- }
- return list.ToArray();
- }
+ get { return selectedCategories; }
}
- public void UpdateCounters(List resultList)
+ public void UpdateCounters(List resultList, Dictionary 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:
@@ -118,12 +112,7 @@ public void Draw()
if (availableCategories != null && availableCategories.Any())
{
- EditorGUI.BeginChangeCheck();
- selectedCategoryMask = EditorGUILayout.MaskField(selectedCategoryMask, availableCategories, EditorStyles.toolbarDropDown, GUILayout.MaxWidth(150));
- if (EditorGUI.EndChangeCheck() && RebuildTestList != null)
- {
- RebuildTestList();
- }
+ TestRunnerGUI.CategorySelectionDropDown(BuildCategorySelectionProvider());
}
else
{
@@ -150,6 +139,27 @@ public void Draw()
}
}
+ public void OnModeGUI()
+ {
+ EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
+ {
+ // TODO: Tabs for editmode, playmode and player
+ }
+ EditorGUILayout.EndHorizontal();
+ }
+
+ private ISelectionDropDownContentProvider BuildCategorySelectionProvider()
+ {
+ var itemProvider = new MultiValueContentProvider(availableCategories, selectedCategories,
+ categories =>
+ {
+ selectedCategories = categories;
+ UpdateTestTreeRoots();
+ });
+
+ return itemProvider;
+ }
+
private static int GetMaxWidth(int count)
{
if (count < 10)
@@ -162,7 +172,7 @@ public void Clear()
PassedHidden = false;
FailedHidden = false;
NotRunHidden = false;
- selectedCategoryMask = 0;
+ selectedCategories = new string[0];
m_SearchString = "";
if (SearchStringChanged != null)
{
diff --git a/UnityEditor.TestRunner/GUI/UITestRunnerFilter.cs b/UnityEditor.TestRunner/GUI/UITestRunnerFilter.cs
index befe609..ff6f695 100644
--- a/UnityEditor.TestRunner/GUI/UITestRunnerFilter.cs
+++ b/UnityEditor.TestRunner/GUI/UITestRunnerFilter.cs
@@ -101,7 +101,7 @@ private bool AreOptionalFiltersEmpty()
return true;
}
- private bool NameMatchesExactly(string name)
+ private bool NameMatchesExactly(string name, HashSet nameLookup)
{
if (AreOptionalFiltersEmpty())
return true;
@@ -109,38 +109,28 @@ private bool NameMatchesExactly(string name)
if (testNames == null || testNames.Length == 0)
return true;
- foreach (var exactName in testNames)
- {
- if (name == exactName)
- return true;
- }
-
- return false;
+ return nameLookup.Contains(name);
}
- private static void ClearAncestors(IEnumerable newResultList, string parentID)
+ private static void ClearAncestors(Dictionary newResultList, string parentID)
{
- if (string.IsNullOrEmpty(parentID))
- return;
- foreach (var result in newResultList)
+ while (!string.IsNullOrEmpty(parentID) && newResultList.TryGetValue(parentID, out var parent))
{
- if (result.Id == parentID)
- {
- result.Clear();
- ClearAncestors(newResultList, result.ParentId);
- break;
- }
+ parent.Clear();
+ parentID = parent.ParentId;
}
}
- public void ClearResults(List newResultList)
+ public void ClearResults(Dictionary newResultList)
{
- foreach (var result in newResultList)
+ var nameLookup = new HashSet(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))
+ NameMatchesExactly(result.FullName, nameLookup))
{
result.Clear();
ClearAncestors(newResultList, result.ParentId);
diff --git a/UnityEditor.TestRunner/GUI/UITestRunnerFilter.cs.meta b/UnityEditor.TestRunner/GUI/UITestRunnerFilter.cs.meta
index 7b46cdc..64ec040 100644
--- a/UnityEditor.TestRunner/GUI/UITestRunnerFilter.cs.meta
+++ b/UnityEditor.TestRunner/GUI/UITestRunnerFilter.cs.meta
@@ -1,3 +1,11 @@
-fileFormatVersion: 2
+fileFormatVersion: 2
guid: 8069e1fc631e461ababf11f19a9c0df3
-timeCreated: 1595586126
\ No newline at end of file
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/UnityEditor.TestRunner/GUI/Views/EditModeTestListGUI.cs b/UnityEditor.TestRunner/GUI/Views/EditModeTestListGUI.cs
deleted file mode 100644
index 2641074..0000000
--- a/UnityEditor.TestRunner/GUI/Views/EditModeTestListGUI.cs
+++ /dev/null
@@ -1,89 +0,0 @@
-using System;
-using System.Linq;
-using UnityEditor.TestTools.TestRunner.Api;
-using UnityEngine;
-using UnityEngine.TestTools;
-
-namespace UnityEditor.TestTools.TestRunner.GUI
-{
- [Serializable]
- internal class EditModeTestListGUI : TestListGUI
- {
- public override TestMode TestMode
- {
- get { return TestMode.EditMode; }
- }
-
- public override void RenderNoTestsInfo()
- {
- if (!TestListGUIHelper.SelectedFolderContainsTestAssembly())
- {
- var noTestText = "No tests to show";
-
- if (!PlayerSettings.playModeTestRunnerEnabled)
- {
- const string testsArePulledFromCustomAssemblies =
- "EditMode tests can be in Editor only Assemblies, either in the editor special folder or Editor only Assembly Definitions that references the \"nunit.framework.dll\" Assembly Reference or any of the Assembly Definition References \"UnityEngine.TestRunner\" or \"UnityEditor.TestRunner\"..";
- noTestText += Environment.NewLine + testsArePulledFromCustomAssemblies;
- }
-
- EditorGUILayout.HelpBox(noTestText, MessageType.Info);
- if (GUILayout.Button("Create EditMode Test Assembly Folder"))
- {
- TestListGUIHelper.AddFolderAndAsmDefForTesting(isEditorOnly: true);
- }
- }
-
- if (!TestListGUIHelper.CanAddEditModeTestScriptAndItWillCompile())
- {
- UnityEngine.GUI.enabled = false;
- EditorGUILayout.HelpBox("EditMode test scripts can only be created in editor test assemblies.", MessageType.Warning);
- }
- if (GUILayout.Button("Create Test Script in current folder"))
- {
- TestListGUIHelper.AddTest();
- }
- UnityEngine.GUI.enabled = true;
- }
-
- public override void PrintHeadPanel()
- {
- base.PrintHeadPanel();
- DrawFilters();
- }
-
- protected override void RunTests(params UITestRunnerFilter[] filters)
- {
- if (EditorUtility.scriptCompilationFailed)
- {
- Debug.LogError("Fix compilation issues before running tests");
- return;
- }
-
- foreach (var filter in filters)
- {
- filter.ClearResults(newResultList.OfType().ToList());
- }
-
- var testRunnerApi = ScriptableObject.CreateInstance();
- testRunnerApi.Execute(new ExecutionSettings
- {
- filters = filters.Select(filter => new Filter
- {
- assemblyNames = filter.assemblyNames,
- categoryNames = filter.categoryNames,
- groupNames = filter.groupNames,
- testMode = TestMode,
- testNames = filter.testNames
- }).ToArray()
- });
- }
-
- public override TestPlatform TestPlatform { get { return TestPlatform.EditMode; } }
-
- protected override bool IsBusy()
- {
- return TestRunnerApi.IsRunActive() || EditorApplication.isCompiling || EditorApplication.isPlaying;
- }
- }
-}
diff --git a/UnityEditor.TestRunner/GUI/Views/EditModeTestListGUI.cs.meta b/UnityEditor.TestRunner/GUI/Views/EditModeTestListGUI.cs.meta
deleted file mode 100644
index 93ff4f1..0000000
--- a/UnityEditor.TestRunner/GUI/Views/EditModeTestListGUI.cs.meta
+++ /dev/null
@@ -1,11 +0,0 @@
-fileFormatVersion: 2
-guid: 0336a32a79bfaed43a3fd2d88b91e974
-MonoImporter:
- externalObjects: {}
- serializedVersion: 2
- defaultReferences: []
- executionOrder: 0
- icon: {instanceID: 0}
- userData:
- assetBundleName:
- assetBundleVariant:
diff --git a/UnityEditor.TestRunner/GUI/Views/PlayModeTestListGUI.cs b/UnityEditor.TestRunner/GUI/Views/PlayModeTestListGUI.cs
deleted file mode 100644
index a620f67..0000000
--- a/UnityEditor.TestRunner/GUI/Views/PlayModeTestListGUI.cs
+++ /dev/null
@@ -1,238 +0,0 @@
-using System;
-using System.IO;
-using System.Linq;
-using UnityEditor.TestTools.TestRunner.Api;
-using UnityEngine;
-using UnityEngine.TestRunner.NUnitExtensions.Runner;
-using UnityEngine.TestTools;
-
-namespace UnityEditor.TestTools.TestRunner.GUI
-{
- [Serializable]
- internal class PlayModeTestListGUI : TestListGUI
- {
- private struct PlayerMenuItem
- {
- public GUIContent name;
- public bool filterSelectedTestsOnly;
- public bool buildOnly;
- }
-
- [SerializeField]
- private int m_SelectedOption;
-
- public override TestMode TestMode
- {
- get { return TestMode.PlayMode; }
- }
-
- 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 ExecuteAction(PlayerMenuItem item)
- {
- var runSettings = new PlayerLauncherTestRunSettings();
- runSettings.buildOnly = item.buildOnly;
- if (runSettings.buildOnly)
- {
- runSettings.buildOnlyLocationPath = PickBuildLocation();
- if (string.IsNullOrEmpty(runSettings.buildOnlyLocationPath))
- {
- Debug.LogWarning("Aborting, build selection was canceled.");
- return;
- }
- }
-
- if (item.filterSelectedTestsOnly)
- RunTestsInPlayer(runSettings, SelectedTestsFilter);
- else
- {
- var filter = new UITestRunnerFilter { categoryNames = m_TestRunnerUIFilter.CategoryFilter };
- RunTestsInPlayer(runSettings, filter);
- }
- }
-
- public override void PrintHeadPanel()
- {
- EditorGUILayout.BeginHorizontal(GUILayout.ExpandHeight(false));
- base.PrintHeadPanel();
-
- PlayerMenuItem[] menuItems;
-
- if (EditorUserBuildSettings.installInBuildFolder)
- {
- menuItems = new []
- {
- // Note: We select here buildOnly = false, so build location dialog won't show up
- // The player won't actually be ran when using together with EditorUserBuildSettings.installInBuildFolder
- new PlayerMenuItem()
- {
- name = new GUIContent("Install All Tests In Build Folder"), buildOnly = false, filterSelectedTestsOnly = false
- },
- new PlayerMenuItem()
- {
- name = new GUIContent("Install Selected Tests In Build Folder"), buildOnly = false, filterSelectedTestsOnly = true
- }
- };
- }
- else
- {
- menuItems = new []
- {
- new PlayerMenuItem()
- {
- name = new GUIContent("Run All Tests"), buildOnly = false, filterSelectedTestsOnly = false
- },
- new PlayerMenuItem()
- {
- name = new GUIContent("Run Selected Tests"), buildOnly = false, filterSelectedTestsOnly = true
- },
- new PlayerMenuItem()
- {
- name = new GUIContent($"{GetBuildText()} All Tests"), buildOnly = true, filterSelectedTestsOnly = false
- },
- new PlayerMenuItem()
- {
- name = new GUIContent($"{GetBuildText()} Selected Tests"), buildOnly = true, filterSelectedTestsOnly = true
- },
- };
- }
-
- m_SelectedOption = Math.Min(m_SelectedOption, menuItems.Length - 1);
- var selectedMenuItem = menuItems[m_SelectedOption];
- if (GUILayout.Button(
- new GUIContent($"{selectedMenuItem.name.text} ({EditorUserBuildSettings.activeBuildTarget})"),
- EditorStyles.toolbarButton))
- {
- ExecuteAction(selectedMenuItem);
- }
-
- 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) => m_SelectedOption = selected,
- menuItems);
- }
-
- EditorGUILayout.EndHorizontal();
- DrawFilters();
- EditorGUILayout.BeginHorizontal(GUILayout.ExpandHeight(false));
- EditorGUILayout.EndHorizontal();
- }
-
- public override void RenderNoTestsInfo()
- {
- if (!TestListGUIHelper.SelectedFolderContainsTestAssembly())
- {
- var noTestText = "No tests to show";
- if (!PlayerSettings.playModeTestRunnerEnabled)
- {
- const string testsArePulledFromCustomAssemblues = "Test Assemblies are defined by Assembly Definitions that references the \"nunit.framework.dll\" Assembly Reference or the Assembly Definition Reference \"UnityEngine.TestRunner\".";
- const string infoTextAboutTestsInAllAssemblies =
- "To have tests in all assemblies enable it in the Test Runner window context menu";
- noTestText += Environment.NewLine + testsArePulledFromCustomAssemblues + Environment.NewLine +
- infoTextAboutTestsInAllAssemblies;
- }
-
- EditorGUILayout.HelpBox(noTestText, MessageType.Info);
- if (GUILayout.Button("Create PlayMode Test Assembly Folder"))
- {
- TestListGUIHelper.AddFolderAndAsmDefForTesting();
- }
- }
-
- if (!TestListGUIHelper.CanAddPlayModeTestScriptAndItWillCompile())
- {
- UnityEngine.GUI.enabled = false;
- EditorGUILayout.HelpBox("PlayMode test scripts can only be created in non editor test assemblies.", MessageType.Warning);
- }
- if (GUILayout.Button("Create Test Script in current folder"))
- {
- TestListGUIHelper.AddTest();
- }
- UnityEngine.GUI.enabled = true;
- }
-
- protected override void RunTests(UITestRunnerFilter[] filters)
- {
- foreach (var filter in filters)
- {
- filter.ClearResults(newResultList.OfType().ToList());
- }
-
- var testRunnerApi = ScriptableObject.CreateInstance();
- testRunnerApi.Execute(new ExecutionSettings()
- {
- filters = filters.Select(filter => new Filter()
- {
- assemblyNames = filter.assemblyNames,
- categoryNames = filter.categoryNames,
- groupNames = filter.groupNames,
- testMode = TestMode,
- testNames = filter.testNames
- }).ToArray()
- });
- }
-
-
- protected void RunTestsInPlayer(PlayerLauncherTestRunSettings runSettings, params UITestRunnerFilter[] filters)
- {
- foreach (var filter in filters)
- {
- filter.ClearResults(newResultList.OfType().ToList());
- }
-
- var testRunnerApi = ScriptableObject.CreateInstance();
- testRunnerApi.Execute(new ExecutionSettings()
- {
- overloadTestRunSettings = runSettings,
- filters = filters.Select(filter => new Filter()
- {
- assemblyNames = filter.assemblyNames,
- categoryNames = filter.categoryNames,
- groupNames = filter.groupNames,
- testMode = TestMode,
- testNames = filter.testNames
- }).ToArray(),
- targetPlatform = EditorUserBuildSettings.activeBuildTarget
- });
- }
-
- public override TestPlatform TestPlatform { get { return TestPlatform.PlayMode; } }
-
- protected override bool IsBusy()
- {
- return TestRunnerApi.IsRunActive() || EditorApplication.isCompiling || EditorApplication.isPlaying;
- }
- }
-}
diff --git a/UnityEditor.TestRunner/GUI/Views/PlayModeTestListGUI.cs.meta b/UnityEditor.TestRunner/GUI/Views/PlayModeTestListGUI.cs.meta
deleted file mode 100644
index 191caab..0000000
--- a/UnityEditor.TestRunner/GUI/Views/PlayModeTestListGUI.cs.meta
+++ /dev/null
@@ -1,11 +0,0 @@
-fileFormatVersion: 2
-guid: c3efd39f2cfb43a4c830d4fd5689900f
-MonoImporter:
- externalObjects: {}
- serializedVersion: 2
- defaultReferences: []
- executionOrder: 0
- icon: {instanceID: 0}
- userData:
- assetBundleName:
- assetBundleVariant:
diff --git a/UnityEditor.TestRunner/GUI/Views/TestListGUIBase.cs b/UnityEditor.TestRunner/GUI/Views/TestListGUIBase.cs
index 18091df..e6c27da 100644
--- a/UnityEditor.TestRunner/GUI/Views/TestListGUIBase.cs
+++ b/UnityEditor.TestRunner/GUI/Views/TestListGUIBase.cs
@@ -1,15 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Text.RegularExpressions;
using UnityEditor.IMGUI.Controls;
using UnityEditor.TestTools.TestRunner.Api;
+using UnityEditor.TestTools.TestRunner.GUI.TestAssets;
using UnityEngine;
-using UnityEngine.TestTools;
namespace UnityEditor.TestTools.TestRunner.GUI
{
- internal abstract class TestListGUI
+ [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");
@@ -23,16 +23,28 @@ internal abstract class TestListGUI
private static readonly GUIContent s_SaveResults = EditorGUIUtility.TrTextContent("Export Results", "Save the latest test results to a file");
[SerializeField]
- protected TestRunnerWindow m_Window;
+ private TestRunnerWindow m_Window;
+ [NonSerialized]
+ private TestRunnerApi m_TestRunnerApi;
+ [NonSerialized]
+ internal TestMode m_TestMode;
+
+ [NonSerialized]
+ internal bool m_RunOnPlatform;
+
+
+ public Dictionary filteredTree { get; set; }
public List newResultList
{
get { return m_NewResultList; }
- set {
- m_ResultByKey = null;
+ set
+ {
m_NewResultList = value;
+ m_ResultByKey = null;
}
}
+
[SerializeField]
private List m_NewResultList = new List();
@@ -42,7 +54,19 @@ internal Dictionary ResultsByKey
get
{
if (m_ResultByKey == null)
- m_ResultByKey = newResultList.ToDictionary(k => k.uniqueId);
+ {
+ try
+ {
+ m_ResultByKey = newResultList.ToDictionary(k => k.uniqueId);
+ }
+ catch (Exception ex)
+ {
+ // Reset the results, so we do not lock the editor in giving errors on this on every frame.
+ newResultList = new List();
+ throw ex;
+ }
+ }
+
return m_ResultByKey;
}
}
@@ -59,11 +83,10 @@ internal Dictionary ResultsByKey
internal TestRunnerUIFilter m_TestRunnerUIFilter = new TestRunnerUIFilter();
private Vector2 m_TestInfoScroll, m_TestListScroll;
- private string m_PreviousProjectPath;
private List m_QueuedResults = new List();
private ITestResultAdaptor m_LatestTestResults;
- protected TestListGUI()
+ public TestListGUI()
{
MonoCecilHelper = new MonoCecilHelper();
AssetsDatabaseHelper = new AssetsDatabaseHelper();
@@ -71,82 +94,90 @@ protected TestListGUI()
GuiHelper = new GuiHelper(MonoCecilHelper, AssetsDatabaseHelper);
}
- protected IMonoCecilHelper MonoCecilHelper { get; private set; }
- protected IAssetsDatabaseHelper AssetsDatabaseHelper { get; private set; }
- protected IGuiHelper GuiHelper { get; private set; }
- protected UITestRunnerFilter[] SelectedTestsFilter => GetSelectedTestsAsFilter(m_TestListTree.GetSelection());
+ private IMonoCecilHelper MonoCecilHelper { get; set; }
+ private IAssetsDatabaseHelper AssetsDatabaseHelper { get; set; }
+ private IGuiHelper GuiHelper { get; set; }
- public abstract TestMode TestMode { get; }
-
- public virtual void PrintHeadPanel()
+ public void PrintHeadPanel()
{
- EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
- using (new EditorGUI.DisabledScope(IsBusy()))
+ if (m_RunOnPlatform)
{
- if (GUILayout.Button(s_GUIRunAllTests, EditorStyles.toolbarButton))
- {
- var filter = new UITestRunnerFilter {categoryNames = m_TestRunnerUIFilter.CategoryFilter};
- RunTests(filter);
- GUIUtility.ExitGUI();
- }
+ EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
+ GUILayout.Label("Running on " + EditorUserBuildSettings.activeBuildTarget);
+ EditorGUILayout.EndHorizontal();
}
- using (new EditorGUI.DisabledScope(m_TestListTree == null || !m_TestListTree.HasSelection() || IsBusy()))
+
+ using (new EditorGUI.DisabledScope(m_TestListTree == null || IsBusy()))
{
- if (GUILayout.Button(s_GUIRunSelectedTests, EditorStyles.toolbarButton))
- {
- RunTests(SelectedTestsFilter);
- GUIUtility.ExitGUI();
- }
+ m_TestRunnerUIFilter.OnModeGUI();
+ DrawFilters();
}
- using (new EditorGUI.DisabledScope(m_TestRunnerUIFilter.FailedCount == 0 || IsBusy()))
+ }
+
+ public void PrintBottomPanel()
+ {
+ using (new EditorGUI.DisabledScope(m_TestListTree == null || IsBusy()))
{
- if (GUILayout.Button(s_GUIRerunFailedTests, EditorStyles.toolbarButton))
+ EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
{
- var failedTestnames = new List();
- foreach (var result in newResultList)
+ using (new EditorGUI.DisabledScope(m_LatestTestResults == null))
{
- if (result.isSuite)
- continue;
- if (result.resultStatus == TestRunnerResult.ResultStatus.Failed ||
- result.resultStatus == TestRunnerResult.ResultStatus.Inconclusive)
- failedTestnames.Add(result.fullName);
+ 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();
+ }
}
- RunTests(new UITestRunnerFilter {testNames = failedTestnames.ToArray(), categoryNames = m_TestRunnerUIFilter.CategoryFilter});
- GUIUtility.ExitGUI();
- }
- }
- using (new EditorGUI.DisabledScope(IsBusy()))
- {
- if (GUILayout.Button(s_GUIClearResults, EditorStyles.toolbarButton))
- {
- foreach (var result in newResultList)
+
+ if (GUILayout.Button(s_GUIClearResults))
{
- result.Clear();
+ foreach (var result in newResultList)
+ {
+ result.Clear();
+ }
+
+ m_TestRunnerUIFilter.UpdateCounters(newResultList, filteredTree);
+ Reload();
+ GUIUtility.ExitGUI();
}
- m_TestRunnerUIFilter.UpdateCounters(newResultList);
- Reload();
- GUIUtility.ExitGUI();
- }
- }
- using (new EditorGUI.DisabledScope(m_LatestTestResults == null))
- {
- if (GUILayout.Button(s_SaveResults, EditorStyles.toolbarButton))
- {
- var filePath = EditorUtility.SaveFilePanel(s_SaveResults.text, "",
- $"TestResults_{DateTime.Now:yyyyMMdd_HHmmss}.xml", "xml");
- if (!string.IsNullOrEmpty(filePath))
+
+ GUILayout.FlexibleSpace();
+
+ using (new EditorGUI.DisabledScope(m_TestRunnerUIFilter.FailedCount == 0))
{
- TestRunnerApi.SaveResultToFile(m_LatestTestResults, filePath);
+ if (GUILayout.Button(s_GUIRerunFailedTests))
+ {
+ RunTests(RunFilterType.RunFailed);
+ GUIUtility.ExitGUI();
+ }
}
- 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();
+ }
}
+ EditorGUILayout.EndHorizontal();
}
- GUILayout.FlexibleSpace();
- EditorGUILayout.EndHorizontal();
}
- protected void DrawFilters()
+ private void DrawFilters()
{
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
m_TestRunnerUIFilter.Draw();
@@ -158,7 +189,7 @@ public bool HasTreeData()
return m_TestListTree != null;
}
- public virtual void RenderTestList()
+ public void RenderTestList()
{
if (m_TestListTree == null)
{
@@ -174,10 +205,14 @@ public virtual void RenderTestList()
{
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();
- m_TestListTree.ReloadData();
+ UpdateTestTree();
m_Window.Repaint();
}
}
@@ -191,12 +226,48 @@ public virtual void RenderTestList()
m_TestListTree.OnGUI(treeRect, treeViewKeyboardControlId);
}
+ m_TestRunnerUIFilter.UpdateCounters(newResultList, filteredTree);
EditorGUILayout.EndScrollView();
}
- public virtual void RenderNoTestsInfo()
+ private void RenderNoTestsInfo()
{
- EditorGUILayout.HelpBox("No tests to show", MessageType.Info);
+ 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(true);
+ }
+ }
+
+ 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()
@@ -234,6 +305,11 @@ public void Repaint()
}
public void Init(TestRunnerWindow window, ITestAdaptor rootTest)
+ {
+ Init(window, new[] {rootTest});
+ }
+
+ private void Init(TestRunnerWindow window, ITestAdaptor[] rootTests)
{
if (m_Window == null)
{
@@ -255,7 +331,7 @@ public void Init(TestRunnerWindow window, ITestAdaptor rootTest)
m_TestListTree.itemDoubleClickedCallback += TestDoubleClickCallback;
m_TestListTree.contextClickItemCallback += TestContextClickCallback;
- var testListTreeViewDataSource = new TestListTreeViewDataSource(m_TestListTree, this, rootTest);
+ var testListTreeViewDataSource = new TestListTreeViewDataSource(m_TestListTree, this, rootTests);
if (!newResultList.Any())
testListTreeViewDataSource.ExpandTreeOnCreation();
@@ -266,11 +342,10 @@ public void Init(TestRunnerWindow window, ITestAdaptor rootTest)
null);
}
- EditorApplication.update += RepaintIfProjectPathChanged;
-
- m_TestRunnerUIFilter.UpdateCounters(newResultList);
+ m_TestRunnerUIFilter.UpdateCounters(newResultList, filteredTree);
m_TestRunnerUIFilter.RebuildTestList = () => m_TestListTree.ReloadData();
- m_TestRunnerUIFilter.SearchStringChanged = s => m_TestListTree.searchString = s;
+ m_TestRunnerUIFilter.UpdateTestTreeRoots = UpdateTestTree;
+ m_TestRunnerUIFilter.SearchStringChanged = s => m_TestListTree.ReloadData();
m_TestRunnerUIFilter.SearchStringCleared = () => FrameSelection();
}
@@ -282,33 +357,55 @@ public void UpdateResult(TestRunnerResult result)
return;
}
- if (ResultsByKey.TryGetValue(result.uniqueId, out var testRunnerResult))
+
+ if (!ResultsByKey.TryGetValue(result.uniqueId, out var testRunnerResult))
{
- testRunnerResult.Update(result);
- Repaint();
- m_Window.Repaint();
+ // 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();
}
- public void UpdateTestTree(ITestAdaptor test)
+ private void UpdateTestTree()
+ {
+ var testList = this;
+ if (m_TestRunnerApi == null)
+ {
+ m_TestRunnerApi= ScriptableObject.CreateInstance();
+ }
+
+ 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(test);
+ (m_TestListTree.data as TestListTreeViewDataSource).UpdateRootTest(tests);
m_TestListTree.ReloadData();
Repaint();
m_Window.Repaint();
}
- public void RunFinished(ITestResultAdaptor results)
- {
- m_LatestTestResults = results;
- }
-
private void UpdateQueuedResults()
{
foreach (var testRunnerResult in m_QueuedResults)
@@ -320,7 +417,7 @@ private void UpdateQueuedResults()
}
m_QueuedResults.Clear();
TestSelectionCallback(m_TestListState.selectedIDs.ToArray());
- m_TestRunnerUIFilter.UpdateCounters(newResultList);
+ m_TestRunnerUIFilter.UpdateCounters(newResultList, filteredTree);
Repaint();
m_Window.Repaint();
}
@@ -346,27 +443,72 @@ internal void TestSelectionCallback(int[] selected)
}
}
- protected virtual void TestDoubleClickCallback(int id)
+ private void TestDoubleClickCallback(int id)
{
if (IsBusy())
return;
- RunTests(GetSelectedTestsAsFilter(new List { id }));
+ RunTests(RunFilterType.RunSpecific, id);
GUIUtility.ExitGUI();
}
- protected virtual void RunTests(params UITestRunnerFilter[] filters)
+ private void RunTests(RunFilterType runFilter, params int[] specificTests)
{
- throw new NotImplementedException();
+ 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().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 (m_TestRunnerApi == null)
+ {
+ m_TestRunnerApi= ScriptableObject.CreateInstance();
+ }
+
+ m_TestRunnerApi.Execute(executionSettings);
+
+ if (executionSettings.targetPlatform != null)
+ {
+ GUIUtility.ExitGUI();
+ }
}
- protected virtual void TestContextClickCallback(int id)
+ private void TestContextClickCallback(int id)
{
if (id == 0)
return;
var m = new GenericMenu();
- var testFilters = GetSelectedTestsAsFilter(m_TestListState.selectedIDs);
var multilineSelection = m_TestListState.selectedIDs.Count > 1;
if (!multilineSelection)
@@ -401,7 +543,7 @@ protected virtual void TestContextClickCallback(int id)
{
m.AddItem(multilineSelection ? s_GUIRunSelectedTests : s_GUIRun,
false,
- data => RunTests(testFilters),
+ data => RunTests(RunFilterType.RunSelected),
"");
}
else
@@ -410,89 +552,119 @@ protected virtual void TestContextClickCallback(int id)
m.ShowAsContext();
}
- private UITestRunnerFilter[] GetSelectedTestsAsFilter(IEnumerable selectedIDs)
+ private enum RunFilterType
{
- var namesToRun = new List();
- var assembliesForNamesToRun = new List();
- var exactNamesToRun = new List();
- var assembliesToRun = new List();
- foreach (var lineId in selectedIDs)
+ RunAll,
+ RunSelected,
+ RunFailed,
+ RunSpecific
+ }
+
+ private struct FilterConstructionStep
+ {
+ public int Id;
+ public TreeViewItem Item;
+ }
+
+ private UITestRunnerFilter[] ConstructFilter(RunFilterType runFilter, int[] specificTests = null)
+ {
+ if (runFilter == RunFilterType.RunAll && !m_TestRunnerUIFilter.IsFiltering)
{
- var line = m_TestListTree.FindItem(lineId);
- if (line is TestTreeViewItem)
+ // Shortcut for RunAll, which will not trigger any explicit tests
+ return new[] {new UITestRunnerFilter()};
+ }
+
+ var includedIds = GetIdsIncludedInRunFilter(runFilter, specificTests);
+ var visitedItems = new HashSet();
+ var openItems = new Stack();
+ var testsToRun = new List();
+
+ foreach (var id in includedIds)
+ {
+ if (visitedItems.Contains(id))
+ continue;
+ openItems.Push(new FilterConstructionStep
{
- var testLine = line as TestTreeViewItem;
- if (testLine.IsGroupNode && !testLine.FullName.Contains("+"))
+ Id = id,
+ Item = m_TestListTree.FindItem(id)
+ });
+ visitedItems.Add(id);
+ while (openItems.Count > 0)
+ {
+ var top = openItems.Pop();
+ var item = top.Item;
+ if (item.hasChildren)
{
- if (testLine.parent != null && testLine.parent.displayName == "Invisible Root Item")
- {
- //Root node selected. Use an empty TestRunnerFilter to run every test
- return new[] {new UITestRunnerFilter()};
- }
-
- if (testLine.FullName.EndsWith(".dll", StringComparison.OrdinalIgnoreCase))
- {
- assembliesToRun.Add(UITestRunnerFilter.AssemblyNameFromPath(testLine.FullName));
- }
- else
+ foreach (var child in item.children)
{
- namesToRun.Add($"^{Regex.Escape(testLine.FullName)}$");
- var assembly = UITestRunnerFilter.AssemblyNameFromPath(testLine.GetAssemblyName());
- if (!string.IsNullOrEmpty(assembly) && !assembliesForNamesToRun.Contains(assembly))
+ var childId = child.id;
+ if (!visitedItems.Contains(childId))
{
- assembliesForNamesToRun.Add(assembly);
+ var testItem = (TestTreeViewItem)child;
+ if (testItem.m_Test.RunState == RunState.Explicit)
+ {
+ // Do not add explicit tests if a ancestor is selected.
+ continue;
+ }
+
+ openItems.Push(new FilterConstructionStep
+ {
+ Id = childId,
+ Item = child
+ });
+ visitedItems.Add(childId);
}
}
}
else
{
- exactNamesToRun.Add(testLine.FullName);
+ var testItem = (TestTreeViewItem)item;
+ if (runFilter == RunFilterType.RunFailed)
+ {
+ ResultsByKey.TryGetValue(testItem.UniqueName, out var testRunnerResult);
+ if (testRunnerResult?.resultStatus == TestRunnerResult.ResultStatus.Failed)
+ {
+ continue;
+ }
+ }
+
+ testsToRun.Add(testItem.FullName);
}
}
}
- var filters = new List();
-
- if (assembliesToRun.Count > 0)
- {
- filters.Add(new UITestRunnerFilter
- {
- assemblyNames = assembliesToRun.ToArray()
- });
- }
-
- if (namesToRun.Count > 0)
+ if (testsToRun.Count == 0)
{
- filters.Add(new UITestRunnerFilter
- {
- groupNames = namesToRun.ToArray(),
- assemblyNames = assembliesForNamesToRun.ToArray()
- });
+ return null;
}
- if (exactNamesToRun.Count > 0)
+ return new[]
{
- filters.Add(new UITestRunnerFilter
+ new UITestRunnerFilter
{
- testNames = exactNamesToRun.ToArray()
- });
- }
+ testNames = testsToRun.ToArray(),
+ categoryNames = m_TestRunnerUIFilter.CategoryFilter
+ }
+ };
+ }
- if (filters.Count == 0)
+ private IEnumerable GetIdsIncludedInRunFilter(RunFilterType runFilter, int[] specificTests)
+ {
+ switch (runFilter)
{
- filters.Add(new UITestRunnerFilter());
- }
+ case RunFilterType.RunSelected:
+ 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.");
+ }
- var categories = m_TestRunnerUIFilter.CategoryFilter.ToArray();
- if (categories.Length > 0)
- {
- foreach (var filter in filters)
- {
- filter.categoryNames = categories;
- }
+ return specificTests;
+ default:
+ return m_TestListTree.GetRowIDs();
}
-
- return filters.ToArray();
}
private TestTreeViewItem GetSelectedTest()
@@ -517,29 +689,40 @@ private void FrameSelection()
}
}
- public abstract TestPlatform TestPlatform { get; }
-
public void RebuildUIFilter()
{
- m_TestRunnerUIFilter.UpdateCounters(newResultList);
+ m_TestRunnerUIFilter.UpdateCounters(newResultList, filteredTree);
if (m_TestRunnerUIFilter.IsFiltering)
{
m_TestListTree.ReloadData();
}
}
- public void RepaintIfProjectPathChanged()
+ private static bool IsBusy()
{
- var path = TestListGUIHelper.GetActiveFolderPath();
- if (path != m_PreviousProjectPath)
- {
- m_PreviousProjectPath = path;
- TestRunnerWindow.s_Instance.Repaint();
- }
+ return TestRunnerApi.IsRunActive() || EditorApplication.isCompiling || EditorApplication.isPlaying;
+ }
- EditorApplication.update -= RepaintIfProjectPathChanged;
+ 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
}
- protected abstract bool IsBusy();
+ private static ExecutionSettings CreateExecutionSettings(BuildTarget? buildTarget, params Filter[] filters)
+ {
+ return new ExecutionSettings(filters)
+ {
+ targetPlatform = buildTarget,
+ };
+ }
}
}
diff --git a/UnityEditor.TestRunner/TestRunner/Callbacks/WindowResultUpdater.cs b/UnityEditor.TestRunner/TestRunner/Callbacks/WindowResultUpdater.cs
index 0463ded..2ecb913 100644
--- a/UnityEditor.TestRunner/TestRunner/Callbacks/WindowResultUpdater.cs
+++ b/UnityEditor.TestRunner/TestRunner/Callbacks/WindowResultUpdater.cs
@@ -4,7 +4,7 @@
namespace UnityEditor.TestTools.TestRunner.GUI
{
- internal class WindowResultUpdater : ICallbacks, ITestTreeRebuildCallbacks
+ internal class WindowResultUpdater : ICallbacks
{
public WindowResultUpdater()
{
@@ -46,15 +46,5 @@ public void TestFinished(ITestResultAdaptor test)
TestRunnerWindow.s_Instance.m_SelectedTestTypes.UpdateResult(result);
}
-
- public void TestTreeRebuild(ITestAdaptor test)
- {
- if (TestRunnerWindow.s_Instance == null)
- {
- return;
- }
-
- TestRunnerWindow.s_Instance.m_SelectedTestTypes.UpdateTestTree(test);
- }
}
}
diff --git a/UnityEditor.TestRunner/TestRunnerWindow.cs b/UnityEditor.TestRunner/TestRunnerWindow.cs
index 0124215..f2491a2 100644
--- a/UnityEditor.TestRunner/TestRunnerWindow.cs
+++ b/UnityEditor.TestRunner/TestRunnerWindow.cs
@@ -1,6 +1,7 @@
using System;
using System.Linq;
using UnityEditor.Callbacks;
+using UnityEditor.Scripting.ScriptCompilation;
using UnityEditor.TestTools.TestRunner.Api;
using UnityEditor.TestTools.TestRunner.GUI;
using UnityEngine;
@@ -40,7 +41,7 @@ static Styles()
private bool m_IsBuilding;
[NonSerialized]
private bool m_Enabled;
- internal TestFilterSettings filterSettings;
+ //internal TestFilterSettings filterSettings;
[SerializeField]
private SplitterState m_Spl = new SplitterState(new float[] { 75, 25 }, new[] { 32, 32 }, null);
@@ -49,17 +50,15 @@ static Styles()
private enum TestRunnerMenuLabels
{
- PlayMode = 0,
- EditMode = 1
+ EditMode = 0,
+ PlayMode,
+ Player
}
[SerializeField]
- private int m_TestTypeToolbarIndex = (int)TestRunnerMenuLabels.EditMode;
- [SerializeField]
- private PlayModeTestListGUI m_PlayModeTestListGUI;
- [SerializeField]
- private EditModeTestListGUI m_EditModeTestListGUI;
-
+ private TestRunnerMenuLabels m_TestTypeToolbarIndex = TestRunnerMenuLabels.EditMode;
internal TestListGUI m_SelectedTestTypes;
+ [SerializeField]
+ private TestListGUI[] m_TestListGUIs;
private ITestRunnerApi m_testRunnerApi;
@@ -100,11 +99,15 @@ private static void CompilationCallback()
private static void OnPlayModeStateChanged(PlayModeStateChange state)
{
- if (s_Instance && state == PlayModeStateChange.EnteredEditMode && s_Instance.m_SelectedTestTypes.HasTreeData())
+ if (s_Instance && state == PlayModeStateChange.EnteredEditMode)
{
- //repaint message details after exit playmode
- s_Instance.m_SelectedTestTypes.TestSelectionCallback(s_Instance.m_SelectedTestTypes.m_TestListState.selectedIDs.ToArray());
- s_Instance.Repaint();
+ var testListGUI = s_Instance.m_SelectedTestTypes;
+ if (testListGUI.HasTreeData())
+ {
+ //repaint message details after exit playmode
+ testListGUI.TestSelectionCallback(testListGUI.m_TestListState.selectedIDs.ToArray());
+ s_Instance.Repaint();
+ }
}
}
@@ -126,7 +129,6 @@ private void OnEnable()
private void Enable()
{
m_Settings = new TestRunnerWindowSettings("UnityEditor.PlaymodeTestsRunnerWindow");
- filterSettings = new TestFilterSettings("UnityTest.IntegrationTestsRunnerWindow");
if (m_SelectedTestTypes == null)
{
@@ -138,37 +140,46 @@ private void Enable()
m_Enabled = true;
}
- private void SelectTestListGUI(int testTypeToolbarIndex)
+ private void SelectTestListGUI(TestRunnerMenuLabels testTypeToolbarIndex)
{
- if (testTypeToolbarIndex == (int)TestRunnerMenuLabels.PlayMode)
- {
- if (m_PlayModeTestListGUI == null)
- {
- m_PlayModeTestListGUI = new PlayModeTestListGUI();
- }
- m_SelectedTestTypes = m_PlayModeTestListGUI;
- }
- else if (testTypeToolbarIndex == (int)TestRunnerMenuLabels.EditMode)
+ if (m_TestListGUIs == null)
{
- if (m_EditModeTestListGUI == null)
+ m_TestListGUIs = new TestListGUI[]
{
- m_EditModeTestListGUI = new EditModeTestListGUI();
- }
- m_SelectedTestTypes = m_EditModeTestListGUI;
+ new TestListGUI()
+ {
+ m_TestMode = TestMode.EditMode,
+ },
+ new TestListGUI()
+ {
+ m_TestMode = TestMode.PlayMode,
+ },
+ new TestListGUI()
+ {
+ m_TestMode = TestMode.PlayMode,
+ m_RunOnPlatform = true
+ }
+ };
}
+
+ m_TestListGUIs[0].m_TestMode = TestMode.EditMode;
+ m_TestListGUIs[0].m_RunOnPlatform = false;
+ m_TestListGUIs[1].m_TestMode = TestMode.PlayMode;
+ m_TestListGUIs[1].m_RunOnPlatform = false;
+ m_TestListGUIs[2].m_TestMode = TestMode.PlayMode;
+ m_TestListGUIs[2].m_RunOnPlatform = true;
+
+ m_SelectedTestTypes = m_TestListGUIs[(int)testTypeToolbarIndex];
}
private void StartRetrieveTestList()
{
- if (!m_SelectedTestTypes.HasTreeData())
+ var listToInit = m_SelectedTestTypes;
+ m_testRunnerApi.RetrieveTestList(listToInit.m_TestMode, rootTest =>
{
- var listToInit = m_SelectedTestTypes;
- m_testRunnerApi.RetrieveTestList(m_SelectedTestTypes.TestMode, rootTest =>
- {
- listToInit.Init(this, rootTest);
- listToInit.Reload();
- });
- }
+ listToInit.Init(this, rootTest);
+ listToInit.Reload();
+ });
}
internal void OnGUI()
@@ -191,7 +202,7 @@ internal void OnGUI()
EditorGUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
var selectedIndex = m_TestTypeToolbarIndex;
- m_TestTypeToolbarIndex = GUILayout.Toolbar(m_TestTypeToolbarIndex, Enum.GetNames(typeof(TestRunnerMenuLabels)), "LargeButton", UnityEngine.GUI.ToolbarButtonSize.FitToContents);
+ m_TestTypeToolbarIndex = (TestRunnerMenuLabels)GUILayout.Toolbar((int)m_TestTypeToolbarIndex, Enum.GetNames(typeof(TestRunnerMenuLabels)), "LargeButton", UnityEngine.GUI.ToolbarButtonSize.FitToContents);
GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal();
@@ -225,6 +236,13 @@ internal void OnGUI()
SplitterGUILayout.EndVerticalSplit();
else
SplitterGUILayout.EndHorizontalSplit();
+
+ EditorGUILayout.BeginVertical();
+ using (new EditorGUI.DisabledScope(EditorApplication.isPlayingOrWillChangePlaymode))
+ {
+ m_SelectedTestTypes.PrintBottomPanel();
+ }
+ EditorGUILayout.EndVertical();
}
///
diff --git a/UnityEngine.TestRunner/NUnitExtensions/Runner/UnityLogCheckDelegatingCommand.cs b/UnityEngine.TestRunner/NUnitExtensions/Runner/UnityLogCheckDelegatingCommand.cs
index fb2fb38..1e6c86b 100644
--- a/UnityEngine.TestRunner/NUnitExtensions/Runner/UnityLogCheckDelegatingCommand.cs
+++ b/UnityEngine.TestRunner/NUnitExtensions/Runner/UnityLogCheckDelegatingCommand.cs
@@ -44,12 +44,16 @@ public IEnumerable ExecuteEnumerable(ITestExecutionContext context)
() => executeEnumerable = enumerableTestMethodCommand.ExecuteEnumerable(context)))
yield break;
+ var innerCommandIsTask = enumerableTestMethodCommand is TaskTestMethodCommand;
foreach (var step in executeEnumerable)
{
// do not check expected logs here - we want to permit expecting and receiving messages to run
- // across frames. (but we do always want to catch a fail immediately.)
- if (!CheckFailingLogs(logScope, context.CurrentResult))
+ // across frames. This means that we break on failing logs and fail on next frame.
+ // An exception is async (Task), in which case we first fail after the task has run, as we cannot cancel the task.
+ if (!innerCommandIsTask && !CheckFailingLogs(logScope, context.CurrentResult))
+ {
yield break;
+ }
yield return step;
}
diff --git a/UnityEngine.TestRunner/NUnitExtensions/TestExtensions.cs b/UnityEngine.TestRunner/NUnitExtensions/TestExtensions.cs
index 642428c..e4e51fe 100644
--- a/UnityEngine.TestRunner/NUnitExtensions/TestExtensions.cs
+++ b/UnityEngine.TestRunner/NUnitExtensions/TestExtensions.cs
@@ -10,32 +10,28 @@ namespace UnityEngine.TestRunner.NUnitExtensions
{
internal static class TestExtensions
{
- private static IEnumerable GetCategoriesInTestAndAncestors(ITest test)
+ private static List ExtractFixtureCategories(ITest test)
{
- var categories = test.Properties[PropertyNames.Category].Cast().ToList();
+ var fixtureCategories = test.Properties[PropertyNames.Category].Cast().ToList();
if (test.Parent != null)
{
- categories.AddRange(GetCategoriesInTestAndAncestors(test.Parent));
+ fixtureCategories.AddRange(ExtractFixtureCategories(test.Parent));
}
- return categories;
- }
-
- public static bool HasCategory(this ITest test, string[] categoryFilter)
- {
- var categories = test.GetAllCategoriesFromTest().Distinct();
- return categoryFilter.Any(c => categories.Any(r => r == c));
+ return fixtureCategories;
}
public static List GetAllCategoriesFromTest(this ITest test)
{
- var categories = GetCategoriesInTestAndAncestors(test).ToList();
+ // Only mark tests as Uncategorized if the test fixture doesn't have a category,
+ // otherwise the test inherits the Fixture category.
+ // Recursively try checking until Parent is null - cause category can be set on higher level.
+ var categories = ExtractFixtureCategories(test);
if (categories.Count == 0 && test is TestMethod)
{
- // only mark tests as Uncategorized if the test fixture doesn't have a category,
- // otherwise the test inherits the Fixture category
categories.Add(CategoryFilterExtended.k_DefaultCategory);
}
+
return categories;
}
diff --git a/package.json b/package.json
index 4ae4094..3d45855 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "com.unity.test-framework",
"displayName": "Test Framework",
- "version": "1.4.0",
+ "version": "1.4.1",
"unity": "2019.4",
"unityRelease": "0a10",
"description": "Test framework for running Edit mode and Play mode tests in Unity.",
@@ -13,7 +13,7 @@
"repository": {
"url": "https://github.com/Unity-Technologies/com.unity.test-framework.git",
"type": "git",
- "revision": "69ea42f7de7b719febb362c0f8df442c9e03e8c7"
+ "revision": "147960f420428a4fc24c5f359e5f198a03567e36"
},
"dependencies": {
"com.unity.ext.nunit": "2.0.3",
@@ -221,13 +221,13 @@
}
],
"relatedPackages": {
- "com.unity.test-framework.tests": "1.4.0"
+ "com.unity.test-framework.tests": "1.4.1"
},
"_upm": {
- "changelog": "- Added api for saving test results to a file.\n- Added a button for saving the test results of the latest run.\n- Applied filtering to the ITestAdaptor argument of `ICallbacks.RunStarted` so that it corresponds to the actual test tree being run.\n- When running in player:\n - the save scene dialog is shown when there are changes in the scene.\n - the scene setup is restored after a completed run.\n- When running in playmode:\n - the scene setup is restored after a completed run.\n- Added overloads of LogAssert.Expect which allow users to expect a log message without specifying a severity, which will match logged messages of any severity.\n- Added new `[ParametrizedIgnore]` attribute which allows ignoring tests based on arguments which were passed to the test method.\n- Added a new PreservedValuesAttribute to allow for using the nunit ValuesAttribute at players with a high stripping level (case DSTR-33).\n- Added api for canceling test runs."
+ "changelog": "- Multiple improvements to the UI, including better dropdowns, filtering, and a new test list view for Player.\n- Fixed uncategorized UI tests filtering for parameterized tests (DSTR-219).\n- In async tests, any failing logs will now first be evaluated after the async method has completed. (DSTR-839)"
},
"upmCi": {
- "footprint": "16dd7a2132eb67a0bf3a8e60756d1770d9d2cc4d"
+ "footprint": "0a4ea4de2ebe5e23c9bd5d30cabc6f5cc66a2423"
},
"documentationUrl": "https://docs.unity3d.com/Packages/com.unity.test-framework@1.4/manual/index.html"
}