chore: import upstream snapshot with attribution
CodeQL / Analyze (csharp) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
dotnet-build-and-test / dotnet-test-functions (push) Has been cancelled
dotnet-build-and-test / paths-filter (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Debug, windows-latest, net9.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net8.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-foundry-hosted-it (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test-check (push) Has been cancelled
dotnet-build-and-test / Integration Test Report (push) Has been cancelled
CodeQL / Analyze (csharp) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
dotnet-build-and-test / dotnet-test-functions (push) Has been cancelled
dotnet-build-and-test / paths-filter (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Debug, windows-latest, net9.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net8.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-foundry-hosted-it (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test-check (push) Has been cancelled
dotnet-build-and-test / Integration Test Report (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
|
||||
namespace Microsoft.Agents.AI.Workflows.UnitTests;
|
||||
|
||||
public partial class InProcessStateTests
|
||||
{
|
||||
private sealed class TurnToken
|
||||
{
|
||||
public int Count { get; }
|
||||
|
||||
public TurnToken() : this(0)
|
||||
{ }
|
||||
|
||||
private TurnToken(int count)
|
||||
{
|
||||
this.Count = count;
|
||||
}
|
||||
|
||||
public TurnToken Next => new(this.Count + 1);
|
||||
}
|
||||
|
||||
private sealed class StateTestExecutor<TState> : TestingExecutor<TurnToken, TurnToken>
|
||||
{
|
||||
private static Func<TurnToken, IWorkflowContext, CancellationToken, ValueTask<TurnToken>>[] WrapActions(ScopeKey stateKey, Func<TState?, TState?>[] stateActions)
|
||||
{
|
||||
Func<TurnToken, IWorkflowContext, CancellationToken, ValueTask<TurnToken>>[] result
|
||||
= new Func<TurnToken, IWorkflowContext, CancellationToken, ValueTask<TurnToken>>[stateActions.Length];
|
||||
|
||||
for (int i = 0; i < stateActions.Length; i++)
|
||||
{
|
||||
result[i] = CreateWrapper(stateActions[i]);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
Func<TurnToken, IWorkflowContext, CancellationToken, ValueTask<TurnToken>> CreateWrapper(Func<TState?, TState?> action)
|
||||
{
|
||||
return
|
||||
async (turn, context, cancellation) =>
|
||||
{
|
||||
TState? state = await context.ReadStateAsync<TState>(stateKey.Key, stateKey.ScopeId.ScopeName, cancellation)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
state = action(state);
|
||||
|
||||
await context.QueueStateUpdateAsync(stateKey.Key, state, stateKey.ScopeId.ScopeName, cancellation);
|
||||
|
||||
return turn.Next;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public ScopeKey StateKey { get; }
|
||||
|
||||
public StateTestExecutor(ScopeKey stateKey, bool loop = false, params Func<TState?, TState?>[] stateActions)
|
||||
: base(stateKey.ScopeId.ExecutorId, loop, WrapActions(stateKey, stateActions))
|
||||
{
|
||||
this.StateKey = stateKey;
|
||||
}
|
||||
}
|
||||
|
||||
private static Func<int?, int?> CreateOrIncrement(int defaultValue = default)
|
||||
=> currState => currState.HasValue ? currState + 1 : defaultValue;
|
||||
|
||||
private static Func<int?, int?> ValidateState(int expectedValue, string? because = null, params object[] becauseArgs)
|
||||
=> currState =>
|
||||
{
|
||||
currState.Should().Be(expectedValue, because, becauseArgs);
|
||||
|
||||
return currState;
|
||||
};
|
||||
|
||||
private static Func<object?, bool> MaxTurns(int maxTurns)
|
||||
=> maybeTurn => maybeTurn is not TurnToken turn || turn.Count < maxTurns;
|
||||
|
||||
[Fact]
|
||||
public async Task InProcessRun_StateShouldPersist_NotCheckpointedAsync()
|
||||
{
|
||||
StateTestExecutor<int?> writer = new(
|
||||
new ScopeKey("Writer", "TestScope", "TestKey"),
|
||||
loop: false,
|
||||
CreateOrIncrement(),
|
||||
CreateOrIncrement()
|
||||
);
|
||||
|
||||
StateTestExecutor<int?> validator = new(
|
||||
new ScopeKey("Validator", "TestScope", "TestKey"),
|
||||
loop: false,
|
||||
ValidateState(0),
|
||||
ValidateState(1)
|
||||
);
|
||||
|
||||
Workflow workflow =
|
||||
new WorkflowBuilder(writer)
|
||||
.AddEdge(writer, validator, MaxTurns(4))
|
||||
.AddEdge(validator, writer, MaxTurns(4)).Build();
|
||||
|
||||
Run run = await InProcessExecution.RunAsync<TurnToken>(workflow, new());
|
||||
|
||||
RunStatus status = await run.GetStatusAsync();
|
||||
status.Should().Be(RunStatus.Idle);
|
||||
|
||||
writer.Completed.Should().BeTrue();
|
||||
validator.Completed.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InProcessRun_StateShouldPersist_CheckpointedAsync()
|
||||
{
|
||||
StateTestExecutor<int?> writer = new(
|
||||
new ScopeKey("Writer", "TestScope", "TestKey"),
|
||||
loop: false,
|
||||
CreateOrIncrement(),
|
||||
CreateOrIncrement()
|
||||
);
|
||||
|
||||
StateTestExecutor<int?> validator = new(
|
||||
new ScopeKey("Validator", "TestScope", "TestKey"),
|
||||
loop: false,
|
||||
ValidateState(0),
|
||||
ValidateState(1)
|
||||
);
|
||||
|
||||
Workflow workflow =
|
||||
new WorkflowBuilder(writer)
|
||||
.AddEdge(writer, validator, MaxTurns(4))
|
||||
.AddEdge(validator, writer, MaxTurns(4)).Build();
|
||||
|
||||
Run checkpointed = await InProcessExecution.RunAsync<TurnToken>(workflow, new(), CheckpointManager.Default);
|
||||
|
||||
checkpointed.Checkpoints.Should().HaveCount(4);
|
||||
|
||||
RunStatus status = await checkpointed.GetStatusAsync();
|
||||
status.Should().Be(RunStatus.Idle);
|
||||
|
||||
writer.Completed.Should().BeTrue();
|
||||
validator.Completed.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InProcessRun_StateShouldError_TwoExecutorsAsync()
|
||||
{
|
||||
ForwardMessageExecutor<TurnToken> forward = new(nameof(ForwardMessageExecutor<>));
|
||||
using StateTestExecutor<int?> testExecutor = new(
|
||||
new ScopeKey("StateTestExecutor", "TestScope", "TestKey"),
|
||||
loop: false,
|
||||
CreateOrIncrement()
|
||||
);
|
||||
|
||||
using StateTestExecutor<int?> testExecutor2 = new(
|
||||
new ScopeKey("StateTestExecutor2", "TestScope", "TestKey"),
|
||||
loop: false,
|
||||
CreateOrIncrement()
|
||||
);
|
||||
|
||||
Workflow workflow =
|
||||
new WorkflowBuilder(forward)
|
||||
.AddFanOutEdge(forward, targets: [testExecutor, testExecutor2])
|
||||
.Build();
|
||||
|
||||
Run runWithFailure = await InProcessExecution.RunAsync(workflow, new TurnToken());
|
||||
|
||||
bool hadFailure = false;
|
||||
foreach (WorkflowEvent evt in runWithFailure.NewEvents)
|
||||
{
|
||||
if (evt is WorkflowErrorEvent errorEvent)
|
||||
{
|
||||
hadFailure.Should().BeFalse("There can be only one!");
|
||||
hadFailure = true;
|
||||
|
||||
errorEvent.Data.Should().BeOfType<InvalidOperationException>()
|
||||
.Subject.Message.Should().Contain("TestKey");
|
||||
}
|
||||
}
|
||||
|
||||
hadFailure.Should().BeTrue();
|
||||
|
||||
//var act = async () => await InProcessExecution.RunAsync(workflow, new TurnToken());
|
||||
//var result = await act.Should()
|
||||
// .ThrowAsync("multiple writers to the same shared scope key");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user