Add EventPipe/DiagnosticsIpc tests (#25457)
authorJohn Salem <josalem@microsoft.com>
Tue, 16 Jul 2019 00:28:57 +0000 (17:28 -0700)
committerGitHub <noreply@github.com>
Tue, 16 Jul 2019 00:28:57 +0000 (17:28 -0700)
* Add Microsoft.Diagnostics.Tools.RuntimeClient to dependencies.props
* add EventPipe tests for provider coherence, buffer size resilience, and rundown event presence

dependencies.props
tests/src/Common/test_dependencies/test_dependencies.csproj
tests/src/tracing/eventpipe/buffersize/buffersize.cs [new file with mode: 0644]
tests/src/tracing/eventpipe/buffersize/buffersize.csproj [new file with mode: 0644]
tests/src/tracing/eventpipe/common/IpcTraceTest.cs [new file with mode: 0644]
tests/src/tracing/eventpipe/common/common.csproj [new file with mode: 0644]
tests/src/tracing/eventpipe/providervalidation/providervalidation.cs [new file with mode: 0644]
tests/src/tracing/eventpipe/providervalidation/providervalidation.csproj [new file with mode: 0644]
tests/src/tracing/eventpipe/rundownvalidation/rundownvalidation.cs [new file with mode: 0644]
tests/src/tracing/eventpipe/rundownvalidation/rundownvalidation.csproj [new file with mode: 0644]

index b04a885..3209bfe 100644 (file)
@@ -26,6 +26,7 @@
     <XunitPackageVersion>2.4.1-pre.build.4059</XunitPackageVersion>
     <XunitPerformanceApiPackageVersion>1.0.0-beta-build0015</XunitPerformanceApiPackageVersion>
     <MicrosoftDiagnosticsTracingTraceEventPackageVersion>2.0.43</MicrosoftDiagnosticsTracingTraceEventPackageVersion>
+    <MicrosoftDiagnosticsToolsRuntimeClientVersion>1.0.4-preview6.19326.1</MicrosoftDiagnosticsToolsRuntimeClientVersion>
     <CommandLineParserVersion>2.2.0</CommandLineParserVersion>
 
     <!-- Scenario tests install this version of Microsoft.NetCore.App, then patch coreclr binaries via xcopy. At the moment it is
index 1764a87..6e07686 100644 (file)
@@ -28,6 +28,9 @@
     <PackageReference Include="System.Runtime.Intrinsics.Experimental">
       <Version>$(MicrosoftPrivateCoreFxNETCoreAppVersion)</Version>
     </PackageReference>
+    <PackageReference Include="Microsoft.Diagnostics.Tools.RuntimeClient">
+      <Version>$(MicrosoftDiagnosticsToolsRuntimeClientVersion)</Version>
+    </PackageReference>
   </ItemGroup>
   <PropertyGroup>
     <TargetFramework>netcoreapp3.0</TargetFramework>
diff --git a/tests/src/tracing/eventpipe/buffersize/buffersize.cs b/tests/src/tracing/eventpipe/buffersize/buffersize.cs
new file mode 100644 (file)
index 0000000..0c2a3de
--- /dev/null
@@ -0,0 +1,68 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Diagnostics.Tracing;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+using Microsoft.Diagnostics.Tools.RuntimeClient;
+using Tracing.Tests.Common;
+
+namespace Tracing.Tests.BufferValidation
+{
+    public sealed class MyEventSource : EventSource
+    {
+        private MyEventSource() {}
+        public static MyEventSource Log = new MyEventSource();
+        public void MyEvent() { WriteEvent(1, "MyEvent"); }
+    }
+
+    public class BufferValidation
+    {
+        public static int Main(string[] args)
+        {
+            // This tests the resilience of message sending with
+            // smaller buffers, specifically 1MB and 4MB
+
+            var providers = new List<Provider>()
+            {
+                new Provider("MyEventSource")
+            };
+
+            var tests = new int[] { 0, 2 }
+                .Select(x => (uint)Math.Pow(2, x))
+                .Select(bufferSize => new SessionConfiguration(circularBufferSizeMB: bufferSize, format: EventPipeSerializationFormat.NetTrace, providers: providers))
+                .Select<SessionConfiguration, Func<int>>(configuration => () => IpcTraceTest.RunAndValidateEventCounts(_expectedEventCounts, _eventGeneratingAction, configuration));
+
+            foreach (var test in tests)
+            {
+                var ret = test();
+                if (ret < 0)
+                    return ret;
+            }
+
+            return 100;
+        }
+
+        private static Dictionary<string, ExpectedEventCount> _expectedEventCounts = new Dictionary<string, ExpectedEventCount>()
+        {
+            // We're testing small buffer sizes, so we expect some [read: many] dropped events
+            // especially on the resource strapped CI machines.  Since the number of dropped events
+            // can be quite large depending on the OS x Arch configuration, we'll only check
+            // for presence and leave counting events to the providervalidation test.
+            { "MyEventSource", -1 }
+        };
+
+        private static Action _eventGeneratingAction = () => 
+        {
+            foreach (var _ in Enumerable.Range(0,1000))
+            {
+                MyEventSource.Log.MyEvent();
+            }
+        };
+    }
+}
\ No newline at end of file
diff --git a/tests/src/tracing/eventpipe/buffersize/buffersize.csproj b/tests/src/tracing/eventpipe/buffersize/buffersize.csproj
new file mode 100644 (file)
index 0000000..8f8b80e
--- /dev/null
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <SchemaVersion>2.0</SchemaVersion>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
+    <TargetFrameworkIdentifier>.NETCoreApp</TargetFrameworkIdentifier>
+    <ProjectGuid>{8E3244CB-407F-4142-BAAB-E7A55901A5FA}</ProjectGuid>
+    <OutputType>exe</OutputType>
+    <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+    <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
+    <CLRTestKind>BuildAndRun</CLRTestKind>
+    <DefineConstants>$(DefineConstants);STATIC</DefineConstants>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <CLRTestPriority>0</CLRTestPriority>
+  </PropertyGroup>
+  <!-- Default configurations to help VS understand the configurations -->
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
+  </PropertyGroup>
+  <ItemGroup>
+    <CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
+      <Visible>False</Visible>
+    </CodeAnalysisDependentAssemblyPaths>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="buffersize.cs" />
+    <ProjectReference Include="../common/common.csproj" />
+  </ItemGroup>
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
+</Project>
diff --git a/tests/src/tracing/eventpipe/common/IpcTraceTest.cs b/tests/src/tracing/eventpipe/common/IpcTraceTest.cs
new file mode 100644 (file)
index 0000000..cebf4b1
--- /dev/null
@@ -0,0 +1,252 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Diagnostics;
+using System.Diagnostics.Tracing;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+using Microsoft.Diagnostics.Tracing;
+using Microsoft.Diagnostics.Tools.RuntimeClient;
+
+namespace Tracing.Tests.Common
+{
+    public class ExpectedEventCount
+    {
+        // The acceptable percent error on the expected value
+        // represented as a floating point value in [0,1].
+        public float Error { get; private set; }
+
+        // The expected count of events. A value of -1 indicates
+        // that count does not matter, and we are simply testing
+        // that the provider exists in the trace.
+        public int Count { get; private set; }
+
+        public ExpectedEventCount(int count, float error = 0.0f)
+        {
+            Count = count;
+            Error = error;
+        }
+
+        public bool Validate(int actualValue)
+        {
+            return Count == -1 || CheckErrorBounds(actualValue);
+        }
+
+        public bool CheckErrorBounds(int actualValue)
+        {
+            return Math.Abs(actualValue - Count) <= (Count * Error);
+        }
+
+        public static implicit operator ExpectedEventCount(int i)
+        {
+            return new ExpectedEventCount(i);
+        }
+
+        public override string ToString()
+        {
+            return $"{Count} +- {Count * Error}";
+        }
+    }
+
+    // This event source is used by the test infra to
+    // to insure that providers have finished being enabled
+    // for the session being observed. Since the client API
+    // returns the pipe for reading _before_ it finishes
+    // enabling the providers to write to that session,
+    // we need to guarantee that our providers are on before
+    // sending events. This is a _unique_ problem I imagine
+    // should _only_ affect scenarios like these tests
+    // where the reading and sending of events are required
+    // to synchronize.
+    public sealed class SentinelEventSource : EventSource
+    {
+        private SentinelEventSource() {}
+        public static SentinelEventSource Log = new SentinelEventSource();
+        public void SentinelEvent() { WriteEvent(1, "SentinelEvent"); }
+    }
+
+    public static class SessionConfigurationExtensions
+    {
+        public static SessionConfiguration InjectSentinel(this SessionConfiguration sessionConfiguration)
+        {
+            var newProviderList = new List<Provider>(sessionConfiguration.Providers);
+            newProviderList.Add(new Provider("SentinelEventSource"));
+            return new SessionConfiguration(sessionConfiguration.CircularBufferSizeInMB, sessionConfiguration.Format, newProviderList.AsReadOnly());
+        }
+    }
+
+    public class IpcTraceTest
+    {
+        // This Action is executed while the trace is being collected.
+        private Action _eventGeneratingAction;
+
+        // A dictionary of event providers to number of events.
+        // A count of -1 indicates that you are only testing for the presence of the provider
+        // and don't care about the number of events sent
+        private Dictionary<string, ExpectedEventCount> _expectedEventCounts;
+        private Dictionary<string, int> _actualEventCounts = new Dictionary<string, int>();
+        private SessionConfiguration _sessionConfiguration;
+
+        // A function to be called with the EventPipeEventSource _before_
+        // the call to `source.Process()`.  The function should return another
+        // function that will be called to check whether the optional test was validated.
+        // Example in situ: providervalidation.cs
+        private Func<EventPipeEventSource, Func<int>> _optionalTraceValidator;
+
+        IpcTraceTest(
+            Dictionary<string, ExpectedEventCount> expectedEventCounts,
+            Action eventGeneratingAction,
+            SessionConfiguration? sessionConfiguration = null,
+            Func<EventPipeEventSource, Func<int>> optionalTraceValidator = null)
+        {
+            _eventGeneratingAction = eventGeneratingAction;
+            _expectedEventCounts = expectedEventCounts;
+            _sessionConfiguration = sessionConfiguration?.InjectSentinel() ?? new SessionConfiguration(
+                circularBufferSizeMB: 1000,
+                format: EventPipeSerializationFormat.NetTrace,
+                providers: new List<Provider> { new Provider("Microsoft-Windows-DotNETRuntime") });
+            _optionalTraceValidator = optionalTraceValidator;
+        }
+
+        private int Fail(string message = "")
+        {
+            Console.WriteLine("Test FAILED!");
+            Console.WriteLine(message);
+            Console.WriteLine("Configuration:");
+            Console.WriteLine("{");
+            Console.WriteLine($"\tbufferSize: {_sessionConfiguration.CircularBufferSizeInMB},");
+            Console.WriteLine("\tproviders: [");
+            foreach (var provider in _sessionConfiguration.Providers)
+            {
+                Console.WriteLine($"\t\t{provider.ToString()},");
+            }
+            Console.WriteLine("\t]");
+            Console.WriteLine("}\n");
+            Console.WriteLine("Expected:");
+            Console.WriteLine("{");
+            foreach (var (k, v) in _expectedEventCounts)
+            {
+                Console.WriteLine($"\t\"{k}\" = {v}");
+            }
+            Console.WriteLine("}\n");
+
+            Console.WriteLine("Actual:");
+            Console.WriteLine("{");
+            foreach (var (k, v) in _actualEventCounts)
+            {
+                Console.WriteLine($"\t\"{k}\" = {v}");
+            }
+            Console.WriteLine("}");
+
+            return -1;
+        }
+
+        private int Validate()
+        {
+            var processId = Process.GetCurrentProcess().Id;
+            var binaryReader = EventPipeClient.CollectTracing(processId, _sessionConfiguration, out var eventpipeSessionId);
+            if (eventpipeSessionId == 0)
+                return -1;
+            
+            // CollectTracing returns before EventPipe::Enable has returned, so the
+            // the sources we want to listen for may not have been enabled yet.
+            // We'll use this sentinel EventSource to check if Enable has finished
+            ManualResetEvent sentinelEventReceived = new ManualResetEvent(false);
+            var sentinelTask = new Task(() =>
+            {
+                while (!sentinelEventReceived.WaitOne(50))
+                {
+                    SentinelEventSource.Log.SentinelEvent();
+                }
+            });
+            sentinelTask.Start();
+
+            EventPipeEventSource source = null;
+            Func<int> optionalTraceValidationCallback = null;
+            var readerTask = new Task(() =>
+            {
+                source = new EventPipeEventSource(binaryReader);
+                source.Dynamic.All += (eventData) =>
+                {
+                    if (eventData.ProviderName == "SentinelEventSource")
+                    {
+                        sentinelEventReceived.Set();
+                    }
+                    else if (_actualEventCounts.TryGetValue(eventData.ProviderName, out _))
+                    {
+                        _actualEventCounts[eventData.ProviderName]++;
+                    }
+                    else
+                    {
+                        _actualEventCounts[eventData.ProviderName] = 1;
+                    }
+                };
+
+                if (_optionalTraceValidator != null)
+                {
+                    optionalTraceValidationCallback = _optionalTraceValidator(source);
+                }
+
+                source.Process();
+            });
+
+            readerTask.Start();
+            sentinelEventReceived.WaitOne();
+            _eventGeneratingAction();
+            EventPipeClient.StopTracing(processId, eventpipeSessionId);
+
+            readerTask.Wait();
+
+            foreach (var (provider, expectedCount) in _expectedEventCounts)
+            {
+                if (_actualEventCounts.TryGetValue(provider, out var actualCount))
+                {
+                    if (!expectedCount.Validate(actualCount))
+                    {
+                        return Fail($"Event count mismatch for provider \"{provider}\": expected {expectedCount}, but saw {actualCount}");
+                    }
+                }
+                else
+                {
+                    return Fail($"No events for provider \"{provider}\"");
+                }
+            }
+
+            if (optionalTraceValidationCallback != null)
+            {
+                return optionalTraceValidationCallback();
+            }
+            else
+            {
+                return 100;
+            }
+        }
+
+        public static int RunAndValidateEventCounts(
+            Dictionary<string, ExpectedEventCount> expectedEventCounts,
+            Action eventGeneratingAction,
+            SessionConfiguration? sessionConfiguration = null,
+            Func<EventPipeEventSource, Func<int>> optionalTraceValidator = null)
+        {
+            Console.WriteLine("TEST STARTING");
+            var test = new IpcTraceTest(expectedEventCounts, eventGeneratingAction, sessionConfiguration, optionalTraceValidator);
+            try
+            {
+                var ret = test.Validate();
+                if (ret == 100)
+                    Console.WriteLine("TEST PASSED!");
+                return ret;
+            }
+            catch (Exception e)
+            {
+                Console.WriteLine("TEST FAILED!");
+                Console.WriteLine(e);
+                return -1;
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/src/tracing/eventpipe/common/common.csproj b/tests/src/tracing/eventpipe/common/common.csproj
new file mode 100644 (file)
index 0000000..3624f90
--- /dev/null
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <SchemaVersion>2.0</SchemaVersion>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
+    <TargetFrameworkIdentifier>.NETCoreApp</TargetFrameworkIdentifier>
+    <ProjectGuid>{8E3244CB-407F-4142-BAAB-E7A55901A5FA}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+    <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
+    <CLRTestKind>SharedLibrary</CLRTestKind>
+    <!-- <DefineConstants>$(DefineConstants);STATIC</DefineConstants> -->
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <IsTestProject>false</IsTestProject>
+  </PropertyGroup>
+  <!-- Default configurations to help VS understand the configurations -->
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
+  </PropertyGroup>
+  <ItemGroup>
+    <CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
+      <Visible>False</Visible>
+    </CodeAnalysisDependentAssemblyPaths>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="IpcTraceTest.cs" />
+  </ItemGroup>
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
+</Project>
diff --git a/tests/src/tracing/eventpipe/providervalidation/providervalidation.cs b/tests/src/tracing/eventpipe/providervalidation/providervalidation.cs
new file mode 100644 (file)
index 0000000..2fd4693
--- /dev/null
@@ -0,0 +1,69 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Diagnostics.Tracing;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+using Microsoft.Diagnostics.Tools.RuntimeClient;
+using Microsoft.Diagnostics.Tracing;
+using Tracing.Tests.Common;
+
+namespace Tracing.Tests.ProviderValidation
+{
+    public sealed class MyEventSource : EventSource
+    {
+        private MyEventSource() {}
+        public static MyEventSource Log = new MyEventSource();
+        public void MyEvent() { WriteEvent(1, "MyEvent"); }
+    }
+
+    public class ProviderValidation
+    {
+        public static int Main(string[] args)
+        {
+            // This test validates that the rundown events are present
+            // and that providers turned on that generate events are being written to
+            // the stream.
+
+            var providers = new List<Provider>()
+            {
+                new Provider("MyEventSource"),
+                new Provider("Microsoft-DotNETCore-SampleProfiler")
+            };
+
+            var tests = new int[] { 4, 10 }
+                .Select(x => (uint)Math.Pow(2, x))
+                .Select(bufferSize => new SessionConfiguration(circularBufferSizeMB: bufferSize, format: EventPipeSerializationFormat.NetTrace,  providers: providers))
+                .Select<SessionConfiguration, Func<int>>(configuration => () => IpcTraceTest.RunAndValidateEventCounts(_expectedEventCounts, _eventGeneratingAction, configuration));
+
+            foreach (var test in tests)
+            {
+                var ret = test();
+                if (ret < 0)
+                    return ret;
+            }
+
+            return 100;
+        }
+
+        private static Dictionary<string, ExpectedEventCount> _expectedEventCounts = new Dictionary<string, ExpectedEventCount>()
+        {
+            { "MyEventSource", new ExpectedEventCount(1000, 0.30f) },
+            { "Microsoft-Windows-DotNETRuntimeRundown", -1 },
+            { "Microsoft-DotNETCore-SampleProfiler", -1 }
+        };
+
+        private static Action _eventGeneratingAction = () => 
+        {
+            foreach (var _ in Enumerable.Range(0,1000))
+            {
+                MyEventSource.Log.MyEvent();
+            }
+        };
+    }
+}
\ No newline at end of file
diff --git a/tests/src/tracing/eventpipe/providervalidation/providervalidation.csproj b/tests/src/tracing/eventpipe/providervalidation/providervalidation.csproj
new file mode 100644 (file)
index 0000000..adfb425
--- /dev/null
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <SchemaVersion>2.0</SchemaVersion>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
+    <TargetFrameworkIdentifier>.NETCoreApp</TargetFrameworkIdentifier>
+    <ProjectGuid>{8E3244CB-407F-4142-BAAB-E7A55901A5FA}</ProjectGuid>
+    <OutputType>exe</OutputType>
+    <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+    <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
+    <CLRTestKind>BuildAndRun</CLRTestKind>
+    <DefineConstants>$(DefineConstants);STATIC</DefineConstants>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <CLRTestPriority>0</CLRTestPriority>
+  </PropertyGroup>
+  <!-- Default configurations to help VS understand the configurations -->
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
+  </PropertyGroup>
+  <ItemGroup>
+    <CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
+      <Visible>False</Visible>
+    </CodeAnalysisDependentAssemblyPaths>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="providervalidation.cs" />
+    <ProjectReference Include="../common/common.csproj" />
+  </ItemGroup>
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
+</Project>
diff --git a/tests/src/tracing/eventpipe/rundownvalidation/rundownvalidation.cs b/tests/src/tracing/eventpipe/rundownvalidation/rundownvalidation.cs
new file mode 100644 (file)
index 0000000..2482081
--- /dev/null
@@ -0,0 +1,55 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Diagnostics.Tracing;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+using Microsoft.Diagnostics.Tools.RuntimeClient;
+using Microsoft.Diagnostics.Tracing;
+using Tracing.Tests.Common;
+using Microsoft.Diagnostics.Tracing.Parsers.Clr;
+
+namespace Tracing.Tests.RundownValidation
+{
+
+    public class RundownValidation
+    {
+        public static int Main(string[] args)
+        {
+            // This test validates that the rundown events are present
+            // and that the rundown contains the necessary events to get
+            // symbols in a nettrace file.
+
+            var providers = new List<Provider>()
+            {
+                new Provider("Microsoft-DotNETCore-SampleProfiler")
+            };
+
+            var configuration = new SessionConfiguration(circularBufferSizeMB: 1024, format: EventPipeSerializationFormat.NetTrace,  providers: providers);
+            return IpcTraceTest.RunAndValidateEventCounts(_expectedEventCounts, _eventGeneratingAction, configuration, _DoesRundownContainMethodEvents);
+        }
+
+        private static Dictionary<string, ExpectedEventCount> _expectedEventCounts = new Dictionary<string, ExpectedEventCount>()
+        {
+            { "Microsoft-Windows-DotNETRuntimeRundown", -1 }
+        };
+
+        // We only care about rundown so skip generating any events.
+        private static Action _eventGeneratingAction = () => { };
+
+        private static Func<EventPipeEventSource, Func<int>> _DoesRundownContainMethodEvents = (source) =>
+        {
+            bool hasMethodDCStopVerbose = false;
+            bool hasMethodILToNativeMap = false;
+            ClrRundownTraceEventParser rundownParser = new ClrRundownTraceEventParser(source);
+            rundownParser.MethodDCStopVerbose += (eventData) => hasMethodDCStopVerbose = true;
+            rundownParser.MethodILToNativeMapDCStop += (eventData) => hasMethodILToNativeMap = true;
+            return () => hasMethodDCStopVerbose && hasMethodILToNativeMap ? 100 : -1;
+        };
+    }
+}
\ No newline at end of file
diff --git a/tests/src/tracing/eventpipe/rundownvalidation/rundownvalidation.csproj b/tests/src/tracing/eventpipe/rundownvalidation/rundownvalidation.csproj
new file mode 100644 (file)
index 0000000..df7e427
--- /dev/null
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <SchemaVersion>2.0</SchemaVersion>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
+    <TargetFrameworkIdentifier>.NETCoreApp</TargetFrameworkIdentifier>
+    <ProjectGuid>{8E3244CB-407F-4142-BAAB-E7A55901A5FA}</ProjectGuid>
+    <OutputType>exe</OutputType>
+    <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+    <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
+    <CLRTestKind>BuildAndRun</CLRTestKind>
+    <DefineConstants>$(DefineConstants);STATIC</DefineConstants>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <CLRTestPriority>0</CLRTestPriority>
+  </PropertyGroup>
+  <!-- Default configurations to help VS understand the configurations -->
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
+  </PropertyGroup>
+  <ItemGroup>
+    <CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
+      <Visible>False</Visible>
+    </CodeAnalysisDependentAssemblyPaths>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="rundownvalidation.cs" />
+    <ProjectReference Include="../common/common.csproj" />
+  </ItemGroup>
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
+</Project>