Skip to content

Commit

Permalink
ConfigExtensions: Tolerate both implementation and breaking API chang…
Browse files Browse the repository at this point in the history
…es in BDN-nightly > 0.13.1.

- Tolerate implementation changes in ManualConfig.GetXxx() Closes #25.
- Tolerate breaking API changes in BenchmarkReport.ctor() and others. See #26.
- Unpin NightlyBDN (Issue resolved dotnet/BenchmarkDotNet#1922).
- Add tests.
  • Loading branch information
mawosoft committed Feb 15, 2022
1 parent 0420b9e commit 4d598df
Show file tree
Hide file tree
Showing 6 changed files with 770 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -105,15 +105,30 @@ private class MockMetricDescriptor : IMetricDescriptor
);
GenerateResult generateResult = GenerateResult.Success(ArtifactsPaths.Empty, Array.Empty<string>());
BuildResult buildResult = BuildResult.Success(generateResult);
BenchmarkReport benchmarkReport = new(
true, benchmarkCase,
generateResult,
buildResult,
Array.Empty<ExecuteResult>(),
new[] { new Measurement(1, IterationMode.Workload, IterationStage.Result, 1, 1, 1) },
default,
new[] { new Metric(new MockMetricDescriptor(), 1) }
);
// HACK Adjust for breaking API changes. This needs to be separated into a separate compat layer
// since it also affects WhatifFilter and further changes may appear at any time.
BenchmarkReport benchmarkReport;
List<ExecuteResult> executeResults = new();
List<Measurement> allMeasurements = new();
allMeasurements.Add(new Measurement(1, IterationMode.Workload, IterationStage.Result, 1, 1, 1));
GcStats gcStats = default;
Metric[] metrics = new[] { new Metric(new MockMetricDescriptor(), 1) };
try
{
//benchmarkReport = new(success: true, benchmarkCase, generateResult, buildResult, executeResults,
// allMeasurements, gcStats, metrics);
benchmarkReport = (BenchmarkReport)Activator.CreateInstance(typeof(BenchmarkReport),
new object[] { true, benchmarkCase, generateResult, buildResult, executeResults,
allMeasurements, gcStats, metrics });
}
catch (Exception)
{
executeResults.Add((ExecuteResult) Activator.CreateInstance(typeof(ExecuteResult),
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null,
new object[] { allMeasurements, gcStats, (ThreadingStats) default }, null));
benchmarkReport = (BenchmarkReport)Activator.CreateInstance(typeof(BenchmarkReport),
new object[] { true, benchmarkCase, generateResult, buildResult, executeResults, metrics });
}
return new Summary(
string.Empty, ImmutableArray.Create(benchmarkReport), HostEnvironmentInfo.GetCurrent(),
string.Empty, string.Empty, TimeSpan.Zero, SummaryExtensions.GetCultureInfo(null),
Expand Down
37 changes: 19 additions & 18 deletions src/Mawosoft.Extensions.BenchmarkDotNet/ConfigExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ public static ManualConfig RemoveColumnsByCategory(this IConfig config, params C
/// <returns>The existing <see cref="ManualConfig"/> with changes applied.</returns>
public static ManualConfig ReplaceExporters(this ManualConfig config, params IExporter[] newExporters)
{
ClearList(config.GetExporters());
ClearEnumerable(config.GetExporters());
return config.AddExporter(newExporters);
}

Expand All @@ -102,7 +102,7 @@ public static ManualConfig ReplaceExporters(this IConfig config, params IExporte
/// <returns>The existing <see cref="ManualConfig"/> with changes applied.</returns>
public static ManualConfig ReplaceLoggers(this ManualConfig config, params ILogger[] newLoggers)
{
ClearList(config.GetLoggers());
ClearEnumerable(config.GetLoggers());
return config.AddLogger(newLoggers);
}

Expand All @@ -115,7 +115,7 @@ public static ManualConfig ReplaceLoggers(this IConfig config, params ILogger[]
/// <returns>The existing <see cref="ManualConfig"/> with changes applied.</returns>
public static ManualConfig ReplaceDiagnosers(this ManualConfig config, params IDiagnoser[] newDiagnosers)
{
ClearList(config.GetDiagnosers());
ClearEnumerable(config.GetDiagnosers());
return config.AddDiagnoser(newDiagnosers);
}

Expand All @@ -128,7 +128,7 @@ public static ManualConfig ReplaceDiagnosers(this IConfig config, params IDiagno
/// <returns>The existing <see cref="ManualConfig"/> with changes applied.</returns>
public static ManualConfig ReplaceAnalysers(this ManualConfig config, params IAnalyser[] newAnalysers)
{
ClearList(config.GetAnalysers());
ClearEnumerable(config.GetAnalysers());
return config.AddAnalyser(newAnalysers);
}

Expand All @@ -141,7 +141,7 @@ public static ManualConfig ReplaceAnalysers(this IConfig config, params IAnalyse
/// <returns>The existing <see cref="ManualConfig"/> with changes applied.</returns>
public static ManualConfig ReplaceJobs(this ManualConfig config, params Job[] newJobs)
{
ClearList(config.GetJobs());
ClearEnumerable(config.GetJobs());
return config.AddJob(newJobs);
}

Expand All @@ -154,7 +154,7 @@ public static ManualConfig ReplaceJobs(this IConfig config, params Job[] newJobs
/// <returns>The existing <see cref="ManualConfig"/> with changes applied.</returns>
public static ManualConfig ReplaceValidators(this ManualConfig config, params IValidator[] newValidators)
{
ClearList(config.GetValidators());
ClearEnumerable(config.GetValidators());
return config.AddValidator(newValidators);
}

Expand All @@ -168,7 +168,7 @@ public static ManualConfig ReplaceValidators(this IConfig config, params IValida
public static ManualConfig ReplaceHardwareCounters(this ManualConfig config,
params HardwareCounter[] newHardwareCounters)
{
ClearHashSet(config.GetHardwareCounters());
ClearEnumerable(config.GetHardwareCounters());
return config.AddHardwareCounters(newHardwareCounters);
}

Expand All @@ -182,7 +182,7 @@ public static ManualConfig ReplaceHardwareCounters(this IConfig config,
/// <returns>The existing <see cref="ManualConfig"/> with changes applied.</returns>
public static ManualConfig ReplaceFilters(this ManualConfig config, params IFilter[] newFilters)
{
ClearList(config.GetFilters());
ClearEnumerable(config.GetFilters());
return config.AddFilter(newFilters);
}

Expand All @@ -196,7 +196,7 @@ public static ManualConfig ReplaceFilters(this IConfig config, params IFilter[]
public static ManualConfig ReplaceLogicalGroupRules(this ManualConfig config,
params BenchmarkLogicalGroupRule[] newLogicalGroupRules)
{
ClearHashSet(config.GetLogicalGroupRules());
ClearEnumerable(config.GetLogicalGroupRules());
return config.AddLogicalGroupRules(newLogicalGroupRules);
}

Expand All @@ -213,14 +213,15 @@ private static List<T> AsList<T>(IEnumerable<T> enumerable)
?? throw new InvalidOperationException($"Failed to get List<{typeof(T).Name}>.");

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static HashSet<T> AsHashSet<T>(IEnumerable<T> enumerable)
=> enumerable as HashSet<T>
?? throw new InvalidOperationException($"Failed to get HashSet<{typeof(T).Name}>.");

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void ClearList<T>(IEnumerable<T> enumerable) => AsList(enumerable).Clear();

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void ClearHashSet<T>(IEnumerable<T> enumerable) => AsHashSet(enumerable).Clear();
private static void ClearEnumerable<T>(IEnumerable<T> enumerable)
{
if (enumerable is List<T> list)
list.Clear();
else if (enumerable is HashSet<T> set)
set.Clear();
else
throw new InvalidOperationException(
$"Failed to get List<{typeof(T).Name}> or HashSet<{typeof(T).Name}>.");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
// Copyright (c) 2021-2022 Matthias Wolf, Mawosoft.

using System;
using System.Collections.Generic;
using System.Linq;
using BenchmarkDotNet.Columns;
using Xunit;
using Xunit.Abstractions;

using static Mawosoft.Extensions.BenchmarkDotNet.ColumnCategoryExtensions;

namespace Mawosoft.Extensions.BenchmarkDotNet.Tests
{
public class ColumnCategoryExtensionsTests
{
[Fact]
public void ToExtended_ExistingCategoriesMatch()
{
string[] categoryNamesArray = Enum.GetNames(typeof(ColumnCategory));
string[] extendedNamesArray = Enum.GetNames(typeof(ExtendedColumnCategory));
HashSet<string> categoryNames = new(categoryNamesArray);
HashSet<string> extendedNames = new(extendedNamesArray);
Assert.Equal(categoryNamesArray.Length, categoryNames.Count);
Assert.Equal(extendedNamesArray.Length, extendedNames.Count);
Assert.ProperSuperset(categoryNames, extendedNames);
foreach (string cname in categoryNames)
{
Assert.Equal(
(ExtendedColumnCategory)Enum.Parse(typeof(ExtendedColumnCategory), cname),
ColumnCategoryExtensions.ToExtended(
(ColumnCategory)Enum.Parse(typeof(ColumnCategory), cname)));
}
}

[Fact]
public void ToExtended_NonExistingCategories_Unknown()
{
Assert.Equal(
ExtendedColumnCategory.Unknown,
ColumnCategoryExtensions.ToExtended(ColumnCategory.Metric + 1));
}

private class GetExtendedColumnCategory_TheoryData : TheoryData<IColumn, ExtendedColumnCategory>
{
// We get all existing IColumn classes via reflection and handle them by name
// to discover any new additions not covered by test.
public GetExtendedColumnCategory_TheoryData()
{
Func<Type, bool> predicate = t => t.IsClass && !t.IsAbstract && typeof(IColumn).IsAssignableFrom(t);

IEnumerable<Type> columnTypes =
typeof(IColumn).Assembly.GetTypes().Where(predicate)
.Concat(typeof(ColumnCategoryExtensions).Assembly.GetTypes().Where(predicate)
.Distinct());

List<string> unexpectedTypes = new();

foreach (Type type in columnTypes)
{
switch (type.Name)
{
// BenchmarkDotNet 0.13.1
case "BaselineColumn":
Add(new BaselineColumn(), ExtendedColumnCategory.Meta);
break;
case "BaselineRatioColumn":
Add(BaselineRatioColumn.RatioMean, ExtendedColumnCategory.Baseline);
break;
case "BaselineScaledColumn":
#pragma warning disable CS0618 // Type or member is obsolete
Add(BaselineScaledColumn.Scaled, ExtendedColumnCategory.Baseline);
#pragma warning restore CS0618 // Type or member is obsolete
break;
case "CategoriesColumn":
Add(new CategoriesColumn(), ExtendedColumnCategory.Category);
break;
case "JobCharacteristicColumn":
Add(JobCharacteristicColumn.AllColumns[0], ExtendedColumnCategory.Job);
break;
case "LogicalGroupColumn":
Add(new LogicalGroupColumn(), ExtendedColumnCategory.Meta);
break;
case "MetricColumn":
Add(new MetricColumn(null), ExtendedColumnCategory.Metric);
break;
case "ParamColumn":
Add(new ParamColumn("head"), ExtendedColumnCategory.Params);
break;
case "RankColumn":
Add(RankColumn.Arabic, ExtendedColumnCategory.Custom);
break;
case "StatisticalTestColumn":
Add(new StatisticalTestColumn(Perfolizer.Mathematics.SignificanceTesting.StatisticalTestKind.Welch, Perfolizer.Mathematics.Thresholds.Threshold.Create(Perfolizer.Mathematics.Thresholds.ThresholdUnit.Ratio, 0), true), ExtendedColumnCategory.Baseline);
break;
case "StatisticColumn":
Add(StatisticColumn.Mean, ExtendedColumnCategory.Statistics);
break;
case "TagColumn":
Add(new TagColumn("tag", p => "tag"), ExtendedColumnCategory.Custom);
break;
case "TargetMethodColumn":
Add(TargetMethodColumn.Namespace, ExtendedColumnCategory.TargetMethod);
Add(TargetMethodColumn.Type, ExtendedColumnCategory.TargetMethod);
Add(TargetMethodColumn.Method, ExtendedColumnCategory.TargetMethod);
break;
// BenchmarkDotNet 0.13.1.x-nightly
case "BaselineAllocationRatioColumn":
IColumn barc = (Activator.CreateInstance(type) as IColumn)!;
Assert.NotNull(barc);
Add(barc, ExtendedColumnCategory.Metric);
break;
// Mawosoft.Extensions.BenchmarkDotNet
case "CombinedParamsColumn":
Add(new CombinedParamsColumn(), ExtendedColumnCategory.Params);
break;
case "JobCharacteristicColumnWithLegend":
Add(new JobCharacteristicColumnWithLegend(JobCharacteristicColumn.AllColumns[0], "legend"), ExtendedColumnCategory.Job);
break;
case "RecyclableParamColumn":
Add(new RecyclableParamColumn(1, "param", false), ExtendedColumnCategory.Params);
break;
default:
unexpectedTypes.Add(type.Name);
break;
}
}

Assert.Empty(unexpectedTypes);
}
}

[Theory]
[ClassData(typeof(GetExtendedColumnCategory_TheoryData))]
internal void GetExtendedColumnCategory_Succeeds(IColumn column, ExtendedColumnCategory extendedColumnCategory)
{
Assert.Equal(extendedColumnCategory, ColumnCategoryExtensions.GetExtendedColumnCategory(column));
}

private class GetExtendedColumnCategories_TheoryData : TheoryData<IColumnProvider, IEnumerable<ExtendedColumnCategory>>
{
// We get all existing IColumnProvider classes via reflection and handle them by name
// to discover any new additions not covered by test.
public GetExtendedColumnCategories_TheoryData()
{
Func<Type, bool> predicate = t => t.IsClass && !t.IsAbstract && typeof(IColumnProvider).IsAssignableFrom(t);

IEnumerable<Type> providerTypes =
typeof(IColumn).Assembly.GetTypes().Where(predicate)
.Concat(typeof(ColumnCategoryExtensions).Assembly.GetTypes().Where(predicate)
.Distinct());

List<string> unexpectedTypes = new();

foreach (Type type in providerTypes)
{
switch (type.Name)
{
// BenchmarkDotNet
case "CompositeColumnProvider":
Add(new CompositeColumnProvider(
DefaultColumnProviders.Descriptor, DefaultColumnProviders.Job),
new[] { ExtendedColumnCategory.TargetMethod, ExtendedColumnCategory.Job });
Add(new CompositeColumnProvider(
new JobColumnSelectionProvider("-all +job", true), new RecyclableParamsColumnProvider()),
new[] { ExtendedColumnCategory.Job, ExtendedColumnCategory.Params });
break;
case "EmptyColumnProvider":
Add(EmptyColumnProvider.Instance, Array.Empty<ExtendedColumnCategory>());
break;
case "SimpleColumnProvider":
Add(new SimpleColumnProvider(
StatisticColumn.Mean, StatisticColumn.Error, TargetMethodColumn.Method),
new[] { ExtendedColumnCategory.Statistics, ExtendedColumnCategory.TargetMethod });
Add(new SimpleColumnProvider(new CategoriesColumn()),
new[] { ExtendedColumnCategory.Category });
break;
case "DescriptorColumnProvider":
Add(DefaultColumnProviders.Descriptor, new[] { ExtendedColumnCategory.TargetMethod });
break;
case "JobColumnProvider":
Add(DefaultColumnProviders.Job, new[] { ExtendedColumnCategory.Job });
break;
case "StatisticsColumnProvider":
Add(DefaultColumnProviders.Statistics, new[] { ExtendedColumnCategory.Statistics });
break;
case "ParamsColumnProvider":
Add(DefaultColumnProviders.Params, new[] { ExtendedColumnCategory.Params });
break;
case "MetricsColumnProvider":
Add(DefaultColumnProviders.Metrics, new[] { ExtendedColumnCategory.Metric });
break;
// Mawosoft.Extensions.BenchmarkDotNet
case "JobColumnSelectionProvider":
Add(new JobColumnSelectionProvider("+all", true), new[] { ExtendedColumnCategory.Job });
break;
case "RecyclableParamsColumnProvider":
Add(new RecyclableParamsColumnProvider(), new[] { ExtendedColumnCategory.Params });
break;
default:
unexpectedTypes.Add(type.Name);
break;
}
}

Assert.Empty(unexpectedTypes);
}
}

[Theory]
[ClassData(typeof(GetExtendedColumnCategories_TheoryData))]
internal void GetExtendedColumnCategories_Succeeds(IColumnProvider provider, IEnumerable<ExtendedColumnCategory> extendedColumnCategories)
{
Assert.Equal(
new HashSet<ExtendedColumnCategory>(extendedColumnCategories),
new HashSet<ExtendedColumnCategory>(ColumnCategoryExtensions.GetExtendedColumnCategories(provider)));
}

// For ColumnCategoryExtensions.RemoveColumnsByCategory see RemoveColumnsByCategory_Succeeds
// in ConfigExtensionsTests.Columns.cs
}
}
Loading

0 comments on commit 4d598df

Please sign in to comment.