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.

SendMessageTests.cs 9.2 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. // Copyright (c) Microsoft Corporation. All rights reserved.
  2. // SendMessageTests.cs
  3. using System.Diagnostics;
  4. using System.Reflection;
  5. using FluentAssertions;
  6. using Microsoft.AutoGen.Contracts;
  7. using Microsoft.Extensions.Logging;
  8. using Xunit;
  9. namespace Microsoft.AutoGen.Core.Tests;
  10. [Trait("Category", "UnitV2")]
  11. public class SendMessageTests
  12. {
  13. private sealed class BasicMessage
  14. {
  15. public string Content { get; set; } = string.Empty;
  16. }
  17. private sealed class SendOnAgent : BaseAgent, IHandle<BasicMessage>
  18. {
  19. private IList<Guid> targetKeys;
  20. public SendOnAgent(AgentId id, IAgentRuntime runtime, string description, IList<Guid> targetKeys, ILogger<BaseAgent>? logger = null)
  21. : base(id, runtime, description, logger)
  22. {
  23. this.targetKeys = targetKeys;
  24. }
  25. public async ValueTask HandleAsync(BasicMessage item, MessageContext messageContext)
  26. {
  27. foreach (Guid targetKey in targetKeys)
  28. {
  29. AgentId targetId = new(nameof(ReceiverAgent), targetKey.ToString());
  30. BasicMessage message = new BasicMessage { Content = $"@{targetKey}: item.Content" };
  31. await this.Runtime.SendMessageAsync(message, targetId);
  32. }
  33. }
  34. }
  35. private sealed class ReceiverAgent : BaseAgent, IHandle<BasicMessage>
  36. {
  37. public List<BasicMessage> Messages { get; } = new();
  38. public ReceiverAgent(AgentId id, IAgentRuntime runtime, string description, ILogger<BaseAgent>? logger = null)
  39. : base(id, runtime, description, logger)
  40. {
  41. }
  42. public ValueTask HandleAsync(BasicMessage item, MessageContext messageContext)
  43. {
  44. this.Messages.Add(item);
  45. return ValueTask.CompletedTask;
  46. }
  47. }
  48. private sealed class ProcessorAgent : BaseAgent, IHandle<BasicMessage, BasicMessage>
  49. {
  50. private Func<string, string> ProcessFunc { get; }
  51. public ProcessorAgent(AgentId id, IAgentRuntime runtime, Func<string, string> processFunc, string description, ILogger<BaseAgent>? logger = null)
  52. : base(id, runtime, description, logger)
  53. {
  54. this.ProcessFunc = processFunc;
  55. }
  56. public ValueTask<BasicMessage> HandleAsync(BasicMessage item, MessageContext messageContext)
  57. {
  58. BasicMessage result = new() { Content = this.ProcessFunc(item.Content) };
  59. return ValueTask.FromResult(result);
  60. }
  61. }
  62. private sealed class TestException : Exception
  63. { }
  64. private sealed class CancelAgent : BaseAgent, IHandle<BasicMessage, BasicMessage>
  65. {
  66. public CancelAgent(AgentId id, IAgentRuntime runtime, string description, ILogger<BaseAgent>? logger = null)
  67. : base(id, runtime, description, logger)
  68. {
  69. }
  70. public ValueTask<BasicMessage> HandleAsync(BasicMessage item, MessageContext messageContext)
  71. {
  72. CancellationToken cancelledToken = new CancellationToken(canceled: true);
  73. cancelledToken.ThrowIfCancellationRequested();
  74. return ValueTask.FromResult(item);
  75. }
  76. }
  77. private sealed class ErrorAgent : BaseAgent, IHandle<BasicMessage, BasicMessage>
  78. {
  79. public ErrorAgent(AgentId id, IAgentRuntime runtime, string description, ILogger<BaseAgent>? logger = null)
  80. : base(id, runtime, description, logger)
  81. {
  82. }
  83. public ValueTask<BasicMessage> HandleAsync(BasicMessage item, MessageContext messageContext)
  84. {
  85. throw new TestException();
  86. }
  87. }
  88. private sealed class SendTestFixture
  89. {
  90. private Dictionary<Type, object> AgentsTypeMap { get; } = new();
  91. public InProcessRuntime Runtime { get; private set; } = new();
  92. public ValueTask<AgentType> RegisterFactoryMapInstances<TAgent>(AgentType type, Func<AgentId, IAgentRuntime, ValueTask<TAgent>> factory)
  93. where TAgent : IHostableAgent
  94. {
  95. Func<AgentId, IAgentRuntime, ValueTask<TAgent>> wrappedFactory = async (id, runtime) =>
  96. {
  97. TAgent agent = await factory(id, runtime);
  98. this.GetAgentInstances<TAgent>()[id] = agent;
  99. return agent;
  100. };
  101. return this.Runtime.RegisterAgentFactoryAsync(type, wrappedFactory);
  102. }
  103. public Dictionary<AgentId, TAgent> GetAgentInstances<TAgent>() where TAgent : IHostableAgent
  104. {
  105. if (!AgentsTypeMap.TryGetValue(typeof(TAgent), out object? maybeAgentMap) ||
  106. maybeAgentMap is not Dictionary<AgentId, TAgent> result)
  107. {
  108. this.AgentsTypeMap[typeof(TAgent)] = result = new Dictionary<AgentId, TAgent>();
  109. }
  110. return result;
  111. }
  112. public async ValueTask<object?> RunTestAsync(AgentId sendTarget, object message, string? messageId = null)
  113. {
  114. messageId ??= Guid.NewGuid().ToString();
  115. await this.Runtime.StartAsync();
  116. object? result = await this.Runtime.SendMessageAsync(message, sendTarget, messageId: messageId);
  117. await this.Runtime.RunUntilIdleAsync();
  118. return result;
  119. }
  120. }
  121. [Fact]
  122. public async Task Test_SendMessage_ReturnsValue()
  123. {
  124. Func<string, string> ProcessFunc = (s) => $"Processed({s})";
  125. SendTestFixture fixture = new SendTestFixture();
  126. await fixture.RegisterFactoryMapInstances(nameof(ProcessorAgent),
  127. (id, runtime) => ValueTask.FromResult(new ProcessorAgent(id, runtime, ProcessFunc, string.Empty)));
  128. AgentId targetAgent = new AgentId(nameof(ProcessorAgent), Guid.NewGuid().ToString());
  129. object? maybeResult = await fixture.RunTestAsync(targetAgent, new BasicMessage { Content = "1" });
  130. maybeResult.Should().NotBeNull()
  131. .And.BeOfType<BasicMessage>()
  132. .And.Match<BasicMessage>(m => m.Content == "Processed(1)");
  133. }
  134. [Fact]
  135. public async Task Test_SendMessage_Cancellation()
  136. {
  137. SendTestFixture fixture = new SendTestFixture();
  138. await fixture.RegisterFactoryMapInstances(nameof(CancelAgent),
  139. (id, runtime) => ValueTask.FromResult(new CancelAgent(id, runtime, string.Empty)));
  140. AgentId targetAgent = new AgentId(nameof(CancelAgent), Guid.NewGuid().ToString());
  141. Func<Task> testAction = () => fixture.RunTestAsync(targetAgent, new BasicMessage { Content = "1" }).AsTask();
  142. // TODO: Do we want to do the unwrap in this case?
  143. await testAction.Should().ThrowAsync<OperationCanceledException>();
  144. }
  145. [Fact]
  146. public async Task Test_SendMessage_Error()
  147. {
  148. SendTestFixture fixture = new SendTestFixture();
  149. await fixture.RegisterFactoryMapInstances(nameof(ErrorAgent),
  150. (id, runtime) => ValueTask.FromResult(new ErrorAgent(id, runtime, string.Empty)));
  151. AgentId targetAgent = new AgentId(nameof(ErrorAgent), Guid.NewGuid().ToString());
  152. Func<Task> testAction = () => fixture.RunTestAsync(targetAgent, new BasicMessage { Content = "1" }).AsTask();
  153. (await testAction.Should().ThrowAsync<TargetInvocationException>())
  154. .WithInnerException<TestException>();
  155. }
  156. [Fact]
  157. public async Task TesT_SendMessage_FromSendMessageHandler()
  158. {
  159. Guid[] targetGuids = [Guid.NewGuid(), Guid.NewGuid()];
  160. SendTestFixture fixture = new SendTestFixture();
  161. Dictionary<AgentId, SendOnAgent> sendAgents = fixture.GetAgentInstances<SendOnAgent>();
  162. Dictionary<AgentId, ReceiverAgent> receiverAgents = fixture.GetAgentInstances<ReceiverAgent>();
  163. await fixture.RegisterFactoryMapInstances(nameof(SendOnAgent),
  164. (id, runtime) => ValueTask.FromResult(new SendOnAgent(id, runtime, string.Empty, targetGuids)));
  165. await fixture.RegisterFactoryMapInstances(nameof(ReceiverAgent),
  166. (id, runtime) => ValueTask.FromResult(new ReceiverAgent(id, runtime, string.Empty)));
  167. AgentId targetAgent = new AgentId(nameof(SendOnAgent), Guid.NewGuid().ToString());
  168. Task testTask = fixture.RunTestAsync(targetAgent, new BasicMessage { Content = "Hello" }).AsTask();
  169. // We do not actually expect to wait the timeout here, but it is still better than waiting the 10 min
  170. // timeout that the tests default to. A failure will fail regardless of what timeout value we set.
  171. TimeSpan timeout = Debugger.IsAttached ? TimeSpan.FromSeconds(120) : TimeSpan.FromSeconds(10);
  172. Task timeoutTask = Task.Delay(timeout);
  173. Task completedTask = await Task.WhenAny([testTask, timeoutTask]);
  174. completedTask.Should().Be(testTask, "SendOnAgent should complete before timeout");
  175. // Check that each of the target agents received the message
  176. foreach (var targetKey in targetGuids)
  177. {
  178. var targetId = new AgentId(nameof(ReceiverAgent), targetKey.ToString());
  179. receiverAgents[targetId].Messages.Should().ContainSingle(m => m.Content == $"@{targetKey}: item.Content");
  180. }
  181. }
  182. }