From 39791f74a654247a56cae712ceda09713a007434 Mon Sep 17 00:00:00 2001 From: Ian Qvist Date: Wed, 1 Jan 2025 19:43:43 +0100 Subject: [PATCH 1/9] Add support for user-supplied project file detection --- src/BenchmarkDotNet/Configs/DebugConfig.cs | 2 + src/BenchmarkDotNet/Configs/DefaultConfig.cs | 6 +++ src/BenchmarkDotNet/Configs/IConfig.cs | 2 + .../Configs/ImmutableConfig.cs | 7 +++- .../Configs/ImmutableConfigBuilder.cs | 4 +- src/BenchmarkDotNet/Configs/ManualConfig.cs | 13 +++++- src/BenchmarkDotNet/Locators/ILocator.cs | 10 +++++ src/BenchmarkDotNet/Locators/LocatorType.cs | 6 +++ .../Locators/ProjectLocator.cs | 40 +++++++++++++++++++ .../Toolchains/CsProj/CsProjGenerator.cs | 37 ++++++++--------- .../MonoAotLLVM/MonoAotLLVMGenerator.cs | 2 +- .../Toolchains/MonoWasm/WasmGenerator.cs | 2 +- .../Toolchains/NativeAot/Generator.cs | 10 ++--- .../Configs/ImmutableConfigTests.cs | 39 ++++++++++++++++++ 14 files changed, 150 insertions(+), 30 deletions(-) create mode 100644 src/BenchmarkDotNet/Locators/ILocator.cs create mode 100644 src/BenchmarkDotNet/Locators/LocatorType.cs create mode 100644 src/BenchmarkDotNet/Locators/ProjectLocator.cs diff --git a/src/BenchmarkDotNet/Configs/DebugConfig.cs b/src/BenchmarkDotNet/Configs/DebugConfig.cs index 0fdda7b08d..23c58b107f 100644 --- a/src/BenchmarkDotNet/Configs/DebugConfig.cs +++ b/src/BenchmarkDotNet/Configs/DebugConfig.cs @@ -8,6 +8,7 @@ using BenchmarkDotNet.Exporters; using BenchmarkDotNet.Filters; using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Locators; using BenchmarkDotNet.Loggers; using BenchmarkDotNet.Order; using BenchmarkDotNet.Reports; @@ -58,6 +59,7 @@ public abstract class DebugConfig : IConfig public IEnumerable GetValidators() => Array.Empty(); public IEnumerable GetColumnProviders() => DefaultColumnProviders.Instance; public IEnumerable GetExporters() => Array.Empty(); + public IEnumerable GetLocators() => new[] { ProjectLocator.Default }; public IEnumerable GetLoggers() => new[] { ConsoleLogger.Default }; public IEnumerable GetDiagnosers() => Array.Empty(); public IEnumerable GetAnalysers() => Array.Empty(); diff --git a/src/BenchmarkDotNet/Configs/DefaultConfig.cs b/src/BenchmarkDotNet/Configs/DefaultConfig.cs index b70333cc1d..a628251b9f 100644 --- a/src/BenchmarkDotNet/Configs/DefaultConfig.cs +++ b/src/BenchmarkDotNet/Configs/DefaultConfig.cs @@ -11,6 +11,7 @@ using BenchmarkDotNet.Exporters.Csv; using BenchmarkDotNet.Filters; using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Locators; using BenchmarkDotNet.Loggers; using BenchmarkDotNet.Order; using BenchmarkDotNet.Portability; @@ -40,6 +41,11 @@ public IEnumerable GetExporters() yield return HtmlExporter.Default; } + public IEnumerable GetLocators() + { + yield return ProjectLocator.Default; + } + public IEnumerable GetLoggers() { if (LinqPadLogger.IsAvailable) diff --git a/src/BenchmarkDotNet/Configs/IConfig.cs b/src/BenchmarkDotNet/Configs/IConfig.cs index b311c235f5..3f5818226c 100644 --- a/src/BenchmarkDotNet/Configs/IConfig.cs +++ b/src/BenchmarkDotNet/Configs/IConfig.cs @@ -8,6 +8,7 @@ using BenchmarkDotNet.Exporters; using BenchmarkDotNet.Filters; using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Locators; using BenchmarkDotNet.Loggers; using BenchmarkDotNet.Order; using BenchmarkDotNet.Reports; @@ -20,6 +21,7 @@ public interface IConfig { IEnumerable GetColumnProviders(); IEnumerable GetExporters(); + IEnumerable GetLocators(); IEnumerable GetLoggers(); IEnumerable GetDiagnosers(); IEnumerable GetAnalysers(); diff --git a/src/BenchmarkDotNet/Configs/ImmutableConfig.cs b/src/BenchmarkDotNet/Configs/ImmutableConfig.cs index b6e03126fd..c4dacd2a80 100644 --- a/src/BenchmarkDotNet/Configs/ImmutableConfig.cs +++ b/src/BenchmarkDotNet/Configs/ImmutableConfig.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Globalization; @@ -10,6 +10,7 @@ using BenchmarkDotNet.Exporters; using BenchmarkDotNet.Filters; using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Locators; using BenchmarkDotNet.Loggers; using BenchmarkDotNet.Order; using BenchmarkDotNet.Reports; @@ -24,6 +25,7 @@ public sealed class ImmutableConfig : IConfig // if something is an array here instead of hashset it means it must have a guaranteed order of elements private readonly ImmutableArray columnProviders; private readonly ImmutableArray exporters; + private readonly ImmutableArray locators; private readonly ImmutableHashSet loggers; private readonly ImmutableHashSet diagnosers; private readonly ImmutableHashSet analysers; @@ -41,6 +43,7 @@ internal ImmutableConfig( ImmutableHashSet uniqueHardwareCounters, ImmutableHashSet uniqueDiagnosers, ImmutableArray uniqueExporters, + ImmutableArray uniqueLocators, ImmutableHashSet uniqueAnalyzers, ImmutableHashSet uniqueValidators, ImmutableHashSet uniqueFilters, @@ -63,6 +66,7 @@ internal ImmutableConfig( hardwareCounters = uniqueHardwareCounters; diagnosers = uniqueDiagnosers; exporters = uniqueExporters; + locators = uniqueLocators; analysers = uniqueAnalyzers; validators = uniqueValidators; filters = uniqueFilters; @@ -92,6 +96,7 @@ internal ImmutableConfig( public IEnumerable GetColumnProviders() => columnProviders; public IEnumerable GetExporters() => exporters; + public IEnumerable GetLocators() => locators; public IEnumerable GetLoggers() => loggers; public IEnumerable GetDiagnosers() => diagnosers; public IEnumerable GetAnalysers() => analysers; diff --git a/src/BenchmarkDotNet/Configs/ImmutableConfigBuilder.cs b/src/BenchmarkDotNet/Configs/ImmutableConfigBuilder.cs index d7c0b5eb0f..1dbe5d605b 100644 --- a/src/BenchmarkDotNet/Configs/ImmutableConfigBuilder.cs +++ b/src/BenchmarkDotNet/Configs/ImmutableConfigBuilder.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using BenchmarkDotNet.Analysers; @@ -44,6 +44,7 @@ public static ImmutableConfig Create(IConfig source) var uniqueHardwareCounters = source.GetHardwareCounters().Where(counter => counter != HardwareCounter.NotSet).ToImmutableHashSet(); var uniqueDiagnosers = GetDiagnosers(source.GetDiagnosers(), uniqueHardwareCounters); var uniqueExporters = GetExporters(source.GetExporters(), uniqueDiagnosers, configAnalyse); + var uniqueLocators = source.GetLocators().ToImmutableArray(); var uniqueAnalyzers = GetAnalysers(source.GetAnalysers(), uniqueDiagnosers); var uniqueValidators = GetValidators(source.GetValidators(), MandatoryValidators, source.Options); @@ -61,6 +62,7 @@ public static ImmutableConfig Create(IConfig source) uniqueHardwareCounters, uniqueDiagnosers, uniqueExporters, + uniqueLocators, uniqueAnalyzers, uniqueValidators, uniqueFilters, diff --git a/src/BenchmarkDotNet/Configs/ManualConfig.cs b/src/BenchmarkDotNet/Configs/ManualConfig.cs index 5ea1be24e9..cfa0877548 100644 --- a/src/BenchmarkDotNet/Configs/ManualConfig.cs +++ b/src/BenchmarkDotNet/Configs/ManualConfig.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.ComponentModel; using System.Globalization; @@ -11,6 +11,7 @@ using BenchmarkDotNet.Extensions; using BenchmarkDotNet.Filters; using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Locators; using BenchmarkDotNet.Loggers; using BenchmarkDotNet.Order; using BenchmarkDotNet.Reports; @@ -26,6 +27,7 @@ public class ManualConfig : IConfig private readonly List columnProviders = new List(); private readonly List exporters = new List(); + private readonly List locators = new List(); private readonly List loggers = new List(); private readonly List diagnosers = new List(); private readonly List analysers = new List(); @@ -39,6 +41,7 @@ public class ManualConfig : IConfig public IEnumerable GetColumnProviders() => columnProviders; public IEnumerable GetExporters() => exporters; + public IEnumerable GetLocators() => locators; public IEnumerable GetLoggers() => loggers; public IEnumerable GetDiagnosers() => diagnosers; public IEnumerable GetAnalysers() => analysers; @@ -139,6 +142,12 @@ public ManualConfig AddExporter(params IExporter[] newExporters) return this; } + public ManualConfig AddLocator(params ILocator[] newLocators) + { + locators.AddRange(newLocators); + return this; + } + [EditorBrowsable(EditorBrowsableState.Never)] [Obsolete("This method will soon be removed, please start using .AddLogger() instead.")] public void Add(params ILogger[] newLoggers) => AddLogger(newLoggers); @@ -256,6 +265,7 @@ public void Add(IConfig config) { columnProviders.AddRange(config.GetColumnProviders()); exporters.AddRange(config.GetExporters()); + locators.AddRange(config.GetLocators()); loggers.AddRange(config.GetLoggers()); diagnosers.AddRange(config.GetDiagnosers()); analysers.AddRange(config.GetAnalysers()); @@ -287,6 +297,7 @@ public void Add(IConfig config) public static ManualConfig CreateMinimumViable() => CreateEmpty() .AddColumnProvider(DefaultColumnProviders.Instance) + .AddLocator(ProjectLocator.Default) .AddLogger(ConsoleLogger.Default); public static ManualConfig Create(IConfig config) diff --git a/src/BenchmarkDotNet/Locators/ILocator.cs b/src/BenchmarkDotNet/Locators/ILocator.cs new file mode 100644 index 0000000000..1779027197 --- /dev/null +++ b/src/BenchmarkDotNet/Locators/ILocator.cs @@ -0,0 +1,10 @@ +using System; +using System.IO; + +namespace BenchmarkDotNet.Locators; + +public interface ILocator +{ + LocatorType LocatorType { get; } + FileInfo Locate(DirectoryInfo rootDirectory, Type type); +} \ No newline at end of file diff --git a/src/BenchmarkDotNet/Locators/LocatorType.cs b/src/BenchmarkDotNet/Locators/LocatorType.cs new file mode 100644 index 0000000000..5afaf0acdb --- /dev/null +++ b/src/BenchmarkDotNet/Locators/LocatorType.cs @@ -0,0 +1,6 @@ +namespace BenchmarkDotNet.Locators; + +public enum LocatorType +{ + ProjectFile +} \ No newline at end of file diff --git a/src/BenchmarkDotNet/Locators/ProjectLocator.cs b/src/BenchmarkDotNet/Locators/ProjectLocator.cs new file mode 100644 index 0000000000..5467d3ff25 --- /dev/null +++ b/src/BenchmarkDotNet/Locators/ProjectLocator.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using BenchmarkDotNet.Exporters; + +namespace BenchmarkDotNet.Locators; + +public class ProjectLocator : ILocator +{ + public static ProjectLocator Default { get; } = new ProjectLocator(); + + public LocatorType LocatorType => LocatorType.ProjectFile; + + public FileInfo Locate(DirectoryInfo rootDirectory, Type type) + { + // important assumption! project's file name === output dll name + string projectName = type.GetTypeInfo().Assembly.GetName().Name; + + var possibleNames = new HashSet { $"{projectName}.csproj", $"{projectName}.fsproj", $"{projectName}.vbproj" }; + var projectFiles = rootDirectory + .EnumerateFiles("*proj", SearchOption.AllDirectories) + .Where(file => possibleNames.Contains(file.Name)) + .ToArray(); + + if (projectFiles.Length == 0) + { + throw new NotSupportedException( + $"Unable to find {projectName} in {rootDirectory.FullName} and its subfolders. Most probably the name of output exe is different than the name of the .(c/f)sproj"); + } + else if (projectFiles.Length > 1) + { + throw new NotSupportedException( + $"Found more than one matching project file for {projectName} in {rootDirectory.FullName} and its subfolders: {string.Join(",", projectFiles.Select(pf => $"'{pf.FullName}'"))}. Benchmark project names needs to be unique."); + } + + return projectFiles[0]; + } +} \ No newline at end of file diff --git a/src/BenchmarkDotNet/Toolchains/CsProj/CsProjGenerator.cs b/src/BenchmarkDotNet/Toolchains/CsProj/CsProjGenerator.cs index 7e91c36ff4..c2b3976bed 100644 --- a/src/BenchmarkDotNet/Toolchains/CsProj/CsProjGenerator.cs +++ b/src/BenchmarkDotNet/Toolchains/CsProj/CsProjGenerator.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; @@ -11,6 +11,7 @@ using BenchmarkDotNet.Extensions; using BenchmarkDotNet.Helpers; using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Locators; using BenchmarkDotNet.Loggers; using BenchmarkDotNet.Running; using BenchmarkDotNet.Toolchains.DotNetCli; @@ -71,7 +72,7 @@ protected override string GetIntermediateDirectoryPath(string buildArtifactsDire protected override void GenerateProject(BuildPartition buildPartition, ArtifactsPaths artifactsPaths, ILogger logger) { var benchmark = buildPartition.RepresentativeBenchmarkCase; - var projectFile = GetProjectFilePath(benchmark.Descriptor.Type, logger); + var projectFile = GetProjectFilePath(benchmark, logger); var xmlDoc = new XmlDocument(); xmlDoc.Load(projectFile.FullName); @@ -246,8 +247,10 @@ private static string GetIndentedXmlString(XmlDocument doc) /// returns a path to the project file which defines the benchmarks /// [PublicAPI] - protected virtual FileInfo GetProjectFilePath(Type benchmarkTarget, ILogger logger) + protected virtual FileInfo GetProjectFilePath(BenchmarkCase benchmark, ILogger logger) { + var benchmarkTarget = benchmark.Descriptor.Type; + if (!GetSolutionRootDirectory(out var rootDirectory) && !GetProjectRootDirectory(out rootDirectory)) { logger.WriteLineError( @@ -255,27 +258,21 @@ protected virtual FileInfo GetProjectFilePath(Type benchmarkTarget, ILogger logg rootDirectory = new DirectoryInfo(Directory.GetCurrentDirectory()); } - // important assumption! project's file name === output dll name - string projectName = benchmarkTarget.GetTypeInfo().Assembly.GetName().Name; + List notFound = new List(); + foreach (ILocator locator in benchmark.Config.GetLocators()) + { + if (locator.LocatorType != LocatorType.ProjectFile) + continue; - var possibleNames = new HashSet { $"{projectName}.csproj", $"{projectName}.fsproj", $"{projectName}.vbproj" }; - var projectFiles = rootDirectory - .EnumerateFiles("*proj", SearchOption.AllDirectories) - .Where(file => possibleNames.Contains(file.Name)) - .ToArray(); + var path = locator.Locate(rootDirectory, benchmarkTarget); - if (projectFiles.Length == 0) - { - throw new NotSupportedException( - $"Unable to find {projectName} in {rootDirectory.FullName} and its subfolders. Most probably the name of output exe is different than the name of the .(c/f)sproj"); - } - else if (projectFiles.Length > 1) - { - throw new NotSupportedException( - $"Found more than one matching project file for {projectName} in {rootDirectory.FullName} and its subfolders: {string.Join(",", projectFiles.Select(pf => $"'{pf.FullName}'"))}. Benchmark project names needs to be unique."); + if (path.Exists) + return path; + + notFound.Add(path.FullName); } - return projectFiles[0]; + throw new NotSupportedException($"Unable to find project file in {rootDirectory}. Attempted location(s): " + string.Join(", ", notFound)); } public override bool Equals(object obj) => obj is CsProjGenerator other && Equals(other); diff --git a/src/BenchmarkDotNet/Toolchains/MonoAotLLVM/MonoAotLLVMGenerator.cs b/src/BenchmarkDotNet/Toolchains/MonoAotLLVM/MonoAotLLVMGenerator.cs index de3cef53d3..906735a425 100644 --- a/src/BenchmarkDotNet/Toolchains/MonoAotLLVM/MonoAotLLVMGenerator.cs +++ b/src/BenchmarkDotNet/Toolchains/MonoAotLLVM/MonoAotLLVMGenerator.cs @@ -28,7 +28,7 @@ public MonoAotLLVMGenerator(string targetFrameworkMoniker, string cliPath, strin protected override void GenerateProject(BuildPartition buildPartition, ArtifactsPaths artifactsPaths, ILogger logger) { BenchmarkCase benchmark = buildPartition.RepresentativeBenchmarkCase; - var projectFile = GetProjectFilePath(benchmark.Descriptor.Type, logger); + var projectFile = GetProjectFilePath(benchmark, logger); string useLLVM = AotCompilerMode == MonoAotCompilerMode.llvm ? "true" : "false"; diff --git a/src/BenchmarkDotNet/Toolchains/MonoWasm/WasmGenerator.cs b/src/BenchmarkDotNet/Toolchains/MonoWasm/WasmGenerator.cs index 7c9aa8826f..e5e4262429 100644 --- a/src/BenchmarkDotNet/Toolchains/MonoWasm/WasmGenerator.cs +++ b/src/BenchmarkDotNet/Toolchains/MonoWasm/WasmGenerator.cs @@ -41,7 +41,7 @@ protected override void GenerateProject(BuildPartition buildPartition, Artifacts protected void GenerateProjectFile(BuildPartition buildPartition, ArtifactsPaths artifactsPaths, bool aot, ILogger logger) { BenchmarkCase benchmark = buildPartition.RepresentativeBenchmarkCase; - var projectFile = GetProjectFilePath(benchmark.Descriptor.Type, logger); + var projectFile = GetProjectFilePath(benchmark, logger); WasmRuntime runtime = (WasmRuntime) buildPartition.Runtime; diff --git a/src/BenchmarkDotNet/Toolchains/NativeAot/Generator.cs b/src/BenchmarkDotNet/Toolchains/NativeAot/Generator.cs index 061b64a9a8..3c97bab265 100644 --- a/src/BenchmarkDotNet/Toolchains/NativeAot/Generator.cs +++ b/src/BenchmarkDotNet/Toolchains/NativeAot/Generator.cs @@ -151,17 +151,17 @@ private string GenerateProjectForNuGetBuild(BuildPartition buildPartition, Artif {GetILCompilerPackageReference()} - + - {string.Join(Environment.NewLine, GetRdXmlFiles(buildPartition.RepresentativeBenchmarkCase.Descriptor.Type, logger).Select(file => $""))} + {string.Join(Environment.NewLine, GetRdXmlFiles(buildPartition.RepresentativeBenchmarkCase, logger).Select(file => $""))} {GetCustomProperties(buildPartition, logger)} "; private string GetCustomProperties(BuildPartition buildPartition, ILogger logger) { - var projectFile = GetProjectFilePath(buildPartition.RepresentativeBenchmarkCase.Descriptor.Type, logger); + var projectFile = GetProjectFilePath(buildPartition.RepresentativeBenchmarkCase, logger); var xmlDoc = new XmlDocument(); xmlDoc.Load(projectFile.FullName); @@ -186,11 +186,11 @@ private string GetInstructionSetSettings(BuildPartition buildPartition) return !string.IsNullOrEmpty(instructionSet) ? $"{instructionSet}" : ""; } - public IEnumerable GetRdXmlFiles(Type benchmarkTarget, ILogger logger) + public IEnumerable GetRdXmlFiles(BenchmarkCase benchmark, ILogger logger) { yield return GeneratedRdXmlFileName; - var projectFile = GetProjectFilePath(benchmarkTarget, logger); + var projectFile = GetProjectFilePath(benchmark, logger); var projectFileFolder = projectFile.DirectoryName; var rdXml = Path.Combine(projectFileFolder, "rd.xml"); if (File.Exists(rdXml)) diff --git a/tests/BenchmarkDotNet.Tests/Configs/ImmutableConfigTests.cs b/tests/BenchmarkDotNet.Tests/Configs/ImmutableConfigTests.cs index 2393cb737c..9313c8b760 100644 --- a/tests/BenchmarkDotNet.Tests/Configs/ImmutableConfigTests.cs +++ b/tests/BenchmarkDotNet.Tests/Configs/ImmutableConfigTests.cs @@ -8,6 +8,7 @@ using BenchmarkDotNet.Environments; using BenchmarkDotNet.Exporters; using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Locators; using BenchmarkDotNet.Loggers; using BenchmarkDotNet.Order; using BenchmarkDotNet.Reports; @@ -452,5 +453,43 @@ public void GenerateWarningWhenExporterDependencyAlreadyExistInConfig() } } + + [Fact] + public void LocatorsAreAddedCorrectly() + { + var mutable = ManualConfig.CreateEmpty(); + + mutable.AddLocator(ProjectLocator.Default); + + var final = ImmutableConfigBuilder.Create(mutable); + + var locator = Assert.Single(final.GetLocators()); + Assert.Same(ProjectLocator.Default, locator); + } + + [Fact] + public void DuplicateLocatorsAreAllowed() + { + var mutable = ManualConfig.CreateEmpty(); + + mutable.AddLocator(ProjectLocator.Default); + mutable.AddLocator(ProjectLocator.Default); + + var final = ImmutableConfigBuilder.Create(mutable); + + Assert.Equal(2, final.GetLocators().Count()); + } + + [Fact] + public void VerifyConfigsHaveLocators() + { + var minVar = ManualConfig.CreateMinimumViable(); + var build1 = ImmutableConfigBuilder.Create(minVar); + Assert.Single(build1.GetLocators()); + + var def = DefaultConfig.Instance; + var build2 = ImmutableConfigBuilder.Create(def); + Assert.Single(build2.GetLocators()); + } } } From 94aa84c992821d2c8c51c12c9beacac8cbf9c984 Mon Sep 17 00:00:00 2001 From: Ian Qvist Date: Thu, 2 Jan 2025 16:44:20 +0100 Subject: [PATCH 2/9] Make ProjectLocator fall back to other locators if enabled --- src/BenchmarkDotNet/Locators/ProjectLocator.cs | 6 +++--- src/BenchmarkDotNet/Toolchains/CsProj/CsProjGenerator.cs | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/BenchmarkDotNet/Locators/ProjectLocator.cs b/src/BenchmarkDotNet/Locators/ProjectLocator.cs index 5467d3ff25..597b589977 100644 --- a/src/BenchmarkDotNet/Locators/ProjectLocator.cs +++ b/src/BenchmarkDotNet/Locators/ProjectLocator.cs @@ -26,10 +26,10 @@ public FileInfo Locate(DirectoryInfo rootDirectory, Type type) if (projectFiles.Length == 0) { - throw new NotSupportedException( - $"Unable to find {projectName} in {rootDirectory.FullName} and its subfolders. Most probably the name of output exe is different than the name of the .(c/f)sproj"); + return null; } - else if (projectFiles.Length > 1) + + if (projectFiles.Length > 1) { throw new NotSupportedException( $"Found more than one matching project file for {projectName} in {rootDirectory.FullName} and its subfolders: {string.Join(",", projectFiles.Select(pf => $"'{pf.FullName}'"))}. Benchmark project names needs to be unique."); diff --git a/src/BenchmarkDotNet/Toolchains/CsProj/CsProjGenerator.cs b/src/BenchmarkDotNet/Toolchains/CsProj/CsProjGenerator.cs index c2b3976bed..5d178ce9f0 100644 --- a/src/BenchmarkDotNet/Toolchains/CsProj/CsProjGenerator.cs +++ b/src/BenchmarkDotNet/Toolchains/CsProj/CsProjGenerator.cs @@ -266,6 +266,9 @@ protected virtual FileInfo GetProjectFilePath(BenchmarkCase benchmark, ILogger l var path = locator.Locate(rootDirectory, benchmarkTarget); + if (path == null) + continue; + if (path.Exists) return path; From b7b21303515acb5ece66b2a32212e75b2820b218 Mon Sep 17 00:00:00 2001 From: Ian Qvist Date: Thu, 2 Jan 2025 17:34:39 +0100 Subject: [PATCH 3/9] Implement most of the suggestion --- src/BenchmarkDotNet/Configs/DebugConfig.cs | 2 +- src/BenchmarkDotNet/Configs/DefaultConfig.cs | 4 +- src/BenchmarkDotNet/Configs/IConfig.cs | 2 +- .../Configs/ImmutableConfig.cs | 8 +- .../Configs/ImmutableConfigBuilder.cs | 4 +- src/BenchmarkDotNet/Configs/ManualConfig.cs | 10 +-- .../{LocatorType.cs => FileLocatorType.cs} | 4 +- src/BenchmarkDotNet/Locators/IFileLocator.cs | 9 ++ src/BenchmarkDotNet/Locators/ILocator.cs | 10 --- src/BenchmarkDotNet/Locators/LocatorArgs.cs | 16 ++++ .../Locators/ProjectFileLocator.cs | 85 +++++++++++++++++++ .../Locators/ProjectLocator.cs | 40 --------- .../Toolchains/CsProj/CsProjGenerator.cs | 35 +++----- .../DotNetCli/DotNetCliGenerator.cs | 19 +---- .../Configs/ImmutableConfigTests.cs | 16 ++-- 15 files changed, 149 insertions(+), 115 deletions(-) rename src/BenchmarkDotNet/Locators/{LocatorType.cs => FileLocatorType.cs} (50%) create mode 100644 src/BenchmarkDotNet/Locators/IFileLocator.cs delete mode 100644 src/BenchmarkDotNet/Locators/ILocator.cs create mode 100644 src/BenchmarkDotNet/Locators/LocatorArgs.cs create mode 100644 src/BenchmarkDotNet/Locators/ProjectFileLocator.cs delete mode 100644 src/BenchmarkDotNet/Locators/ProjectLocator.cs diff --git a/src/BenchmarkDotNet/Configs/DebugConfig.cs b/src/BenchmarkDotNet/Configs/DebugConfig.cs index 23c58b107f..99ba3baeb9 100644 --- a/src/BenchmarkDotNet/Configs/DebugConfig.cs +++ b/src/BenchmarkDotNet/Configs/DebugConfig.cs @@ -59,7 +59,7 @@ public abstract class DebugConfig : IConfig public IEnumerable GetValidators() => Array.Empty(); public IEnumerable GetColumnProviders() => DefaultColumnProviders.Instance; public IEnumerable GetExporters() => Array.Empty(); - public IEnumerable GetLocators() => new[] { ProjectLocator.Default }; + public IEnumerable GetFileLocators() => new[] { ProjectFileLocator.Default }; public IEnumerable GetLoggers() => new[] { ConsoleLogger.Default }; public IEnumerable GetDiagnosers() => Array.Empty(); public IEnumerable GetAnalysers() => Array.Empty(); diff --git a/src/BenchmarkDotNet/Configs/DefaultConfig.cs b/src/BenchmarkDotNet/Configs/DefaultConfig.cs index a628251b9f..c0c258cd54 100644 --- a/src/BenchmarkDotNet/Configs/DefaultConfig.cs +++ b/src/BenchmarkDotNet/Configs/DefaultConfig.cs @@ -41,9 +41,9 @@ public IEnumerable GetExporters() yield return HtmlExporter.Default; } - public IEnumerable GetLocators() + public IEnumerable GetFileLocators() { - yield return ProjectLocator.Default; + yield return ProjectFileLocator.Default; } public IEnumerable GetLoggers() diff --git a/src/BenchmarkDotNet/Configs/IConfig.cs b/src/BenchmarkDotNet/Configs/IConfig.cs index 3f5818226c..7335641534 100644 --- a/src/BenchmarkDotNet/Configs/IConfig.cs +++ b/src/BenchmarkDotNet/Configs/IConfig.cs @@ -21,7 +21,7 @@ public interface IConfig { IEnumerable GetColumnProviders(); IEnumerable GetExporters(); - IEnumerable GetLocators(); + IEnumerable GetFileLocators(); IEnumerable GetLoggers(); IEnumerable GetDiagnosers(); IEnumerable GetAnalysers(); diff --git a/src/BenchmarkDotNet/Configs/ImmutableConfig.cs b/src/BenchmarkDotNet/Configs/ImmutableConfig.cs index c4dacd2a80..1526001fbf 100644 --- a/src/BenchmarkDotNet/Configs/ImmutableConfig.cs +++ b/src/BenchmarkDotNet/Configs/ImmutableConfig.cs @@ -25,7 +25,7 @@ public sealed class ImmutableConfig : IConfig // if something is an array here instead of hashset it means it must have a guaranteed order of elements private readonly ImmutableArray columnProviders; private readonly ImmutableArray exporters; - private readonly ImmutableArray locators; + private readonly ImmutableArray fileLocators; private readonly ImmutableHashSet loggers; private readonly ImmutableHashSet diagnosers; private readonly ImmutableHashSet analysers; @@ -43,7 +43,7 @@ internal ImmutableConfig( ImmutableHashSet uniqueHardwareCounters, ImmutableHashSet uniqueDiagnosers, ImmutableArray uniqueExporters, - ImmutableArray uniqueLocators, + ImmutableArray uniqueFileLocators, ImmutableHashSet uniqueAnalyzers, ImmutableHashSet uniqueValidators, ImmutableHashSet uniqueFilters, @@ -66,7 +66,7 @@ internal ImmutableConfig( hardwareCounters = uniqueHardwareCounters; diagnosers = uniqueDiagnosers; exporters = uniqueExporters; - locators = uniqueLocators; + fileLocators = uniqueFileLocators; analysers = uniqueAnalyzers; validators = uniqueValidators; filters = uniqueFilters; @@ -96,7 +96,7 @@ internal ImmutableConfig( public IEnumerable GetColumnProviders() => columnProviders; public IEnumerable GetExporters() => exporters; - public IEnumerable GetLocators() => locators; + public IEnumerable GetFileLocators() => fileLocators; public IEnumerable GetLoggers() => loggers; public IEnumerable GetDiagnosers() => diagnosers; public IEnumerable GetAnalysers() => analysers; diff --git a/src/BenchmarkDotNet/Configs/ImmutableConfigBuilder.cs b/src/BenchmarkDotNet/Configs/ImmutableConfigBuilder.cs index 1dbe5d605b..6394c0e999 100644 --- a/src/BenchmarkDotNet/Configs/ImmutableConfigBuilder.cs +++ b/src/BenchmarkDotNet/Configs/ImmutableConfigBuilder.cs @@ -44,7 +44,7 @@ public static ImmutableConfig Create(IConfig source) var uniqueHardwareCounters = source.GetHardwareCounters().Where(counter => counter != HardwareCounter.NotSet).ToImmutableHashSet(); var uniqueDiagnosers = GetDiagnosers(source.GetDiagnosers(), uniqueHardwareCounters); var uniqueExporters = GetExporters(source.GetExporters(), uniqueDiagnosers, configAnalyse); - var uniqueLocators = source.GetLocators().ToImmutableArray(); + var uniqueFileLocators = source.GetFileLocators().ToImmutableArray(); var uniqueAnalyzers = GetAnalysers(source.GetAnalysers(), uniqueDiagnosers); var uniqueValidators = GetValidators(source.GetValidators(), MandatoryValidators, source.Options); @@ -62,7 +62,7 @@ public static ImmutableConfig Create(IConfig source) uniqueHardwareCounters, uniqueDiagnosers, uniqueExporters, - uniqueLocators, + uniqueFileLocators, uniqueAnalyzers, uniqueValidators, uniqueFilters, diff --git a/src/BenchmarkDotNet/Configs/ManualConfig.cs b/src/BenchmarkDotNet/Configs/ManualConfig.cs index cfa0877548..5ee881c2fd 100644 --- a/src/BenchmarkDotNet/Configs/ManualConfig.cs +++ b/src/BenchmarkDotNet/Configs/ManualConfig.cs @@ -27,7 +27,7 @@ public class ManualConfig : IConfig private readonly List columnProviders = new List(); private readonly List exporters = new List(); - private readonly List locators = new List(); + private readonly List locators = new List(); private readonly List loggers = new List(); private readonly List diagnosers = new List(); private readonly List analysers = new List(); @@ -41,7 +41,7 @@ public class ManualConfig : IConfig public IEnumerable GetColumnProviders() => columnProviders; public IEnumerable GetExporters() => exporters; - public IEnumerable GetLocators() => locators; + public IEnumerable GetFileLocators() => locators; public IEnumerable GetLoggers() => loggers; public IEnumerable GetDiagnosers() => diagnosers; public IEnumerable GetAnalysers() => analysers; @@ -142,7 +142,7 @@ public ManualConfig AddExporter(params IExporter[] newExporters) return this; } - public ManualConfig AddLocator(params ILocator[] newLocators) + public ManualConfig AddFileLocator(params IFileLocator[] newLocators) { locators.AddRange(newLocators); return this; @@ -265,7 +265,7 @@ public void Add(IConfig config) { columnProviders.AddRange(config.GetColumnProviders()); exporters.AddRange(config.GetExporters()); - locators.AddRange(config.GetLocators()); + locators.AddRange(config.GetFileLocators()); loggers.AddRange(config.GetLoggers()); diagnosers.AddRange(config.GetDiagnosers()); analysers.AddRange(config.GetAnalysers()); @@ -297,7 +297,7 @@ public void Add(IConfig config) public static ManualConfig CreateMinimumViable() => CreateEmpty() .AddColumnProvider(DefaultColumnProviders.Instance) - .AddLocator(ProjectLocator.Default) + .AddFileLocator(ProjectFileLocator.Default) .AddLogger(ConsoleLogger.Default); public static ManualConfig Create(IConfig config) diff --git a/src/BenchmarkDotNet/Locators/LocatorType.cs b/src/BenchmarkDotNet/Locators/FileLocatorType.cs similarity index 50% rename from src/BenchmarkDotNet/Locators/LocatorType.cs rename to src/BenchmarkDotNet/Locators/FileLocatorType.cs index 5afaf0acdb..5242edfc6c 100644 --- a/src/BenchmarkDotNet/Locators/LocatorType.cs +++ b/src/BenchmarkDotNet/Locators/FileLocatorType.cs @@ -1,6 +1,6 @@ namespace BenchmarkDotNet.Locators; -public enum LocatorType +public enum FileLocatorType { - ProjectFile + Project } \ No newline at end of file diff --git a/src/BenchmarkDotNet/Locators/IFileLocator.cs b/src/BenchmarkDotNet/Locators/IFileLocator.cs new file mode 100644 index 0000000000..77f77e0c05 --- /dev/null +++ b/src/BenchmarkDotNet/Locators/IFileLocator.cs @@ -0,0 +1,9 @@ +using System.IO; + +namespace BenchmarkDotNet.Locators; + +public interface IFileLocator +{ + FileLocatorType LocatorType { get; } + bool TryLocate(LocatorArgs locatorArgs, out FileInfo fileInfo); +} \ No newline at end of file diff --git a/src/BenchmarkDotNet/Locators/ILocator.cs b/src/BenchmarkDotNet/Locators/ILocator.cs deleted file mode 100644 index 1779027197..0000000000 --- a/src/BenchmarkDotNet/Locators/ILocator.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; -using System.IO; - -namespace BenchmarkDotNet.Locators; - -public interface ILocator -{ - LocatorType LocatorType { get; } - FileInfo Locate(DirectoryInfo rootDirectory, Type type); -} \ No newline at end of file diff --git a/src/BenchmarkDotNet/Locators/LocatorArgs.cs b/src/BenchmarkDotNet/Locators/LocatorArgs.cs new file mode 100644 index 0000000000..cfc72a6710 --- /dev/null +++ b/src/BenchmarkDotNet/Locators/LocatorArgs.cs @@ -0,0 +1,16 @@ +using BenchmarkDotNet.Loggers; +using BenchmarkDotNet.Running; + +namespace BenchmarkDotNet.Locators; + +public class LocatorArgs +{ + public LocatorArgs(BenchmarkCase benchmarkCase, ILogger logger) + { + BenchmarkCase = benchmarkCase; + Logger = logger; + } + + public BenchmarkCase BenchmarkCase { get; } + public ILogger Logger { get; } +} \ No newline at end of file diff --git a/src/BenchmarkDotNet/Locators/ProjectFileLocator.cs b/src/BenchmarkDotNet/Locators/ProjectFileLocator.cs new file mode 100644 index 0000000000..9725220674 --- /dev/null +++ b/src/BenchmarkDotNet/Locators/ProjectFileLocator.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using BenchmarkDotNet.Exporters; +using BenchmarkDotNet.Running; + +namespace BenchmarkDotNet.Locators; + +public class ProjectFileLocator : IFileLocator +{ + private static readonly string[] ProjectExtensions = { ".csproj", ".fsproj", ".vbroj" }; + + public static ProjectFileLocator Default { get; } = new ProjectFileLocator(); + + public FileLocatorType LocatorType => FileLocatorType.Project; + + public bool TryLocate(LocatorArgs locatorArgs, out FileInfo fileInfo) + { + if (!GetRootDirectory(IsRootSolutionFolder, out var rootDirectory) && !GetRootDirectory(IsRootProjectFolder, out rootDirectory)) + { + rootDirectory = new DirectoryInfo(Directory.GetCurrentDirectory()); + } + + // important assumption! project's file name === output dll name + var type = locatorArgs.BenchmarkCase.Descriptor.Type; + var projectName = type.GetTypeInfo().Assembly.GetName().Name; + + var possibleNames = new HashSet { $"{projectName}.csproj", $"{projectName}.fsproj", $"{projectName}.vbproj" }; + var projectFiles = rootDirectory + .EnumerateFiles("*proj", SearchOption.AllDirectories) + .Where(file => possibleNames.Contains(file.Name)) + .ToArray(); + + if (projectFiles.Length == 0) + { + fileInfo = null; + return false; + } + + if (projectFiles.Length > 1) + { + throw new InvalidOperationException( + $"Found more than one matching project file for {projectName} in {rootDirectory.FullName} and its subfolders: {string.Join(",", projectFiles.Select(pf => $"'{pf.FullName}'"))}. Benchmark project names needs to be unique."); + } + + fileInfo = projectFiles[0]; + return true; + } + + private static bool GetRootDirectory(Func condition, out DirectoryInfo? directoryInfo) + { + directoryInfo = null; + try + { + directoryInfo = new DirectoryInfo(Directory.GetCurrentDirectory()); + while (directoryInfo != null) + { + if (condition(directoryInfo)) + { + return true; + } + + directoryInfo = directoryInfo.Parent; + } + } + catch + { + return false; + } + + return false; + } + + private static bool IsRootSolutionFolder(DirectoryInfo directoryInfo) + => directoryInfo + .GetFileSystemInfos() + .Any(fileInfo => fileInfo.Extension == ".sln" || fileInfo.Name == "global.json"); + + private static bool IsRootProjectFolder(DirectoryInfo directoryInfo) + => directoryInfo + .GetFileSystemInfos() + .Any(fileInfo => ProjectExtensions.Contains(fileInfo.Extension)); +} \ No newline at end of file diff --git a/src/BenchmarkDotNet/Locators/ProjectLocator.cs b/src/BenchmarkDotNet/Locators/ProjectLocator.cs deleted file mode 100644 index 597b589977..0000000000 --- a/src/BenchmarkDotNet/Locators/ProjectLocator.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using BenchmarkDotNet.Exporters; - -namespace BenchmarkDotNet.Locators; - -public class ProjectLocator : ILocator -{ - public static ProjectLocator Default { get; } = new ProjectLocator(); - - public LocatorType LocatorType => LocatorType.ProjectFile; - - public FileInfo Locate(DirectoryInfo rootDirectory, Type type) - { - // important assumption! project's file name === output dll name - string projectName = type.GetTypeInfo().Assembly.GetName().Name; - - var possibleNames = new HashSet { $"{projectName}.csproj", $"{projectName}.fsproj", $"{projectName}.vbproj" }; - var projectFiles = rootDirectory - .EnumerateFiles("*proj", SearchOption.AllDirectories) - .Where(file => possibleNames.Contains(file.Name)) - .ToArray(); - - if (projectFiles.Length == 0) - { - return null; - } - - if (projectFiles.Length > 1) - { - throw new NotSupportedException( - $"Found more than one matching project file for {projectName} in {rootDirectory.FullName} and its subfolders: {string.Join(",", projectFiles.Select(pf => $"'{pf.FullName}'"))}. Benchmark project names needs to be unique."); - } - - return projectFiles[0]; - } -} \ No newline at end of file diff --git a/src/BenchmarkDotNet/Toolchains/CsProj/CsProjGenerator.cs b/src/BenchmarkDotNet/Toolchains/CsProj/CsProjGenerator.cs index 5d178ce9f0..b3c9cae613 100644 --- a/src/BenchmarkDotNet/Toolchains/CsProj/CsProjGenerator.cs +++ b/src/BenchmarkDotNet/Toolchains/CsProj/CsProjGenerator.cs @@ -3,8 +3,6 @@ using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.IO; -using System.Linq; -using System.Reflection; using System.Text; using System.Xml; using BenchmarkDotNet.Characteristics; @@ -249,33 +247,26 @@ private static string GetIndentedXmlString(XmlDocument doc) [PublicAPI] protected virtual FileInfo GetProjectFilePath(BenchmarkCase benchmark, ILogger logger) { - var benchmarkTarget = benchmark.Descriptor.Type; + var notFound = new List(); + var args = new LocatorArgs(benchmark, logger); - if (!GetSolutionRootDirectory(out var rootDirectory) && !GetProjectRootDirectory(out rootDirectory)) + foreach (var locator in benchmark.Config.GetFileLocators()) { - logger.WriteLineError( - $"Unable to find .sln or .csproj file. Will use current directory {Directory.GetCurrentDirectory()} to search for project file. If you don't use .sln file on purpose it should not be a problem."); - rootDirectory = new DirectoryInfo(Directory.GetCurrentDirectory()); - } - - List notFound = new List(); - foreach (ILocator locator in benchmark.Config.GetLocators()) - { - if (locator.LocatorType != LocatorType.ProjectFile) - continue; - - var path = locator.Locate(rootDirectory, benchmarkTarget); - - if (path == null) + if (locator.LocatorType != FileLocatorType.Project) + { continue; + } - if (path.Exists) - return path; + if (locator.TryLocate(args, out var fileInfo)) + { + if (fileInfo.Exists) + return fileInfo; - notFound.Add(path.FullName); + notFound.Add(fileInfo.FullName); + } } - throw new NotSupportedException($"Unable to find project file in {rootDirectory}. Attempted location(s): " + string.Join(", ", notFound)); + throw new FileNotFoundException("Unable to find project file. Attempted location(s): " + string.Join(", ", notFound)); } public override bool Equals(object obj) => obj is CsProjGenerator other && Equals(other); diff --git a/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliGenerator.cs b/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliGenerator.cs index a432fbf881..2534eb5c19 100644 --- a/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliGenerator.cs +++ b/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliGenerator.cs @@ -10,8 +10,6 @@ namespace BenchmarkDotNet.Toolchains.DotNetCli [PublicAPI] public abstract class DotNetCliGenerator : GeneratorBase { - private static readonly string[] ProjectExtensions = { ".csproj", ".fsproj", ".vbroj" }; - [PublicAPI] public string TargetFrameworkMoniker { get; } [PublicAPI] public string CliPath { get; } @@ -38,7 +36,7 @@ protected DotNetCliGenerator(string targetFrameworkMoniker, string cliPath, stri /// protected override string GetBuildArtifactsDirectoryPath(BuildPartition buildPartition, string programName) { - if (GetSolutionRootDirectory(out var directoryInfo)) + if (!GetRootDirectory(IsRootSolutionFolder, out var directoryInfo)) { return Path.Combine(directoryInfo.FullName, programName); } @@ -51,16 +49,6 @@ protected override string GetBuildArtifactsDirectoryPath(BuildPartition buildPar return Path.Combine(parent.FullName, programName); } - internal static bool GetSolutionRootDirectory(out DirectoryInfo directoryInfo) - { - return GetRootDirectory(IsRootSolutionFolder, out directoryInfo); - } - - internal static bool GetProjectRootDirectory(out DirectoryInfo directoryInfo) - { - return GetRootDirectory(IsRootProjectFolder, out directoryInfo); - } - internal static bool GetRootDirectory(Func condition, out DirectoryInfo? directoryInfo) { directoryInfo = null; @@ -112,10 +100,5 @@ private static bool IsRootSolutionFolder(DirectoryInfo directoryInfo) => directoryInfo .GetFileSystemInfos() .Any(fileInfo => fileInfo.Extension == ".sln" || fileInfo.Name == "global.json"); - - private static bool IsRootProjectFolder(DirectoryInfo directoryInfo) - => directoryInfo - .GetFileSystemInfos() - .Any(fileInfo => ProjectExtensions.Contains(fileInfo.Extension)); } } \ No newline at end of file diff --git a/tests/BenchmarkDotNet.Tests/Configs/ImmutableConfigTests.cs b/tests/BenchmarkDotNet.Tests/Configs/ImmutableConfigTests.cs index 9313c8b760..f7a9c13ca8 100644 --- a/tests/BenchmarkDotNet.Tests/Configs/ImmutableConfigTests.cs +++ b/tests/BenchmarkDotNet.Tests/Configs/ImmutableConfigTests.cs @@ -459,12 +459,12 @@ public void LocatorsAreAddedCorrectly() { var mutable = ManualConfig.CreateEmpty(); - mutable.AddLocator(ProjectLocator.Default); + mutable.AddLocator(ProjectFileLocator.Default); var final = ImmutableConfigBuilder.Create(mutable); - var locator = Assert.Single(final.GetLocators()); - Assert.Same(ProjectLocator.Default, locator); + var locator = Assert.Single(final.GetFileLocators()); + Assert.Same(ProjectFileLocator.Default, locator); } [Fact] @@ -472,12 +472,12 @@ public void DuplicateLocatorsAreAllowed() { var mutable = ManualConfig.CreateEmpty(); - mutable.AddLocator(ProjectLocator.Default); - mutable.AddLocator(ProjectLocator.Default); + mutable.AddLocator(ProjectFileLocator.Default); + mutable.AddLocator(ProjectFileLocator.Default); var final = ImmutableConfigBuilder.Create(mutable); - Assert.Equal(2, final.GetLocators().Count()); + Assert.Equal(2, final.GetFileLocators().Count()); } [Fact] @@ -485,11 +485,11 @@ public void VerifyConfigsHaveLocators() { var minVar = ManualConfig.CreateMinimumViable(); var build1 = ImmutableConfigBuilder.Create(minVar); - Assert.Single(build1.GetLocators()); + Assert.Single(build1.GetFileLocators()); var def = DefaultConfig.Instance; var build2 = ImmutableConfigBuilder.Create(def); - Assert.Single(build2.GetLocators()); + Assert.Single(build2.GetFileLocators()); } } } From fd4bb75d739736588fbe8fd5435516a687465989 Mon Sep 17 00:00:00 2001 From: Ian Qvist Date: Sun, 5 Jan 2025 11:58:34 +0100 Subject: [PATCH 4/9] Use fallback logic instead --- src/BenchmarkDotNet/Configs/DebugConfig.cs | 2 +- src/BenchmarkDotNet/Configs/DefaultConfig.cs | 5 +- src/BenchmarkDotNet/Configs/ManualConfig.cs | 1 - .../Locators/ProjectFileLocator.cs | 85 ------------------- .../Toolchains/CsProj/CsProjGenerator.cs | 34 +++++++- .../DotNetCli/DotNetCliGenerator.cs | 19 ++++- .../Configs/ImmutableConfigTests.cs | 30 ++++--- 7 files changed, 66 insertions(+), 110 deletions(-) delete mode 100644 src/BenchmarkDotNet/Locators/ProjectFileLocator.cs diff --git a/src/BenchmarkDotNet/Configs/DebugConfig.cs b/src/BenchmarkDotNet/Configs/DebugConfig.cs index 99ba3baeb9..820fa423c4 100644 --- a/src/BenchmarkDotNet/Configs/DebugConfig.cs +++ b/src/BenchmarkDotNet/Configs/DebugConfig.cs @@ -59,7 +59,7 @@ public abstract class DebugConfig : IConfig public IEnumerable GetValidators() => Array.Empty(); public IEnumerable GetColumnProviders() => DefaultColumnProviders.Instance; public IEnumerable GetExporters() => Array.Empty(); - public IEnumerable GetFileLocators() => new[] { ProjectFileLocator.Default }; + public IEnumerable GetFileLocators() => Array.Empty(); public IEnumerable GetLoggers() => new[] { ConsoleLogger.Default }; public IEnumerable GetDiagnosers() => Array.Empty(); public IEnumerable GetAnalysers() => Array.Empty(); diff --git a/src/BenchmarkDotNet/Configs/DefaultConfig.cs b/src/BenchmarkDotNet/Configs/DefaultConfig.cs index c0c258cd54..6092c05114 100644 --- a/src/BenchmarkDotNet/Configs/DefaultConfig.cs +++ b/src/BenchmarkDotNet/Configs/DefaultConfig.cs @@ -41,10 +41,7 @@ public IEnumerable GetExporters() yield return HtmlExporter.Default; } - public IEnumerable GetFileLocators() - { - yield return ProjectFileLocator.Default; - } + public IEnumerable GetFileLocators() => Array.Empty(); public IEnumerable GetLoggers() { diff --git a/src/BenchmarkDotNet/Configs/ManualConfig.cs b/src/BenchmarkDotNet/Configs/ManualConfig.cs index 5ee881c2fd..c05b5c45db 100644 --- a/src/BenchmarkDotNet/Configs/ManualConfig.cs +++ b/src/BenchmarkDotNet/Configs/ManualConfig.cs @@ -297,7 +297,6 @@ public void Add(IConfig config) public static ManualConfig CreateMinimumViable() => CreateEmpty() .AddColumnProvider(DefaultColumnProviders.Instance) - .AddFileLocator(ProjectFileLocator.Default) .AddLogger(ConsoleLogger.Default); public static ManualConfig Create(IConfig config) diff --git a/src/BenchmarkDotNet/Locators/ProjectFileLocator.cs b/src/BenchmarkDotNet/Locators/ProjectFileLocator.cs deleted file mode 100644 index 9725220674..0000000000 --- a/src/BenchmarkDotNet/Locators/ProjectFileLocator.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using BenchmarkDotNet.Exporters; -using BenchmarkDotNet.Running; - -namespace BenchmarkDotNet.Locators; - -public class ProjectFileLocator : IFileLocator -{ - private static readonly string[] ProjectExtensions = { ".csproj", ".fsproj", ".vbroj" }; - - public static ProjectFileLocator Default { get; } = new ProjectFileLocator(); - - public FileLocatorType LocatorType => FileLocatorType.Project; - - public bool TryLocate(LocatorArgs locatorArgs, out FileInfo fileInfo) - { - if (!GetRootDirectory(IsRootSolutionFolder, out var rootDirectory) && !GetRootDirectory(IsRootProjectFolder, out rootDirectory)) - { - rootDirectory = new DirectoryInfo(Directory.GetCurrentDirectory()); - } - - // important assumption! project's file name === output dll name - var type = locatorArgs.BenchmarkCase.Descriptor.Type; - var projectName = type.GetTypeInfo().Assembly.GetName().Name; - - var possibleNames = new HashSet { $"{projectName}.csproj", $"{projectName}.fsproj", $"{projectName}.vbproj" }; - var projectFiles = rootDirectory - .EnumerateFiles("*proj", SearchOption.AllDirectories) - .Where(file => possibleNames.Contains(file.Name)) - .ToArray(); - - if (projectFiles.Length == 0) - { - fileInfo = null; - return false; - } - - if (projectFiles.Length > 1) - { - throw new InvalidOperationException( - $"Found more than one matching project file for {projectName} in {rootDirectory.FullName} and its subfolders: {string.Join(",", projectFiles.Select(pf => $"'{pf.FullName}'"))}. Benchmark project names needs to be unique."); - } - - fileInfo = projectFiles[0]; - return true; - } - - private static bool GetRootDirectory(Func condition, out DirectoryInfo? directoryInfo) - { - directoryInfo = null; - try - { - directoryInfo = new DirectoryInfo(Directory.GetCurrentDirectory()); - while (directoryInfo != null) - { - if (condition(directoryInfo)) - { - return true; - } - - directoryInfo = directoryInfo.Parent; - } - } - catch - { - return false; - } - - return false; - } - - private static bool IsRootSolutionFolder(DirectoryInfo directoryInfo) - => directoryInfo - .GetFileSystemInfos() - .Any(fileInfo => fileInfo.Extension == ".sln" || fileInfo.Name == "global.json"); - - private static bool IsRootProjectFolder(DirectoryInfo directoryInfo) - => directoryInfo - .GetFileSystemInfos() - .Any(fileInfo => ProjectExtensions.Contains(fileInfo.Extension)); -} \ No newline at end of file diff --git a/src/BenchmarkDotNet/Toolchains/CsProj/CsProjGenerator.cs b/src/BenchmarkDotNet/Toolchains/CsProj/CsProjGenerator.cs index b3c9cae613..f06d946f4c 100644 --- a/src/BenchmarkDotNet/Toolchains/CsProj/CsProjGenerator.cs +++ b/src/BenchmarkDotNet/Toolchains/CsProj/CsProjGenerator.cs @@ -3,6 +3,8 @@ using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.IO; +using System.Linq; +using System.Reflection; using System.Text; using System.Xml; using BenchmarkDotNet.Characteristics; @@ -247,7 +249,6 @@ private static string GetIndentedXmlString(XmlDocument doc) [PublicAPI] protected virtual FileInfo GetProjectFilePath(BenchmarkCase benchmark, ILogger logger) { - var notFound = new List(); var args = new LocatorArgs(benchmark, logger); foreach (var locator in benchmark.Config.GetFileLocators()) @@ -261,12 +262,37 @@ protected virtual FileInfo GetProjectFilePath(BenchmarkCase benchmark, ILogger l { if (fileInfo.Exists) return fileInfo; - - notFound.Add(fileInfo.FullName); } } - throw new FileNotFoundException("Unable to find project file. Attempted location(s): " + string.Join(", ", notFound)); + if (!GetSolutionRootDirectory(out var rootDirectory) && !GetProjectRootDirectory(out rootDirectory)) + { + logger.WriteLineError( + $"Unable to find .sln or .csproj file. Will use current directory {Directory.GetCurrentDirectory()} to search for project file. If you don't use .sln file on purpose it should not be a problem."); + rootDirectory = new DirectoryInfo(Directory.GetCurrentDirectory()); + } + + // important assumption! project's file name === output dll name + string projectName = benchmark.Descriptor.Type.GetTypeInfo().Assembly.GetName().Name; + + var possibleNames = new HashSet { $"{projectName}.csproj", $"{projectName}.fsproj", $"{projectName}.vbproj" }; + var projectFiles = rootDirectory + .EnumerateFiles("*proj", SearchOption.AllDirectories) + .Where(file => possibleNames.Contains(file.Name)) + .ToArray(); + + if (projectFiles.Length == 0) + { + throw new FileNotFoundException( + $"Unable to find {projectName} in {rootDirectory.FullName} and its subfolders. Most probably the name of output exe is different than the name of the .(c/f)sproj"); + } + else if (projectFiles.Length > 1) + { + throw new InvalidOperationException( + $"Found more than one matching project file for {projectName} in {rootDirectory.FullName} and its subfolders: {string.Join(",", projectFiles.Select(pf => $"'{pf.FullName}'"))}. Benchmark project names needs to be unique."); + } + + return projectFiles[0]; } public override bool Equals(object obj) => obj is CsProjGenerator other && Equals(other); diff --git a/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliGenerator.cs b/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliGenerator.cs index 2534eb5c19..a432fbf881 100644 --- a/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliGenerator.cs +++ b/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliGenerator.cs @@ -10,6 +10,8 @@ namespace BenchmarkDotNet.Toolchains.DotNetCli [PublicAPI] public abstract class DotNetCliGenerator : GeneratorBase { + private static readonly string[] ProjectExtensions = { ".csproj", ".fsproj", ".vbroj" }; + [PublicAPI] public string TargetFrameworkMoniker { get; } [PublicAPI] public string CliPath { get; } @@ -36,7 +38,7 @@ protected DotNetCliGenerator(string targetFrameworkMoniker, string cliPath, stri /// protected override string GetBuildArtifactsDirectoryPath(BuildPartition buildPartition, string programName) { - if (!GetRootDirectory(IsRootSolutionFolder, out var directoryInfo)) + if (GetSolutionRootDirectory(out var directoryInfo)) { return Path.Combine(directoryInfo.FullName, programName); } @@ -49,6 +51,16 @@ protected override string GetBuildArtifactsDirectoryPath(BuildPartition buildPar return Path.Combine(parent.FullName, programName); } + internal static bool GetSolutionRootDirectory(out DirectoryInfo directoryInfo) + { + return GetRootDirectory(IsRootSolutionFolder, out directoryInfo); + } + + internal static bool GetProjectRootDirectory(out DirectoryInfo directoryInfo) + { + return GetRootDirectory(IsRootProjectFolder, out directoryInfo); + } + internal static bool GetRootDirectory(Func condition, out DirectoryInfo? directoryInfo) { directoryInfo = null; @@ -100,5 +112,10 @@ private static bool IsRootSolutionFolder(DirectoryInfo directoryInfo) => directoryInfo .GetFileSystemInfos() .Any(fileInfo => fileInfo.Extension == ".sln" || fileInfo.Name == "global.json"); + + private static bool IsRootProjectFolder(DirectoryInfo directoryInfo) + => directoryInfo + .GetFileSystemInfos() + .Any(fileInfo => ProjectExtensions.Contains(fileInfo.Extension)); } } \ No newline at end of file diff --git a/tests/BenchmarkDotNet.Tests/Configs/ImmutableConfigTests.cs b/tests/BenchmarkDotNet.Tests/Configs/ImmutableConfigTests.cs index f7a9c13ca8..c3107cfd6d 100644 --- a/tests/BenchmarkDotNet.Tests/Configs/ImmutableConfigTests.cs +++ b/tests/BenchmarkDotNet.Tests/Configs/ImmutableConfigTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using BenchmarkDotNet.Analysers; using BenchmarkDotNet.Columns; @@ -459,12 +460,13 @@ public void LocatorsAreAddedCorrectly() { var mutable = ManualConfig.CreateEmpty(); - mutable.AddLocator(ProjectFileLocator.Default); + TestFileLocator expected = new TestFileLocator(); + mutable.AddFileLocator(expected); var final = ImmutableConfigBuilder.Create(mutable); - var locator = Assert.Single(final.GetFileLocators()); - Assert.Same(ProjectFileLocator.Default, locator); + var actual = Assert.Single(final.GetFileLocators()); + Assert.Same(expected, actual); } [Fact] @@ -472,24 +474,24 @@ public void DuplicateLocatorsAreAllowed() { var mutable = ManualConfig.CreateEmpty(); - mutable.AddLocator(ProjectFileLocator.Default); - mutable.AddLocator(ProjectFileLocator.Default); + TestFileLocator locator = new TestFileLocator(); + + mutable.AddFileLocator(locator); + mutable.AddFileLocator(locator); var final = ImmutableConfigBuilder.Create(mutable); Assert.Equal(2, final.GetFileLocators().Count()); } - [Fact] - public void VerifyConfigsHaveLocators() + private class TestFileLocator : IFileLocator { - var minVar = ManualConfig.CreateMinimumViable(); - var build1 = ImmutableConfigBuilder.Create(minVar); - Assert.Single(build1.GetFileLocators()); - - var def = DefaultConfig.Instance; - var build2 = ImmutableConfigBuilder.Create(def); - Assert.Single(build2.GetFileLocators()); + public FileLocatorType LocatorType => FileLocatorType.Project; + public bool TryLocate(LocatorArgs locatorArgs, out FileInfo fileInfo) + { + fileInfo = new FileInfo(""); + return true; + } } } } From 4d84092ea6e8764c5161fad422f6f6ab4996c913 Mon Sep 17 00:00:00 2001 From: Ian Qvist Date: Sun, 5 Jan 2025 12:16:28 +0100 Subject: [PATCH 5/9] Add a few docs and use default code style --- src/BenchmarkDotNet/Locators/IFileLocator.cs | 13 +++++++++++++ .../Toolchains/CsProj/CsProjGenerator.cs | 2 ++ .../Configs/ImmutableConfigTests.cs | 5 ++--- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/BenchmarkDotNet/Locators/IFileLocator.cs b/src/BenchmarkDotNet/Locators/IFileLocator.cs index 77f77e0c05..7573d93cef 100644 --- a/src/BenchmarkDotNet/Locators/IFileLocator.cs +++ b/src/BenchmarkDotNet/Locators/IFileLocator.cs @@ -2,8 +2,21 @@ namespace BenchmarkDotNet.Locators; +/// +/// Locators can be used to extend the default behavior of finding files +/// public interface IFileLocator { + /// + /// The type of locator + /// FileLocatorType LocatorType { get; } + + /// + /// Tries to locate a file + /// + /// The arguments such as benchmark and logger + /// The file is provided by the implementation + /// True when a file was successfully found, False otherwise. bool TryLocate(LocatorArgs locatorArgs, out FileInfo fileInfo); } \ No newline at end of file diff --git a/src/BenchmarkDotNet/Toolchains/CsProj/CsProjGenerator.cs b/src/BenchmarkDotNet/Toolchains/CsProj/CsProjGenerator.cs index f06d946f4c..961cca4e3d 100644 --- a/src/BenchmarkDotNet/Toolchains/CsProj/CsProjGenerator.cs +++ b/src/BenchmarkDotNet/Toolchains/CsProj/CsProjGenerator.cs @@ -251,6 +251,7 @@ protected virtual FileInfo GetProjectFilePath(BenchmarkCase benchmark, ILogger l { var args = new LocatorArgs(benchmark, logger); + // Try locators first. Logic is provided by the user for uses-cases such as they have set AssemblyName to a custom value. foreach (var locator in benchmark.Config.GetFileLocators()) { if (locator.LocatorType != FileLocatorType.Project) @@ -265,6 +266,7 @@ protected virtual FileInfo GetProjectFilePath(BenchmarkCase benchmark, ILogger l } } + // Fall back to default project detection logic if (!GetSolutionRootDirectory(out var rootDirectory) && !GetProjectRootDirectory(out rootDirectory)) { logger.WriteLineError( diff --git a/tests/BenchmarkDotNet.Tests/Configs/ImmutableConfigTests.cs b/tests/BenchmarkDotNet.Tests/Configs/ImmutableConfigTests.cs index c3107cfd6d..d45e656096 100644 --- a/tests/BenchmarkDotNet.Tests/Configs/ImmutableConfigTests.cs +++ b/tests/BenchmarkDotNet.Tests/Configs/ImmutableConfigTests.cs @@ -460,7 +460,7 @@ public void LocatorsAreAddedCorrectly() { var mutable = ManualConfig.CreateEmpty(); - TestFileLocator expected = new TestFileLocator(); + var expected = new TestFileLocator(); mutable.AddFileLocator(expected); var final = ImmutableConfigBuilder.Create(mutable); @@ -474,8 +474,7 @@ public void DuplicateLocatorsAreAllowed() { var mutable = ManualConfig.CreateEmpty(); - TestFileLocator locator = new TestFileLocator(); - + var locator = new TestFileLocator(); mutable.AddFileLocator(locator); mutable.AddFileLocator(locator); From 33ab1dd208d2a31a2cdb0e5ac829aa4812f1e6bc Mon Sep 17 00:00:00 2001 From: Ian Qvist Date: Mon, 6 Jan 2025 14:04:49 +0100 Subject: [PATCH 6/9] Include file locator paths in exception message and rename LocatorArgs to FileLocatorArgs --- .../{LocatorArgs.cs => FileLocatorArgs.cs} | 4 ++-- src/BenchmarkDotNet/Locators/IFileLocator.cs | 4 ++-- .../Toolchains/CsProj/CsProjGenerator.cs | 15 ++++++++++++--- .../Configs/ImmutableConfigTests.cs | 2 +- 4 files changed, 17 insertions(+), 8 deletions(-) rename src/BenchmarkDotNet/Locators/{LocatorArgs.cs => FileLocatorArgs.cs} (72%) diff --git a/src/BenchmarkDotNet/Locators/LocatorArgs.cs b/src/BenchmarkDotNet/Locators/FileLocatorArgs.cs similarity index 72% rename from src/BenchmarkDotNet/Locators/LocatorArgs.cs rename to src/BenchmarkDotNet/Locators/FileLocatorArgs.cs index cfc72a6710..ddbf31d229 100644 --- a/src/BenchmarkDotNet/Locators/LocatorArgs.cs +++ b/src/BenchmarkDotNet/Locators/FileLocatorArgs.cs @@ -3,9 +3,9 @@ namespace BenchmarkDotNet.Locators; -public class LocatorArgs +public class FileLocatorArgs { - public LocatorArgs(BenchmarkCase benchmarkCase, ILogger logger) + public FileLocatorArgs(BenchmarkCase benchmarkCase, ILogger logger) { BenchmarkCase = benchmarkCase; Logger = logger; diff --git a/src/BenchmarkDotNet/Locators/IFileLocator.cs b/src/BenchmarkDotNet/Locators/IFileLocator.cs index 7573d93cef..4dd32417e5 100644 --- a/src/BenchmarkDotNet/Locators/IFileLocator.cs +++ b/src/BenchmarkDotNet/Locators/IFileLocator.cs @@ -15,8 +15,8 @@ public interface IFileLocator /// /// Tries to locate a file /// - /// The arguments such as benchmark and logger + /// The arguments such as benchmark and logger /// The file is provided by the implementation /// True when a file was successfully found, False otherwise. - bool TryLocate(LocatorArgs locatorArgs, out FileInfo fileInfo); + bool TryLocate(FileLocatorArgs fileLocatorArgs, out FileInfo fileInfo); } \ No newline at end of file diff --git a/src/BenchmarkDotNet/Toolchains/CsProj/CsProjGenerator.cs b/src/BenchmarkDotNet/Toolchains/CsProj/CsProjGenerator.cs index 961cca4e3d..75bcad6a2a 100644 --- a/src/BenchmarkDotNet/Toolchains/CsProj/CsProjGenerator.cs +++ b/src/BenchmarkDotNet/Toolchains/CsProj/CsProjGenerator.cs @@ -249,9 +249,10 @@ private static string GetIndentedXmlString(XmlDocument doc) [PublicAPI] protected virtual FileInfo GetProjectFilePath(BenchmarkCase benchmark, ILogger logger) { - var args = new LocatorArgs(benchmark, logger); + var args = new FileLocatorArgs(benchmark, logger); // Try locators first. Logic is provided by the user for uses-cases such as they have set AssemblyName to a custom value. + var notFound = new List(); foreach (var locator in benchmark.Config.GetFileLocators()) { if (locator.LocatorType != FileLocatorType.Project) @@ -263,6 +264,8 @@ protected virtual FileInfo GetProjectFilePath(BenchmarkCase benchmark, ILogger l { if (fileInfo.Exists) return fileInfo; + + notFound.Add(fileInfo.FullName); } } @@ -285,8 +288,14 @@ protected virtual FileInfo GetProjectFilePath(BenchmarkCase benchmark, ILogger l if (projectFiles.Length == 0) { - throw new FileNotFoundException( - $"Unable to find {projectName} in {rootDirectory.FullName} and its subfolders. Most probably the name of output exe is different than the name of the .(c/f)sproj"); + string message; + + if (notFound.Count > 0) + message = $"Unable to find {projectName} in any of the paths: {string.Join(", ", notFound)} or in {rootDirectory.FullName} and its subfolders"; + else + message = $"Unable to find {projectName} in {rootDirectory.FullName} and its subfolders. Most probably the name of output exe is different than the name of the .(c/f)sproj"; + + throw new FileNotFoundException(message); } else if (projectFiles.Length > 1) { diff --git a/tests/BenchmarkDotNet.Tests/Configs/ImmutableConfigTests.cs b/tests/BenchmarkDotNet.Tests/Configs/ImmutableConfigTests.cs index d45e656096..d7e17701e6 100644 --- a/tests/BenchmarkDotNet.Tests/Configs/ImmutableConfigTests.cs +++ b/tests/BenchmarkDotNet.Tests/Configs/ImmutableConfigTests.cs @@ -486,7 +486,7 @@ public void DuplicateLocatorsAreAllowed() private class TestFileLocator : IFileLocator { public FileLocatorType LocatorType => FileLocatorType.Project; - public bool TryLocate(LocatorArgs locatorArgs, out FileInfo fileInfo) + public bool TryLocate(FileLocatorArgs fileLocatorArgs, out FileInfo fileInfo) { fileInfo = new FileInfo(""); return true; From 44790712570b7dea3cbda6cd3f7d070b6514824e Mon Sep 17 00:00:00 2001 From: Ian Qvist Date: Mon, 6 Jan 2025 18:58:32 +0100 Subject: [PATCH 7/9] Update src/BenchmarkDotNet/Toolchains/CsProj/CsProjGenerator.cs Co-authored-by: Tim Cassell <35501420+timcassell@users.noreply.github.com> --- src/BenchmarkDotNet/Toolchains/CsProj/CsProjGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BenchmarkDotNet/Toolchains/CsProj/CsProjGenerator.cs b/src/BenchmarkDotNet/Toolchains/CsProj/CsProjGenerator.cs index 75bcad6a2a..e0684ac9bf 100644 --- a/src/BenchmarkDotNet/Toolchains/CsProj/CsProjGenerator.cs +++ b/src/BenchmarkDotNet/Toolchains/CsProj/CsProjGenerator.cs @@ -293,7 +293,7 @@ protected virtual FileInfo GetProjectFilePath(BenchmarkCase benchmark, ILogger l if (notFound.Count > 0) message = $"Unable to find {projectName} in any of the paths: {string.Join(", ", notFound)} or in {rootDirectory.FullName} and its subfolders"; else - message = $"Unable to find {projectName} in {rootDirectory.FullName} and its subfolders. Most probably the name of output exe is different than the name of the .(c/f)sproj"; + message = $"Unable to find {projectName} in {rootDirectory.FullName} and its subfolders. Most probably the name of output exe is different than the name of the .(c/f)sproj. You can add an IFileLocator to the config if this is on purpose."; throw new FileNotFoundException(message); } From fcd632e4316f49e9fa8ad2f7f3af564de232edc9 Mon Sep 17 00:00:00 2001 From: Ian Qvist Date: Mon, 6 Jan 2025 22:36:34 +0100 Subject: [PATCH 8/9] Create integration test --- BenchmarkDotNet.sln | 7 +++ .../AssemblyNameIsSetBenchmarks.cs | 13 +++++ ...otNet.IntegrationTests.FileLocators.csproj | 14 ++++++ .../BenchmarkDotNet.IntegrationTests.csproj | 6 +++ .../FileLocatorTests.cs | 49 +++++++++++++++++++ 5 files changed, 89 insertions(+) create mode 100644 tests/BenchmarkDotNet.IntegrationTests.FileLocators/AssemblyNameIsSetBenchmarks.cs create mode 100644 tests/BenchmarkDotNet.IntegrationTests.FileLocators/BenchmarkDotNet.IntegrationTests.FileLocators.csproj create mode 100644 tests/BenchmarkDotNet.IntegrationTests/FileLocatorTests.cs diff --git a/BenchmarkDotNet.sln b/BenchmarkDotNet.sln index 1df6c0aabd..f47fde8160 100644 --- a/BenchmarkDotNet.sln +++ b/BenchmarkDotNet.sln @@ -59,6 +59,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BenchmarkDotNet.Exporters.P EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BenchmarkDotNet.Exporters.Plotting.Tests", "tests\BenchmarkDotNet.Exporters.Plotting.Tests\BenchmarkDotNet.Exporters.Plotting.Tests.csproj", "{199AC83E-30BD-40CD-87CE-0C838AC0320D}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BenchmarkDotNet.IntegrationTests.FileLocators", "tests\BenchmarkDotNet.IntegrationTests.FileLocators\BenchmarkDotNet.IntegrationTests.FileLocators.csproj", "{7AD9FCF9-69B5-4984-93AC-D6E30344DADC}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -161,6 +163,10 @@ Global {199AC83E-30BD-40CD-87CE-0C838AC0320D}.Debug|Any CPU.Build.0 = Debug|Any CPU {199AC83E-30BD-40CD-87CE-0C838AC0320D}.Release|Any CPU.ActiveCfg = Release|Any CPU {199AC83E-30BD-40CD-87CE-0C838AC0320D}.Release|Any CPU.Build.0 = Release|Any CPU + {7AD9FCF9-69B5-4984-93AC-D6E30344DADC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7AD9FCF9-69B5-4984-93AC-D6E30344DADC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7AD9FCF9-69B5-4984-93AC-D6E30344DADC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7AD9FCF9-69B5-4984-93AC-D6E30344DADC}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -190,6 +196,7 @@ Global {2E2283A3-6DA6-4482-8518-99D6D9F689AB} = {D6597E3A-6892-4A68-8E14-042FC941FDA2} {B92ECCEF-7C27-4012-9E19-679F3C40A6A6} = {D6597E3A-6892-4A68-8E14-042FC941FDA2} {199AC83E-30BD-40CD-87CE-0C838AC0320D} = {14195214-591A-45B7-851A-19D3BA2413F9} + {7AD9FCF9-69B5-4984-93AC-D6E30344DADC} = {14195214-591A-45B7-851A-19D3BA2413F9} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4D9AF12B-1F7F-45A7-9E8C-E4E46ADCBD1F} diff --git a/tests/BenchmarkDotNet.IntegrationTests.FileLocators/AssemblyNameIsSetBenchmarks.cs b/tests/BenchmarkDotNet.IntegrationTests.FileLocators/AssemblyNameIsSetBenchmarks.cs new file mode 100644 index 0000000000..0dc62277d9 --- /dev/null +++ b/tests/BenchmarkDotNet.IntegrationTests.FileLocators/AssemblyNameIsSetBenchmarks.cs @@ -0,0 +1,13 @@ +using BenchmarkDotNet.Attributes; + +namespace BenchmarkDotNet.IntegrationTests.FileLocators +{ + public class AssemblyNameIsSetBenchmarks + { + [Benchmark] + public string Benchmark() + { + return "This will only run when a FileLocator is set due to in the csproj"; + } + } +} \ No newline at end of file diff --git a/tests/BenchmarkDotNet.IntegrationTests.FileLocators/BenchmarkDotNet.IntegrationTests.FileLocators.csproj b/tests/BenchmarkDotNet.IntegrationTests.FileLocators/BenchmarkDotNet.IntegrationTests.FileLocators.csproj new file mode 100644 index 0000000000..fb596bb37f --- /dev/null +++ b/tests/BenchmarkDotNet.IntegrationTests.FileLocators/BenchmarkDotNet.IntegrationTests.FileLocators.csproj @@ -0,0 +1,14 @@ + + + + net8.0 + + + MyCustomName + false + false + + + + + \ No newline at end of file diff --git a/tests/BenchmarkDotNet.IntegrationTests/BenchmarkDotNet.IntegrationTests.csproj b/tests/BenchmarkDotNet.IntegrationTests/BenchmarkDotNet.IntegrationTests.csproj index 16f12c41b4..03f03b0a79 100644 --- a/tests/BenchmarkDotNet.IntegrationTests/BenchmarkDotNet.IntegrationTests.csproj +++ b/tests/BenchmarkDotNet.IntegrationTests/BenchmarkDotNet.IntegrationTests.csproj @@ -31,6 +31,12 @@ + + + + + + diff --git a/tests/BenchmarkDotNet.IntegrationTests/FileLocatorTests.cs b/tests/BenchmarkDotNet.IntegrationTests/FileLocatorTests.cs new file mode 100644 index 0000000000..601eaa85df --- /dev/null +++ b/tests/BenchmarkDotNet.IntegrationTests/FileLocatorTests.cs @@ -0,0 +1,49 @@ +using System; +using System.IO; +using System.Linq; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.IntegrationTests.FileLocators; +using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Locators; +using Xunit; +using Xunit.Abstractions; + +namespace BenchmarkDotNet.IntegrationTests +{ + public class FileLocatorTests : BenchmarkTestExecutor + { + public FileLocatorTests(ITestOutputHelper output) : base(output) { } + + [Fact] + public void ExecutionWithoutFileLocatorShouldFail() + { + var config = ManualConfig.CreateMinimumViable() + .AddJob(Job.Dry); + + var summary = CanExecute(config, false); + Assert.True(summary.Reports.All(r => !r.BuildResult.IsBuildSuccess)); + } + + [Fact] + public void ExecutionWithFileLocatorShouldSucceed() + { + var config = ManualConfig.CreateMinimumViable() + .AddJob(Job.Dry) + .AddFileLocator(new CustomFileLocator()); + + CanExecute(config); + } + + private class CustomFileLocator : IFileLocator + { + public FileLocatorType LocatorType => FileLocatorType.Project; + + public bool TryLocate(FileLocatorArgs args, out FileInfo fileInfo) + { + // We manually locate the csproj file, since the default logic of using the AssemblyName does not work + fileInfo = new FileInfo(Path.Combine(Environment.CurrentDirectory, "../../../../BenchmarkDotNet.IntegrationTests.FileLocators/BenchmarkDotNet.IntegrationTests.FileLocators.csproj")); + return true; + } + } + } +} \ No newline at end of file From 4802d245666f2dedf530e3699bd695df0f1ec8d9 Mon Sep 17 00:00:00 2001 From: Ian Qvist Date: Mon, 6 Jan 2025 23:03:40 +0100 Subject: [PATCH 9/9] Support .NET Framework csproj tool chain as well --- .../BenchmarkDotNet.IntegrationTests.FileLocators.csproj | 2 +- .../BenchmarkDotNet.IntegrationTests.csproj | 6 +----- .../BenchmarkDotNet.IntegrationTests/FileLocatorTests.cs | 9 +++++++-- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/tests/BenchmarkDotNet.IntegrationTests.FileLocators/BenchmarkDotNet.IntegrationTests.FileLocators.csproj b/tests/BenchmarkDotNet.IntegrationTests.FileLocators/BenchmarkDotNet.IntegrationTests.FileLocators.csproj index fb596bb37f..3cfa0494e4 100644 --- a/tests/BenchmarkDotNet.IntegrationTests.FileLocators/BenchmarkDotNet.IntegrationTests.FileLocators.csproj +++ b/tests/BenchmarkDotNet.IntegrationTests.FileLocators/BenchmarkDotNet.IntegrationTests.FileLocators.csproj @@ -1,7 +1,7 @@  - net8.0 + net462;net8.0 MyCustomName diff --git a/tests/BenchmarkDotNet.IntegrationTests/BenchmarkDotNet.IntegrationTests.csproj b/tests/BenchmarkDotNet.IntegrationTests/BenchmarkDotNet.IntegrationTests.csproj index 03f03b0a79..5a08fe5a24 100644 --- a/tests/BenchmarkDotNet.IntegrationTests/BenchmarkDotNet.IntegrationTests.csproj +++ b/tests/BenchmarkDotNet.IntegrationTests/BenchmarkDotNet.IntegrationTests.csproj @@ -26,17 +26,13 @@ + - - - - - diff --git a/tests/BenchmarkDotNet.IntegrationTests/FileLocatorTests.cs b/tests/BenchmarkDotNet.IntegrationTests/FileLocatorTests.cs index 601eaa85df..d41fbf1341 100644 --- a/tests/BenchmarkDotNet.IntegrationTests/FileLocatorTests.cs +++ b/tests/BenchmarkDotNet.IntegrationTests/FileLocatorTests.cs @@ -5,6 +5,7 @@ using BenchmarkDotNet.IntegrationTests.FileLocators; using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Locators; +using BenchmarkDotNet.Toolchains.CsProj; using Xunit; using Xunit.Abstractions; @@ -18,7 +19,9 @@ public FileLocatorTests(ITestOutputHelper output) : base(output) { } public void ExecutionWithoutFileLocatorShouldFail() { var config = ManualConfig.CreateMinimumViable() - .AddJob(Job.Dry); + .AddJob(Job.Dry + .WithToolchain(CsProjClassicNetToolchain.Net462) + .WithToolchain(CsProjCoreToolchain.NetCoreApp80)); var summary = CanExecute(config, false); Assert.True(summary.Reports.All(r => !r.BuildResult.IsBuildSuccess)); @@ -28,7 +31,9 @@ public void ExecutionWithoutFileLocatorShouldFail() public void ExecutionWithFileLocatorShouldSucceed() { var config = ManualConfig.CreateMinimumViable() - .AddJob(Job.Dry) + .AddJob(Job.Dry + .WithToolchain(CsProjClassicNetToolchain.Net462) + .WithToolchain(CsProjCoreToolchain.NetCoreApp80)) .AddFileLocator(new CustomFileLocator()); CanExecute(config);