diff --git a/Content/Library/.github/workflows/benchmark.yml b/Content/Library/.github/workflows/benchmark.yml new file mode 100644 index 00000000..e839b58f --- /dev/null +++ b/Content/Library/.github/workflows/benchmark.yml @@ -0,0 +1,40 @@ +name: Benchmark + +on: + push: + branches: + - MyReleaseBranch + pull_request: + branches: + - MyReleaseBranch + +jobs: + benchmark: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - name: Setup necessary dotnet SDKs + uses: actions/setup-dotnet@v4 + with: + dotnet-version: | + 8.x + - name: Run benchmarks + run: | + chmod +x ./build.sh + ./build.sh RunBenchmarks + env: + CI: true + CONFIGURATION: Release + - name: Store benchmark result + uses: benchmark-action/github-action-benchmark@v1 + with: + name: MyLib.1 Benchmark + tool: 'benchmarkdotnet' + output-file-path: 'benchmarks/**/BenchmarkDotNet.Artifacts/results/*.json' + github-token: ${{ secrets.GITHUB_TOKEN }} + auto-push: true + # Show alert with commit comment on detecting possible performance regression + alert-threshold: '200%' + comment-on-alert: true + fail-on-alert: false + alert-comment-cc-users: '@MyGithubUsername' \ No newline at end of file diff --git a/Content/Library/Directory.Packages.props b/Content/Library/Directory.Packages.props index 0fe73715..138830ce 100644 --- a/Content/Library/Directory.Packages.props +++ b/Content/Library/Directory.Packages.props @@ -29,5 +29,6 @@ + \ No newline at end of file diff --git a/Content/Library/MyLib.1.sln b/Content/Library/MyLib.1.sln index 5115b934..f5a61754 100644 --- a/Content/Library/MyLib.1.sln +++ b/Content/Library/MyLib.1.sln @@ -11,6 +11,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{ACBEE43C EndProject Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "MyLib.1.Tests", "tests\MyLib.1.Tests\MyLib.1.Tests.fsproj", "{1CA2E092-2320-451D-A4F0-9ED7C7C528CA}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{B5B6CE6A-9B36-4F0F-8624-9B2CE14AC287}" +EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "MyLib.1.Benchmarks", "benchmarks\MyLib.1.Benchmarks\MyLib.1.Benchmarks.fsproj", "{E6C4F8E5-9B5D-4A12-8F1E-2A9C7D3E4B6F}" +EndProject Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "build", "build\build.fsproj", "{40D2259D-991D-44C4-B45D-C88CE0710C23}" EndProject Global @@ -62,9 +66,22 @@ Global {40D2259D-991D-44C4-B45D-C88CE0710C23}.Release|x64.Build.0 = Release|Any CPU {40D2259D-991D-44C4-B45D-C88CE0710C23}.Release|x86.ActiveCfg = Release|Any CPU {40D2259D-991D-44C4-B45D-C88CE0710C23}.Release|x86.Build.0 = Release|Any CPU + {E6C4F8E5-9B5D-4A12-8F1E-2A9C7D3E4B6F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E6C4F8E5-9B5D-4A12-8F1E-2A9C7D3E4B6F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E6C4F8E5-9B5D-4A12-8F1E-2A9C7D3E4B6F}.Debug|x64.ActiveCfg = Debug|x64 + {E6C4F8E5-9B5D-4A12-8F1E-2A9C7D3E4B6F}.Debug|x64.Build.0 = Debug|x64 + {E6C4F8E5-9B5D-4A12-8F1E-2A9C7D3E4B6F}.Debug|x86.ActiveCfg = Debug|x86 + {E6C4F8E5-9B5D-4A12-8F1E-2A9C7D3E4B6F}.Debug|x86.Build.0 = Debug|x86 + {E6C4F8E5-9B5D-4A12-8F1E-2A9C7D3E4B6F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E6C4F8E5-9B5D-4A12-8F1E-2A9C7D3E4B6F}.Release|Any CPU.Build.0 = Release|Any CPU + {E6C4F8E5-9B5D-4A12-8F1E-2A9C7D3E4B6F}.Release|x64.ActiveCfg = Release|x64 + {E6C4F8E5-9B5D-4A12-8F1E-2A9C7D3E4B6F}.Release|x64.Build.0 = Release|x64 + {E6C4F8E5-9B5D-4A12-8F1E-2A9C7D3E4B6F}.Release|x86.ActiveCfg = Release|x86 + {E6C4F8E5-9B5D-4A12-8F1E-2A9C7D3E4B6F}.Release|x86.Build.0 = Release|x86 EndGlobalSection GlobalSection(NestedProjects) = preSolution {5D30E174-2538-47AC-8443-318C8C5DC2C9} = {C397A34C-84F1-49E7-AEBC-2F9F2B196216} {1CA2E092-2320-451D-A4F0-9ED7C7C528CA} = {ACBEE43C-7A88-4FB1-9B06-DB064D22B29F} + {E6C4F8E5-9B5D-4A12-8F1E-2A9C7D3E4B6F} = {B5B6CE6A-9B36-4F0F-8624-9B2CE14AC287} EndGlobalSection EndGlobal diff --git a/Content/Library/README.md b/Content/Library/README.md index ecd89172..b8e00862 100644 --- a/Content/Library/README.md +++ b/Content/Library/README.md @@ -76,6 +76,7 @@ src/MyLib.1/bin/ - `GenerateCoverageReport` - Code coverage is run during `DotnetTest` and this generates a report via [ReportGenerator](https://github.com/danielpalme/ReportGenerator). - `ShowCoverageReport` - Shows the report generated in `GenerateCoverageReport`. - `WatchTests` - Runs [dotnet watch](https://docs.microsoft.com/en-us/aspnet/core/tutorials/dotnet-watch?view=aspnetcore-3.0) with the test projects. Useful for rapid feedback loops. +- `RunBenchmarks` - Runs [BenchmarkDotNet](https://benchmarkdotnet.org/) benchmarks in the `benchmarks` folder. - `GenerateAssemblyInfo` - Generates [AssemblyInfo](https://docs.microsoft.com/en-us/dotnet/api/microsoft.visualbasic.applicationservices.assemblyinfo?view=netframework-4.8) for libraries. - `DotnetPack` - Runs [dotnet pack](https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-pack). This includes running [Source Link](https://github.com/dotnet/sourcelink). - `SourceLinkTest` - Runs a Source Link test tool to verify Source Links were properly generated. diff --git a/Content/Library/benchmarks/Directory.Build.props b/Content/Library/benchmarks/Directory.Build.props new file mode 100644 index 00000000..971b8f01 --- /dev/null +++ b/Content/Library/benchmarks/Directory.Build.props @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/Content/Library/benchmarks/MyLib.1.Benchmarks/Library.Benchmarks.fs b/Content/Library/benchmarks/MyLib.1.Benchmarks/Library.Benchmarks.fs new file mode 100644 index 00000000..31768413 --- /dev/null +++ b/Content/Library/benchmarks/MyLib.1.Benchmarks/Library.Benchmarks.fs @@ -0,0 +1,30 @@ +namespace MyLib._1.Benchmarks + +open BenchmarkDotNet.Attributes +open MyLib._1 +open System + +[] +type LibraryBenchmarks() = + + let samplePerson = { + Say.Person.Name = "Benchmark" + Say.Person.FavoriteNumber = 42 + Say.Person.FavoriteColor = Say.FavoriteColor.Blue + Say.Person.DateOfBirth = DateTimeOffset.Now + } + + [] + member _.HelloPerson() = Say.helloPerson samplePerson + + [] + member _.AddNumbers() = Say.add 100 200 + + [] + member _.AddNumbersLoop() = + let mutable result = 0 + + for i in 1..1000 do + result <- Say.add result i + + result diff --git a/Content/Library/benchmarks/MyLib.1.Benchmarks/MyLib.1.Benchmarks.fsproj b/Content/Library/benchmarks/MyLib.1.Benchmarks/MyLib.1.Benchmarks.fsproj new file mode 100644 index 00000000..f0dcb31b --- /dev/null +++ b/Content/Library/benchmarks/MyLib.1.Benchmarks/MyLib.1.Benchmarks.fsproj @@ -0,0 +1,18 @@ + + + + Exe + net8.0 + false + + + + + + + + + + + + \ No newline at end of file diff --git a/Content/Library/benchmarks/MyLib.1.Benchmarks/Program.fs b/Content/Library/benchmarks/MyLib.1.Benchmarks/Program.fs new file mode 100644 index 00000000..19b8c4b0 --- /dev/null +++ b/Content/Library/benchmarks/MyLib.1.Benchmarks/Program.fs @@ -0,0 +1,11 @@ +module Program + +open BenchmarkDotNet.Running +open MyLib._1.Benchmarks + +[] +let main args = + BenchmarkRunner.Run() + |> ignore + + 0 diff --git a/Content/Library/build/build.fs b/Content/Library/build/build.fs index b809220c..19e83d89 100644 --- a/Content/Library/build/build.fs +++ b/Content/Library/build/build.fs @@ -505,6 +505,23 @@ let watchTests _ = cancelEvent.Cancel <- true +let benchmarksGlob = + rootDirectory + "benchmarks/**/*.??proj" + +let runBenchmarks _ = + !!benchmarksGlob + |> Seq.iter (fun proj -> + let result = DotNet.exec id "run" $"--project \"{proj}\" --configuration Release" + + if + result.ExitCode + <> 0 + then + Trace.traceError $"Warning: Benchmark failed for project %s{proj}" + Trace.traceError "This may be due to sandbox environment limitations" + ) + let generateAssemblyInfo _ = let (|Fsproj|Csproj|Vbproj|) (projFileName: string) = @@ -727,6 +744,7 @@ let initTargets () = Target.create "GenerateCoverageReport" generateCoverageReport Target.create "ShowCoverageReport" showCoverageReport Target.create "WatchTests" watchTests + Target.create "RunBenchmarks" runBenchmarks Target.create "GenerateAssemblyInfo" generateAssemblyInfo Target.create "DotnetPack" dotnetPack Target.create "SourceLinkTest" sourceLinkTest @@ -782,6 +800,9 @@ let initTargets () = "DotnetBuild" ==>! "WatchDocs" + "DotnetBuild" + ==>! "RunBenchmarks" + "DotnetTest" ==> "GenerateCoverageReport" ==>! "ShowCoverageReport" diff --git a/tests/MiniScaffold.Tests/Tests.fs b/tests/MiniScaffold.Tests/Tests.fs index 371469f6..a39ea912 100755 --- a/tests/MiniScaffold.Tests/Tests.fs +++ b/tests/MiniScaffold.Tests/Tests.fs @@ -129,8 +129,15 @@ module Tests = "-n MyCoolLib --githubUsername CoolPersonNo2", [ yield! projectStructureAsserts + Assert.``File exists`` ".github/workflows/benchmark.yml" + Assert.``File exists`` + "benchmarks/MyCoolLib.Benchmarks/MyCoolLib.Benchmarks.fsproj" + Assert.``File exists`` + "benchmarks/MyCoolLib.Benchmarks/Library.Benchmarks.fs" + Assert.``File exists`` "benchmarks/MyCoolLib.Benchmarks/Program.fs" Assert.``project can build target`` "DotnetPack" Assert.``project can build target`` "BuildDocs" + Assert.``project can build target`` "RunBenchmarks" ] @@ -187,6 +194,12 @@ module Tests = "-n fsharp-data-sample --githubUsername CoolPersonNo2", [ yield! projectStructureAsserts + Assert.``File exists`` ".github/workflows/benchmark.yml" + Assert.``File exists`` + "benchmarks/fsharp-data-sample.Benchmarks/fsharp-data-sample.Benchmarks.fsproj" + Assert.``File exists`` + "benchmarks/fsharp-data-sample.Benchmarks/Library.Benchmarks.fs" + Assert.``File exists`` "benchmarks/fsharp-data-sample.Benchmarks/Program.fs" Assert.``project can build target`` "DotnetPack" ] testCase,