<commit_before>﻿using System;
using System.IO;
using System.Reflection;
using System.Threading;
using BenchmarkDotNet.Extensions;
using BenchmarkDotNet.Helpers;
using BenchmarkDotNet.Plugins.Loggers;
using BenchmarkDotNet.Plugins.Toolchains.Results;
using BenchmarkDotNet.Tasks;

namespace BenchmarkDotNet.Plugins.Toolchains.Classic
{
    internal class BenchmarkClassicGenerator : IBenchmarkGenerator
    {
        public const string MainClassName = "Program";
        private readonly IBenchmarkLogger logger;

        public BenchmarkClassicGenerator(IBenchmarkLogger logger)
        {
            this.logger = logger;
        }

        public BenchmarkGenerateResult GenerateProject(Benchmark benchmark)
        {
            var result = CreateProjectDirectory(benchmark);
            GenerateProgramFile(result.DirectoryPath, benchmark);
            GenerateProjectFile(result.DirectoryPath, benchmark);
            GenerateProjectBuildFile(result.DirectoryPath);
            GenerateAppConfigFile(result.DirectoryPath, benchmark.Task.Configuration);
            return result;
        }

        private void GenerateProgramFile(string projectDir, Benchmark benchmark)
        {
            var target = benchmark.Target;
            var isVoid = target.Method.ReturnType == typeof(void);

            var operationsPerInvoke = target.OperationsPerInvoke;

            var targetTypeNamespace = string.IsNullOrWhiteSpace(target.Type.Namespace)
                ? ""
                : $"using {target.Type.Namespace};";

            // As "using System;" is always included in the template, don't emit it again
            var emptyReturnTypeNamespace = target.Method.ReturnType == typeof(void) || 
                                           target.Method.ReturnType.Namespace == "System" || 
                                           string.IsNullOrWhiteSpace(target.Method.ReturnType.Namespace);
            var targetMethodReturnTypeNamespace = emptyReturnTypeNamespace
                ? ""
                : $"using {target.Method.ReturnType.Namespace};";

            var targetTypeName = target.Type.FullName.Replace('+', '.');
            var targetMethodName = target.Method.Name;

            var targetMethodReturnType = isVoid
                ? "void"
                : target.Method.ReturnType.GetCorrectTypeName();
            var targetMethodResultHolder = isVoid
                ? ""
                : $"private {targetMethodReturnType} value;";
            var targetMethodHoldValue = isVoid
                ? ""
                : "value = ";
            var targetMethodDelegateType = isVoid
                ? "Action "
                : $"Func<{targetMethodReturnType}> ";

            // setupMethod is optional, so default to an empty delegate, so there is always something that can be invoked
            var setupMethodName = target.SetupMethod != null
                ? target.SetupMethod.Name
                : "() => { }";

            var idleImplementation = isVoid
                ? ""
                : $"return default({targetMethodReturnType});";

            var paramsContent = "";
            if (!benchmark.Task.ParametersSets.IsEmpty())
            {
                var typeQualifier = benchmark.Task.ParametersSets.IsStatic
                    ? $"{target.Type.Name}"
                    : "instance";
                paramsContent = $"{typeQualifier}.{benchmark.Task.ParametersSets.ParamFieldOrProperty} = BenchmarkParameters.ParseArgs(args).IntParam;";
            }

            string runBenchmarkTemplate = "";
            switch (benchmark.Task.Configuration.Mode)
            {
                case BenchmarkMode.SingleRun:
                    runBenchmarkTemplate = ResourceHelper.LoadTemplate("BenchmarkSingleRun.txt");
                    break;
                case BenchmarkMode.Throughput:
                    runBenchmarkTemplate = ResourceHelper.LoadTemplate("BenchmarkThroughput.txt");
                    break;
            }

            var targetBenchmarkTaskArguments =
                $"{benchmark.Task.ProcessCount}, " +
                $"{nameof(BenchmarkTask.Configuration).ToCamelCase()}: new {nameof(BenchmarkConfiguration)}({benchmark.Task.Configuration.ToCtorDefinition()}), " +
                $"{nameof(BenchmarkTask.ParametersSets).ToCamelCase()}: new {nameof(BenchmarkParametersSets)}({benchmark.Task.ParametersSets.ToCtorDefinition()})";

            var contentTemplate = ResourceHelper.LoadTemplate("BenchmarkProgram.txt");
            var content = contentTemplate.
                Replace("$RunBenchmarkContent$", runBenchmarkTemplate).
                Replace("$OperationsPerInvoke$", operationsPerInvoke.ToInvariantString()).
                Replace("$TargetTypeNamespace$", targetTypeNamespace).
                Replace("$TargetMethodReturnTypeNamespace$", targetMethodReturnTypeNamespace).
                Replace("$TargetTypeName$", targetTypeName).
                Replace("$TargetMethodName$", targetMethodName).
                Replace("$TargetMethodResultHolder$", targetMethodResultHolder).
                Replace("$TargetMethodDelegateType$", targetMethodDelegateType).
                Replace("$TargetMethodHoldValue$", targetMethodHoldValue).
                Replace("$TargetMethodReturnType$", targetMethodReturnType).
                Replace("$SetupMethodName$", setupMethodName).
                Replace("$IdleImplementation$", idleImplementation).
                Replace("$AdditionalLogic$", target.AdditionalLogic).
                Replace("$TargetBenchmarkTaskArguments$", targetBenchmarkTaskArguments).
                Replace("$ParamsContent$", paramsContent);

            string fileName = Path.Combine(projectDir, MainClassName + ".cs");
            File.WriteAllText(fileName, content);
        }

