Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
11 changes: 9 additions & 2 deletions BenchmarkDotNet.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27130.2027
# Visual Studio Version 17
VisualStudioVersion = 17.8.34004.107
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{D6597E3A-6892-4A68-8E14-042FC941FDA2}"
EndProject
Expand Down Expand Up @@ -51,6 +51,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BenchmarkDotNet.Diagnostics
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BenchmarkDotNet.IntegrationTests.ManualRunning.MultipleFrameworks", "tests\BenchmarkDotNet.IntegrationTests.ManualRunning.MultipleFrameworks\BenchmarkDotNet.IntegrationTests.ManualRunning.MultipleFrameworks.csproj", "{AACA2C63-A85B-47AB-99FC-72C3FF408B14}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BenchmarkDotNet.TestAdapter", "src\BenchmarkDotNet.TestAdapter\BenchmarkDotNet.TestAdapter.csproj", "{4C9C89B8-7C4E-4ECF-B3C9-324C8772EDAC}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -137,6 +139,10 @@ Global
{AACA2C63-A85B-47AB-99FC-72C3FF408B14}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AACA2C63-A85B-47AB-99FC-72C3FF408B14}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AACA2C63-A85B-47AB-99FC-72C3FF408B14}.Release|Any CPU.Build.0 = Release|Any CPU
{4C9C89B8-7C4E-4ECF-B3C9-324C8772EDAC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4C9C89B8-7C4E-4ECF-B3C9-324C8772EDAC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4C9C89B8-7C4E-4ECF-B3C9-324C8772EDAC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4C9C89B8-7C4E-4ECF-B3C9-324C8772EDAC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -162,6 +168,7 @@ Global
{B620D10A-CD8E-4A34-8B27-FD6257E63AD0} = {63B94FD6-3F3D-4E04-9727-48E86AC4384C}
{C5BDA61F-3A56-4B59-901D-0A17E78F4076} = {D6597E3A-6892-4A68-8E14-042FC941FDA2}
{AACA2C63-A85B-47AB-99FC-72C3FF408B14} = {14195214-591A-45B7-851A-19D3BA2413F9}
{4C9C89B8-7C4E-4ECF-B3C9-324C8772EDAC} = {D6597E3A-6892-4A68-8E14-042FC941FDA2}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {4D9AF12B-1F7F-45A7-9E8C-E4E46ADCBD1F}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net462;net8.0</TargetFrameworks>
<GenerateProgramFile>false</GenerateProgramFile>
</PropertyGroup>
<ItemGroup>
<Compile Include="Program.fs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\BenchmarkDotNet\BenchmarkDotNet.csproj" />
<ProjectReference Include="..\..\src\BenchmarkDotNet.TestAdapter\BenchmarkDotNet.TestAdapter.csproj"/>
</ItemGroup>

<ItemGroup Condition=" '$(TargetFrameworkIdentifier)' == '.NETFramework' ">
Expand All @@ -21,5 +23,6 @@
<ItemGroup>
<PackageReference Update="FSharp.Core" Version="4.6.0" />
<PackageReference Update="System.ValueTuple" Version="4.5.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<NoWarn>$(NoWarn);CA1018;CA5351;CA1825</NoWarn>
<GenerateProgramFile>false</GenerateProgramFile>
</PropertyGroup>
<ItemGroup Condition=" '$(TargetFrameworkIdentifier)' == '.NETFramework' ">
<Reference Include="System.Reflection" />
Expand All @@ -19,10 +20,12 @@
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="System.Drawing.Common" Version="4.7.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\BenchmarkDotNet.Diagnostics.dotTrace\BenchmarkDotNet.Diagnostics.dotTrace.csproj" />
<ProjectReference Include="..\..\src\BenchmarkDotNet\BenchmarkDotNet.csproj" />
<ProjectReference Include="..\..\src\BenchmarkDotNet.Diagnostics.Windows\BenchmarkDotNet.Diagnostics.Windows.csproj" />
<ProjectReference Include="..\..\src\BenchmarkDotNet.TestAdapter\BenchmarkDotNet.TestAdapter.csproj" />
</ItemGroup>
</Project>
94 changes: 94 additions & 0 deletions src/BenchmarkDotNet.TestAdapter/BenchmarkCaseExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Characteristics;
using BenchmarkDotNet.Exporters;
using BenchmarkDotNet.Extensions;
using BenchmarkDotNet.Running;
using Microsoft.TestPlatform.AdapterUtilities;
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
using System;

namespace BenchmarkDotNet.TestAdapter
{
/// <summary>
/// A set of extensions for BenchmarkCase to support converting to VSTest TestCase objects.
/// </summary>
internal static class BenchmarkCaseExtensions
{
/// <summary>
/// Converts a BDN BenchmarkCase to a VSTest TestCase.
/// </summary>
/// <param name="benchmarkCase">The BenchmarkCase to convert.</param>
/// <param name="assemblyPath">The dll or exe of the benchmark project.</param>
/// <param name="includeJobInName">Whether or not the display name should include the job name.</param>
/// <returns>The VSTest TestCase.</returns>
internal static TestCase ToVsTestCase(this BenchmarkCase benchmarkCase, string assemblyPath, bool includeJobInName = false)
{
var benchmarkMethod = benchmarkCase.Descriptor.WorkloadMethod;
var fullClassName = benchmarkCase.Descriptor.Type.GetCorrectCSharpTypeName();
var benchmarkMethodName = benchmarkCase.Descriptor.WorkloadMethod.Name;
var benchmarkFullMethodName = $"{fullClassName}.{benchmarkMethodName}";

// Display name has arguments as well.
var displayMethodName = FullNameProvider.GetMethodName(benchmarkCase);
if (includeJobInName)
displayMethodName += $" [{benchmarkCase.GetUnrandomizedJobDisplayInfo()}]";

var displayName = $"{fullClassName}.{displayMethodName}";

var vsTestCase = new TestCase(benchmarkFullMethodName, VsTestAdapter.ExecutorUri, assemblyPath)
{
DisplayName = displayName,
Id = GetTestCaseId(benchmarkCase)
};

var benchmarkAttribute = benchmarkMethod.ResolveAttribute<BenchmarkAttribute>();
if (benchmarkAttribute != null)
{
vsTestCase.CodeFilePath = benchmarkAttribute.SourceCodeFile;
vsTestCase.LineNumber = benchmarkAttribute.SourceCodeLineNumber;
}

var categories = DefaultCategoryDiscoverer.Instance.GetCategories(benchmarkMethod);
foreach (var category in categories)
vsTestCase.Traits.Add("Category", category);

vsTestCase.Traits.Add("", "BenchmarkDotNet");

return vsTestCase;
}

/// <summary>
/// If an ID is not provided, a random string is used for the ID. This method will identify if randomness was
/// used for the ID and return the Job's DisplayInfo with that randomness removed so that the same benchmark
/// can be referenced across multiple processes.
/// </summary>
/// <param name="benchmarkCase">The benchmark case.</param>
/// <returns>The benchmark case' job's DisplayInfo without randomness.</returns>
internal static string GetUnrandomizedJobDisplayInfo(this BenchmarkCase benchmarkCase)
{
var jobDisplayInfo = benchmarkCase.Job.DisplayInfo;
if (!benchmarkCase.Job.HasValue(CharacteristicObject.IdCharacteristic) && benchmarkCase.Job.ResolvedId.StartsWith("Job-", StringComparison.OrdinalIgnoreCase))
{
// Replace Job-ABCDEF with Job
jobDisplayInfo = "Job" + jobDisplayInfo.Substring(benchmarkCase.Job.ResolvedId.Length);
}

return jobDisplayInfo;
}

/// <summary>
/// Gets an ID for a given BenchmarkCase that is uniquely identifiable from discovery to execution phase.
/// </summary>
/// <param name="benchmarkCase">The benchmark case.</param>
/// <returns>The test case ID.</returns>
internal static Guid GetTestCaseId(this BenchmarkCase benchmarkCase)
{
var testIdProvider = new TestIdProvider();
testIdProvider.AppendString(VsTestAdapter.ExecutorUriString);
testIdProvider.AppendString(benchmarkCase.Descriptor.DisplayInfo);
testIdProvider.AppendString(benchmarkCase.GetUnrandomizedJobDisplayInfo());
testIdProvider.AppendString(benchmarkCase.Parameters.DisplayInfo);
return testIdProvider.GetId();
}
}
}
28 changes: 28 additions & 0 deletions src/BenchmarkDotNet.TestAdapter/BenchmarkDotNet.TestAdapter.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\build\common.props" />
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net462</TargetFrameworks>
<AssemblyTitle>BenchmarkDotNet.TestAdapter</AssemblyTitle>
<AssemblyName>BenchmarkDotNet.TestAdapter</AssemblyName>
<PackageId>BenchmarkDotNet.TestAdapter</PackageId>
<ProduceReferenceAssembly>True</ProduceReferenceAssembly>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.TestPlatform.AdapterUtilities" Version="17.7.2" />
<PackageReference Include="Microsoft.TestPlatform.ObjectModel" Version="17.7.2" />
<PackageReference Include="Microsoft.TestPlatform.TranslationLayer" Version="17.7.2" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\BenchmarkDotNet\BenchmarkDotNet.csproj" />
</ItemGroup>

<!-- Include files in nuget package for generating entry point -->
<ItemGroup>
<Compile Remove="Package\EntryPoint.*" />
<None Include="Package\EntryPoint.*" Pack="true" PackagePath="entrypoints\" />
<None Include="Package\BenchmarkDotNet.TestAdapter.props" Pack="true" PackagePath="build\" />
</ItemGroup>
</Project>
48 changes: 48 additions & 0 deletions src/BenchmarkDotNet.TestAdapter/BenchmarkEnumerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using BenchmarkDotNet.Extensions;
using BenchmarkDotNet.Helpers;
using BenchmarkDotNet.Running;
using BenchmarkDotNet.Toolchains;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace BenchmarkDotNet.TestAdapter
{
/// <summary>
/// A class used for enumerating all the benchmarks in an assembly.
/// </summary>
internal static class BenchmarkEnumerator
{
/// <summary>
/// Returns all the BenchmarkRunInfo objects from a given assembly.
/// </summary>
/// <param name="assemblyPath">The dll or exe of the benchmark project.</param>
/// <returns>The benchmarks inside the assembly.</returns>
public static BenchmarkRunInfo[] GetBenchmarksFromAssemblyPath(string assemblyPath)
{
var assembly = Assembly.LoadFrom(assemblyPath);

var isDebugAssembly = assembly.IsJitOptimizationDisabled() ?? false;

return GenericBenchmarksBuilder.GetRunnableBenchmarks(assembly.GetRunnableBenchmarks())
.Select(type =>
{
var benchmarkRunInfo = BenchmarkConverter.TypeToBenchmarks(type);
if (isDebugAssembly)
{
// If the assembly is a debug assembly, then only display them if they will run in-process
// This will allow people to debug their benchmarks using VSTest if they wish.
benchmarkRunInfo = new BenchmarkRunInfo(
benchmarkRunInfo.BenchmarksCases.Where(c => c.GetToolchain().IsInProcess).ToArray(),
benchmarkRunInfo.Type,
benchmarkRunInfo.Config);
}

return benchmarkRunInfo;
})
.Where(runInfo => runInfo.BenchmarksCases.Length > 0)
.ToArray();
}
}
}
86 changes: 86 additions & 0 deletions src/BenchmarkDotNet.TestAdapter/BenchmarkExecutor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Running;
using BenchmarkDotNet.TestAdapter.Remoting;
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

namespace BenchmarkDotNet.TestAdapter
{
/// <summary>
/// A class used for executing benchmarks
/// </summary>
internal class BenchmarkExecutor
{
private readonly CancellationTokenSource cts = new ();

/// <summary>
/// Runs all the benchmarks in the given assembly, updating the TestExecutionRecorder as they get run.
/// </summary>
/// <param name="assemblyPath">The dll or exe of the benchmark project.</param>
/// <param name="recorder">The interface used to record the current test execution progress.</param>
/// <param name="benchmarkIds">
/// An optional list of benchmark IDs specifying which benchmarks to run.
/// These IDs are the same as the ones generated for the VSTest TestCase.
/// </param>
public void RunBenchmarks(string assemblyPath, TestExecutionRecorderWrapper recorder, HashSet<Guid>? benchmarkIds = null)
{
var benchmarks = BenchmarkEnumerator.GetBenchmarksFromAssemblyPath(assemblyPath);
var testCases = new List<TestCase>();

var filteredBenchmarks = new List<BenchmarkRunInfo>();
foreach (var benchmark in benchmarks)
{
var needsJobInfo = benchmark.BenchmarksCases.Select(c => c.Job.DisplayInfo).Distinct().Count() > 1;
var filteredCases = new List<BenchmarkCase>();
foreach (var benchmarkCase in benchmark.BenchmarksCases)
{
var testId = benchmarkCase.GetTestCaseId();
if (benchmarkIds != null && benchmarkIds.Contains(testId))
{
filteredCases.Add(benchmarkCase);
testCases.Add(benchmarkCase.ToVsTestCase(assemblyPath, needsJobInfo));
}
}

if (filteredCases.Count > 0)
{
filteredBenchmarks.Add(new BenchmarkRunInfo(filteredCases.ToArray(), benchmark.Type, benchmark.Config));
}
}

benchmarks = filteredBenchmarks.ToArray();

if (benchmarks.Length == 0)
return;

// Create an event processor which will subscribe to events and push them to VSTest
var eventProcessor = new VsTestEventProcessor(testCases, recorder, cts.Token);

// Create a logger which will forward all log messages in BDN to the VSTest logger.
var logger = new VsTestLogger(recorder.GetLogger());

// Modify all the benchmarks so that the event process and logger is added.
benchmarks = benchmarks
.Select(b => new BenchmarkRunInfo(
b.BenchmarksCases,
b.Type,
b.Config.AddEventProcessor(eventProcessor).AddLogger(logger).CreateImmutableConfig()))
.ToArray();

// Run all the benchmarks, and ensure that any tests that don't have a result yet are sent.
BenchmarkRunner.Run(benchmarks);
eventProcessor.SendUnsentTestResults();
}

/// <summary>
/// Stop the benchmarks when next able.
/// </summary>
public void Cancel()
{
cts.Cancel();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<!-- Disables generation of MSTest entrypoint -->
<GenerateProgramFile>false</GenerateProgramFile>
<EntryPointSourceDirectory>$(MSBuildThisFileDirectory)..\entrypoints\</EntryPointSourceDirectory>
</PropertyGroup>
<ItemGroup Condition="'$(GenerateBDNEntryPoint)' == 'true'">
<Compile Include="$(EntryPointSourceDirectory)EntryPoint.cs" Condition="'$(Language)' == 'C#'" Visible="false"/>
<CompileAfter Include="$(EntryPointSourceDirectory)EntryPoint.fs" Condition="'$(Language)' == 'F#'" Visible="false"/>
<Compile Include="$(EntryPointSourceDirectory)EntryPoint.vb" Condition="'$(Language)' == 'VB'" Visible="false"/>
</ItemGroup>
</Project>
7 changes: 7 additions & 0 deletions src/BenchmarkDotNet.TestAdapter/Package/EntryPoint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
using BenchmarkDotNet.Running;
using System.Reflection;

public class __AutoGeneratedEntryPointClass
{
public static void Main(string[] args) => BenchmarkSwitcher.FromAssembly(typeof(__AutoGeneratedEntryPointClass).Assembly).Run(args);
}
8 changes: 8 additions & 0 deletions src/BenchmarkDotNet.TestAdapter/Package/EntryPoint.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module __AutoGeneratedEntryPointClass
open System.Reflection;
open BenchmarkDotNet.Running

[<EntryPoint>]
let main argv =
BenchmarkSwitcher.FromAssembly(Assembly.GetEntryAssembly()).Run(argv) |> ignore
0 // return an integer exit code
10 changes: 10 additions & 0 deletions src/BenchmarkDotNet.TestAdapter/Package/EntryPoint.vb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Imports System.Reflection
Imports BenchmarkDotNet.Running

Namespace Global
Module __AutoGeneratedEntryPointClass
Sub Main(args As String())
Dim summary = BenchmarkSwitcher.FromAssembly(Assembly.GetEntryAssembly()).Run(args)
End Sub
End Module
End Namespace
Loading