File: ItemSpecModifiersBenchmark.cs
Web Access
Project: ..\..\..\src\MSBuild.Benchmarks\MSBuild.Benchmarks.csproj (MSBuild.Benchmarks)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using BenchmarkDotNet.Attributes;
using Microsoft.Build.Framework;
 
namespace MSBuild.Benchmarks;
 
[MemoryDiagnoser]
public class ItemSpecModifiersBenchmark
{
    private string _currentDirectory = null!;
    private string _itemSpec = null!;
    private string _definingProjectEscaped = null!;
    private string _recursiveDirModifier = null!;
 
    [GlobalSetup]
    public void GlobalSetup()
    {
        _currentDirectory = Path.GetTempPath();
        _itemSpec = Path.Combine(_currentDirectory, "src", "Framework", "ItemSpecModifiers.cs");
        _definingProjectEscaped = Path.Combine(_currentDirectory, "src", "Framework", "Microsoft.Build.Framework.csproj");
 
        // Ensure the file exists so time-based modifiers can resolve.
        Directory.CreateDirectory(Path.GetDirectoryName(_itemSpec)!);
        if (!File.Exists(_itemSpec))
        {
            File.WriteAllText(_itemSpec, string.Empty);
        }
 
        _recursiveDirModifier = ItemSpecModifiers.RecursiveDir;
    }
 
    [GlobalCleanup]
    public void GlobalCleanup()
    {
        if (File.Exists(_itemSpec))
        {
            File.Delete(_itemSpec);
        }
    }
 
    // -----------------------------------------------------------------------
    // FrozenSet lookup – covers IsItemSpecModifier for all known modifiers
    // plus a miss.
    // -----------------------------------------------------------------------
 
    [Benchmark]
    public int IsItemSpecModifier_AllModifiers()
    {
        int count = 0;
        foreach (string modifier in ItemSpecModifiers.All)
        {
            if (ItemSpecModifiers.IsItemSpecModifier(modifier))
            {
                count++;
            }
        }
 
        // Also check a miss.
        if (ItemSpecModifiers.IsItemSpecModifier("SomeCustomMetadata"))
        {
            count++;
        }
 
        return count;
    }
 
    // -----------------------------------------------------------------------
    // IsDerivableItemSpecModifier – RecursiveDir is the only modifier that
    // hits the length+char guard returning false.
    // -----------------------------------------------------------------------
 
    [Benchmark]
    public bool IsDerivableItemSpecModifier_RecursiveDir()
        => ItemSpecModifiers.IsDerivableItemSpecModifier(_recursiveDirModifier);
 
    // -----------------------------------------------------------------------
    // GetItemSpecModifier – FullPath is the most commonly used modifier and
    // the baseline for the caching benchmark below.
    // -----------------------------------------------------------------------
 
    [Benchmark]
    public string GetItemSpecModifier_FullPath()
        => ItemSpecModifiers.GetItemSpecModifier(_itemSpec, ItemSpecModifiers.FullPath, _currentDirectory, _definingProjectEscaped);
 
    // -----------------------------------------------------------------------
    // GetItemSpecModifier – Directory has the most complex logic: resolves
    // FullPath internally, strips the root, and differs by OS.
    // -----------------------------------------------------------------------
 
    [Benchmark]
    public string GetItemSpecModifier_Directory()
        => ItemSpecModifiers.GetItemSpecModifier(_itemSpec, ItemSpecModifiers.Directory, _currentDirectory, _definingProjectEscaped);
 
    // -----------------------------------------------------------------------
    // GetItemSpecModifier – file-time modifier (I/O-bound). All three time
    // modifiers share the same code shape; ModifiedTime uses
    // FileUtilities.GetFileInfoNoThrow which is the most common path.
    // -----------------------------------------------------------------------
 
    [Benchmark]
    public string GetItemSpecModifier_ModifiedTime()
        => ItemSpecModifiers.GetItemSpecModifier(_itemSpec, ItemSpecModifiers.ModifiedTime, _currentDirectory, _definingProjectEscaped);
 
    // -----------------------------------------------------------------------
    // GetItemSpecModifier – DefiningProjectDirectory is the most expensive
    // defining-project modifier: it recursively resolves both RootDir and
    // Directory on the defining project path.
    // -----------------------------------------------------------------------
 
    [Benchmark]
    public string GetItemSpecModifier_DefiningProjectDirectory()
        => ItemSpecModifiers.GetItemSpecModifier(_itemSpec, ItemSpecModifiers.DefiningProjectDirectory, _currentDirectory, _definingProjectEscaped);
}