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 ///