diff --git a/test/Treasure.Utils.Argument.Benchmarks/Combine.ps1 b/test/Treasure.Utils.Argument.Benchmarks/Combine.ps1 new file mode 100644 index 0000000..84cb885 --- /dev/null +++ b/test/Treasure.Utils.Argument.Benchmarks/Combine.ps1 @@ -0,0 +1,107 @@ +#Requires -PSEdition Core + +[CmdletBinding()] +Param ( + [string[]] $TFMs = @('net6.0', 'net6.0-windows', 'net7.0', 'net7.0-windows'), + [string[]] $Columns = @( 'Runtime', 'TFM', 'LibraryTfm', 'Method', 'Mean', 'Error', 'StdDev', 'Code Size'), + [string] $ArtifactInputRoot = (Join-Path $PSScriptRoot '..' '..' '__benchmarks'), + [string] $ResultOutputPath = (Join-Path $PSScriptRoot '..' '..' '__benchmarks' 'results.md'), + [string[]] $MethodSortOrder = @( + 'ArgumentNotNull', + 'ArgumentNullExceptionThrowIfNull', + 'IfValueIsNullThrowArgumentNullException', + 'ArgumentNotNullOrEmpty', + 'ArgumentExceptionThrowIfNullOrEmpty', + 'IfValueIsNullOrEmptyThrowArgumentException', + 'ArgumentNotNullOrWhiteSpace', + 'ArgumentExceptionThrowIfNullOrWhiteSpace', + 'IfValueIsNullOrWhiteSpaceThrowArgumentException') +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version 2 + +function BuildTableRowFromData ($rowData) { + $row = '|' + + foreach ($data in $rowData) { + $row += " $data |" + } + + return $row +} + +function BuildTableRowFromColumnsAndData ($columns, $data) { + BuildTableRowFromData ($columns | ForEach-Object { $data.$_ }) +} + +function GetLibraryTfmName ($tfm) { + if ($tfm -eq 'net6.0') { + return 'net6.0' + } + + if ($tfm -eq 'net7.0') { + return 'net7.0' + } + + if ($tfm.Contains('-')) { + return 'netstandard2.1' + } + + throw "Unknown TFM '$tfm'." +} + +function GetLibraryTfmValue ($data, $libraryTfm) { + if ($data.Method.Contains('Exception')) { + return 'NA' + } + + return $libraryTfm +} + +if (!(Test-Path $ArtifactInputRoot)) { + Write-Error "Artifact input root path '$ArtifactInputRoot' does not exist." +} + +$csvData = @() +$TFMs + | ForEach-Object { + [PSCustomObject] @{ + libraryTfm = (GetLibraryTfmName $_) + tfm = $_ + path = (Join-Path $ArtifactInputRoot "$_.artifacts/results/Tests-report.csv") + } + } + | Where-Object { Test-Path $_.path } + | ForEach-Object { + $libraryTfm = $_.libraryTfm + $tfm = $_.tfm + Import-Csv -Path $_.path + | ForEach-Object { + $_ | Add-Member -NotePropertyName 'LibraryTfm' -NotePropertyValue (GetLibraryTfmValue $_ $libraryTfm) -PassThru + | Add-Member -NotePropertyName 'TFM' -NotePropertyValue $tfm -PassThru + } + } + | ForEach-Object { $csvData += $_ } +$csvData = $csvData | Sort-Object { $MethodSortOrder.IndexOf($_.Method) }, Runtime, LibraryTfm, TFM + +# Write header to markdown file +Set-Content -Path $ResultOutputPath -Value '# Results' +Add-Content -Path $ResultOutputPath -Value '' +Add-Content -Path $ResultOutputPath -Value (BuildTableRowFromData $Columns) +Add-Content -Path $ResultOutputPath -Value (BuildTableRowFromData ($Columns | ForEach-Object { '-'*$_.Length })) + +# Write to markdown file +$emptyRow = BuildTableRowFromData ($Columns | ForEach-Object { '' } ) +$lastMethod = $csvData[0].Method +$csvData | ForEach-Object { + $method = $_.Method + + if ($method -ne $lastMethod) { + Add-Content -Path $ResultOutputPath -Value $emptyRow + $lastMethod = $method + } + + $mdLine = BuildTableRowFromColumnsAndData $Columns $_ + Add-Content -Path $ResultOutputPath -Value $mdLine +} diff --git a/test/Treasure.Utils.Argument.Benchmarks/Program.cs b/test/Treasure.Utils.Argument.Benchmarks/Program.cs index df0b37b..1890891 100644 --- a/test/Treasure.Utils.Argument.Benchmarks/Program.cs +++ b/test/Treasure.Utils.Argument.Benchmarks/Program.cs @@ -1,17 +1,20 @@ -// dotnet run -c Release -f net6.0 --filter "*" --runtimes net6.0 net7.0 +// .\Run.ps1 +// or +// dotnet run -c Release -f [net6.0 net7.0] --filter "*" -#pragma warning disable IDE0211 #pragma warning disable CA1050 #pragma warning disable CA1812 #pragma warning disable CA1822 +#pragma warning disable CA1510 #pragma warning disable CS1591 +#pragma warning disable IDE0211 using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running; using Treasure.Utils; -BenchmarkSwitcher.FromAssembly(typeof(Tests).Assembly).Run(args); +BenchmarkRunner.Run(args: args); [HideColumns("value")] [DisassemblyDiagnoser] @@ -19,13 +22,87 @@ public class Tests { [Benchmark] [Arguments("foo")] - public void NotNull(string? value) => Argument.NotNull(value); + public void ArgumentNotNull(string? value) + { + Argument.NotNull(value); + } + + [Benchmark] + [Arguments("foo")] + public void ArgumentNullExceptionThrowIfNull(string? value) + { + ArgumentNullException.ThrowIfNull(value); + } + + [Benchmark] + [Arguments("foo")] + public void IfValueIsNullThrowArgumentNullException(string? value) + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + } + + [Benchmark] + [Arguments("foo")] + public void ArgumentNotNullOrEmpty(string? value) + { + Argument.NotNullOrEmpty(value); + } +#if NET7_0_OR_GREATER [Benchmark] [Arguments("foo")] - public void NotNullOrEmpty(string? value) => Argument.NotNullOrEmpty(value); + public void ArgumentExceptionThrowIfNullOrEmpty(string? value) + { + ArgumentException.ThrowIfNullOrEmpty(value); + } +#endif [Benchmark] [Arguments("foo")] - public void NotNullOrWhiteSpace(string? value) => Argument.NotNullOrWhiteSpace(value); + public void IfValueIsNullOrEmptyThrowArgumentException(string? value) + { + if (value is null) + { + throw new ArgumentNullException(value); + } + + if (string.IsNullOrEmpty(value)) + { + throw new ArgumentException("Value cannot be null or empty.", nameof(value)); + } + } + + [Benchmark] + [Arguments("foo")] + public void ArgumentNotNullOrWhiteSpace(string? value) + { + Argument.NotNullOrWhiteSpace(value); + } + +#if NET8_0_OR_GREATER + [Benchmark] + [Arguments("foo")] + public void ArgumentExceptionThrowIfNullOrWhiteSpace(string? value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + } +#endif + + [Benchmark] + [Arguments("foo")] + public void IfValueIsNullOrWhiteSpaceThrowArgumentException(string? value) + { + if (value is null) + { + throw new ArgumentNullException(value); + } + + if (string.IsNullOrWhiteSpace(value)) + { + throw new ArgumentException("Value cannot be null or empty.", nameof(value)); + } + } } diff --git a/test/Treasure.Utils.Argument.Benchmarks/Run.ps1 b/test/Treasure.Utils.Argument.Benchmarks/Run.ps1 new file mode 100644 index 0000000..71bb3b0 --- /dev/null +++ b/test/Treasure.Utils.Argument.Benchmarks/Run.ps1 @@ -0,0 +1,25 @@ +#Requires -PSEdition Core + +[CmdletBinding()] +Param ( + [string[]] $TFMs = @('net6.0', 'net6.0-windows', 'net7.0', 'net7.0-windows'), + [string] $ArtifactOutputRoot = (Join-Path $PSScriptRoot '..' '..' '__benchmarks'), + [string] $ResultOutputPath = (Join-Path $PSScriptRoot '..' '..' '__benchmarks' 'results.md') +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version 2 + +Push-Location $PSScriptRoot +try { + foreach ($tfm in $TFMs) { + $tfmBenchmarkOutput = Join-Path $ArtifactOutputRoot "$tfm.artifacts" + dotnet run -c Release -f $tfm --filter "*" -- -a $tfmBenchmarkOutput + } +} +finally { + Pop-Location +} + +$combineScriptPath = Join-Path $PSScriptRoot 'Combine.ps1' +. $combineScriptPath -TFMs $TFMs -ArtifactInputRoot $ArtifactOutputRoot -ResultOutputPath $ResultOutputPath