Skip to content

Commit

Permalink
Merge pull request #132 from nowsprinting/chore/log
Browse files Browse the repository at this point in the history
Optimize logging and add verbose option
  • Loading branch information
nowsprinting authored May 12, 2024
2 parents 5e39a50 + 987af98 commit bc42dc7
Show file tree
Hide file tree
Showing 19 changed files with 538 additions and 152 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ Configurations in `MonkeyConfig`:
- **SecondsToErrorForNoInteractiveComponent**: Seconds to determine that an error has occurred when an object that can be interacted with does not exist
- **Random**: Random generator
- **Logger**: Logger
- **Verbose**: Output verbose log if true
- **Gizmos**: Show Gizmos on `GameView` during running monkey test if true
- **Screenshots**: Take screenshots during running the monkey test if set a `ScreenshotOptions` instance.
- **Directory**: Directory to save screenshots. If omitted, the directory specified by command line argument "-testHelperScreenshotDirectory" is used. If the command line argument is also omitted, `Application.persistentDataPath` + "/TestHelper/Screenshots/" is used.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public static bool IsInteractable(Component component)
{
// UI element
var selectable = component as Selectable;
if (selectable != null)
if (selectable)
{
return selectable.interactable;
}
Expand Down
43 changes: 40 additions & 3 deletions Runtime/DefaultStrategies/DefaultReachableStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@

using System;
using System.Collections.Generic;
using System.Text;
using TestHelper.Monkey.ScreenPointStrategies;
using UnityEngine;
using UnityEngine.EventSystems;
using Object = UnityEngine.Object;

namespace TestHelper.Monkey.DefaultStrategies
{
Expand All @@ -23,10 +25,12 @@ public static class DefaultReachableStrategy
/// <param name="gameObject"></param>
/// <param name="eventData">Specify if avoid GC memory allocation</param>
/// <param name="results">Specify if avoid GC memory allocation</param>
/// <param name="verboseLogger">Output verbose log if need</param>
/// <returns>True if this GameObject is reachable from user</returns>
public static bool IsReachable(GameObject gameObject,
PointerEventData eventData = null,
List<RaycastResult> results = null)
List<RaycastResult> results = null,
ILogger verboseLogger = null)
{
eventData = eventData ?? new PointerEventData(EventSystem.current);
eventData.position = GetScreenPoint.Invoke(gameObject);
Expand All @@ -35,12 +39,38 @@ public static bool IsReachable(GameObject gameObject,
results.Clear();

EventSystem.current.RaycastAll(eventData, results);
return results.Count > 0 && IsSameOrChildObject(gameObject, results[0].gameObject.transform);
if (results.Count == 0)
{
if (verboseLogger != null)
{
var message = new StringBuilder(CreateMessage(gameObject, eventData.position));
message.Append(" Raycast is not hit.");
verboseLogger.Log(message.ToString());
}

return false;
}

var isSameOrChildObject = IsSameOrChildObject(gameObject, results[0].gameObject.transform);
if (!isSameOrChildObject && verboseLogger != null)
{
var message = new StringBuilder(CreateMessage(gameObject, eventData.position));
message.Append(" Raycast hit other objects: ");
foreach (var result in results)
{
message.Append(result.gameObject.name);
message.Append(", ");
}

verboseLogger.Log(message.ToString(0, message.Length - 2));
}

return isSameOrChildObject;
}

private static bool IsSameOrChildObject(GameObject target, Transform hitObjectTransform)
{
while (hitObjectTransform != null)
while (hitObjectTransform)
{
if (hitObjectTransform == target.transform)
{
Expand All @@ -52,5 +82,12 @@ private static bool IsSameOrChildObject(GameObject target, Transform hitObjectTr

return false;
}

private static string CreateMessage(Object gameObject, Vector2 position)
{
var x = (int)position.x;
var y = (int)position.y;
return $"Not reachable to {gameObject.name}({gameObject.GetInstanceID()}), position=({x},{y}).";
}
}
}
5 changes: 3 additions & 2 deletions Runtime/Extensions/GameObjectExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,9 @@ public static Camera GetAssociatedCamera(this GameObject gameObject)
/// <param name="pointerEventData">Specify if avoid GC memory allocation</param>
/// <param name="raycastResults">Specify if avoid GC memory allocation</param>
/// <returns>true: this object can control by user</returns>
[Obsolete("Use DefaultReachableStrategy instead.")]
public static bool IsReachable(this GameObject gameObject,
Func<GameObject, PointerEventData, List<RaycastResult>, bool> isReachable = null,
Func<GameObject, PointerEventData, List<RaycastResult>, ILogger, bool> isReachable = null,
PointerEventData pointerEventData = null,
List<RaycastResult> raycastResults = null)
{
Expand All @@ -80,7 +81,7 @@ public static bool IsReachable(this GameObject gameObject,
raycastResults = raycastResults ?? new List<RaycastResult>();

raycastResults.Clear();
return isReachable.Invoke(gameObject, pointerEventData, raycastResults);
return isReachable.Invoke(gameObject, pointerEventData, raycastResults, null);
}

/// <summary>
Expand Down
6 changes: 3 additions & 3 deletions Runtime/GameObjectFinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ namespace TestHelper.Monkey
public class GameObjectFinder
{
private readonly double _timeoutSeconds;
private readonly Func<GameObject, PointerEventData, List<RaycastResult>, bool> _isReachable;
private readonly Func<GameObject, PointerEventData, List<RaycastResult>, ILogger, bool> _isReachable;
private readonly Func<Component, bool> _isComponentInteractable;
private readonly PointerEventData _eventData = new PointerEventData(EventSystem.current);
private readonly List<RaycastResult> _results = new List<RaycastResult>();
Expand All @@ -36,7 +36,7 @@ public class GameObjectFinder
/// <param name="isComponentInteractable">The function returns the <c>Component</c> is interactable or not.
/// Default is <c>DefaultComponentInteractableStrategy.IsInteractable</c>.</param>
public GameObjectFinder(double timeoutSeconds = 1.0d,
Func<GameObject, PointerEventData, List<RaycastResult>, bool> isReachable = null,
Func<GameObject, PointerEventData, List<RaycastResult>, ILogger, bool> isReachable = null,
Func<Component, bool> isComponentInteractable = null)
{
Assert.IsTrue(timeoutSeconds > MinTimeoutSeconds,
Expand Down Expand Up @@ -71,7 +71,7 @@ private enum Reason
return (null, Reason.NotMatchPath);
}

if (reachable && !_isReachable.Invoke(foundObject, _eventData, _results))
if (reachable && !_isReachable.Invoke(foundObject, _eventData, _results, null))
{
return (null, Reason.NotReachable);
}
Expand Down
2 changes: 1 addition & 1 deletion Runtime/Hints/InteractiveComponentHint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ private void Refresh()
var interactiveComponentCollector = new InteractiveComponentCollector();
foreach (var component in interactiveComponentCollector.FindInteractableComponents())
{
var dst = component.gameObject.IsReachable(isReachable: DefaultReachableStrategy.IsReachable)
var dst = DefaultReachableStrategy.IsReachable(component.gameObject)
? _tmpReallyInteractives
: _tmpNotReallyInteractives;

Expand Down
10 changes: 5 additions & 5 deletions Runtime/InteractiveComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,13 @@ public class InteractiveComponent
[SuppressMessage("ReSharper", "InconsistentNaming")]
public GameObject gameObject => component.gameObject;

private readonly Func<GameObject, PointerEventData, List<RaycastResult>, bool> _isReachable;
private readonly Func<GameObject, PointerEventData, List<RaycastResult>, ILogger, bool> _isReachable;
private readonly IEnumerable<IOperator> _operators;
private readonly PointerEventData _eventData = new PointerEventData(EventSystem.current);
private readonly List<RaycastResult> _results = new List<RaycastResult>();

private InteractiveComponent(MonoBehaviour component,
Func<GameObject, PointerEventData, List<RaycastResult>, bool> isReachable = null,
Func<GameObject, PointerEventData, List<RaycastResult>, ILogger, bool> isReachable = null,
IEnumerable<IOperator> operators = null)
{
this.component = component;
Expand All @@ -64,7 +64,7 @@ private InteractiveComponent(MonoBehaviour component,
/// <param name="operators">All available operators in autopilot/tests. Usually defined in <c>MonkeyConfig</c></param>
/// <returns>Returns new InteractableComponent instance from MonoBehaviour. If MonoBehaviour is not interactable so, return null.</returns>
public static InteractiveComponent CreateInteractableComponent(MonoBehaviour component,
Func<GameObject, PointerEventData, List<RaycastResult>, bool> isReachable = null,
Func<GameObject, PointerEventData, List<RaycastResult>, ILogger, bool> isReachable = null,
Func<Component, bool> isComponentInteractable = null,
IEnumerable<IOperator> operators = null)
{
Expand All @@ -90,7 +90,7 @@ public static InteractiveComponent CreateInteractableComponent(MonoBehaviour com
/// <returns>Returns new InteractableComponent instance from GameObject. If GameObject is not interactable so, return null.</returns>
[Obsolete("Obsolete due to non-deterministic behavior when GameObject has multiple interactable components.")]
public static InteractiveComponent CreateInteractableComponent(GameObject gameObject,
Func<GameObject, PointerEventData, List<RaycastResult>, bool> isReachable = null,
Func<GameObject, PointerEventData, List<RaycastResult>, ILogger, bool> isReachable = null,
Func<Component, bool> isComponentInteractable = null,
IEnumerable<IOperator> operators = null)
{
Expand Down Expand Up @@ -128,7 +128,7 @@ public bool IsReallyInteractiveFromUser(Func<GameObject, Vector2> screenPointStr
[Obsolete("Use GameObjectExtensions.IsReachable() instead")]
public bool IsReachable()
{
return gameObject.IsReachable(_isReachable, _eventData, _results);
return _isReachable(gameObject, _eventData, _results, null);
}

/// <summary>
Expand Down
6 changes: 3 additions & 3 deletions Runtime/InteractiveComponentCollector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ namespace TestHelper.Monkey
// TODO: Rename to InteractableComponentsFinder
public class InteractiveComponentCollector
{
private readonly Func<GameObject, PointerEventData, List<RaycastResult>, bool> _isReachable;
private readonly Func<GameObject, PointerEventData, List<RaycastResult>, ILogger, bool> _isReachable;
private readonly Func<Component, bool> _isInteractable;
private readonly IEnumerable<IOperator> _operators;
private readonly PointerEventData _eventData = new PointerEventData(EventSystem.current);
Expand All @@ -34,7 +34,7 @@ public class InteractiveComponentCollector
/// Default is <c>DefaultComponentInteractableStrategy.IsInteractable</c>.</param>
/// <param name="operators">All available operators in autopilot/tests. Usually defined in <c>MonkeyConfig</c></param>
public InteractiveComponentCollector(
Func<GameObject, PointerEventData, List<RaycastResult>, bool> isReachable = null,
Func<GameObject, PointerEventData, List<RaycastResult>, ILogger, bool> isReachable = null,
Func<Component, bool> isInteractable = null,
IEnumerable<IOperator> operators = null)
{
Expand Down Expand Up @@ -87,7 +87,7 @@ public IEnumerable<Component> FindReachableInteractableComponents()
{
foreach (var interactableComponent in FindInteractableComponents())
{
if (_isReachable.Invoke(interactableComponent.gameObject, _eventData, _results))
if (_isReachable.Invoke(interactableComponent.gameObject, _eventData, _results, null))
{
yield return interactableComponent;
}
Expand Down
Loading

0 comments on commit bc42dc7

Please sign in to comment.