mirror of
https://github.com/microsoft/autogen.git
synced 2025-07-26 02:11:24 +00:00

Factors out RunContext management into separate classes: * Defines a LifecycleObject abstraction to manage initialization / deinitialization * Defines RunContextStack to enable a stack of init/deinit layers * Defines a RunManager to own ensuring proper semantics for performing a "single execution at a time" call while enabling a polymorphic base. Implements Reset Closes #5799 Unblocks #5800 (needs RunContext refactor too)
194 lines
8.3 KiB
C#
194 lines
8.3 KiB
C#
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// LifecycleObjectTests.cs
|
|
|
|
using FluentAssertions;
|
|
using Microsoft.AutoGen.AgentChat.GroupChat;
|
|
using Xunit;
|
|
|
|
namespace Microsoft.AutoGen.AgentChat.Tests;
|
|
|
|
internal sealed class LifecycleObjectFixture : LifecycleObject
|
|
{
|
|
public enum LifecycleState
|
|
{
|
|
Deinitialized,
|
|
Initialized
|
|
}
|
|
|
|
public LifecycleState State { get; private set; }
|
|
|
|
public Func<ValueTask> DeinitializeOverride { get; set; } = () => ValueTask.CompletedTask;
|
|
public Func<ValueTask> InitializeOverride { get; set; } = () => ValueTask.CompletedTask;
|
|
|
|
public Action InitializeErrorOverride { get; set; }
|
|
public Action DeinitializeErrorOverride { get; set; }
|
|
|
|
private int initializeCallCount;
|
|
private int deinitializeCallCount;
|
|
private int initializeErrorCount;
|
|
private int deinitializeErrorCount;
|
|
|
|
public int InitializeCallCount => this.initializeCallCount;
|
|
public int DeinitializeCallCount => this.deinitializeCallCount;
|
|
public int InitializeErrorCount => this.initializeErrorCount;
|
|
public int DeinitializeErrorCount => this.deinitializeErrorCount;
|
|
|
|
public LifecycleObjectFixture()
|
|
{
|
|
this.State = LifecycleState.Deinitialized;
|
|
|
|
this.InitializeErrorOverride = base.OnInitializeError;
|
|
this.DeinitializeErrorOverride = base.OnDeinitializeError;
|
|
}
|
|
|
|
protected override void OnInitializeError()
|
|
{
|
|
Interlocked.Increment(ref this.initializeErrorCount);
|
|
|
|
this.InitializeErrorOverride();
|
|
}
|
|
|
|
protected override void OnDeinitializeError()
|
|
{
|
|
Interlocked.Increment(ref this.deinitializeErrorCount);
|
|
|
|
this.DeinitializeErrorOverride();
|
|
}
|
|
|
|
protected sealed override ValueTask DeinitializeCore()
|
|
{
|
|
Interlocked.Increment(ref this.deinitializeCallCount);
|
|
this.State = LifecycleState.Deinitialized;
|
|
|
|
return DeinitializeOverride();
|
|
}
|
|
|
|
protected sealed override ValueTask InitializeCore()
|
|
{
|
|
Interlocked.Increment(ref this.initializeCallCount);
|
|
this.State = LifecycleState.Initialized;
|
|
|
|
return InitializeOverride();
|
|
}
|
|
}
|
|
|
|
public class LifecycleObjectTests
|
|
{
|
|
/*
|
|
We should be testing the following conditions:
|
|
- SmokeTest: Happy path: Initialize, Deinitialize, Initialize, Deinitialize, validate states and call counts
|
|
- Error handling: Initialize, Initialize; Deinitialize; Initialize, Deinitialize, Deinitialize
|
|
*/
|
|
|
|
[Fact]
|
|
public async Task InitializeAndDeinitialize_SucceedsTwice()
|
|
{
|
|
// Arrange
|
|
LifecycleObjectFixture fixture = new();
|
|
|
|
// Validate preconditions
|
|
fixture.State.Should().Be(LifecycleObjectFixture.LifecycleState.Deinitialized, "LifecycleObject should be in Deinitialized state initially");
|
|
fixture.InitializeCallCount.Should().Be(0, "Initialize should not have been called yet");
|
|
fixture.DeinitializeCallCount.Should().Be(0, "Deinitialize should not have been called yet");
|
|
fixture.InitializeErrorCount.Should().Be(0, "there should be no initialization errors");
|
|
fixture.DeinitializeErrorCount.Should().Be(0, "there should be no deinitialization errors");
|
|
|
|
// Act
|
|
await fixture.InitializeAsync();
|
|
|
|
// Validate postconditions 1
|
|
fixture.State.Should().Be(LifecycleObjectFixture.LifecycleState.Initialized, "LifecycleObject should be in Initialized state after Initialize");
|
|
fixture.InitializeCallCount.Should().Be(1, "Initialize should have been called once");
|
|
fixture.DeinitializeCallCount.Should().Be(0, "Deinitialize should not have been called yet");
|
|
fixture.InitializeErrorCount.Should().Be(0, "there should be no initialization errors");
|
|
fixture.DeinitializeErrorCount.Should().Be(0, "there should be no deinitialization errors");
|
|
|
|
// Act 2
|
|
await fixture.DeinitializeAsync();
|
|
|
|
// Validate postconditions 2
|
|
fixture.State.Should().Be(LifecycleObjectFixture.LifecycleState.Deinitialized, "LifecycleObject should be in Deinitialized state after Deinitialize");
|
|
fixture.InitializeCallCount.Should().Be(1, "Initialize should have been called once");
|
|
fixture.DeinitializeCallCount.Should().Be(1, "Deinitialize should have been called once");
|
|
fixture.InitializeErrorCount.Should().Be(0, "there should be no initialization errors");
|
|
fixture.DeinitializeErrorCount.Should().Be(0, "there should be no deinitialization errors");
|
|
|
|
// Act 3
|
|
|
|
await fixture.InitializeAsync();
|
|
|
|
// Validate postconditions 3
|
|
|
|
fixture.State.Should().Be(LifecycleObjectFixture.LifecycleState.Initialized, "LifecycleObject should be in Initialized state after Initialize");
|
|
fixture.InitializeCallCount.Should().Be(2, "Initialize should have been called twice");
|
|
fixture.DeinitializeCallCount.Should().Be(1, "Deinitialize should have been called once");
|
|
fixture.InitializeErrorCount.Should().Be(0, "there should be no initialization errors");
|
|
fixture.DeinitializeErrorCount.Should().Be(0, "there should be no deinitialization errors");
|
|
|
|
// Act 4
|
|
|
|
await fixture.DeinitializeAsync();
|
|
|
|
// Validate postconditions 4
|
|
|
|
fixture.State.Should().Be(LifecycleObjectFixture.LifecycleState.Deinitialized, "LifecycleObject should be in Deinitialized state after Deinitialize");
|
|
fixture.InitializeCallCount.Should().Be(2, "Initialize should have been called twice");
|
|
fixture.DeinitializeCallCount.Should().Be(2, "Deinitialize should have been called twice");
|
|
fixture.InitializeErrorCount.Should().Be(0, "there should be no initialization errors");
|
|
fixture.DeinitializeErrorCount.Should().Be(0, "there should be no deinitialization errors");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Initialize_FailsWhenInitialized()
|
|
{
|
|
// Testing two things: We should expect InvalidOperationException by default, and that we called into the override
|
|
|
|
// Arrange
|
|
LifecycleObjectFixture fixture = new();
|
|
await fixture.InitializeAsync();
|
|
|
|
// Act
|
|
Func<Task> secondInitialization = async () => await fixture.InitializeAsync();
|
|
|
|
// Assert
|
|
await secondInitialization.Should().ThrowAsync<InvalidOperationException>("LifecycleObject.InitializeAsync should throw InvalidOperationException when initialized");
|
|
|
|
fixture.InitializeCallCount.Should().Be(1, "Initialize should have been called once successfully");
|
|
fixture.InitializeErrorCount.Should().Be(1, "there should be one initialization error");
|
|
fixture.DeinitializeCallCount.Should().Be(0, "Deinitialize should not have been called yet");
|
|
fixture.DeinitializeErrorCount.Should().Be(0, "there should be no deinitialization errors");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Deinitialize_FailsWhenNotInitialized()
|
|
{
|
|
// Arrange
|
|
LifecycleObjectFixture fixture = new();
|
|
|
|
// Act
|
|
Func<Task> deinitialization = async () => await fixture.DeinitializeAsync();
|
|
|
|
// Assert
|
|
await deinitialization.Should().ThrowAsync<InvalidOperationException>("LifecycleObject.DeinitializeAsync should throw InvalidOperationException when not initialized");
|
|
|
|
fixture.InitializeCallCount.Should().Be(0, "Initialize should not have been called yet");
|
|
fixture.InitializeErrorCount.Should().Be(0, "there should be no initialization errors");
|
|
fixture.DeinitializeCallCount.Should().Be(0, "Deinitialize should not have been called successfully yet");
|
|
fixture.DeinitializeErrorCount.Should().Be(1, "there should be one deinitialization error");
|
|
|
|
// Act 2
|
|
await fixture.InitializeAsync();
|
|
await fixture.DeinitializeAsync();
|
|
|
|
Func<Task> secondDeinitialization = async () => await fixture.DeinitializeAsync();
|
|
|
|
// Assert 2
|
|
await secondDeinitialization.Should().ThrowAsync<InvalidOperationException>("LifecycleObject.DeinitializeAsync should throw InvalidOperationException when not initialized");
|
|
|
|
fixture.InitializeCallCount.Should().Be(1, "Initialize should have been called once successfully");
|
|
fixture.InitializeErrorCount.Should().Be(0, "there should be no initialization errors");
|
|
fixture.DeinitializeCallCount.Should().Be(1, "Deinitialize should have been called successfully once");
|
|
fixture.DeinitializeErrorCount.Should().Be(2, "there should be two deinitialization errors");
|
|
}
|
|
}
|