Skip to content

Commit

Permalink
GD-129-4: Add c# scene runner (#223)
Browse files Browse the repository at this point in the history
* GD-129-4: Add scene runner
  • Loading branch information
MikeSchulze authored Mar 14, 2022
1 parent fa55502 commit 560fe0a
Show file tree
Hide file tree
Showing 11 changed files with 712 additions and 20 deletions.
27 changes: 26 additions & 1 deletion addons/gdUnit3/src/Assertions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace GdUnit3
{
Expand Down Expand Up @@ -62,9 +63,33 @@ public sealed class Assertions
/// An Assertion to verify for expecting exceptions
/// </summary>
/// <param name="supplier">A function callback where throw possible exceptions</param>
/// <returns></returns>
/// <returns>IExceptionAssert</returns>
public static IExceptionAssert AssertThrown<T>(Func<T> supplier) => new ExceptionAssert<T>(supplier);

/// <summary>
/// An Assertion to verify for expecting exceptions when performing a task.
/// <example>
/// <code>
/// await AssertThrown(task.WithTimeout(500))
/// .ContinueWith(result => result.Result.HasMessage("timed out after 500ms."));
/// </code>
/// </example>
/// </summary>
/// <param name="task">A task where throw possible exceptions</param>
/// <returns>a task of <c>IExceptionAssert</c> to await</returns>
public async static Task<IExceptionAssert> AssertThrown<T>(Task<T> task)
{
try
{
await task;
return default;
}
catch (Exception e)
{
return new ExceptionAssert<T>(e);
}
}

/// ----------- Helpers -------------------------------------------------------------------------------------------------------

///<summary>
Expand Down
199 changes: 199 additions & 0 deletions addons/gdUnit3/src/SceneRunner.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
using System;
using System.Threading.Tasks;

namespace GdUnit3
{
using Godot;

/// <summary>
/// Scene runner to test interactions like keybord/mouse inputs on a Godot scene.
/// </summary>
public interface SceneRunner : IDisposable
{

/// <summary>
/// Loads a scene into the SceneRunner to be simmulated.
/// </summary>
/// <param name="resourcePath">The path to the scene resource.</param>
/// <param name="verbose">Prints detailt infos on scene simmulation.</param>
/// <returns></returns>
public static SceneRunner Load(string resourcePath, bool verbose = false) => new Core.SceneRunner(resourcePath, verbose);

/// <summary>
/// Sets the actual mouse position relative to the viewport.
/// </summary>
/// <param name="position">The position in x/y coordinates</param>
/// <returns></returns>
SceneRunner SetMousePos(Vector2 position);

/// <summary>
/// Simulates that a key has been pressed.
/// </summary>
/// <param name="keyCode">the key code e.g. 'KeyList.Enter'</param>
/// <param name="shift">false by default set to true if simmulate shift is press</param>
/// <param name="control">false by default set to true if simmulate control is press</param>
/// <returns>SceneRunner</returns>
SceneRunner SimulateKeyPressed(KeyList keyCode, bool shift = false, bool control = false);

/// <summary>
/// Simulates that a key is pressed.
/// </summary>
/// <param name="keyCode">the key code e.g. 'KeyList.Enter'</param>
/// <param name="shift">false by default set to true if simmulate shift is press</param>
/// <param name="control">false by default set to true if simmulate control is press</param>
/// <returns>SceneRunner</returns>
SceneRunner SimulateKeyPress(KeyList keyCode, bool shift = false, bool control = false);

/// <summary>
/// Simulates that a key has been released.
/// </summary>
/// <param name="keyCode">the key code e.g. 'KeyList.Enter'</param>
/// <param name="shift">false by default set to true if simmulate shift is press</param>
/// <param name="control">false by default set to true if simmulate control is press</param>
/// <returns>SceneRunner</returns>
SceneRunner SimulateKeyRelease(KeyList keyCode, bool shift = false, bool control = false);

/// <summary>
/// Simulates a mouse moved to relative position by given speed.
/// </summary>
/// <param name="relative">The mouse position relative to the previous position (position at the last frame).</param>
/// <param name="speed">The mouse speed in pixels per second.</param>
/// <returns>SceneRunner</returns>
SceneRunner SimulateMouseMove(Vector2 relative, Vector2 speeds = default);

/// <summary>
/// Simulates a mouse button pressed.
/// </summary>
/// <param name="button">The mouse button identifier, one of the ButtonList button or button wheel constants.</param>
/// <returns>SceneRunner</returns>
SceneRunner SimulateMouseButtonPressed(ButtonList button);

/// <summary>
/// Simulates a mouse button press. (holding)
/// </summary>
/// <param name="button">The mouse button identifier, one of the ButtonList button or button wheel constants.</param>
/// <returns>SceneRunner</returns>
SceneRunner SimulateMouseButtonPress(ButtonList button);

/// <summary>
/// Simulates a mouse button released.
/// </summary>
/// <param name="button">The mouse button identifier, one of the ButtonList button or button wheel constants.</param>
/// <returns>SceneRunner</returns>
SceneRunner SimulateMouseButtonRelease(ButtonList button);

/// <summary>
/// Sets how fast or slow the scene simulation is processed (clock ticks versus the real).
/// <code>
/// 'It defaults to 1.0. A value of 2.0 means the game moves twice as fast as real life,'
/// 'whilst a value of 0.5 means the game moves at half the regular speed'
/// </code>
/// </summary>
/// <param name="timeFactor"></param>
/// <returns>SceneRunner</returns>
SceneRunner SetTimeFactor(double timeFactor = 1.0);

/// <summary>
/// Simulates scene processing for a certain number of frames by given delta peer frame by ignoring the current time factor
/// <example>
/// <code>
/// 'Waits until 100 frames are rendered with a delta of 20ms peer frame'
/// await runner.SimulateFrames(100, 20);
/// </code>
/// </example>
/// </summary>
/// <param name="frames">amount of frames to process</param>
/// <param name="deltaPeerFrame">the time delta between a frame in milliseconds</param>
/// <returns></returns>
Task<SceneRunner> SimulateFrames(uint frames, uint deltaPeerFrame);

/// <summary>
/// Simulates scene processing for a certain number of frames.
/// <example>
/// <code>
/// 'Waits until 100 frames are rendered'
/// await runner.SimulateFrames(100);
/// </code>
/// </example>
/// </summary>
/// <param name="frames">amount of frames to process</param>
/// <returns></returns>
Task<SceneRunner> SimulateFrames(uint frames);

/// <summary>
/// Waits until next frame is processed (signal idle_frame)
/// <example>
/// <code>
/// 'Waits until next frame is processed'
/// await runner.AwaitOnIdleFrame();
/// </code>
/// </example>
/// <code>await OnIdleFrame();</code>
/// </summary>
/// <returns>SignalAwaiter</returns>
SignalAwaiter AwaitOnIdleFrame();

/// <summary>
/// Waits for given signal is emited.
/// <example>
/// <code>
/// 'Waits for signal "mySignal"'
/// await runner.AwaitOnSignal("mySignal");
/// </code>
/// </example>
/// </summary>
/// <param name="signal">The name of signal to wait</param>
/// <returns>SignalAwaiter</returns>
SignalAwaiter AwaitOnSignal(string signal);

/// <summary>
/// Waits for a specific amount of milliseconds.
/// <example>
/// <code>
/// 'Waits for two seconds'
/// await runner.AwaitOnMillis(2000);
/// </code>
/// </example>
/// </summary>
/// <param name="timeMillis">Seconds to wait. 1.0 for one Second</param>
/// <returns>SignalAwaiter</returns>
Task AwaitOnMillis(uint timeMillis);

/// <summary>
/// Access to current running scene
/// </summary>
/// <returns>Node</returns>
Node Scene();

/// <summary>
/// Shows the running scene and moves the window to the foreground.
/// </summary>
void MoveWindowToForeground();

/// <summary>
/// Invokes the method by given name and arguments.
/// </summary>
/// <param name="name">The name of method to invoke</param>
/// <param name="args">The function arguments</param>
/// <returns>The invoced method return value</returns>
/// <exception cref="MissingMethodException"/>
public object Invoke(string name, params object[] args);

/// <summary>
/// Returns the property by given name.
/// </summary>
/// <typeparam name="T">The type of the property</typeparam>
/// <param name="name">The parameter name</param>
/// <returns>Returns the value of property or throws a MissingFieldException</returns>
/// <exception cref="MissingFieldException"/>
public T GetProperty<T>(string name);

/// <summary>
/// Finds the node by given name.
/// </summary>
/// <param name="name">The name of node to find</param>
/// <param name="recursive">Allow recursive search</param>
/// <returns>The node if found or Null</returns>
public Node FindNode(string name, bool recursive = true);
}
}
9 changes: 5 additions & 4 deletions addons/gdUnit3/src/asserts/Comparable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,21 +60,21 @@ public static Result IsEqual<T>(T left, T right, MODE compareMode = MODE.CASE_SE
if (left == null || right == null)
return new Result(false, left, right);

if (object.ReferenceEquals(left, right))
return new Result(true, left, right, r);

var type = left.GetType();
if (type.IsEnum)
return new Result(left.Equals(right), left, right, r);

if (type.IsPrimitive || typeof(string).Equals(type) || left is IEquatable<T>)
if (type.IsPrimitive || typeof(string).Equals(type) || left is IEquatable<T> || left is System.ValueType)
{
//Godot.GD.PrintS("IsPrimitive", type, left, right);
if (left is String && compareMode == MODE.CASE_INSENSITIVE)
return new Result(left.ToString().ToLower().Equals(right.ToString().ToLower()), left, right, r);
return new Result(left.Equals(right), left, right, r);
}

if (object.ReferenceEquals(left, right))
return new Result(true, left, right, r);

if (type.IsArray)
{
var la = left as Array;
Expand Down Expand Up @@ -116,6 +116,7 @@ public static Result IsEqual<T>(T left, T right, MODE compareMode = MODE.CASE_SE
if (!left.GetType().Equals(right.GetType()))
return new Result(false, left, right, r);


// deep compare
foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
{
Expand Down
16 changes: 8 additions & 8 deletions addons/gdUnit3/src/asserts/ExceptionAssert.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Threading.Tasks;

namespace GdUnit3.Asserts
{
Expand All @@ -10,14 +11,13 @@ internal sealed class ExceptionAssert<T> : IExceptionAssert

public ExceptionAssert(Func<T> supplier)
{
try
{
supplier.Invoke();
}
catch (Exception e)
{
Current = e;
}
try { supplier.Invoke(); }
catch (Exception e) { Current = e; }
}

public ExceptionAssert(Exception e)
{
Current = e;
}

public IExceptionAssert IsInstanceOf<ExpectedType>()
Expand Down
Loading

0 comments on commit 560fe0a

Please sign in to comment.