Skip to content

Commit

Permalink
Add latest polled value to WaitForConditionOn extra context builder (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
sbergen authored Oct 12, 2024
1 parent 70d1185 commit d24342b
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 10 deletions.
7 changes: 4 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ Experimental features are also **not** under semantic versioning.
## [Unreleased]

### Changed
- Breaking change: `RunAs(Simulated)Loop` is now an `async` method.
This had to be done to support Unity's async cancellation state to propagate to state strings.
- Breaking change: Make interface members that were always only intended for internal use internal.
This was not possible before C#8, but now that we no longer support older Unity versions,
it can be done, allowing more flexibility to change things without breaking things in the future.
it can be done, allowing more flexibility without breaking changes in the future.
- Breaking change: `RunAs(Simulated)Loop` is now an `async` method.
This had to be done to support Unity's async cancellation state to propagate to state strings.
- Breaking change: `WaitForConditionOn` extra context builder accepts the latest polled value as an argument.

### Fixed
- Fix instructions that were canceled because of a failure not showing up as canceled on Unity.
Expand Down
13 changes: 13 additions & 0 deletions com.beatwaves.responsible/Runtime/Docs/Inherit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,19 @@ public void CallerMemberWithDescription<T>(
{
}

/// <inheritdoc cref="CallerMember{T1, T2, T3, T4}"/>
/// <param name="description">Description of the operation, to be included in the state output.</param>
public void CallerMemberWithDescription<T1, T2, T3>(
string description,
T1 arg1,
T2 arg2,
T3 arg3,
string memberName = "",
string sourceFilePath = "",
int sourceLineNumber = 0)
{
}

/// <inheritdoc cref="CallerMember{T1, T2, T3}"/>
/// <param name="description">Description of the operation, to be included in the state output.</param>
/// <param name="extraContext">Action for producing extra context into state descriptions.</param>
Expand Down
10 changes: 7 additions & 3 deletions com.beatwaves.responsible/Runtime/Responsibly.WaitFor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,18 @@ public static partial class Responsibly
/// </returns>
/// <param name="getObject">Function that returns the object to test <paramref name="condition"/> on.</param>
/// <param name="condition">Condition to check with the return value of <paramref name="getObject"/>.</param>
/// <param name="extraContext">
/// Action for producing extra context into state descriptions.
/// The last value returned by <paramref name="getObject"/> is passed as the second argument.
/// </param>
/// <typeparam name="T">Type of the object to wait on, and result of the returned wait condition.</typeparam>
/// <inheritdoc cref="Docs.Inherit.CallerMemberWithDescriptionAndContext{T1, T2}"/>
/// <inheritdoc cref="Docs.Inherit.CallerMemberWithDescription{T1, T2, T3}"/>
[Pure]
public static ITestWaitCondition<T> WaitForConditionOn<T>(
string description,
Func<T> getObject,
Func<T, bool> condition,
Action<StateStringBuilder> extraContext = null,
Action<StateStringBuilder, T> extraContext = null,
[CallerMemberName] string memberName = "",
[CallerFilePath] string sourceFilePath = "",
[CallerLineNumber] int sourceLineNumber = 0)
Expand Down Expand Up @@ -73,7 +77,7 @@ public static ITestWaitCondition<object> WaitForCondition(
description,
() => Unit.Instance,
_ => condition(),
extraContext,
extraContext.DiscardingState<object>(),
new SourceContext(nameof(WaitForCondition), memberName, sourceFilePath, sourceLineNumber));

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public PollingWaitCondition(
string description,
Func<T> getConditionState,
Func<T, bool> condition,
[CanBeNull] Action<StateStringBuilder> extraContext,
[CanBeNull] Action<StateStringBuilder, T> extraContext,
SourceContext sourceContext)
: base(() => new State(description, getConditionState, condition, extraContext, sourceContext))
{
Expand All @@ -24,6 +24,7 @@ private class State : TestOperationState<T>, IDiscreteWaitConditionState
{
private readonly Func<T> getConditionState;
private readonly Func<T, bool> condition;
private T lastPolledValue;

public string Description { get; }
public Action<StateStringBuilder> ExtraContext { get; }
Expand All @@ -32,14 +33,16 @@ public State(
string description,
Func<T> getConditionState,
Func<T, bool> condition,
[CanBeNull] Action<StateStringBuilder> extraContext,
[CanBeNull] Action<StateStringBuilder, T> extraContext,
SourceContext sourceContext)
: base(sourceContext)
{
this.Description = description;
this.getConditionState = getConditionState;
this.getConditionState = () => this.lastPolledValue = getConditionState();
this.condition = condition;
this.ExtraContext = extraContext;
this.ExtraContext = extraContext != null
? b => extraContext(b, this.lastPolledValue)
: null;
}

protected override Task<T> ExecuteInner(RunContext runContext, CancellationToken cancellationToken)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
using System;
using JetBrains.Annotations;
using Responsible.State;

namespace Responsible.Utilities
{
internal static class ActionExtensions
{
[CanBeNull]
public static Action<StateStringBuilder, TState> DiscardingState<TState>(
[CanBeNull] this Action<StateStringBuilder> extraContext) => extraContext != null
? (builder, _) => extraContext(builder)
: null;

public static Func<object> ReturnUnit<T>(this Action<T> action, T arg) => () =>
{
action(arg);
Expand Down
42 changes: 42 additions & 0 deletions src/Responsible.Tests/WaitForConditionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,48 @@ public async Task WaitForCondition_ContainsCorrectDetails_WhenCanceled()
.FailureDetails();
}

[Test]
public void WaitForConditionOn_DoesNotCallExtraContext_WhenNotExecuted()
{
var extraContextRequested = false;

var state = WaitForConditionOn(
"Not executed",
() => false,
_ => false,
(_, _) => extraContextRequested = true)
.ExpectWithinSeconds(1)
.CreateState();

_ = state.ToString();
extraContextRequested.Should().BeFalse();
}

[Test]
public async Task WaitForConditionOn_ContainsCorrectDetailsFromLastValue_WhenTimedOut()
{
var state = 0;
var checkCount = 0;
var task = WaitForConditionOn(
"With details",
() => ++state,
val =>
{
++checkCount;
return val == 42;
},
(builder, val) => builder.AddDetails($"Expected 42, but got {val}"))
.ExpectWithinSeconds(1)
.ToTask(this.Executor);

this.Scheduler.AdvanceFrame(TimeSpan.FromSeconds(2));

var error = await AwaitFailureExceptionForUnity(task);
StateAssert.StringContainsInOrder(error.Message)
.Failed("With details")
.Details($"Expected 42, but got {checkCount}");
}


[Test]
public void InlinedOutput_IsGeneratedForInitialState_WhenExpected()
Expand Down

0 comments on commit d24342b

Please sign in to comment.