Skip to content

Commit c1f210c

Browse files
fixes(configuration): Not unique exporter for exporter type (#1796)
* fixes(configuration): Not unique exporter for exporter type * fixes(configuration): Assembly Config Attribute Priority Assembly-level configuration attributes must have a lower priority than class to allow overriding their behavior. * feat(configuration): Add property ConfigAnalyse to IConfig * feat(configuration): Implements IConfig.ConfigAnalyse * feat: Display ConfigAnalyse on Benchmark Summary * feat(configuration): Implement test * feat: omit 'Config Issues' header if there are no any issues. * fixes: Renamed ConfigAnalyse in ConfigAnalysisConclusion
1 parent 63c0ced commit c1f210c

File tree

10 files changed

+124
-18
lines changed

10 files changed

+124
-18
lines changed

src/BenchmarkDotNet/Analysers/ConclusionHelper.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@ namespace BenchmarkDotNet.Analysers
77
{
88
public static class ConclusionHelper
99
{
10-
public static void Print(ILogger logger, List<Conclusion> conclusions)
10+
public static void Print(ILogger logger, IEnumerable<Conclusion> conclusions)
1111
{
1212
PrintFiltered(conclusions, ConclusionKind.Error, "Errors", logger.WriteLineError);
1313
PrintFiltered(conclusions, ConclusionKind.Warning, "Warnings", logger.WriteLineError);
1414
PrintFiltered(conclusions, ConclusionKind.Hint, "Hints", logger.WriteLineHint);
1515
}
1616

17-
private static void PrintFiltered(List<Conclusion> conclusions, ConclusionKind kind, string title, Action<string> printLine)
17+
private static void PrintFiltered(IEnumerable<Conclusion> conclusions, ConclusionKind kind, string title, Action<string> printLine)
1818
{
1919
var filtered = conclusions.Where(c => c.Kind == kind).ToArray();
2020
if (filtered.Any())

src/BenchmarkDotNet/Configs/DebugConfig.cs

+3
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ public override IEnumerable<Job> GetJobs()
5252

5353
public abstract class DebugConfig : IConfig
5454
{
55+
private readonly static Conclusion[] emptyConclusion = Array.Empty<Conclusion>();
5556
public abstract IEnumerable<Job> GetJobs();
5657

5758
public IEnumerable<IValidator> GetValidators() => Array.Empty<IValidator>();
@@ -83,5 +84,7 @@ public string ArtifactsPath
8384
public IEnumerable<BenchmarkLogicalGroupRule> GetLogicalGroupRules() => Array.Empty<BenchmarkLogicalGroupRule>();
8485

8586
public ConfigOptions Options => ConfigOptions.KeepBenchmarkFiles | ConfigOptions.DisableOptimizationsValidator;
87+
88+
public IReadOnlyList<Conclusion> ConfigAnalysisConclusion => emptyConclusion;
8689
}
8790
}

src/BenchmarkDotNet/Configs/DefaultConfig.cs

+3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ namespace BenchmarkDotNet.Configs
2020
public class DefaultConfig : IConfig
2121
{
2222
public static readonly IConfig Instance = new DefaultConfig();
23+
private readonly static Conclusion[] emptyConclusion = Array.Empty<Conclusion>();
2324

2425
private DefaultConfig()
2526
{
@@ -91,6 +92,8 @@ public string ArtifactsPath
9192
}
9293
}
9394

95+
public IReadOnlyList<Conclusion> ConfigAnalysisConclusion => emptyConclusion;
96+
9497
public IEnumerable<Job> GetJobs() => Array.Empty<Job>();
9598

9699
public IEnumerable<BenchmarkLogicalGroupRule> GetLogicalGroupRules() => Array.Empty<BenchmarkLogicalGroupRule>();

src/BenchmarkDotNet/Configs/IConfig.cs

+6-1
Original file line numberDiff line numberDiff line change
@@ -51,5 +51,10 @@ public interface IConfig
5151
/// the auto-generated project build timeout
5252
/// </summary>
5353
TimeSpan BuildTimeout { get; }
54+
55+
/// <summary>
56+
/// Collect any errors or warnings when composing the configuration
57+
/// </summary>
58+
IReadOnlyList<Conclusion> ConfigAnalysisConclusion { get; }
5459
}
55-
}
60+
}

