You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

RunContextStackTests.cs 9.4 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. // Copyright (c) Microsoft Corporation. All rights reserved.
  2. // RunContextStackTests.cs
  3. using FluentAssertions;
  4. using Microsoft.AutoGen.AgentChat.GroupChat;
  5. using Moq;
  6. using Xunit;
  7. namespace Microsoft.AutoGen.AgentChat.Tests;
  8. [Trait("Category", "UnitV2")]
  9. public class RunContextStackTests
  10. {
  11. public static IRunContextLayer CreateLayer(Action<Mock<IRunContextLayer>>? setupAction = null)
  12. {
  13. Mock<IRunContextLayer> layer = new();
  14. if (setupAction != null)
  15. {
  16. setupAction(layer);
  17. }
  18. else
  19. {
  20. layer.Setup(l => l.InitializeAsync()).Returns(ValueTask.CompletedTask);
  21. layer.Setup(l => l.DeinitializeAsync()).Returns(ValueTask.CompletedTask);
  22. }
  23. return layer.Object;
  24. }
  25. [Fact]
  26. public async Task Initialize_SucceedsWithNoLayers()
  27. {
  28. // Arrange
  29. RunContextStack stack = new RunContextStack();
  30. // Act
  31. Func<Task> func = async () => await stack.InitializeAsync();
  32. // Assert
  33. await func.Should().NotThrowAsync("RunContextStack should work without context frames");
  34. }
  35. [Fact]
  36. public async Task Deinitialize_SucceedsWithNoLayers()
  37. {
  38. // Arrange
  39. RunContextStack stack = new RunContextStack();
  40. await stack.InitializeAsync();
  41. // Act
  42. Func<Task> func = async () => await stack.DeinitializeAsync();
  43. // Assert
  44. await func.Should().NotThrowAsync("RunContextStack should work without context frames");
  45. }
  46. [Fact]
  47. public async Task PushLayer_FailsWhenInitialized()
  48. {
  49. // Arrange
  50. RunContextStack stack = new RunContextStack();
  51. await stack.InitializeAsync();
  52. // Act
  53. Action pushLayerAction = () => stack.PushLayer(CreateLayer());
  54. // Assert
  55. pushLayerAction.Should().Throw<InvalidOperationException>("RunContextStack should not allow pushing layers when initialized");
  56. }
  57. [Fact]
  58. public async Task PopLayer_FailsWhenInitialized()
  59. {
  60. // Arrange
  61. RunContextStack stack = new RunContextStack();
  62. await stack.InitializeAsync();
  63. // Act
  64. Action popLayerAction = stack.PopLayer;
  65. // Assert
  66. popLayerAction.Should().Throw<InvalidOperationException>("RunContextStack should not allow popping layers when initialized");
  67. }
  68. [Fact]
  69. public Task InitializeDeinitialize_ShouldInvokeLayersInOrder_WhenPushed()
  70. {
  71. return PrepareAndRun_LayerOrderTest(Arrange);
  72. static RunContextStack Arrange(IEnumerable<IRunContextLayer> layers)
  73. {
  74. RunContextStack stack = new RunContextStack();
  75. foreach (IRunContextLayer layer in layers)
  76. {
  77. stack.PushLayer(layer);
  78. }
  79. return stack;
  80. }
  81. }
  82. [Fact]
  83. public Task InitializeDeinitialize_ShouldInvokeLayersInOrder_WhenConstructed()
  84. {
  85. return PrepareAndRun_LayerOrderTest(Arrange);
  86. static RunContextStack Arrange(IEnumerable<IRunContextLayer> layers)
  87. {
  88. return new RunContextStack([.. layers]);
  89. }
  90. }
  91. private async Task PrepareAndRun_LayerOrderTest(Func<IEnumerable<IRunContextLayer>, RunContextStack> arrangeStack)
  92. {
  93. bool bottomLayerInit = false;
  94. bool bottomLayerDeinit = false;
  95. bool topLayerInit = false;
  96. bool topLayerDeinit = false;
  97. // Arrange
  98. IRunContextLayer topLayer = CreateLayer(mock =>
  99. {
  100. mock.Setup(l => l.InitializeAsync()).Callback(
  101. () =>
  102. {
  103. topLayerInit.Should().BeFalse("Top Layer should not have been initialized yet");
  104. bottomLayerInit.Should().BeFalse("Bottom Layer should not have been initialized yet");
  105. topLayerInit = true;
  106. }
  107. ).Returns(ValueTask.CompletedTask).Verifiable();
  108. mock.Setup(l => l.DeinitializeAsync()).Callback(
  109. () =>
  110. {
  111. topLayerInit.Should().BeTrue("Top Layer should have been initialized");
  112. bottomLayerInit.Should().BeTrue("Bottom Layer should have been initialized");
  113. bottomLayerDeinit.Should().BeTrue("Bottom Layer should be deinitialized before Top Layer");
  114. topLayerDeinit.Should().BeFalse("Top Layer should not have been deinitialized yet");
  115. topLayerDeinit = true;
  116. }).Returns(ValueTask.CompletedTask).Verifiable();
  117. });
  118. IRunContextLayer bottomLayer = CreateLayer(mock =>
  119. {
  120. mock.Setup(l => l.InitializeAsync()).Callback(
  121. () =>
  122. {
  123. topLayerInit.Should().BeTrue("Top Layer should have been initialized before Bottom Layer");
  124. bottomLayerInit.Should().BeFalse("Bottom Layer should not have been initialized yet");
  125. bottomLayerInit = true;
  126. }
  127. ).Returns(ValueTask.CompletedTask).Verifiable();
  128. mock.Setup(l => l.DeinitializeAsync()).Callback(
  129. () =>
  130. {
  131. topLayerInit.Should().BeTrue("Top Layer should have been initialized");
  132. bottomLayerInit.Should().BeTrue("Bottom Layer should have been initialized");
  133. bottomLayerDeinit.Should().BeFalse("Bottom Layer should not have been deinitialized yet");
  134. topLayerDeinit.Should().BeFalse("Top Layer should not have been deinitialized yet");
  135. bottomLayerDeinit = true;
  136. }).Returns(ValueTask.CompletedTask).Verifiable();
  137. });
  138. RunContextStack stack = arrangeStack([bottomLayer, topLayer]);
  139. // Act
  140. await stack.InitializeAsync();
  141. // Assert
  142. Mock.Get(topLayer).Verify(l => l.InitializeAsync(), Times.Once);
  143. Mock.Get(bottomLayer).Verify(l => l.InitializeAsync(), Times.Once);
  144. bottomLayerInit.Should().BeTrue("Top Layer should have been initialized");
  145. topLayerInit.Should().BeTrue("Bottom Layer should have been initialized");
  146. // Act 2
  147. await stack.DeinitializeAsync();
  148. // Assert 2
  149. Mock.Get(bottomLayer).Verify(l => l.DeinitializeAsync(), Times.Once);
  150. Mock.Get(topLayer).Verify(l => l.DeinitializeAsync(), Times.Once);
  151. topLayerDeinit.Should().BeTrue("Bottom Layer should have been deinitialized");
  152. bottomLayerDeinit.Should().BeTrue("Top Layer should have been deinitialized");
  153. }
  154. [Fact]
  155. public async Task CreateOverrides_GetsInvokedOnError()
  156. {
  157. int initializeErrors = 0;
  158. int deinitializeErrors = 0;
  159. // Arrange
  160. IRunContextLayer overrides = RunContextStack.OverrideErrors(
  161. initializeError: () => initializeErrors++,
  162. deinitializeError: () => deinitializeErrors++);
  163. RunContextStack stack = new RunContextStack(overrides);
  164. // Act
  165. Func<Task> deinitializeAction = async () => await stack.DeinitializeAsync();
  166. // Assert
  167. // The first Deinitialize should throw because we only override after the top layer it initialized
  168. await deinitializeAction.Should().ThrowAsync<InvalidOperationException>("Deinitialize should throw an exception");
  169. // Act 2
  170. await stack.InitializeAsync();
  171. Func<Task> initializeAgainAction = async () => await stack.InitializeAsync();
  172. // Assert 2
  173. // The second Initialize should not throw, because the overrides should be applied
  174. await initializeAgainAction.Should().NotThrowAsync("Initialize should not throw an exception");
  175. initializeErrors.Should().Be(1, "There should be one initialization error");
  176. deinitializeErrors.Should().Be(0, "There should not have been an overriden invocation of a deinitialize error.");
  177. }
  178. [Fact]
  179. public async Task Enter_DisposableWorksIdempotently()
  180. {
  181. int initializeCount = 0;
  182. int deinitializeCount = 0;
  183. // Arrange
  184. IRunContextLayer layer = CreateLayer(mock =>
  185. {
  186. mock.Setup(l => l.InitializeAsync()).Callback(() => initializeCount++).Returns(ValueTask.CompletedTask);
  187. mock.Setup(l => l.DeinitializeAsync()).Callback(() => deinitializeCount++).Returns(ValueTask.CompletedTask);
  188. });
  189. RunContextStack stack = new RunContextStack(layer);
  190. // Act
  191. IAsyncDisposable exitDisposable = await stack.Enter();
  192. // Assert
  193. initializeCount.Should().Be(1, "Layer should have been initialized once");
  194. deinitializeCount.Should().Be(0, "Layer should not have been deinitialized yet");
  195. // Act 2
  196. await exitDisposable.DisposeAsync();
  197. // Assert 2
  198. initializeCount.Should().Be(1, "Layer should have been initialized once");
  199. deinitializeCount.Should().Be(1, "Layer should have been deinitialized once");
  200. // Act 3
  201. Func<Task> disposeAgain = async () => await exitDisposable.DisposeAsync();
  202. // Assert 3
  203. await disposeAgain.Should().NotThrowAsync("Dispose should be idempotent");
  204. initializeCount.Should().Be(1, "Layer should have been initialized once");
  205. deinitializeCount.Should().Be(1, "Layer should have been deinitialized once");
  206. }
  207. }