From 13adf2c5fc908739587a26acb4399b071c19f2b4 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 7 Oct 2025 00:59:13 +0000
Subject: [PATCH 1/7] Initial plan
From 58b03b835a40de23772b22eccdc6e407ec2dffa9 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 7 Oct 2025 01:09:00 +0000
Subject: [PATCH 2/7] Create initial TraceParserGen.Tests project with test
framework
Co-authored-by: brianrob <6210322+brianrob@users.noreply.github.com>
---
PerfView.sln | 15 +
.../ParserGenerationTests.cs | 280 ++++++++++++++++++
src/TraceParserGen.Tests/TestBase.cs | 109 +++++++
.../TraceParserGen.Tests.csproj | 40 +++
.../inputs/SimpleTest.manifest.xml | 54 ++++
5 files changed, 498 insertions(+)
create mode 100644 src/TraceParserGen.Tests/ParserGenerationTests.cs
create mode 100644 src/TraceParserGen.Tests/TestBase.cs
create mode 100644 src/TraceParserGen.Tests/TraceParserGen.Tests.csproj
create mode 100644 src/TraceParserGen.Tests/inputs/SimpleTest.manifest.xml
diff --git a/PerfView.sln b/PerfView.sln
index e55ed9e79..acffcf78b 100644
--- a/PerfView.sln
+++ b/PerfView.sln
@@ -87,6 +87,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{1CAEF854-292
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PerfView.Tutorial", "src\PerfView.Tutorial\PerfView.Tutorial.csproj", "{DE35BED9-0E03-4DAC-A003-1ACBBF816973}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TraceParserGen.Tests", "src\TraceParserGen.Tests\TraceParserGen.Tests.csproj", "{F127C664-2F56-429B-BAA6-636034F766EF}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -405,12 +407,25 @@ Global
{DE35BED9-0E03-4DAC-A003-1ACBBF816973}.Release|x64.Build.0 = Release|Any CPU
{DE35BED9-0E03-4DAC-A003-1ACBBF816973}.Release|x86.ActiveCfg = Release|Any CPU
{DE35BED9-0E03-4DAC-A003-1ACBBF816973}.Release|x86.Build.0 = Release|Any CPU
+ {F127C664-2F56-429B-BAA6-636034F766EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F127C664-2F56-429B-BAA6-636034F766EF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F127C664-2F56-429B-BAA6-636034F766EF}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {F127C664-2F56-429B-BAA6-636034F766EF}.Debug|x64.Build.0 = Debug|Any CPU
+ {F127C664-2F56-429B-BAA6-636034F766EF}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {F127C664-2F56-429B-BAA6-636034F766EF}.Debug|x86.Build.0 = Debug|Any CPU
+ {F127C664-2F56-429B-BAA6-636034F766EF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F127C664-2F56-429B-BAA6-636034F766EF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F127C664-2F56-429B-BAA6-636034F766EF}.Release|x64.ActiveCfg = Release|Any CPU
+ {F127C664-2F56-429B-BAA6-636034F766EF}.Release|x64.Build.0 = Release|Any CPU
+ {F127C664-2F56-429B-BAA6-636034F766EF}.Release|x86.ActiveCfg = Release|Any CPU
+ {F127C664-2F56-429B-BAA6-636034F766EF}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{DE35BED9-0E03-4DAC-A003-1ACBBF816973} = {1CAEF854-2923-45FA-ACB8-6523A7E45896}
+ {F127C664-2F56-429B-BAA6-636034F766EF} = {1CAEF854-2923-45FA-ACB8-6523A7E45896}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {9F85A2A3-E0DF-4826-9BBA-4DFFA0F17150}
diff --git a/src/TraceParserGen.Tests/ParserGenerationTests.cs b/src/TraceParserGen.Tests/ParserGenerationTests.cs
new file mode 100644
index 000000000..41c318a19
--- /dev/null
+++ b/src/TraceParserGen.Tests/ParserGenerationTests.cs
@@ -0,0 +1,280 @@
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Text;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace TraceParserGen.Tests
+{
+ ///
+ /// Tests for TraceParserGen.exe that validate it can generate parsers from manifests
+ ///
+ public class ParserGenerationTests : TestBase
+ {
+ public ParserGenerationTests(ITestOutputHelper output) : base(output)
+ {
+ }
+
+ [Fact]
+ public void CanGenerateParserFromManifest()
+ {
+ // Arrange
+ string manifestPath = Path.Combine(TestDataDir, "SimpleTest.manifest.xml");
+ string outputCsPath = Path.Combine(OutputDir, "SimpleTestParser.cs");
+
+ Output.WriteLine($"Manifest: {manifestPath}");
+ Output.WriteLine($"Output: {outputCsPath}");
+
+ Assert.True(File.Exists(manifestPath), $"Manifest file not found: {manifestPath}");
+
+ // Act - Step 1: Run TraceParserGen.exe
+ string traceParserGenPath = GetTraceParserGenExePath();
+ Output.WriteLine($"TraceParserGen.exe: {traceParserGenPath}");
+
+ var exitCode = RunTraceParserGen(traceParserGenPath, manifestPath, outputCsPath);
+
+ // Assert - Step 1: Verify TraceParserGen succeeded
+ Assert.Equal(0, exitCode);
+ Assert.True(File.Exists(outputCsPath), $"Generated C# file not found: {outputCsPath}");
+
+ // Verify the generated file has expected content
+ string generatedContent = File.ReadAllText(outputCsPath);
+ Assert.Contains("class", generatedContent);
+ Assert.Contains("TraceEventParser", generatedContent);
+
+ Output.WriteLine("Successfully generated parser from manifest");
+
+ // Act - Step 2: Create and build a test console application
+ string testProjectDir = Path.Combine(OutputDir, "TestApp");
+ Directory.CreateDirectory(testProjectDir);
+
+ CreateTestConsoleApp(testProjectDir, outputCsPath);
+
+ // Act - Step 3: Build the test application
+ var buildExitCode = BuildTestApp(testProjectDir);
+ Assert.Equal(0, buildExitCode);
+
+ // Act - Step 4: Run the test application
+ var runExitCode = RunTestApp(testProjectDir);
+
+ // Assert - Step 4: Verify test app ran successfully (no crashes, no asserts)
+ Assert.Equal(0, runExitCode);
+
+ Output.WriteLine("Test completed successfully");
+ }
+
+ private int RunTraceParserGen(string exePath, string manifestPath, string outputPath)
+ {
+ ProcessStartInfo startInfo;
+
+ // On Linux/Mac, we need to use mono to run .NET Framework executables
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ startInfo = new ProcessStartInfo
+ {
+ FileName = exePath,
+ Arguments = $"\"{manifestPath}\" \"{outputPath}\"",
+ UseShellExecute = false,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ CreateNoWindow = true
+ };
+ }
+ else
+ {
+ startInfo = new ProcessStartInfo
+ {
+ FileName = "mono",
+ Arguments = $"\"{exePath}\" \"{manifestPath}\" \"{outputPath}\"",
+ UseShellExecute = false,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ CreateNoWindow = true
+ };
+ }
+
+ Output.WriteLine($"Running: {startInfo.FileName} {startInfo.Arguments}");
+
+ using (var process = Process.Start(startInfo))
+ {
+ var output = process.StandardOutput.ReadToEnd();
+ var error = process.StandardError.ReadToEnd();
+
+ process.WaitForExit();
+
+ if (!string.IsNullOrWhiteSpace(output))
+ {
+ Output.WriteLine("STDOUT:");
+ Output.WriteLine(output);
+ }
+
+ if (!string.IsNullOrWhiteSpace(error))
+ {
+ Output.WriteLine("STDERR:");
+ Output.WriteLine(error);
+ }
+
+ return process.ExitCode;
+ }
+ }
+
+ private void CreateTestConsoleApp(string projectDir, string generatedParserPath)
+ {
+ // Create the .csproj file
+ string csprojContent = $@"
+
+ Exe
+ net462
+ true
+
+
+
+ {GetTraceEventAssemblyPath()}
+
+
+";
+
+ File.WriteAllText(Path.Combine(projectDir, "TestApp.csproj"), csprojContent);
+
+ // Copy the generated parser file
+ string destParserPath = Path.Combine(projectDir, Path.GetFileName(generatedParserPath));
+ File.Copy(generatedParserPath, destParserPath, true);
+
+ // Create Program.cs that uses reflection to instantiate parsers
+ string programContent = @"using System;
+using System.Linq;
+using System.Reflection;
+using Microsoft.Diagnostics.Tracing;
+
+class Program
+{
+ static int Main(string[] args)
+ {
+ try
+ {
+ Console.WriteLine(""Starting parser test..."");
+
+ // Find all TraceEventParser-derived types in the current assembly
+ var assembly = Assembly.GetExecutingAssembly();
+ var parserTypes = assembly.GetTypes()
+ .Where(t => typeof(TraceEventParser).IsAssignableFrom(t) && !t.IsAbstract)
+ .ToList();
+
+ Console.WriteLine($""Found {parserTypes.Count} parser type(s)"");
+
+ foreach (var parserType in parserTypes)
+ {
+ Console.WriteLine($"" Testing parser: {parserType.Name}"");
+
+ // Create an instance of the parser
+ // TraceEventParser constructors typically take a TraceEventSource parameter
+ // Since we don't have a real source, we'll just verify the type can be instantiated
+ // by checking if it has expected methods
+
+ var enumerateMethod = parserType.GetMethod(""EnumerateTemplates"",
+ BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
+
+ if (enumerateMethod != null)
+ {
+ Console.WriteLine($"" Found EnumerateTemplates method"");
+ }
+ else
+ {
+ Console.WriteLine($"" WARNING: EnumerateTemplates method not found"");
+ }
+ }
+
+ Console.WriteLine(""Parser test completed successfully"");
+ return 0;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($""ERROR: {ex.Message}"");
+ Console.WriteLine(ex.StackTrace);
+ return 1;
+ }
+ }
+}";
+
+ File.WriteAllText(Path.Combine(projectDir, "Program.cs"), programContent);
+ }
+
+ private int BuildTestApp(string projectDir)
+ {
+ var startInfo = new ProcessStartInfo
+ {
+ FileName = "dotnet",
+ Arguments = "build -c Release",
+ WorkingDirectory = projectDir,
+ UseShellExecute = false,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ CreateNoWindow = true
+ };
+
+ Output.WriteLine($"Building test app in: {projectDir}");
+
+ using (var process = Process.Start(startInfo))
+ {
+ var output = process.StandardOutput.ReadToEnd();
+ var error = process.StandardError.ReadToEnd();
+
+ process.WaitForExit();
+
+ if (!string.IsNullOrWhiteSpace(output))
+ {
+ Output.WriteLine("Build STDOUT:");
+ Output.WriteLine(output);
+ }
+
+ if (!string.IsNullOrWhiteSpace(error))
+ {
+ Output.WriteLine("Build STDERR:");
+ Output.WriteLine(error);
+ }
+
+ return process.ExitCode;
+ }
+ }
+
+ private int RunTestApp(string projectDir)
+ {
+ var startInfo = new ProcessStartInfo
+ {
+ FileName = "dotnet",
+ Arguments = "run -c Release --no-build",
+ WorkingDirectory = projectDir,
+ UseShellExecute = false,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ CreateNoWindow = true
+ };
+
+ Output.WriteLine($"Running test app in: {projectDir}");
+
+ using (var process = Process.Start(startInfo))
+ {
+ var output = process.StandardOutput.ReadToEnd();
+ var error = process.StandardError.ReadToEnd();
+
+ process.WaitForExit();
+
+ if (!string.IsNullOrWhiteSpace(output))
+ {
+ Output.WriteLine("Run STDOUT:");
+ Output.WriteLine(output);
+ }
+
+ if (!string.IsNullOrWhiteSpace(error))
+ {
+ Output.WriteLine("Run STDERR:");
+ Output.WriteLine(error);
+ }
+
+ return process.ExitCode;
+ }
+ }
+ }
+}
diff --git a/src/TraceParserGen.Tests/TestBase.cs b/src/TraceParserGen.Tests/TestBase.cs
new file mode 100644
index 000000000..2ce3fbb3c
--- /dev/null
+++ b/src/TraceParserGen.Tests/TestBase.cs
@@ -0,0 +1,109 @@
+using System;
+using System.IO;
+using Xunit.Abstractions;
+
+namespace TraceParserGen.Tests
+{
+ ///
+ /// Base class for TraceParserGen tests
+ ///
+ public abstract class TestBase
+ {
+ protected static string OriginalInputDir = FindInputDir();
+ protected static string TestDataDir = Path.GetFullPath("inputs");
+ protected static string BaseOutputDir = Path.GetFullPath("output");
+
+ protected TestBase(ITestOutputHelper output)
+ {
+ Output = output;
+ OutputDir = Path.Combine(BaseOutputDir, Guid.NewGuid().ToString("N").Substring(0, 8));
+
+ // Ensure output directory exists
+ if (!Directory.Exists(OutputDir))
+ {
+ Directory.CreateDirectory(OutputDir);
+ }
+ }
+
+ protected ITestOutputHelper Output { get; }
+
+ protected string OutputDir { get; }
+
+ ///
+ /// Finds the input directory for test files
+ ///
+ private static string FindInputDir()
+ {
+ string dir = Environment.CurrentDirectory;
+ while (dir != null)
+ {
+ string candidate = Path.Combine(dir, @"TraceParserGen.Tests\inputs");
+ if (Directory.Exists(candidate))
+ {
+ return Path.GetFullPath(candidate);
+ }
+
+ dir = Path.GetDirectoryName(dir);
+ }
+ return @"%PERFVIEW%\src\TraceParserGen.Tests\inputs";
+ }
+
+ ///
+ /// Gets the path to the TraceParserGen.exe executable
+ ///
+ protected string GetTraceParserGenExePath()
+ {
+ // Search for TraceParserGen.exe in common build output locations
+ string[] searchPaths = new[]
+ {
+ // Relative to test output directory
+ Path.Combine(Environment.CurrentDirectory, @"../../../../TraceParserGen/bin/Release/net462/TraceParserGen.exe"),
+ Path.Combine(Environment.CurrentDirectory, @"../../../../TraceParserGen/bin/Debug/net462/TraceParserGen.exe"),
+
+ // Also try from bin directory (for net462 tests)
+ Path.Combine(Environment.CurrentDirectory, @"../../../TraceParserGen/bin/Release/net462/TraceParserGen.exe"),
+ Path.Combine(Environment.CurrentDirectory, @"../../../TraceParserGen/bin/Debug/net462/TraceParserGen.exe"),
+ };
+
+ foreach (var path in searchPaths)
+ {
+ var fullPath = Path.GetFullPath(path);
+ if (File.Exists(fullPath))
+ {
+ return fullPath;
+ }
+ }
+
+ throw new FileNotFoundException("Could not find TraceParserGen.exe. Please build the TraceParserGen project first.");
+ }
+
+ ///
+ /// Gets the path to the TraceEvent assembly
+ ///
+ protected string GetTraceEventAssemblyPath()
+ {
+ // Search for TraceEvent DLL in common build output locations
+ string[] searchPaths = new[]
+ {
+ // Relative to test output directory
+ Path.Combine(Environment.CurrentDirectory, @"../../../../TraceEvent/bin/Release/netstandard2.0/Microsoft.Diagnostics.Tracing.TraceEvent.dll"),
+ Path.Combine(Environment.CurrentDirectory, @"../../../../TraceEvent/bin/Debug/netstandard2.0/Microsoft.Diagnostics.Tracing.TraceEvent.dll"),
+
+ // Also try from bin directory (for net462 tests)
+ Path.Combine(Environment.CurrentDirectory, @"../../../TraceEvent/bin/Release/netstandard2.0/Microsoft.Diagnostics.Tracing.TraceEvent.dll"),
+ Path.Combine(Environment.CurrentDirectory, @"../../../TraceEvent/bin/Debug/netstandard2.0/Microsoft.Diagnostics.Tracing.TraceEvent.dll"),
+ };
+
+ foreach (var path in searchPaths)
+ {
+ var fullPath = Path.GetFullPath(path);
+ if (File.Exists(fullPath))
+ {
+ return fullPath;
+ }
+ }
+
+ throw new FileNotFoundException("Could not find Microsoft.Diagnostics.Tracing.TraceEvent.dll. Please build the TraceEvent project first.");
+ }
+ }
+}
diff --git a/src/TraceParserGen.Tests/TraceParserGen.Tests.csproj b/src/TraceParserGen.Tests/TraceParserGen.Tests.csproj
new file mode 100644
index 000000000..5d541566c
--- /dev/null
+++ b/src/TraceParserGen.Tests/TraceParserGen.Tests.csproj
@@ -0,0 +1,40 @@
+
+
+
+
+ net462;net8.0
+ TraceParserGen.Tests
+ TraceParserGen.Tests
+ Unit tests for TraceParserGen.
+ Copyright © Microsoft 2024
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+ True
+
+
+
diff --git a/src/TraceParserGen.Tests/inputs/SimpleTest.manifest.xml b/src/TraceParserGen.Tests/inputs/SimpleTest.manifest.xml
new file mode 100644
index 000000000..cf73a47d8
--- /dev/null
+++ b/src/TraceParserGen.Tests/inputs/SimpleTest.manifest.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
From 1d97363317946baf5d353b156c3f2dd259f71cd4 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 7 Oct 2025 01:10:47 +0000
Subject: [PATCH 3/7] Add README and platform-aware test skipping
Co-authored-by: brianrob <6210322+brianrob@users.noreply.github.com>
---
.../ParserGenerationTests.cs | 8 ++
src/TraceParserGen.Tests/README.md | 85 +++++++++++++++++++
2 files changed, 93 insertions(+)
create mode 100644 src/TraceParserGen.Tests/README.md
diff --git a/src/TraceParserGen.Tests/ParserGenerationTests.cs b/src/TraceParserGen.Tests/ParserGenerationTests.cs
index 41c318a19..c9f726c25 100644
--- a/src/TraceParserGen.Tests/ParserGenerationTests.cs
+++ b/src/TraceParserGen.Tests/ParserGenerationTests.cs
@@ -20,6 +20,14 @@ public ParserGenerationTests(ITestOutputHelper output) : base(output)
[Fact]
public void CanGenerateParserFromManifest()
{
+ // Skip on non-Windows platforms since TraceParserGen.exe is a .NET Framework app
+ // In a real environment, this would run on Windows with proper .NET Framework support
+ if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ Output.WriteLine("Skipping test on non-Windows platform. TraceParserGen.exe requires .NET Framework.");
+ return;
+ }
+
// Arrange
string manifestPath = Path.Combine(TestDataDir, "SimpleTest.manifest.xml");
string outputCsPath = Path.Combine(OutputDir, "SimpleTestParser.cs");
diff --git a/src/TraceParserGen.Tests/README.md b/src/TraceParserGen.Tests/README.md
new file mode 100644
index 000000000..147c326fa
--- /dev/null
+++ b/src/TraceParserGen.Tests/README.md
@@ -0,0 +1,85 @@
+# TraceParserGen.Tests
+
+This project contains automated tests for the TraceParserGen tool, which generates C# parser classes from ETW manifests or EventSource implementations.
+
+## Overview
+
+TraceParserGen.Tests implements a comprehensive test framework that validates the entire code generation pipeline:
+
+1. **Run TraceParserGen.exe** with test input (manifest file or EventSource DLL)
+2. **Verify successful generation** of C# parser files
+3. **Create a temporary console project** that references TraceEvent and includes the generated parser
+4. **Build the temporary project** to ensure the generated code compiles
+5. **Run the test application** to verify no runtime errors or assertions occur
+
+## Test Structure
+
+### Test Files
+
+- **ParserGenerationTests.cs**: Main test class containing test cases for parser generation
+- **TestBase.cs**: Base class providing common test infrastructure and helper methods
+- **inputs/**: Directory containing test input files (manifests, sample DLLs)
+
+### Sample Test
+
+The `CanGenerateParserFromManifest` test demonstrates the full pipeline:
+- Uses a simple ETW manifest (`SimpleTest.manifest.xml`) as input
+- Generates a parser class from the manifest
+- Creates a temporary console app that uses reflection to find and instantiate the parser
+- Builds and runs the console app to ensure everything works
+
+## Requirements
+
+- **Windows**: Tests require Windows with .NET Framework support to run TraceParserGen.exe
+- **TraceParserGen**: The TraceParserGen project must be built before running tests
+- **TraceEvent**: The TraceEvent project must be built before running tests
+
+## Running Tests
+
+```bash
+# Build dependencies first
+dotnet build src/TraceParserGen/TraceParserGen.csproj -c Release
+dotnet build src/TraceEvent/TraceEvent.csproj -c Release
+
+# Run tests
+dotnet test src/TraceParserGen.Tests/TraceParserGen.Tests.csproj -c Release
+```
+
+## Adding New Tests
+
+To add a new test case:
+
+1. Add your test input file (manifest or DLL) to the `inputs/` directory
+2. Create a new test method in `ParserGenerationTests.cs`
+3. Follow the pattern of the existing `CanGenerateParserFromManifest` test:
+ - Call `RunTraceParserGen()` to generate the parser
+ - Call `CreateTestConsoleApp()` to create a test application
+ - Call `BuildTestApp()` to build the test application
+ - Call `RunTestApp()` to verify the generated code works
+
+## Test Console Application
+
+The test creates a temporary console application that:
+- References the Microsoft.Diagnostics.Tracing.TraceEvent library
+- Includes the generated parser C# file
+- Uses reflection to discover all TraceEventParser-derived types
+- Verifies the parsers can be instantiated and have the expected methods
+
+This approach allows us to test that the generated code:
+- Compiles successfully
+- Contains valid C# syntax
+- Implements the expected TraceEventParser interface
+- Can be used in a real application
+
+## Platform Notes
+
+Tests are designed to run on Windows where TraceParserGen.exe (a .NET Framework application) can run natively. On non-Windows platforms, tests will skip with an informational message.
+
+## Future Enhancements
+
+Potential improvements to the test framework:
+- Add tests for EventSource-based parser generation (using DLLs as input)
+- Add tests for complex manifest scenarios (multiple providers, complex templates)
+- Add validation of generated parser output against expected baselines
+- Add performance benchmarks for parser generation
+- Add tests that actually parse ETL files with the generated parsers
From 3fd081c865c745d921d254316566069c86a9e520 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 7 Oct 2025 03:01:35 +0000
Subject: [PATCH 4/7] Address PR feedback: improve test framework with trace
file validation
Co-authored-by: brianrob <6210322+brianrob@users.noreply.github.com>
---
.../ParserGenerationTests.cs | 151 +++++++++++-------
src/TraceParserGen.Tests/TestBase.cs | 55 +------
.../TraceParserGen.Tests.csproj | 11 ++
3 files changed, 110 insertions(+), 107 deletions(-)
diff --git a/src/TraceParserGen.Tests/ParserGenerationTests.cs b/src/TraceParserGen.Tests/ParserGenerationTests.cs
index c9f726c25..0d20db71c 100644
--- a/src/TraceParserGen.Tests/ParserGenerationTests.cs
+++ b/src/TraceParserGen.Tests/ParserGenerationTests.cs
@@ -75,33 +75,15 @@ public void CanGenerateParserFromManifest()
private int RunTraceParserGen(string exePath, string manifestPath, string outputPath)
{
- ProcessStartInfo startInfo;
-
- // On Linux/Mac, we need to use mono to run .NET Framework executables
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
- {
- startInfo = new ProcessStartInfo
- {
- FileName = exePath,
- Arguments = $"\"{manifestPath}\" \"{outputPath}\"",
- UseShellExecute = false,
- RedirectStandardOutput = true,
- RedirectStandardError = true,
- CreateNoWindow = true
- };
- }
- else
+ var startInfo = new ProcessStartInfo
{
- startInfo = new ProcessStartInfo
- {
- FileName = "mono",
- Arguments = $"\"{exePath}\" \"{manifestPath}\" \"{outputPath}\"",
- UseShellExecute = false,
- RedirectStandardOutput = true,
- RedirectStandardError = true,
- CreateNoWindow = true
- };
- }
+ FileName = exePath,
+ Arguments = $"\"{manifestPath}\" \"{outputPath}\"",
+ UseShellExecute = false,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ CreateNoWindow = true
+ };
Output.WriteLine($"Running: {startInfo.FileName} {startInfo.Arguments}");
@@ -130,6 +112,10 @@ private int RunTraceParserGen(string exePath, string manifestPath, string output
private void CreateTestConsoleApp(string projectDir, string generatedParserPath)
{
+ // Get the path to TraceEvent assembly - it's in the test project's output directory
+ // since we have a ProjectReference
+ string traceEventAssembly = Path.Combine(Environment.CurrentDirectory, "Microsoft.Diagnostics.Tracing.TraceEvent.dll");
+
// Create the .csproj file
string csprojContent = $@"
@@ -139,7 +125,7 @@ private void CreateTestConsoleApp(string projectDir, string generatedParserPath)
- {GetTraceEventAssemblyPath()}
+ {traceEventAssembly}
";
@@ -150,18 +136,27 @@ private void CreateTestConsoleApp(string projectDir, string generatedParserPath)
string destParserPath = Path.Combine(projectDir, Path.GetFileName(generatedParserPath));
File.Copy(generatedParserPath, destParserPath, true);
- // Create Program.cs that uses reflection to instantiate parsers
- string programContent = @"using System;
+ // Create a simple trace file to use for testing
+ // We'll use one of the existing test trace files
+ string sampleTracePath = Path.Combine(TestDataDir, "..", "..", "TraceEvent", "TraceEvent.Tests", "inputs", "net.4.5.x86.etl.zip");
+ if (!File.Exists(sampleTracePath))
+ {
+ // If the sample trace doesn't exist, we'll skip the trace file test
+ sampleTracePath = "";
+ }
+
+ // Create Program.cs that uses the generated parser with a real trace file
+ string programContent = $@"using System;
using System.Linq;
using System.Reflection;
using Microsoft.Diagnostics.Tracing;
class Program
-{
+{{
static int Main(string[] args)
- {
+ {{
try
- {
+ {{
Console.WriteLine(""Starting parser test..."");
// Find all TraceEventParser-derived types in the current assembly
@@ -170,41 +165,79 @@ static int Main(string[] args)
.Where(t => typeof(TraceEventParser).IsAssignableFrom(t) && !t.IsAbstract)
.ToList();
- Console.WriteLine($""Found {parserTypes.Count} parser type(s)"");
+ Console.WriteLine($""Found {{parserTypes.Count}} parser type(s)"");
- foreach (var parserType in parserTypes)
- {
- Console.WriteLine($"" Testing parser: {parserType.Name}"");
-
- // Create an instance of the parser
- // TraceEventParser constructors typically take a TraceEventSource parameter
- // Since we don't have a real source, we'll just verify the type can be instantiated
- // by checking if it has expected methods
+ if (parserTypes.Count == 0)
+ {{
+ Console.WriteLine(""ERROR: No parser types found"");
+ return 1;
+ }}
+
+ // Test with a trace file if provided
+ string traceFilePath = args.Length > 0 ? args[0] : ""{sampleTracePath.Replace("\\", "\\\\")}"";
+
+ if (!string.IsNullOrEmpty(traceFilePath) && System.IO.File.Exists(traceFilePath))
+ {{
+ Console.WriteLine($""Using trace file: {{traceFilePath}}"");
- var enumerateMethod = parserType.GetMethod(""EnumerateTemplates"",
- BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
+ using (var source = TraceEventDispatcher.GetDispatcherFromFileName(traceFilePath))
+ {{
+ foreach (var parserType in parserTypes)
+ {{
+ Console.WriteLine($"" Testing parser: {{parserType.Name}}"");
+
+ // Create an instance of the parser
+ var parser = (TraceEventParser)Activator.CreateInstance(parserType, source);
+
+ int eventCount = 0;
+
+ // Hook the All event to count events processed by this parser
+ parser.All += (TraceEvent data) =>
+ {{
+ eventCount++;
+ }};
+
+ // Process the trace (this will trigger events if any match)
+ source.Process();
+
+ Console.WriteLine($"" Processed {{eventCount}} event(s) from this parser"");
+ }}
+ }}
+ }}
+ else
+ {{
+ Console.WriteLine(""No trace file available, skipping trace processing test"");
- if (enumerateMethod != null)
- {
- Console.WriteLine($"" Found EnumerateTemplates method"");
- }
- else
- {
- Console.WriteLine($"" WARNING: EnumerateTemplates method not found"");
- }
- }
+ // Just verify we can reflect on the parser types
+ foreach (var parserType in parserTypes)
+ {{
+ Console.WriteLine($"" Validating parser: {{parserType.Name}}"");
+
+ var enumerateMethod = parserType.GetMethod(""EnumerateTemplates"",
+ BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
+
+ if (enumerateMethod != null)
+ {{
+ Console.WriteLine($"" Found EnumerateTemplates method"");
+ }}
+ else
+ {{
+ Console.WriteLine($"" WARNING: EnumerateTemplates method not found"");
+ }}
+ }}
+ }}
Console.WriteLine(""Parser test completed successfully"");
return 0;
- }
+ }}
catch (Exception ex)
- {
- Console.WriteLine($""ERROR: {ex.Message}"");
+ {{
+ Console.WriteLine($""ERROR: {{ex.Message}}"");
Console.WriteLine(ex.StackTrace);
return 1;
- }
- }
-}";
+ }}
+ }}
+}}";
File.WriteAllText(Path.Combine(projectDir, "Program.cs"), programContent);
}
@@ -252,7 +285,7 @@ private int RunTestApp(string projectDir)
var startInfo = new ProcessStartInfo
{
FileName = "dotnet",
- Arguments = "run -c Release --no-build",
+ Arguments = "run -c Release",
WorkingDirectory = projectDir,
UseShellExecute = false,
RedirectStandardOutput = true,
diff --git a/src/TraceParserGen.Tests/TestBase.cs b/src/TraceParserGen.Tests/TestBase.cs
index 2ce3fbb3c..748a1e46f 100644
--- a/src/TraceParserGen.Tests/TestBase.cs
+++ b/src/TraceParserGen.Tests/TestBase.cs
@@ -53,57 +53,16 @@ private static string FindInputDir()
///
protected string GetTraceParserGenExePath()
{
- // Search for TraceParserGen.exe in common build output locations
- string[] searchPaths = new[]
- {
- // Relative to test output directory
- Path.Combine(Environment.CurrentDirectory, @"../../../../TraceParserGen/bin/Release/net462/TraceParserGen.exe"),
- Path.Combine(Environment.CurrentDirectory, @"../../../../TraceParserGen/bin/Debug/net462/TraceParserGen.exe"),
-
- // Also try from bin directory (for net462 tests)
- Path.Combine(Environment.CurrentDirectory, @"../../../TraceParserGen/bin/Release/net462/TraceParserGen.exe"),
- Path.Combine(Environment.CurrentDirectory, @"../../../TraceParserGen/bin/Debug/net462/TraceParserGen.exe"),
- };
-
- foreach (var path in searchPaths)
+ // TraceParserGen.exe is copied to the output directory during build
+ string exePath = Path.Combine(Environment.CurrentDirectory, "TraceParserGen.exe");
+
+ if (!File.Exists(exePath))
{
- var fullPath = Path.GetFullPath(path);
- if (File.Exists(fullPath))
- {
- return fullPath;
- }
+ throw new FileNotFoundException($"Could not find TraceParserGen.exe at {exePath}. Please build the TraceParserGen.Tests project.");
}
-
- throw new FileNotFoundException("Could not find TraceParserGen.exe. Please build the TraceParserGen project first.");
+
+ return exePath;
}
- ///
- /// Gets the path to the TraceEvent assembly
- ///
- protected string GetTraceEventAssemblyPath()
- {
- // Search for TraceEvent DLL in common build output locations
- string[] searchPaths = new[]
- {
- // Relative to test output directory
- Path.Combine(Environment.CurrentDirectory, @"../../../../TraceEvent/bin/Release/netstandard2.0/Microsoft.Diagnostics.Tracing.TraceEvent.dll"),
- Path.Combine(Environment.CurrentDirectory, @"../../../../TraceEvent/bin/Debug/netstandard2.0/Microsoft.Diagnostics.Tracing.TraceEvent.dll"),
-
- // Also try from bin directory (for net462 tests)
- Path.Combine(Environment.CurrentDirectory, @"../../../TraceEvent/bin/Release/netstandard2.0/Microsoft.Diagnostics.Tracing.TraceEvent.dll"),
- Path.Combine(Environment.CurrentDirectory, @"../../../TraceEvent/bin/Debug/netstandard2.0/Microsoft.Diagnostics.Tracing.TraceEvent.dll"),
- };
-
- foreach (var path in searchPaths)
- {
- var fullPath = Path.GetFullPath(path);
- if (File.Exists(fullPath))
- {
- return fullPath;
- }
- }
-
- throw new FileNotFoundException("Could not find Microsoft.Diagnostics.Tracing.TraceEvent.dll. Please build the TraceEvent project first.");
- }
}
}
diff --git a/src/TraceParserGen.Tests/TraceParserGen.Tests.csproj b/src/TraceParserGen.Tests/TraceParserGen.Tests.csproj
index 5d541566c..5fd9e6489 100644
--- a/src/TraceParserGen.Tests/TraceParserGen.Tests.csproj
+++ b/src/TraceParserGen.Tests/TraceParserGen.Tests.csproj
@@ -21,6 +21,9 @@
+
+ false
+
@@ -29,6 +32,14 @@
+
+
+
+
+
+
+
+
From 8e71d4e6c51ba3ba86f5e542daa82a3745dd2222 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 7 Oct 2025 03:17:46 +0000
Subject: [PATCH 5/7] Remove else path - always create parser and process trace
Co-authored-by: brianrob <6210322+brianrob@users.noreply.github.com>
---
.../ParserGenerationTests.cs | 70 ++++++-------------
1 file changed, 23 insertions(+), 47 deletions(-)
diff --git a/src/TraceParserGen.Tests/ParserGenerationTests.cs b/src/TraceParserGen.Tests/ParserGenerationTests.cs
index 0d20db71c..3637626ad 100644
--- a/src/TraceParserGen.Tests/ParserGenerationTests.cs
+++ b/src/TraceParserGen.Tests/ParserGenerationTests.cs
@@ -139,12 +139,7 @@ private void CreateTestConsoleApp(string projectDir, string generatedParserPath)
// Create a simple trace file to use for testing
// We'll use one of the existing test trace files
string sampleTracePath = Path.Combine(TestDataDir, "..", "..", "TraceEvent", "TraceEvent.Tests", "inputs", "net.4.5.x86.etl.zip");
- if (!File.Exists(sampleTracePath))
- {
- // If the sample trace doesn't exist, we'll skip the trace file test
- sampleTracePath = "";
- }
-
+
// Create Program.cs that uses the generated parser with a real trace file
string programContent = $@"using System;
using System.Linq;
@@ -173,57 +168,38 @@ static int Main(string[] args)
return 1;
}}
- // Test with a trace file if provided
+ // Get trace file path (from args or use default)
string traceFilePath = args.Length > 0 ? args[0] : ""{sampleTracePath.Replace("\\", "\\\\")}"";
- if (!string.IsNullOrEmpty(traceFilePath) && System.IO.File.Exists(traceFilePath))
+ if (!System.IO.File.Exists(traceFilePath))
{{
- Console.WriteLine($""Using trace file: {{traceFilePath}}"");
-
- using (var source = TraceEventDispatcher.GetDispatcherFromFileName(traceFilePath))
- {{
- foreach (var parserType in parserTypes)
- {{
- Console.WriteLine($"" Testing parser: {{parserType.Name}}"");
-
- // Create an instance of the parser
- var parser = (TraceEventParser)Activator.CreateInstance(parserType, source);
-
- int eventCount = 0;
-
- // Hook the All event to count events processed by this parser
- parser.All += (TraceEvent data) =>
- {{
- eventCount++;
- }};
-
- // Process the trace (this will trigger events if any match)
- source.Process();
-
- Console.WriteLine($"" Processed {{eventCount}} event(s) from this parser"");
- }}
- }}
+ Console.WriteLine($""ERROR: Trace file not found: {{traceFilePath}}"");
+ return 1;
}}
- else
+
+ Console.WriteLine($""Using trace file: {{traceFilePath}}"");
+
+ using (var source = TraceEventDispatcher.GetDispatcherFromFileName(traceFilePath))
{{
- Console.WriteLine(""No trace file available, skipping trace processing test"");
-
- // Just verify we can reflect on the parser types
foreach (var parserType in parserTypes)
{{
- Console.WriteLine($"" Validating parser: {{parserType.Name}}"");
+ Console.WriteLine($"" Testing parser: {{parserType.Name}}"");
- var enumerateMethod = parserType.GetMethod(""EnumerateTemplates"",
- BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
+ // Create an instance of the parser
+ var parser = (TraceEventParser)Activator.CreateInstance(parserType, source);
- if (enumerateMethod != null)
- {{
- Console.WriteLine($"" Found EnumerateTemplates method"");
- }}
- else
+ int eventCount = 0;
+
+ // Hook the All event to count events processed by this parser
+ parser.All += (TraceEvent data) =>
{{
- Console.WriteLine($"" WARNING: EnumerateTemplates method not found"");
- }}
+ eventCount++;
+ }};
+
+ // Process the trace (this will trigger events if any match)
+ source.Process();
+
+ Console.WriteLine($"" Processed {{eventCount}} event(s) from this parser"");
}}
}}
From c609979dab2644d084b1256a952cacd59f464be9 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 7 Oct 2025 15:40:06 +0000
Subject: [PATCH 6/7] Simplify test project: target net462 only, use TEMP dir,
remove TraceEvent reference
Co-authored-by: brianrob <6210322+brianrob@users.noreply.github.com>
---
src/TraceParserGen.Tests/ParserGenerationTests.cs | 9 ---------
src/TraceParserGen.Tests/TestBase.cs | 11 +++--------
src/TraceParserGen.Tests/TraceParserGen.Tests.csproj | 3 +--
3 files changed, 4 insertions(+), 19 deletions(-)
diff --git a/src/TraceParserGen.Tests/ParserGenerationTests.cs b/src/TraceParserGen.Tests/ParserGenerationTests.cs
index 3637626ad..ce9b357cd 100644
--- a/src/TraceParserGen.Tests/ParserGenerationTests.cs
+++ b/src/TraceParserGen.Tests/ParserGenerationTests.cs
@@ -1,7 +1,6 @@
using System;
using System.Diagnostics;
using System.IO;
-using System.Runtime.InteropServices;
using System.Text;
using Xunit;
using Xunit.Abstractions;
@@ -20,14 +19,6 @@ public ParserGenerationTests(ITestOutputHelper output) : base(output)
[Fact]
public void CanGenerateParserFromManifest()
{
- // Skip on non-Windows platforms since TraceParserGen.exe is a .NET Framework app
- // In a real environment, this would run on Windows with proper .NET Framework support
- if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
- {
- Output.WriteLine("Skipping test on non-Windows platform. TraceParserGen.exe requires .NET Framework.");
- return;
- }
-
// Arrange
string manifestPath = Path.Combine(TestDataDir, "SimpleTest.manifest.xml");
string outputCsPath = Path.Combine(OutputDir, "SimpleTestParser.cs");
diff --git a/src/TraceParserGen.Tests/TestBase.cs b/src/TraceParserGen.Tests/TestBase.cs
index 748a1e46f..6d6b639f3 100644
--- a/src/TraceParserGen.Tests/TestBase.cs
+++ b/src/TraceParserGen.Tests/TestBase.cs
@@ -10,19 +10,14 @@ namespace TraceParserGen.Tests
public abstract class TestBase
{
protected static string OriginalInputDir = FindInputDir();
- protected static string TestDataDir = Path.GetFullPath("inputs");
- protected static string BaseOutputDir = Path.GetFullPath("output");
+ protected static string TestDataDir = "inputs";
protected TestBase(ITestOutputHelper output)
{
Output = output;
- OutputDir = Path.Combine(BaseOutputDir, Guid.NewGuid().ToString("N").Substring(0, 8));
+ OutputDir = Path.Combine(Path.GetTempPath(), "TraceParserGen.Tests", Guid.NewGuid().ToString("N").Substring(0, 8));
- // Ensure output directory exists
- if (!Directory.Exists(OutputDir))
- {
- Directory.CreateDirectory(OutputDir);
- }
+ Directory.CreateDirectory(OutputDir);
}
protected ITestOutputHelper Output { get; }
diff --git a/src/TraceParserGen.Tests/TraceParserGen.Tests.csproj b/src/TraceParserGen.Tests/TraceParserGen.Tests.csproj
index 5fd9e6489..38950003c 100644
--- a/src/TraceParserGen.Tests/TraceParserGen.Tests.csproj
+++ b/src/TraceParserGen.Tests/TraceParserGen.Tests.csproj
@@ -2,7 +2,7 @@
- net462;net8.0
+ net462
TraceParserGen.Tests
TraceParserGen.Tests
Unit tests for TraceParserGen.
@@ -20,7 +20,6 @@
-
false
From 81bdbb379382164f19f81d558c3b0f8dc4ad1ce2 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 7 Oct 2025 15:50:18 +0000
Subject: [PATCH 7/7] Remove FindInputDir and use ProjectReference for
TraceEvent in test apps
Co-authored-by: brianrob <6210322+brianrob@users.noreply.github.com>
---
.../ParserGenerationTests.cs | 20 +++++++++++------
src/TraceParserGen.Tests/TestBase.cs | 22 +------------------
2 files changed, 14 insertions(+), 28 deletions(-)
diff --git a/src/TraceParserGen.Tests/ParserGenerationTests.cs b/src/TraceParserGen.Tests/ParserGenerationTests.cs
index ce9b357cd..a8293a003 100644
--- a/src/TraceParserGen.Tests/ParserGenerationTests.cs
+++ b/src/TraceParserGen.Tests/ParserGenerationTests.cs
@@ -103,11 +103,19 @@ private int RunTraceParserGen(string exePath, string manifestPath, string output
private void CreateTestConsoleApp(string projectDir, string generatedParserPath)
{
- // Get the path to TraceEvent assembly - it's in the test project's output directory
- // since we have a ProjectReference
- string traceEventAssembly = Path.Combine(Environment.CurrentDirectory, "Microsoft.Diagnostics.Tracing.TraceEvent.dll");
+ // Find the TraceEvent.csproj relative to the test assembly location
+ // The test runs from bin\Release\net462, so we go up to src and then to TraceEvent
+ string testAssemblyDir = Environment.CurrentDirectory;
+ string srcDir = Path.GetFullPath(Path.Combine(testAssemblyDir, "..", "..", "..", ".."));
+ string traceEventProjectPath = Path.Combine(srcDir, "TraceEvent", "TraceEvent.csproj");
- // Create the .csproj file
+ // Verify the path exists
+ if (!File.Exists(traceEventProjectPath))
+ {
+ throw new FileNotFoundException($"Could not find TraceEvent.csproj at {traceEventProjectPath}");
+ }
+
+ // Create the .csproj file with ProjectReference
string csprojContent = $@"
Exe
@@ -115,9 +123,7 @@ private void CreateTestConsoleApp(string projectDir, string generatedParserPath)
true
-
- {traceEventAssembly}
-
+
";
diff --git a/src/TraceParserGen.Tests/TestBase.cs b/src/TraceParserGen.Tests/TestBase.cs
index 6d6b639f3..e78272066 100644
--- a/src/TraceParserGen.Tests/TestBase.cs
+++ b/src/TraceParserGen.Tests/TestBase.cs
@@ -9,8 +9,7 @@ namespace TraceParserGen.Tests
///
public abstract class TestBase
{
- protected static string OriginalInputDir = FindInputDir();
- protected static string TestDataDir = "inputs";
+ protected static string TestDataDir = Path.Combine(Environment.CurrentDirectory, "inputs");
protected TestBase(ITestOutputHelper output)
{
@@ -24,25 +23,6 @@ protected TestBase(ITestOutputHelper output)
protected string OutputDir { get; }
- ///
- /// Finds the input directory for test files
- ///
- private static string FindInputDir()
- {
- string dir = Environment.CurrentDirectory;
- while (dir != null)
- {
- string candidate = Path.Combine(dir, @"TraceParserGen.Tests\inputs");
- if (Directory.Exists(candidate))
- {
- return Path.GetFullPath(candidate);
- }
-
- dir = Path.GetDirectoryName(dir);
- }
- return @"%PERFVIEW%\src\TraceParserGen.Tests\inputs";
- }
-
///
/// Gets the path to the TraceParserGen.exe executable
///