Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions PerfView.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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}
Expand Down
294 changes: 294 additions & 0 deletions src/TraceParserGen.Tests/ParserGenerationTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,294 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using Xunit;
using Xunit.Abstractions;

namespace TraceParserGen.Tests
{
/// <summary>
/// Tests for TraceParserGen.exe that validate it can generate parsers from manifests
/// </summary>
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)
{
var startInfo = new ProcessStartInfo
{
FileName = exePath,
Arguments = $"\"{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)
{
// 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");

// 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 = $@"<Project Sdk=""Microsoft.NET.Sdk"">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net462</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include=""{traceEventProjectPath}"" />
</ItemGroup>
</Project>";

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 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");

// 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
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)"");

if (parserTypes.Count == 0)
{{
Console.WriteLine(""ERROR: No parser types found"");
return 1;
}}

// Get trace file path (from args or use default)
string traceFilePath = args.Length > 0 ? args[0] : ""{sampleTracePath.Replace("\\", "\\\\")}"";

if (!System.IO.File.Exists(traceFilePath))
{{
Console.WriteLine($""ERROR: Trace file not found: {{traceFilePath}}"");
return 1;
}}

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(""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",
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;
}
}
}
}
Loading