Stop pipelines in tests after receiving expected events. (#2273)
authorJustin Anderson <jander-msft@users.noreply.github.com>
Fri, 14 May 2021 20:11:59 +0000 (13:11 -0700)
committerGitHub <noreply@github.com>
Fri, 14 May 2021 20:11:59 +0000 (13:11 -0700)
* Stop pipelines in tests after receiving expected events.

src/tests/Microsoft.Diagnostics.Monitoring.EventPipe/EventCounterPipelineUnitTests.cs
src/tests/Microsoft.Diagnostics.Monitoring.EventPipe/EventLogsPipelineUnitTests.cs
src/tests/Microsoft.Diagnostics.Monitoring.EventPipe/EventTracePipelineUnitTests.cs
src/tests/Microsoft.Diagnostics.Monitoring.EventPipe/PipelineTestUtilities.cs

index 931e72434ae0a2301d13dede08a0b41f5d5cb7fa..2aca29c7edc945f40a42b71d73cd1c90a71ae793 100644 (file)
@@ -2,20 +2,15 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
+using Microsoft.Diagnostics.NETCore.Client;
+using Microsoft.Diagnostics.NETCore.Client.UnitTests;
 using System;
 using System.Collections.Generic;
-using System.IO;
 using System.Linq;
-using System.Runtime.InteropServices;
-using System.Text.Json;
 using System.Threading;
 using System.Threading.Tasks;
-using Microsoft.Diagnostics.NETCore.Client;
-using Microsoft.Diagnostics.NETCore.Client.UnitTests;
-using Microsoft.Extensions.Logging;
 using Xunit;
 using Xunit.Abstractions;
-using Xunit.Extensions;
 
 namespace Microsoft.Diagnostics.Monitoring.EventPipe.UnitTests
 {
@@ -30,19 +25,43 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe.UnitTests
 
         private sealed class TestMetricsLogger : ICountersLogger
         {
-            private readonly ITestOutputHelper _output;
+            private readonly List<string> _expectedCounters = new List<string>();
             private Dictionary<string, ICounterPayload> _metrics = new Dictionary<string, ICounterPayload>();
+            private readonly TaskCompletionSource<object> _foundExpectedCountersSource;
 
-            public TestMetricsLogger(ITestOutputHelper output)
+            public TestMetricsLogger(IDictionary<string, IEnumerable<string>> expectedCounters, TaskCompletionSource<object> foundExpectedCountersSource)
             {
-                _output = output;
+                _foundExpectedCountersSource = foundExpectedCountersSource;
+
+                if (expectedCounters.Count > 0)
+                {
+                    foreach (string providerName in expectedCounters.Keys)
+                    {
+                        foreach (string counterName in expectedCounters[providerName])
+                        {
+                            _expectedCounters.Add(CreateKey(providerName, counterName));
+                        }
+                    }
+                }
+                else
+                {
+                    foundExpectedCountersSource.SetResult(null);
+                }
             }
 
             public IEnumerable<ICounterPayload> Metrics => _metrics.Values;
 
             public void Log(ICounterPayload metric)
             {
-                _metrics[string.Concat(metric.Provider, "_", metric.Name)] = metric;
+                string key = CreateKey(metric);
+
+                _metrics[key] = metric;
+
+                // Complete the task source if the last expected key was removed.
+                if (_expectedCounters.Remove(key) && _expectedCounters.Count == 0)
+                {
+                    _foundExpectedCountersSource.TrySetResult(null);
+                }
             }
 
             public void PipelineStarted()
@@ -52,15 +71,31 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe.UnitTests
             public void PipelineStopped()
             {
             }
+
+            private static string CreateKey(ICounterPayload payload)
+            {
+                return CreateKey(payload.Provider, payload.Name);
+            }
+
+            private static string CreateKey(string providerName, string counterName)
+            {
+                return $"{providerName}_{counterName}";
+            }
         }
 
         [Fact]
         public async Task TestCounterEventPipeline()
         {
-            var logger = new TestMetricsLogger(_output);
             var expectedCounters = new[] { "cpu-usage", "working-set" };
             string expectedProvider = "System.Runtime";
 
+            IDictionary<string, IEnumerable<string>> expectedMap = new Dictionary<string, IEnumerable<string>>();
+            expectedMap.Add(expectedProvider, expectedCounters);
+
+            var foundExpectedCountersSource = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
+
+            var logger = new TestMetricsLogger(expectedMap, foundExpectedCountersSource);
+
             await using (var testExecution = StartTraceeProcess("CounterRemoteTest"))
             {
                 //TestRunner should account for start delay to make sure that the diagnostic pipe is available.
@@ -81,7 +116,11 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe.UnitTests
                     RefreshInterval = TimeSpan.FromSeconds(1)
                 }, new[] { logger });
 
-                await PipelineTestUtilities.ExecutePipelineWithDebugee(pipeline, testExecution);
+                await PipelineTestUtilities.ExecutePipelineWithDebugee(
+                    _output,
+                    pipeline,
+                    testExecution,
+                    foundExpectedCountersSource);
             }
 
             Assert.True(logger.Metrics.Any());
index afe0c304bad252d50098b8a79a5f66600f8c1f7f..f9eb43ab1f65db1fdab7dad920c31a412a99dc9f 100644 (file)
@@ -206,7 +206,7 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe.UnitTests
                 }
                 await using var pipeline = new EventLogsPipeline(client, logSettings, loggerFactory);
 
-                await PipelineTestUtilities.ExecutePipelineWithDebugee(pipeline, testExecution);
+                await PipelineTestUtilities.ExecutePipelineWithDebugee(_output, pipeline, testExecution);
             }
 
             outputStream.Position = 0L;