src/BenchmarkDotNet/Configs/ImmutableConfig.cs

+6-2
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ internal ImmutableConfig(
5050
IOrderer orderer,
5151
SummaryStyle summaryStyle,
5252
ConfigOptions options,
53-
TimeSpan buildTimeout)
53+
TimeSpan buildTimeout,
54+
IReadOnlyList<Conclusion> configAnalysisConclusion)
5455
{
5556
columnProviders = uniqueColumnProviders;
5657
loggers = uniqueLoggers;
@@ -69,6 +70,7 @@ internal ImmutableConfig(
6970
SummaryStyle = summaryStyle;
7071
Options = options;
7172
BuildTimeout = buildTimeout;
73+
ConfigAnalysisConclusion = configAnalysisConclusion;
7274
}
7375

7476
public ConfigUnionRule UnionRule { get; }
@@ -108,5 +110,7 @@ public IDiagnoser GetCompositeDiagnoser(BenchmarkCase benchmarkCase, RunMode run
108110

109111
return diagnosersForGivenMode.Any() ? new CompositeDiagnoser(diagnosersForGivenMode) : null;
110112
}
113+
114+
public IReadOnlyList<Conclusion> ConfigAnalysisConclusion { get; private set; }
111115
}
112-
}
116+
}

src/BenchmarkDotNet/Configs/ImmutableConfigBuilder.cs

+60-10
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,11 @@ public static ImmutableConfig Create(IConfig source)
3636
{
3737
var uniqueColumnProviders = source.GetColumnProviders().Distinct().ToImmutableArray();
3838
var uniqueLoggers = source.GetLoggers().ToImmutableHashSet();
39+
var configAnalyse = new List<Conclusion>();
3940

4041
var uniqueHardwareCounters = source.GetHardwareCounters().ToImmutableHashSet();
4142
var uniqueDiagnosers = GetDiagnosers(source.GetDiagnosers(), uniqueHardwareCounters);
42-
var uniqueExporters = GetExporters(source.GetExporters(), uniqueDiagnosers);
43+
var uniqueExporters = GetExporters(source.GetExporters(), uniqueDiagnosers, configAnalyse);
4344
var uniqueAnalyzers = GetAnalysers(source.GetAnalysers(), uniqueDiagnosers);
4445

4546
var uniqueValidators = GetValidators(source.GetValidators(), MandatoryValidators, source.Options);
@@ -66,7 +67,8 @@ public static ImmutableConfig Create(IConfig source)
6667
source.Orderer ?? DefaultOrderer.Instance,
6768
source.SummaryStyle ?? SummaryStyle.Default,
6869
source.Options,
69-
source.BuildTimeout
70+
source.BuildTimeout,
71+
configAnalyse.AsReadOnly()
7072
);
7173
}
7274

@@ -90,18 +92,43 @@ private static ImmutableHashSet<IDiagnoser> GetDiagnosers(IEnumerable<IDiagnoser
9092
return builder.ToImmutable();
9193
}
9294

