Files
agent-framework/dotnet/tests/Microsoft.Agents.Workflows.UnitTests/StreamingAggregatorsTests.cs
T
Jacob Alber 39e071c430 .NET: Update Workflow Input/Output Redesign (#881)
* feat: Make Executor id field mandatory

When checkpointing is involved, it is critical to keep executor ids consistent between runs, even when recreating a new object tree for the workflow.

The default id-setting mechanism generated a guid for part of the id, making it not work when restoring from a checkpoint.

This change prevents this situation from arising.

* feat: Enable running untyped Workflows

With the change to enable delay-instantiation of executors and support for async Executor factory methods, we must instantiate the starting executor to know what are the valid input types for the workflow.

To avoid forcing instantiation every time, and to better support workflows with multiple input types, we enable support for build and interacting with the base Workflow type without type annotations, and remove the requirement to know a valid input type when initiating a run.

* feat: Support Output from any executor and multiple outputs.
2025-09-25 02:03:22 +00:00

120 lines
4.4 KiB
C#

// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
namespace Microsoft.Agents.Workflows.UnitTests;
public class StreamingAggregatorsTests
{
private static TResult? ApplyStreamingAggregator<TInput, TResult>(
Func<TResult?, TInput, TResult?> aggregator,
IEnumerable<TInput> inputs,
TResult? runningResult = default)
{
foreach (TInput input in inputs)
{
runningResult = aggregator(runningResult, input);
}
return runningResult!;
}
[Fact]
public void Test_StreamingAggregators_First()
{
IEnumerable<int?> inputs = [1, 2, 3];
Func<int?, int?, int?> aggregator = StreamingAggregators.First<int?>();
int? runningResult = ApplyStreamingAggregator(aggregator, inputs);
runningResult.Should().Be(1);
// Ensure that subsequent inputs do not change the result
ApplyStreamingAggregator(aggregator, inputs.Skip(1), runningResult.Value)
.Should()
.Be(1, "subsequent inputs should not change the result of First aggregator");
}
[Fact]
public void Test_StreamingAggregators_First_WithConversion()
{
IEnumerable<int?> inputs = [2, 4, 6];
Func<int?, int?, int?> aggregator = StreamingAggregators.First<int?, int?>(input => input / 2);
int? runningResult = ApplyStreamingAggregator(aggregator, inputs);
runningResult.Should().Be(1);
// Ensure that subsequent inputs do not change the result
ApplyStreamingAggregator(aggregator, inputs.Skip(1), runningResult.Value)
.Should()
.Be(1, "subsequent inputs should not change the result of First aggregator with conversion");
}
[Fact]
public void Test_StreamingAggregators_Last()
{
IEnumerable<int> inputs = [1, 2, 3];
Func<int, int, int> aggregator = StreamingAggregators.Last<int>();
int? runningResult = ApplyStreamingAggregator(aggregator, inputs);
runningResult.Should().Be(3);
// Ensure that subsequent inputs do change the result
ApplyStreamingAggregator(aggregator, inputs.Take(2), runningResult.Value)
.Should()
.Be(2, "subsequent inputs should change the result of Last aggregator");
}
[Fact]
public void Test_StreamingAggregators_Last_WithConversion()
{
IEnumerable<int> inputs = [2, 4, 6];
Func<int, int, int> aggregator = StreamingAggregators.Last<int, int>(input => input / 2);
int? runningResult = ApplyStreamingAggregator(aggregator, inputs);
runningResult.Should().Be(3);
// Ensure that subsequent inputs do change the result
ApplyStreamingAggregator(aggregator, inputs.Take(2), runningResult.Value)
.Should()
.Be(2, "subsequent inputs should change the result of Last aggregator");
}
[Fact]
public void Test_StreamingAggregators_Union()
{
IEnumerable<int> inputs = [1, 2, 3];
Func<IEnumerable<int>?, int, IEnumerable<int>?> aggregator = StreamingAggregators.Union<int>();
IEnumerable<int>? runningResult = ApplyStreamingAggregator(aggregator, inputs);
runningResult.Should().BeEquivalentTo([1, 2, 3], "Union should accumulate all inputs in order");
// Ensure that subsequent inputs concatenate to the existing results
inputs = [4, 5];
ApplyStreamingAggregator(aggregator, inputs, runningResult)
.Should()
.BeEquivalentTo([1, 2, 3, 4, 5], "Union should accumulate all inputs in order including subsequent inputs");
}
[Fact]
public void Test_StreamingAggregators_Union_WithConversion()
{
IEnumerable<int> inputs = [2, 4, 6];
Func<IEnumerable<int>?, int, IEnumerable<int>?> aggregator = StreamingAggregators.Union<int, int>(input => input / 2);
IEnumerable<int>? runningResult = ApplyStreamingAggregator(aggregator, inputs);
runningResult.Should().BeEquivalentTo([1, 2, 3],
"Union with conversion should accumulate all converted inputs in order");
// Ensure that subsequent inputs concatenate to the existing results
inputs = [8, 10];
ApplyStreamingAggregator(aggregator, inputs, runningResult)
.Should()
.BeEquivalentTo([1, 2, 3, 4, 5],
"Union with conversion should accumulate all converted inputs in order including subsequent inputs");
}
}