        protected virtual void GenerateProjectFile(string projectDir, Benchmark benchmark)
        {
            var configuration = benchmark.Task.Configuration;
            var platform = configuration.Platform.ToConfig();
            var framework = configuration.Framework.ToConfig(benchmark.Target.Type);

            var template = ResourceHelper.LoadTemplate("BenchmarkCsproj.txt");
            var content = template.
                Replace("$Platform$", platform).
                Replace("$Framework$", framework).
                Replace("$TargetAssemblyReference$", GetReferenceToAssembly(benchmark.Target.Type)).
                Replace("$TargetMethodReturnTypeAssemblyReference$", GetReferenceToAssembly(benchmark.Target.Method.ReturnType));

            string fileName = Path.Combine(projectDir, MainClassName + ".csproj");
            File.WriteAllText(fileName, content);

            // Ensure BenchmarkDotNet.dll is in the correct place (e.g. when running in LINQPad)
            EnsureDependancyInCorrectLocation(typeof(BenchmarkAttribute), projectDir);

            EnsureDependancyInCorrectLocation(benchmark.Target.Type, projectDir);
            EnsureDependancyInCorrectLocation(benchmark.Target.Method.ReturnType, projectDir);
        }

        protected virtual void GenerateProjectBuildFile(string projectDir)
        {
            var content = ResourceHelper.LoadTemplate("BuildBenchmark.txt");
            string fileName = Path.Combine(projectDir, "BuildBenchmark.bat");
            File.WriteAllText(fileName, content);
        }

        private static string GetReferenceToAssembly(Type type)
        {
            var template = @"    <Reference Include=""$AssemblyName$"">
      <HintPath>..\$AssemblyFileName$</HintPath>
    </Reference>";
            var assembly = type.Assembly;
            var fileName = new FileInfo(type.Assembly.Location).Name;
            return fileName == "mscorlib.dll"
                ? ""
                : template.
                    Replace("$AssemblyName$", assembly.GetName(false).Name).
                    Replace("$AssemblyFileName$", fileName);
        }

        private void GenerateAppConfigFile(string projectDir, BenchmarkConfiguration configuration)
        {
            var useLagacyJit = configuration.JitVersion.ToConfig();

            var template = ResourceHelper.LoadTemplate(configuration.JitVersion == BenchmarkJitVersion.HostJit ? "BenchmarkAppConfigEmpty.txt" : "BenchmarkAppConfig.txt");
            var content = template.
                Replace("$UseLagacyJit$", useLagacyJit);

            string fileName = Path.Combine(projectDir, "app.config");
            File.WriteAllText(fileName, content);
        }