93-
private static ImmutableArray<IExporter> GetExporters(IEnumerable<IExporter> exporters, ImmutableHashSet<IDiagnoser> uniqueDiagnosers)
95+
private static ImmutableArray<IExporter> GetExporters(IEnumerable<IExporter> exporters,
96+
ImmutableHashSet<IDiagnoser> uniqueDiagnosers,
97+
IList<Conclusion> configAnalyse)
9498
{
95-
var result = new List<IExporter>();
99+
100+
void AddWarning(string message)
101+
{
102+
var conclusion = Conclusion.CreateWarning("Configuration", message);
103+
configAnalyse.Add(conclusion);
104+
}
105+
106+
var mergeDictionary = new Dictionary<System.Type, IExporter>();
96107

97108
foreach (var exporter in exporters)
98-
if (!result.Contains(exporter))
99-
result.Add(exporter);
109+
{
110+
var exporterType = exporter.GetType();
111+
if (mergeDictionary.ContainsKey(exporterType))
112+
{
113+
AddWarning($"The exporter {exporterType} is already present in configuration. There may be unexpected results.");
114+
}
115+
mergeDictionary[exporterType] = exporter;
116+
}
117+
100118

101119
foreach (var diagnoser in uniqueDiagnosers)
102-
foreach (var exporter in diagnoser.Exporters)
103-
if (!result.Contains(exporter))
104-
result.Add(exporter);
120+
foreach (var exporter in diagnoser.Exporters)
121+
{
122+
var exporterType = exporter.GetType();
123+
if (mergeDictionary.ContainsKey(exporterType))
124+
{
125+
AddWarning($"The exporter {exporterType} of {diagnoser.GetType().Name} is already present in configuration. There may be unexpected results.");
126+
}
127+
mergeDictionary[exporterType] = exporter;
128+
};
129+
130+
var result = mergeDictionary.Values.ToList();
131+
105132

106133
var hardwareCounterDiagnoser = uniqueDiagnosers.OfType<IHardwareCountersDiagnoser>().SingleOrDefault();
107134
var disassemblyDiagnoser = uniqueDiagnosers.OfType<DisassemblyDiagnoser>().SingleOrDefault();
@@ -113,8 +140,31 @@ private static ImmutableArray<IExporter> GetExporters(IEnumerable<IExporter> exp
113140
for (int i = result.Count - 1; i >=0; i--)
114141
if (result[i] is IExporterDependencies exporterDependencies)
115142
foreach (var dependency in exporterDependencies.Dependencies)
116-
if (!result.Contains(dependency))
143+
/*
144+
* When exporter that depends on an other already present in the configuration print warning.
145+
*
146+
* Example:
147+
*
148+
* // Global Current Culture separator is Semicolon;
149+
* [CsvMeasurementsExporter(CsvSeparator.Comma)] // force use Comma
150+
* [RPlotExporter]
151+
* public class MyBanch
152+
* {
153+
*
154+
* }
155+
*
156+
* RPlotExporter is depend from CsvMeasurementsExporter.Default
157+
*
158+
* On active logger will by print:
159+
* "The CsvMeasurementsExporter is already present in the configuration. There may be unexpected results of RPlotExporter.
160+
*
161+
*/
162+
if (!result.Any(exporter=> exporter.GetType() == dependency.GetType()))
117163
result.Insert(i, dependency); // All the exporter dependencies should be added before the exporter
164+
else
165+
{
166+
AddWarning($"The {dependency.Name} is already present in the configuration. There may be unexpected results of {result[i].GetType().Name}.");
167+
}
118168

119169
result.Sort((left, right) => (left is IExporterDependencies).CompareTo(right is IExporterDependencies)); // the case when they were defined by user in wrong order ;)
120170

src/BenchmarkDotNet/Configs/ManualConfig.cs

+5-2
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ public class ManualConfig : IConfig
2929
private readonly List<Job> jobs = new List<Job>();
3030
private readonly HashSet<HardwareCounter> hardwareCounters = new HashSet<HardwareCounter>();
3131
private readonly List<IFilter> filters = new List<IFilter>();
32-
private readonly List<BenchmarkLogicalGroupRule> logicalGroupRules = new List<BenchmarkLogicalGroupRule>();
32+
private readonly HashSet<BenchmarkLogicalGroupRule> logicalGroupRules = new HashSet<BenchmarkLogicalGroupRule>();
33+
private readonly static Conclusion[] emptyConclusion = Array.Empty<Conclusion>();
3334

3435
public IEnumerable<IColumnProvider> GetColumnProviders() => columnProviders;
3536
public IEnumerable<IExporter> GetExporters() => exporters;
@@ -50,6 +51,8 @@ public class ManualConfig : IConfig
5051
[PublicAPI] public SummaryStyle SummaryStyle { get; set; }
5152
[PublicAPI] public TimeSpan BuildTimeout { get; set; } = DefaultConfig.Instance.BuildTimeout;
5253

54+
public IReadOnlyList<Conclusion> ConfigAnalysisConclusion => emptyConclusion;
55+
5356
public ManualConfig WithOption(ConfigOptions option, bool value)
5457
{
5558
Options = Options.Set(value, option);
@@ -277,4 +280,4 @@ private static TimeSpan GetBuildTimeout(TimeSpan current, TimeSpan other)
277280
? other
278281
: TimeSpan.FromMilliseconds(Math.Max(current.TotalMilliseconds, other.TotalMilliseconds));
279282
}
280-
}
283+
}