index 3eed11736a01be47d312eab6c7df8246bdcec81c..13dd2bbb8062182bd65f53471cda4ff4296ba404 100644 (file)
@@ -4,6 +4,7 @@
 
 using System;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.IO;
 using System.Linq;
 using System.Runtime.InteropServices;
@@ -32,7 +33,6 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe.UnitTests
         [Fact]
         public async Task TestTraceStopAsync()
         {
-            using var buffer = new MemoryStream();
             Stream eventStream = null;
             await using (var testExecution = StartTraceeProcess("TraceStopTest"))
             {
@@ -45,32 +45,37 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe.UnitTests
                     Configuration = new CpuProfileConfiguration()
                 };
 
+                var foundProviderSource = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
+
                 await using var pipeline = new EventTracePipeline(client, settings, async (s, token) =>
                 {
-                    await s.CopyToAsync(buffer);
                     eventStream = s;
-                });
 
-                await PipelineTestUtilities.ExecutePipelineWithDebugee(pipeline, testExecution);
-            }
+                    using var eventSource = new EventPipeEventSource(s);
+                    
+                    // Dispose event source when cancelled.
+                    using var _ = token.Register(() => eventSource.Dispose());
 
-            //Validate that the stream is only valid for the lifetime of the callback in the trace pipeline.
-            Assert.Throws<ObjectDisposedException>(() => eventStream.Read(new byte[4], 0, 4));
+                    eventSource.Dynamic.All += (TraceEvent obj) =>
+                    {
+                        if (string.Equals(obj.ProviderName, MonitoringSourceConfiguration.SampleProfilerProviderName, StringComparison.OrdinalIgnoreCase))
+                        {
+                            foundProviderSource.TrySetResult(null);
+                        }
+                    };
 
-            Assert.True(buffer.Length > 0);
+                    await Task.Run(() => Assert.True(eventSource.Process()), token);
+                });
 
-            var eventSource = new EventPipeEventSource(buffer);
-            bool foundCpuProvider = false;
+                await PipelineTestUtilities.ExecutePipelineWithDebugee(
+                    _output,
+                    pipeline,
+                    testExecution,
+                    foundProviderSource);
+            }
 
-            eventSource.Dynamic.All += (TraceEvent obj) =>
-            {
-                if (string.Equals(obj.ProviderName, MonitoringSourceConfiguration.SampleProfilerProviderName, StringComparison.OrdinalIgnoreCase))
-                {
-                    foundCpuProvider = true;
-                }
-            };
-            Assert.True(eventSource.Process());
-            Assert.True(foundCpuProvider);
+            //Validate that the stream is only valid for the lifetime of the callback in the trace pipeline.
+            Assert.Throws<ObjectDisposedException>(() => eventStream.Read(new byte[4], 0, 4));   
         }
 
         [SkippableFact]
@@ -102,7 +107,12 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe.UnitTests
                     return Task.CompletedTask;
                 });
 
-                await Assert.ThrowsAsync<OperationCanceledException>(async () => await PipelineTestUtilities.ExecutePipelineWithDebugee(pipeline, testExecution, cancellationTokenSource.Token));
+                await Assert.ThrowsAsync<OperationCanceledException>(
+                    async () => await PipelineTestUtilities.ExecutePipelineWithDebugee(
+                        _output,
+                        pipeline,
+                        testExecution,
+                        cancellationTokenSource.Token));
             }
 
             //Validate that the stream is only valid for the lifetime of the callback in the trace pipeline.
index 168068ec362e7b5236ec3ef6a151596da0edf10c..1769288212769f9215d30ac71f863d55a2bb2ff7 100644 (file)
@@ -3,14 +3,23 @@
 // See the LICENSE file in the project root for more information.
 
 using Microsoft.Diagnostics.NETCore.Client.UnitTests;
+using System;
 using System.Threading;
 using System.Threading.Tasks;
+using Xunit.Abstractions;
 
 namespace Microsoft.Diagnostics.Monitoring.EventPipe.UnitTests
 {
     internal static class PipelineTestUtilities
     {
-        public static async Task ExecutePipelineWithDebugee(Pipeline pipeline, RemoteTestExecution testExecution, CancellationToken token = default)
+        public static async Task ExecutePipelineWithDebugee(ITestOutputHelper outputHelper, Pipeline pipeline, RemoteTestExecution testExecution, TaskCompletionSource<object> waitTaskSource = null)
+        {
+            using var cancellation = new CancellationTokenSource(TimeSpan.FromMinutes(1));
+
+            await ExecutePipelineWithDebugee(outputHelper, pipeline, testExecution, cancellation.Token, waitTaskSource);
+        }
+
+        public static async Task ExecutePipelineWithDebugee(ITestOutputHelper outputHelper, Pipeline pipeline, RemoteTestExecution testExecution, CancellationToken token, TaskCompletionSource<object> waitTaskSource = null)
         {
             Task processingTask = pipeline.RunAsync(token);
 
@@ -28,6 +37,18 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe.UnitTests
 
             try
             {
+                // Optionally wait on caller before allowing the pipeline to stop.
+                if (null != waitTaskSource)
+                {
+                    using var _ = token.Register(() =>
+                    {
+                        outputHelper.WriteLine("Did not receive completion signal before cancellation.");
+                        waitTaskSource.TrySetCanceled(token);
+                    });
+
+                    await waitTaskSource.Task;
+                }
+
                 //Signal for the pipeline to stop
                 await pipeline.StopAsync(token);