static void AddConsumers(ConversationThread conversation, ChartTable chart, OutputTimelineOptions options) { foreach (var consumer in conversation.Consumers.OrderBy(x => x.Message.StartTime)) { var sb = new StringBuilder(60); if (conversation.Depth > 1) { sb.Append(' ', (conversation.Depth - 1) * 2); } if (conversation.Depth > 0) { sb.Append("\x2514 "); } sb.Append("Consume "); sb.Append(options.MessageType(consumer.Message)); chart.Add(sb.ToString(), consumer.Message.StartTime, consumer.Message.ElapsedTime, consumer.Message.Address); } }
/// <summary> /// Output a timeline of messages published, sent, and consumed by the test harness. /// </summary> /// <param name="harness"></param> /// <param name="textWriter"></param> /// <param name="configure">Configure the timeout output options</param> /// <exception cref="ArgumentNullException"></exception> public static async Task OutputTimeline(this BusTestHarness harness, TextWriter textWriter, Action <OutputTimelineOptions> configure = default) { if (harness == null) { throw new ArgumentNullException(nameof(harness)); } if (textWriter == null) { throw new ArgumentNullException(nameof(textWriter)); } var options = new OutputTimelineOptions(); configure?.Invoke(options); options.Apply(harness); await harness.InactivityTask.ConfigureAwait(false); var produced = new List <Message>(); await foreach (var message in harness.Published.SelectAsync(_ => true).ConfigureAwait(false)) { produced.Add(new Message(message)); } await foreach (var message in harness.Sent.SelectAsync(_ => true).ConfigureAwait(false)) { produced.Add(new Message(message)); } var consumed = new List <Message>(); await foreach (var message in harness.Consumed.SelectAsync(_ => true).ConfigureAwait(false)) { consumed.Add(new Message(message)); } Dictionary <Guid?, ConversationThread> conversations = produced.GroupBy(m => m.ConversationId).Select(x => { List <Message> messages = x.OrderBy(m => m.StartTime).ToList(); var initiator = messages.FirstOrDefault(m => m.ParentMessageId == default) ?? messages.First(); var initiatorThread = new ConversationThread(initiator, 1); var stack = new Stack <ConversationThread>(); stack.Push(initiatorThread); while (stack.Any()) { var thread = stack.Pop(); List <Message> consumes = consumed.Where(message => message.MessageId == thread.Message.MessageId).ToList(); thread.Consumers.AddRange(consumes.Select(message => new ConversationConsumer(message))); IEnumerable <Message> threadMessages = x.Where(m => m.ParentMessageId == thread.Message.MessageId); foreach (var message in threadMessages) { var nextThread = new ConversationThread(message, thread.Depth + 1); thread.Nodes.Add(nextThread); stack.Push(nextThread); } } return(initiatorThread); }).ToDictionary(x => x.Message.ConversationId); var chart = new ChartTable(); foreach (var conversation in conversations.Values.OrderBy(x => x.Message.StartTime)) { var whitespace = new string(' ', (conversation.Depth - 1) * 2); var conversationLine = $"{whitespace}{conversation.Message.EventType} {options.MessageType(conversation.Message)}"; chart.Add(conversationLine, conversation.Message.StartTime, conversation.Message.ElapsedTime, conversation.Message.Address); AddConsumers(conversation, chart, options); var stack = new Stack <ConversationThread>(conversation.Nodes.OrderByDescending(x => x.Message.StartTime)); while (stack.Any()) { var current = stack.Pop(); whitespace = new string(' ', (current.Depth - 1) * 2); var line = $"{whitespace}{current.Message.EventType} {options.MessageType(current.Message)}"; chart.Add(line, current.Message.StartTime, current.Message.ElapsedTime, current.Message.Address); AddConsumers(current, chart, options); foreach (var node in current.Nodes.OrderByDescending(x => x.Message.StartTime)) { stack.Push(node); } } } options.GetTable(chart) .SetColumn(1, "Duration", typeof(int)) .SetRightNumberAlignment() .OutputTo(textWriter) .Write(); }