src/BenchmarkDotNet/Running/BenchmarkConverter.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ private static ImmutableConfig GetFullTypeConfig(Type type, IConfig config)
8989
var typeAttributes = type.GetCustomAttributes(true).OfType<IConfigSource>();
9090
var assemblyAttributes = type.Assembly.GetCustomAttributes().OfType<IConfigSource>();
9191

92-
foreach (var configFromAttribute in typeAttributes.Concat(assemblyAttributes))
92+
foreach (var configFromAttribute in assemblyAttributes.Concat(typeAttributes))
9393
config = ManualConfig.Union(config, configFromAttribute.Config);
9494

9595
return ImmutableConfigBuilder.Create(config);

src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs

+6
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,12 @@ private static void PrintSummary(ILogger logger, ImmutableConfig config, Summary
255255
// TODO: make exporter
256256
ConclusionHelper.Print(logger, config.GetCompositeAnalyser().Analyse(summary).Distinct().ToList());
257257

258+
if (config.ConfigAnalysisConclusion.Any())
259+
{
260+
logger.WriteLineHeader("// * Config Issues *");
261+
ConclusionHelper.Print(logger, config.ConfigAnalysisConclusion);
262+
}
263+
258264
// TODO: move to conclusions
259265
var columnWithLegends = summary.Table.Columns.Select(c => c.OriginalColumn).Where(c => !string.IsNullOrEmpty(c.Legend)).ToList();
260266
var effectiveTimeUnit = summary.Table.EffectiveSummaryStyle.TimeUnit;

tests/BenchmarkDotNet.Tests/Configs/ImmutableConfigTests.cs

+32
Original file line numberDiff line numberDiff line change
@@ -376,5 +376,37 @@ public class TestExporterDependency : IExporter
376376
public string Name => nameof(TestExporterDependency);
377377
public void ExportToLog(Summary summary, ILogger logger) { }
378378
}
379+
380+
[Fact]
381+
public void GenerateWarningWhenExporterDependencyAlreadyExistInConfig()
382+
{
383+
System.Globalization.CultureInfo currentCulture = default;
384+
System.Globalization.CultureInfo currentUICulture = default;
385+
{
386+
var ct = System.Threading.Thread.CurrentThread;
387+
currentCulture = ct.CurrentCulture;
388+
currentUICulture = ct.CurrentUICulture;
389+
ct.CurrentCulture = System.Globalization.CultureInfo.InvariantCulture;
390+
ct.CurrentUICulture = System.Globalization.CultureInfo.InvariantCulture;
391+
}
392+
try
393+
{
394+
var mutable = ManualConfig.CreateEmpty();
395+
mutable.AddExporter(new BenchmarkDotNet.Exporters.Csv.CsvMeasurementsExporter(BenchmarkDotNet.Exporters.Csv.CsvSeparator.Comma));
396+
mutable.AddExporter(RPlotExporter.Default);
397+
398+
var final = ImmutableConfigBuilder.Create(mutable);
399+
400+
Assert.Equal(1, final.ConfigAnalysisConclusion.Count);
401+
}
402+
finally
403+
{
404+
var ct = System.Threading.Thread.CurrentThread;
405+
ct.CurrentCulture = currentCulture;
406+
ct.CurrentUICulture = currentUICulture;
407+
408+
}
409+
410+
}
379411
}
380412
}

0 commit comments

Comments
 (0)