Skip to content

Commit

Permalink
Add documentation for experimental Godot support (#231)
Browse files Browse the repository at this point in the history
  • Loading branch information
sbergen authored May 12, 2024
1 parent 23f3987 commit e48e3ae
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Experimental features are also **not** under semantic versioning.
### Added
- Added support for Unity 2022
- Added `RepeatUntil` extension on test instruction
- Added documentation for experimental Godot support

### Changed
- Never collapse responders in state strings (even when completed or not executed).
Expand Down
158 changes: 158 additions & 0 deletions docfx/godot.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# Experimental Godot support

[Godot 4](https://godotengine.org/) support (C# only)
in Responsible is currently **experimental**
and currently requires a bit of manual setup.
As I don't use Godot myself,
I am not confident in publishing a package for Godot support
until I've received some feedback from more experienced Godot developers.

Opening [issues](https://github.com/sbergen/Responsible/issues) for Godot support with reproduction steps is highly welcome, though!

## Installation & setup

### Overview

1. You will need to install some unit testing framework,
for example [GdUnit4](https://mikeschulze.github.io/gdUnit4/).
2. Add a NuGet reference to
[Beatwaves.Responsible](https://www.nuget.org/packages/Beatwaves.Responsible).
(Using `<PackageReference>` or a NuGet GUI in your IDE).
3. Add implementations for a `TestInstructionExecutor` and `ITestScheduler` (examples below).
4. Write tests using the executor.

### Example implementations

The examples below have been tested with GdUnit4,
and use C# 11.

#### `GodotTestScheduler`

The test scheduler provides Responsible the concepts of
frames, time, and frame-based polling.
In Godot, frame-based polling requires an active `Node`,
so the scheduler requires a parent node in its constructor.

```cs
using System;
using Godot;
using Responsible.Utilities;

namespace Responsible.Godot;

internal partial class GodotTestScheduler : Node, ITestScheduler
{
private readonly RetryingPoller _poller = new RetryingPoller();

public GodotTestScheduler(Node parent)
{
parent.AddChild(this);
}

public int FrameNow => (int)Engine.GetProcessFrames();

public DateTimeOffset TimeNow => DateTimeOffset.Now;

public IDisposable RegisterPollCallback(Action action) =>
_poller.RegisterPollCallback(action);

public override void _Process(double delta)
{
_poller.Poll();
}
}
```

#### `GodotTestInstructionExecutor`

The implementation below is a minimum viable implementation.
As noted in the TODO comment,
something was only printing the inner exception on failures,
so a quick and dirty workaround for this was included.

```cs
using System;
using Godot;

namespace Responsible.Godot;

internal class GodotTestInstructionExecutor : TestInstructionExecutor
{
public GodotTestInstructionExecutor(
Node parent,
IGlobalContextProvider globalContextProvider = null)
: base(
new GodotTestScheduler(parent),
externalResultSource: null,
failureListener: new GodotFailureListener(),
globalContextProvider,
rethrowableExceptions: null)
{
}

private class GodotFailureListener : IFailureListener
{
public void OperationFailed(Exception exception, string failureMessage)
{
// TODO: Wrap the message in a new exception,
// as something (GdUnit4?) is only printing the inner exception details.
throw new Exception(failureMessage);
}
}
}
```

#### Example test (using GdUnit4)

This test is just a proof of concept that proves the test scheduler works:
It waits for 42 frames and 1 second, and then fails.

```cs
using GdUnit4;
using System.Threading.Tasks;
using Responsible;
using Responsible.Godot;
using static Responsible.Responsibly;

namespace MyTestNamespace;

[TestSuite]
public class PaddleTest
{
[TestCase]
public async Task TestResponsible()
{
var sceneRunner = ISceneRunner.Load("res://MyScene.tscn");
var executor = new GodotTestInstructionExecutor(sceneRunner.Scene());
await WaitForFrames(42)
.ContinueWith(WaitForSeconds(1))
.ContinueWith(Do("Throw an error", () => throw new Exception("Fail!")))
.ToTask(executor);
}
}
```

Running the test should provide output similar to the below:
```
Test operation execution failed!
Failure context:
[�] WAIT FOR 42 FRAME(S) (Completed in 0,68 s � 43 frames)
[�] WAIT FOR 0:00:01 (Completed in 1,01 s � 61 frames)
[!] Throw an error (Failed after 0,00 s � 0 frames)
Failed with:
System.Exception:
Fail!
Test operation stack:
[Do] TestResponsible (at <path-to-file>)
[ContinueWith] TestResponsible (at <path-to-file>)
[ToTask] TestResponsible (at <path-to-file>)
Error: System.Exception: Fail!
<stack-trace-here>
```

Yes, there's something odd going on with the UTF-8 characters -
this has not yet been investigated further.
1 change: 1 addition & 0 deletions docfx/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Additionally, in [Unity](https://unity.com/):
* Observe test execution progress while they are running in the Editor
* Stop worrying about a specific long-standing [Unity bug](https://issuetracker.unity3d.com/issues/unitytests-do-not-fail-when-nested-coroutines-throws-an-exception)

[Godot](https://godotengine.org/) now also has [experimental support](godot.md)!

## Usage and Output Example

Expand Down
2 changes: 2 additions & 0 deletions docfx/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
href: dotnet.md
- name: Unity
href: unity.md
- name: Godot
href: godot.md
- name: Design Overview
href: design.md
- name: API Documentation
Expand Down

0 comments on commit e48e3ae

Please sign in to comment.