        private static BenchmarkGenerateResult CreateProjectDirectory(Benchmark benchmark)
        {
            var directoryPath = Path.Combine(Directory.GetCurrentDirectory(), benchmark.Caption);
            bool exist = Directory.Exists(directoryPath);
            Exception deleteException = null;
            for (int attempt = 0; attempt < 3 && exist; attempt++)
            {
                if (attempt != 0)
                    Thread.Sleep(500); // Previous benchmark run didn't release some files
                try
                {
                    Directory.Delete(directoryPath, true);
                    exist = Directory.Exists(directoryPath);
                }
                catch (Exception e)
                {
                    // Can't delete the directory =(
                    deleteException = e;
                }
            }
            if (exist)
                return new BenchmarkGenerateResult(directoryPath, false, deleteException);
            if (!Directory.Exists(directoryPath))<commit_msg>it should have never happened but it does when debugging: DirectoryNotFoundEx when trying to delete dir after calling .Exists()<commit_after>﻿using System;
using System.IO;
using System.Reflection;
using System.Threading;
using BenchmarkDotNet.Extensions;
using BenchmarkDotNet.Helpers;
using BenchmarkDotNet.Plugins.Loggers;
using BenchmarkDotNet.Plugins.Toolchains.Results;
using BenchmarkDotNet.Tasks;

namespace BenchmarkDotNet.Plugins.Toolchains.Classic
{
    internal class BenchmarkClassicGenerator : IBenchmarkGenerator
    {
        public const string MainClassName = "Program";
        private readonly IBenchmarkLogger logger;

        public BenchmarkClassicGenerator(IBenchmarkLogger logger)
        {
            this.logger = logger;
        }

        public BenchmarkGenerateResult GenerateProject(Benchmark benchmark)
        {
            var result = CreateProjectDirectory(benchmark);
            GenerateProgramFile(result.DirectoryPath, benchmark);
            GenerateProjectFile(result.DirectoryPath, benchmark);
            GenerateProjectBuildFile(result.DirectoryPath);
            GenerateAppConfigFile(result.DirectoryPath, benchmark.Task.Configuration);
            return result;
        }

        private void GenerateProgramFile(string projectDir, Benchmark benchmark)
        {
            var target = benchmark.Target;
            var isVoid = target.Method.ReturnType == typeof(void);

            var operationsPerInvoke = target.OperationsPerInvoke;

            var targetTypeNamespace = string.IsNullOrWhiteSpace(target.Type.Namespace)
                ? ""
                : $"using {target.Type.Namespace};";

            // As "using System;" is always included in the template, don't emit it again
            var emptyReturnTypeNamespace = target.Method.ReturnType == typeof(void) || 
                                           target.Method.ReturnType.Namespace == "System" || 
                                           string.IsNullOrWhiteSpace(target.Method.ReturnType.Namespace);
            var targetMethodReturnTypeNamespace = emptyReturnTypeNamespace
                ? ""
                : $"using {target.Method.ReturnType.Namespace};";

            var targetTypeName = target.Type.FullName.Replace('+', '.');
            var targetMethodName = target.Method.Name;

            var targetMethodReturnType = isVoid
                ? "void"
                : target.Method.ReturnType.GetCorrectTypeName();
            var targetMethodResultHolder = isVoid
                ? ""
                : $"private {targetMethodReturnType} value;";
            var targetMethodHoldValue = isVoid
                ? ""
                : "value = ";
            var targetMethodDelegateType = isVoid
                ? "Action "
                : $"Func<{targetMethodReturnType}> ";

            // setupMethod is optional, so default to an empty delegate, so there is always something that can be invoked
            var setupMethodName = target.SetupMethod != null
                ? target.SetupMethod.Name
                : "() => { }";

            var idleImplementation = isVoid
                ? ""
                : $"return default({targetMethodReturnType});";

            var paramsContent = "";
            if (!benchmark.Task.ParametersSets.IsEmpty())
            {
                var typeQualifier = benchmark.Task.ParametersSets.IsStatic
                    ? $"{target.Type.Name}"
                    : "instance";
                paramsContent = $"{typeQualifier}.{benchmark.Task.ParametersSets.ParamFieldOrProperty} = BenchmarkParameters.ParseArgs(args).IntParam;";
            }

            string runBenchmarkTemplate = "";
            switch (benchmark.Task.Configuration.Mode)
            {
                case BenchmarkMode.SingleRun:
                    runBenchmarkTemplate = ResourceHelper.LoadTemplate("BenchmarkSingleRun.txt");
                    break;
                case BenchmarkMode.Throughput:
                    runBenchmarkTemplate = ResourceHelper.LoadTemplate("BenchmarkThroughput.txt");
                    break;
            }

            var targetBenchmarkTaskArguments =
                $"{benchmark.Task.ProcessCount}, " +
                $"{nameof(BenchmarkTask.Configuration).ToCamelCase()}: new {nameof(BenchmarkConfiguration)}({benchmark.Task.Configuration.ToCtorDefinition()}), " +
                $"{nameof(BenchmarkTask.ParametersSets).ToCamelCase()}: new {nameof(BenchmarkParametersSets)}({benchmark.Task.ParametersSets.ToCtorDefinition()})";

            var contentTemplate = ResourceHelper.LoadTemplate("BenchmarkProgram.txt");
            var content = contentTemplate.
                Replace("$RunBenchmarkContent$", runBenchmarkTemplate).
                Replace("$OperationsPerInvoke$", operationsPerInvoke.ToInvariantString()).
                Replace("$TargetTypeNamespace$", targetTypeNamespace).
                Replace("$TargetMethodReturnTypeNamespace$", targetMethodReturnTypeNamespace).
                Replace("$TargetTypeName$", targetTypeName).
                Replace("$TargetMethodName$", targetMethodName).
                Replace("$TargetMethodResultHolder$", targetMethodResultHolder).
                Replace("$TargetMethodDelegateType$", targetMethodDelegateType).
                Replace("$TargetMethodHoldValue$", targetMethodHoldValue).
                Replace("$TargetMethodReturnType$", targetMethodReturnType).
                Replace("$SetupMethodName$", setupMethodName).
                Replace("$IdleImplementation$", idleImplementation).
                Replace("$AdditionalLogic$", target.AdditionalLogic).
                Replace("$TargetBenchmarkTaskArguments$", targetBenchmarkTaskArguments).
                Replace("$ParamsContent$", paramsContent);

            string fileName = Path.Combine(projectDir, MainClassName + ".cs");
            File.WriteAllText(fileName, content);
        }

        protected virtual void GenerateProjectFile(string projectDir, Benchmark benchmark)
        {
            var configuration = benchmark.Task.Configuration;
            var platform = configuration.Platform.ToConfig();
            var framework = configuration.Framework.ToConfig(benchmark.Target.Type);

            var template = ResourceHelper.LoadTemplate("BenchmarkCsproj.txt");
            var content = template.
                Replace("$Platform$", platform).
                Replace("$Framework$", framework).
                Replace("$TargetAssemblyReference$", GetReferenceToAssembly(benchmark.Target.Type)).
                Replace("$TargetMethodReturnTypeAssemblyReference$", GetReferenceToAssembly(benchmark.Target.Method.ReturnType));

            string fileName = Path.Combine(projectDir, MainClassName + ".csproj");
            File.WriteAllText(fileName, content);

            // Ensure BenchmarkDotNet.dll is in the correct place (e.g. when running in LINQPad)
            EnsureDependancyInCorrectLocation(typeof(BenchmarkAttribute), projectDir);

            EnsureDependancyInCorrectLocation(benchmark.Target.Type, projectDir);
            EnsureDependancyInCorrectLocation(benchmark.Target.Method.ReturnType, projectDir);
        }

        protected virtual void GenerateProjectBuildFile(string projectDir)
        {
            var content = ResourceHelper.LoadTemplate("BuildBenchmark.txt");
            string fileName = Path.Combine(projectDir, "BuildBenchmark.bat");
            File.WriteAllText(fileName, content);
        }

        private static string GetReferenceToAssembly(Type type)
        {
            var template = @"    <Reference Include=""$AssemblyName$"">
      <HintPath>..\$AssemblyFileName$</HintPath>
    </Reference>";
            var assembly = type.Assembly;
            var fileName = new FileInfo(type.Assembly.Location).Name;
            return fileName == "mscorlib.dll"
                ? ""
                : template.
                    Replace("$AssemblyName$", assembly.GetName(false).Name).
                    Replace("$AssemblyFileName$", fileName);
        }

        private void GenerateAppConfigFile(string projectDir, BenchmarkConfiguration configuration)
        {
            var useLagacyJit = configuration.JitVersion.ToConfig();

            var template = ResourceHelper.LoadTemplate(configuration.JitVersion == BenchmarkJitVersion.HostJit ? "BenchmarkAppConfigEmpty.txt" : "BenchmarkAppConfig.txt");
            var content = template.
                Replace("$UseLagacyJit$", useLagacyJit);

            string fileName = Path.Combine(projectDir, "app.config");
            File.WriteAllText(fileName, content);
        }

        private static BenchmarkGenerateResult CreateProjectDirectory(Benchmark benchmark)
        {
            var directoryPath = Path.Combine(Directory.GetCurrentDirectory(), benchmark.Caption);
            bool exist = Directory.Exists(directoryPath);
            Exception deleteException = null;
            for (int attempt = 0; attempt < 3 && exist; attempt++)
            {
                if (attempt != 0)
                    Thread.Sleep(500); // Previous benchmark run didn't release some files
                try
                {
                    Directory.Delete(directoryPath, true);
                    exist = Directory.Exists(directoryPath);
                }
                catch (DirectoryNotFoundException)
                {
                    exist = false;
                    break;
                }
                catch (Exception e)
                {
                    // Can't delete the directory =(
                    deleteException = e;
                }
            }
            if (exist)
                return new BenchmarkGenerateResult(directoryPath, false, deleteException);
            if (!Directory.Exists(directoryPath))
{'fraction_non_alphanumeric': 0.09258781657811936, 'fraction_numerical': 0.00061897147573116, 'mean_word_length': 2.1846254927726676, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 15, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1464929', 'n_tokens_mistral': 4727, 'n_tokens_neox': 4401, 'n_words': 842}