File: Tracing\EventSourceValidator.cs
Web Access
Project: src\src\Testing\src\Microsoft.AspNetCore.InternalTesting.csproj (Microsoft.AspNetCore.InternalTesting)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Collections.Generic;
using System.Diagnostics.Tracing;
using System.Reflection;
using Xunit;
 
namespace Microsoft.AspNetCore.InternalTesting.Tracing;
 
/// <summary>
/// Validates that <see cref="EventSource"/>-derived classes have consistent
/// <see cref="EventAttribute.EventId"/> values and <c>WriteEvent</c> call arguments.
/// This catches drift caused by bad merge resolution or missed updates that would
/// otherwise surface only as runtime errors.
/// </summary>
public static class EventSourceValidator
{
    /// <summary>
    /// Validates all <c>[Event]</c>-attributed methods on <typeparamref name="T"/>.
    /// </summary>
    /// <typeparam name="T">A type that derives from <see cref="EventSource"/>.</typeparam>
    public static void ValidateEventSourceIds<T>() where T : EventSource
        => ValidateEventSourceIds(typeof(T));
 
    /// <summary>
    /// Validates all <c>[Event]</c>-attributed methods on the given <see cref="EventSource"/>-derived type.
    /// <para>
    /// Uses <see cref="EventSource.GenerateManifest(Type, string, EventManifestOptions)"/> with
    /// <see cref="EventManifestOptions.Strict"/> to perform IL-level validation that the integer
    /// argument passed to each <c>WriteEvent</c> call matches the <c>[Event(id)]</c> attribute
    /// on the calling method. This is the same validation the .NET runtime itself uses.
    /// </para>
    /// <para>
    /// Additionally checks for duplicate <see cref="EventAttribute.EventId"/> values across methods.
    /// </para>
    /// </summary>
    /// <param name="eventSourceType">A type that derives from <see cref="EventSource"/>.</param>
    public static void ValidateEventSourceIds(Type eventSourceType)
    {
        if (eventSourceType is null)
        {
            throw new ArgumentNullException(nameof(eventSourceType));
        }
 
        Assert.True(
            typeof(EventSource).IsAssignableFrom(eventSourceType),
            $"Type '{eventSourceType.FullName}' does not derive from EventSource.");
 
        var errors = new List<string>();
 
        // Check for duplicate Event IDs across methods.
        var seenIds = new Dictionary<int, string>();
        var methods = eventSourceType.GetMethods(
            BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
 
        foreach (var method in methods)
        {
            var eventAttr = method.GetCustomAttribute<EventAttribute>();
            if (eventAttr is null)
            {
                continue;
            }
 
            if (seenIds.TryGetValue(eventAttr.EventId, out var existingMethod))
            {
                errors.Add(
                    $"Duplicate EventId {eventAttr.EventId}: methods '{existingMethod}' and '{method.Name}' share the same ID.");
            }
            else
            {
                seenIds[eventAttr.EventId] = method.Name;
            }
        }
 
        // Use GenerateManifest with Strict mode to validate that each method's
        // WriteEvent(id, ...) call uses an ID that matches its [Event(id)] attribute.
        // Internally this uses GetHelperCallFirstArg to IL-inspect the method body
        // and extract the integer constant passed to WriteEvent — the same validation
        // the .NET runtime performs when constructing an EventSource.
        try
        {
            var manifest = EventSource.GenerateManifest(
                eventSourceType,
                assemblyPathToIncludeInManifest: "assemblyPathForValidation",
                flags: EventManifestOptions.Strict);
 
            if (manifest is null)
            {
                errors.Add("GenerateManifest returned null, indicating the type is not a valid EventSource.");
            }
        }
        catch (ArgumentException ex)
        {
            errors.Add(ex.Message);
        }
 
        if (errors.Count > 0)
        {
            Assert.Fail(
                $"EventSource '{eventSourceType.FullName}' has event ID validation error(s):" +
                Environment.NewLine + string.Join(Environment.NewLine, errors));
        }
    }
}