diff --git a/.github/workflows/create-labels.yaml b/.github/workflows/create-labels.yaml index 49e6298..ce661f5 100644 --- a/.github/workflows/create-labels.yaml +++ b/.github/workflows/create-labels.yaml @@ -20,7 +20,12 @@ jobs: color: "b60205" }); } catch (error) { - // Ignore if label already exists + if (error.status === 422 && error.response?.data?.errors?.[0]?.code === 'already_exists') { + console.log('Label "dependabot - security" already exists, skipping creation'); + } else { + console.error('Failed to create label "dependabot - security":', error.message); + throw error; + } } - name: Create "dependabot-dependencies" label uses: actions/github-script@v6 @@ -34,5 +39,48 @@ jobs: color: "d93f0b" }); } catch (error) { - // Ignore if label already exists + if (error.status === 422 && error.response?.data?.errors?.[0]?.code === 'already_exists') { + console.log('Label "dependabot-dependencies" already exists, skipping creation'); + } else { + console.error('Failed to create label "dependabot-dependencies":', error.message); + throw error; + } + } + - name: Create "dependencies" label + uses: actions/github-script@v6 + with: + script: | + try { + await github.rest.issues.createLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + name: "dependencies", + color: "0366d6" + }); + } catch (error) { + if (error.status === 422 && error.response?.data?.errors?.[0]?.code === 'already_exists') { + console.log('Label "dependencies" already exists, skipping creation'); + } else { + console.error('Failed to create label "dependencies":', error.message); + throw error; + } + } + - name: Create "dotnet" label + uses: actions/github-script@v6 + with: + script: | + try { + await github.rest.issues.createLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + name: "dotnet", + color: "512bd4" + }); + } catch (error) { + if (error.status === 422 && error.response?.data?.errors?.[0]?.code === 'already_exists') { + console.log('Label "dotnet" already exists, skipping creation'); + } else { + console.error('Failed to create label "dotnet":', error.message); + throw error; + } } diff --git a/.github/workflows/docfx.yaml b/.github/workflows/docfx.yaml index 372921e..f821240 100644 --- a/.github/workflows/docfx.yaml +++ b/.github/workflows/docfx.yaml @@ -21,7 +21,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: '8.0.x' + dotnet-version: '10.0.x' - name: Install DocFX run: dotnet tool update docfx --global @@ -34,6 +34,15 @@ jobs: run: docfx build working-directory: docfx_project + - name: Verify build output + run: | + if [ ! -d "docfx_project/_site" ]; then + echo "Error: _site directory not found!" + exit 1 + fi + echo "Build successful. Contents of _site:" + ls -la docfx_project/_site + - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index b62a4f6..b629085 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -204,7 +204,7 @@ jobs: } test-macos-core: - name: "Stage 2b: macOS Tests (.NET 5.0-10.0)" + name: "Stage 2b: macOS Tests (.NET 6.0-10.0)" runs-on: macos-latest needs: test-linux-core if: github.repository != 'Chris-Wolfgang/repo-template' @@ -217,8 +217,6 @@ jobs: uses: actions/setup-dotnet@v4 with: dotnet-version: | - 3.1.x - 5.0.x 6.0.x 7.0.x 8.0.x @@ -231,9 +229,10 @@ jobs: - name: Build solution run: dotnet build --no-restore --configuration Release - - name: Run tests (.NET Core 3.1 - 10.0) + - name: Run tests (.NET 6.0 - 10.0 only - ARM64 compatible) run: | - frameworks=(netcoreapp3.1 net5.0 net6.0 net7.0 net8.0 net9.0 net10.0) + # Skip .NET Core 3.1 and .NET 5.0 - no ARM64 support on macOS + frameworks=(net6.0 net7.0 net8.0 net9.0 net10.0) for fw in "${frameworks[@]}"; do echo "==========================================" @@ -247,11 +246,27 @@ jobs: --logger "console;verbosity=normal" || exit 1 done - - name: Echo message for .NET Core 3.1 - if: startsWith(runner.os, 'macOS') && always() + - name: Display macOS architecture info + if: always() run: | - echo "ℹ️ Note: Skipping .NET Core 3.1 on macOS (no ARM64 support)" - echo " .NET Core 3.1 is tested on Linux and Windows" + echo "" + echo "==========================================" + echo "ℹ️ macOS Testing Notes" + echo "==========================================" + echo "Architecture: $(uname -m)" + echo "" + echo "Skipped frameworks (no ARM64 support):" + echo " - .NET Core 3.1 ❌" + echo " - .NET 5.0 ❌" + echo "" + echo "Tested frameworks (ARM64 compatible):" + echo " - .NET 6.0 ✅" + echo " - .NET 7.0 ✅" + echo " - .NET 8.0 ✅" + echo " - .NET 9.0 ✅" + echo " - .NET 10.0 ✅" + echo "" + echo ".NET Core 3.1 and 5.0 are tested on Linux and Windows" # ============================================================================ # STAGE 3: Windows - .NET Framework 4.x Tests (Gated by Stage 2) diff --git a/TryPattern.sln b/TryPattern.sln index 0d4b6c1..6ad173a 100644 --- a/TryPattern.sln +++ b/TryPattern.sln @@ -1,19 +1,56 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31903.59 +# Visual Studio Version 18 +VisualStudioVersion = 18.1.11312.151 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TryPattern", "src\TryPattern\TryPattern.csproj", "{EFF2BD2C-23ED-49AB-9C66-7A410A80A7E9}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{0AB3BF05-4346-4AA6-1389-037BE0695223}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TryPattern.Tests", "tests\TryPattern.Tests\TryPattern.Tests.csproj", "{A05E9BE9-21B5-44FA-98F0-61DD27BF1D24}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{66320409-64EC-F7C5-3DEF-65E7510DAAD1}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TryPattern.Benchmarks", "benchmarks\TryPattern.Benchmarks\TryPattern.Benchmarks.csproj", "{8A302909-AB73-4A3A-B02D-822697606F8C}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wolfgang.TryPattern.Benchmarks", "benchmarks\Wolfgang.TryPattern.Benchmarks\Wolfgang.TryPattern.Benchmarks.csproj", "{ACB0B913-A788-CAA1-A2E7-B67D795408AD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wolfgang.TryPattern", "src\Wolfgang.TryPattern\Wolfgang.TryPattern.csproj", "{3FB74DD2-4B26-0557-3592-895CBAB98A02}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wolfgang.TryPattern.Tests", "tests\Wolfgang.TryPattern.Tests\Wolfgang.TryPattern.Tests.csproj", "{4F845621-E0AF-82E7-36F6-4656DB52ED78}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{E2B4145B-6A0B-4011-9F72-08D7B0223858}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wolfgang.TryPattern.Example1", "examples\Wolfgang.TryPattern.Example1\Wolfgang.TryPattern.Example1.csproj", "{E4EC43C9-6498-710F-9177-96F30D1D6B65}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{A8070992-C05E-4286-8005-05AF955C4CAB}" + ProjectSection(SolutionItems) = preProject + docs\index.html = docs\index.html + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docfx_project", "docfx_project", "{7122EC59-A4D2-4111-91EE-5C93EE4FFFAF}" + ProjectSection(SolutionItems) = preProject + docfx_project\docfx.json = docfx_project\docfx.json + docfx_project\index.md = docfx_project\index.md + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{CEDF58AF-A696-4F7B-8D0C-9FC53C343B33}" + ProjectSection(SolutionItems) = preProject + .github\CODEOWNERS = .github\CODEOWNERS + .github\copilot-instructions.md = .github\copilot-instructions.md + .github\dependabot.yml = .github\dependabot.yml + .github\pull_request_template.md = .github\pull_request_template.md + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{00469A08-D076-4CAE-A96A-827445F6BFD9}" + ProjectSection(SolutionItems) = preProject + .github\workflows\create-labels.yaml = .github\workflows\create-labels.yaml + .github\workflows\docfx.yaml = .github\workflows\docfx.yaml + .github\workflows\pr.yaml = .github\workflows\pr.yaml + .github\workflows\release.yaml = .github\workflows\release.yaml + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ISSUE_TEMPLATE", "ISSUE_TEMPLATE", "{691A7AFE-8055-46AE-8D60-C397094BBCD3}" + ProjectSection(SolutionItems) = preProject + .github\ISSUE_TEMPLATE\BUG_REPORT.yaml = .github\ISSUE_TEMPLATE\BUG_REPORT.yaml + .github\ISSUE_TEMPLATE\feature_request.md = .github\ISSUE_TEMPLATE\feature_request.md + EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -25,49 +62,67 @@ Global Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {EFF2BD2C-23ED-49AB-9C66-7A410A80A7E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EFF2BD2C-23ED-49AB-9C66-7A410A80A7E9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EFF2BD2C-23ED-49AB-9C66-7A410A80A7E9}.Debug|x64.ActiveCfg = Debug|Any CPU - {EFF2BD2C-23ED-49AB-9C66-7A410A80A7E9}.Debug|x64.Build.0 = Debug|Any CPU - {EFF2BD2C-23ED-49AB-9C66-7A410A80A7E9}.Debug|x86.ActiveCfg = Debug|Any CPU - {EFF2BD2C-23ED-49AB-9C66-7A410A80A7E9}.Debug|x86.Build.0 = Debug|Any CPU - {EFF2BD2C-23ED-49AB-9C66-7A410A80A7E9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EFF2BD2C-23ED-49AB-9C66-7A410A80A7E9}.Release|Any CPU.Build.0 = Release|Any CPU - {EFF2BD2C-23ED-49AB-9C66-7A410A80A7E9}.Release|x64.ActiveCfg = Release|Any CPU - {EFF2BD2C-23ED-49AB-9C66-7A410A80A7E9}.Release|x64.Build.0 = Release|Any CPU - {EFF2BD2C-23ED-49AB-9C66-7A410A80A7E9}.Release|x86.ActiveCfg = Release|Any CPU - {EFF2BD2C-23ED-49AB-9C66-7A410A80A7E9}.Release|x86.Build.0 = Release|Any CPU - {A05E9BE9-21B5-44FA-98F0-61DD27BF1D24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A05E9BE9-21B5-44FA-98F0-61DD27BF1D24}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A05E9BE9-21B5-44FA-98F0-61DD27BF1D24}.Debug|x64.ActiveCfg = Debug|Any CPU - {A05E9BE9-21B5-44FA-98F0-61DD27BF1D24}.Debug|x64.Build.0 = Debug|Any CPU - {A05E9BE9-21B5-44FA-98F0-61DD27BF1D24}.Debug|x86.ActiveCfg = Debug|Any CPU - {A05E9BE9-21B5-44FA-98F0-61DD27BF1D24}.Debug|x86.Build.0 = Debug|Any CPU - {A05E9BE9-21B5-44FA-98F0-61DD27BF1D24}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A05E9BE9-21B5-44FA-98F0-61DD27BF1D24}.Release|Any CPU.Build.0 = Release|Any CPU - {A05E9BE9-21B5-44FA-98F0-61DD27BF1D24}.Release|x64.ActiveCfg = Release|Any CPU - {A05E9BE9-21B5-44FA-98F0-61DD27BF1D24}.Release|x64.Build.0 = Release|Any CPU - {A05E9BE9-21B5-44FA-98F0-61DD27BF1D24}.Release|x86.ActiveCfg = Release|Any CPU - {A05E9BE9-21B5-44FA-98F0-61DD27BF1D24}.Release|x86.Build.0 = Release|Any CPU - {8A302909-AB73-4A3A-B02D-822697606F8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8A302909-AB73-4A3A-B02D-822697606F8C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8A302909-AB73-4A3A-B02D-822697606F8C}.Debug|x64.ActiveCfg = Debug|Any CPU - {8A302909-AB73-4A3A-B02D-822697606F8C}.Debug|x64.Build.0 = Debug|Any CPU - {8A302909-AB73-4A3A-B02D-822697606F8C}.Debug|x86.ActiveCfg = Debug|Any CPU - {8A302909-AB73-4A3A-B02D-822697606F8C}.Debug|x86.Build.0 = Debug|Any CPU - {8A302909-AB73-4A3A-B02D-822697606F8C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8A302909-AB73-4A3A-B02D-822697606F8C}.Release|Any CPU.Build.0 = Release|Any CPU - {8A302909-AB73-4A3A-B02D-822697606F8C}.Release|x64.ActiveCfg = Release|Any CPU - {8A302909-AB73-4A3A-B02D-822697606F8C}.Release|x64.Build.0 = Release|Any CPU - {8A302909-AB73-4A3A-B02D-822697606F8C}.Release|x86.ActiveCfg = Release|Any CPU - {8A302909-AB73-4A3A-B02D-822697606F8C}.Release|x86.Build.0 = Release|Any CPU + {ACB0B913-A788-CAA1-A2E7-B67D795408AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ACB0B913-A788-CAA1-A2E7-B67D795408AD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ACB0B913-A788-CAA1-A2E7-B67D795408AD}.Debug|x64.ActiveCfg = Debug|Any CPU + {ACB0B913-A788-CAA1-A2E7-B67D795408AD}.Debug|x64.Build.0 = Debug|Any CPU + {ACB0B913-A788-CAA1-A2E7-B67D795408AD}.Debug|x86.ActiveCfg = Debug|Any CPU + {ACB0B913-A788-CAA1-A2E7-B67D795408AD}.Debug|x86.Build.0 = Debug|Any CPU + {ACB0B913-A788-CAA1-A2E7-B67D795408AD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ACB0B913-A788-CAA1-A2E7-B67D795408AD}.Release|Any CPU.Build.0 = Release|Any CPU + {ACB0B913-A788-CAA1-A2E7-B67D795408AD}.Release|x64.ActiveCfg = Release|Any CPU + {ACB0B913-A788-CAA1-A2E7-B67D795408AD}.Release|x64.Build.0 = Release|Any CPU + {ACB0B913-A788-CAA1-A2E7-B67D795408AD}.Release|x86.ActiveCfg = Release|Any CPU + {ACB0B913-A788-CAA1-A2E7-B67D795408AD}.Release|x86.Build.0 = Release|Any CPU + {3FB74DD2-4B26-0557-3592-895CBAB98A02}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3FB74DD2-4B26-0557-3592-895CBAB98A02}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3FB74DD2-4B26-0557-3592-895CBAB98A02}.Debug|x64.ActiveCfg = Debug|Any CPU + {3FB74DD2-4B26-0557-3592-895CBAB98A02}.Debug|x64.Build.0 = Debug|Any CPU + {3FB74DD2-4B26-0557-3592-895CBAB98A02}.Debug|x86.ActiveCfg = Debug|Any CPU + {3FB74DD2-4B26-0557-3592-895CBAB98A02}.Debug|x86.Build.0 = Debug|Any CPU + {3FB74DD2-4B26-0557-3592-895CBAB98A02}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3FB74DD2-4B26-0557-3592-895CBAB98A02}.Release|Any CPU.Build.0 = Release|Any CPU + {3FB74DD2-4B26-0557-3592-895CBAB98A02}.Release|x64.ActiveCfg = Release|Any CPU + {3FB74DD2-4B26-0557-3592-895CBAB98A02}.Release|x64.Build.0 = Release|Any CPU + {3FB74DD2-4B26-0557-3592-895CBAB98A02}.Release|x86.ActiveCfg = Release|Any CPU + {3FB74DD2-4B26-0557-3592-895CBAB98A02}.Release|x86.Build.0 = Release|Any CPU + {4F845621-E0AF-82E7-36F6-4656DB52ED78}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4F845621-E0AF-82E7-36F6-4656DB52ED78}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4F845621-E0AF-82E7-36F6-4656DB52ED78}.Debug|x64.ActiveCfg = Debug|Any CPU + {4F845621-E0AF-82E7-36F6-4656DB52ED78}.Debug|x64.Build.0 = Debug|Any CPU + {4F845621-E0AF-82E7-36F6-4656DB52ED78}.Debug|x86.ActiveCfg = Debug|Any CPU + {4F845621-E0AF-82E7-36F6-4656DB52ED78}.Debug|x86.Build.0 = Debug|Any CPU + {4F845621-E0AF-82E7-36F6-4656DB52ED78}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4F845621-E0AF-82E7-36F6-4656DB52ED78}.Release|Any CPU.Build.0 = Release|Any CPU + {4F845621-E0AF-82E7-36F6-4656DB52ED78}.Release|x64.ActiveCfg = Release|Any CPU + {4F845621-E0AF-82E7-36F6-4656DB52ED78}.Release|x64.Build.0 = Release|Any CPU + {4F845621-E0AF-82E7-36F6-4656DB52ED78}.Release|x86.ActiveCfg = Release|Any CPU + {4F845621-E0AF-82E7-36F6-4656DB52ED78}.Release|x86.Build.0 = Release|Any CPU + {E4EC43C9-6498-710F-9177-96F30D1D6B65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E4EC43C9-6498-710F-9177-96F30D1D6B65}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E4EC43C9-6498-710F-9177-96F30D1D6B65}.Debug|x64.ActiveCfg = Debug|Any CPU + {E4EC43C9-6498-710F-9177-96F30D1D6B65}.Debug|x64.Build.0 = Debug|Any CPU + {E4EC43C9-6498-710F-9177-96F30D1D6B65}.Debug|x86.ActiveCfg = Debug|Any CPU + {E4EC43C9-6498-710F-9177-96F30D1D6B65}.Debug|x86.Build.0 = Debug|Any CPU + {E4EC43C9-6498-710F-9177-96F30D1D6B65}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E4EC43C9-6498-710F-9177-96F30D1D6B65}.Release|Any CPU.Build.0 = Release|Any CPU + {E4EC43C9-6498-710F-9177-96F30D1D6B65}.Release|x64.ActiveCfg = Release|Any CPU + {E4EC43C9-6498-710F-9177-96F30D1D6B65}.Release|x64.Build.0 = Release|Any CPU + {E4EC43C9-6498-710F-9177-96F30D1D6B65}.Release|x86.ActiveCfg = Release|Any CPU + {E4EC43C9-6498-710F-9177-96F30D1D6B65}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {EFF2BD2C-23ED-49AB-9C66-7A410A80A7E9} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {A05E9BE9-21B5-44FA-98F0-61DD27BF1D24} = {0AB3BF05-4346-4AA6-1389-037BE0695223} - {8A302909-AB73-4A3A-B02D-822697606F8C} = {66320409-64EC-F7C5-3DEF-65E7510DAAD1} + {ACB0B913-A788-CAA1-A2E7-B67D795408AD} = {66320409-64EC-F7C5-3DEF-65E7510DAAD1} + {3FB74DD2-4B26-0557-3592-895CBAB98A02} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {4F845621-E0AF-82E7-36F6-4656DB52ED78} = {0AB3BF05-4346-4AA6-1389-037BE0695223} + {E4EC43C9-6498-710F-9177-96F30D1D6B65} = {E2B4145B-6A0B-4011-9F72-08D7B0223858} + {00469A08-D076-4CAE-A96A-827445F6BFD9} = {CEDF58AF-A696-4F7B-8D0C-9FC53C343B33} + {691A7AFE-8055-46AE-8D60-C397094BBCD3} = {CEDF58AF-A696-4F7B-8D0C-9FC53C343B33} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {4B78A421-6F38-4A02-8673-B44E9F682D0F} EndGlobalSection EndGlobal diff --git a/benchmarks/.placeholder b/benchmarks/.placeholder deleted file mode 100644 index e69de29..0000000 diff --git a/benchmarks/TryPattern.Benchmarks/Program.cs b/benchmarks/Wolfgang.TryPattern.Benchmarks/Program.cs similarity index 64% rename from benchmarks/TryPattern.Benchmarks/Program.cs rename to benchmarks/Wolfgang.TryPattern.Benchmarks/Program.cs index 8952306..4a24500 100644 --- a/benchmarks/TryPattern.Benchmarks/Program.cs +++ b/benchmarks/Wolfgang.TryPattern.Benchmarks/Program.cs @@ -1,4 +1,4 @@ using BenchmarkDotNet.Running; -using TryPattern.Benchmarks; +using Wolfgang.TryPattern.Benchmarks; BenchmarkRunner.Run(); diff --git a/benchmarks/TryPattern.Benchmarks/TryBenchmarks.cs b/benchmarks/Wolfgang.TryPattern.Benchmarks/TryBenchmarks.cs similarity index 98% rename from benchmarks/TryPattern.Benchmarks/TryBenchmarks.cs rename to benchmarks/Wolfgang.TryPattern.Benchmarks/TryBenchmarks.cs index 46efef7..88261d3 100644 --- a/benchmarks/TryPattern.Benchmarks/TryBenchmarks.cs +++ b/benchmarks/Wolfgang.TryPattern.Benchmarks/TryBenchmarks.cs @@ -1,7 +1,7 @@ using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Jobs; -namespace TryPattern.Benchmarks; +namespace Wolfgang.TryPattern.Benchmarks; [SimpleJob(RuntimeMoniker.Net80)] [MemoryDiagnoser] diff --git a/benchmarks/TryPattern.Benchmarks/TryPattern.Benchmarks.csproj b/benchmarks/Wolfgang.TryPattern.Benchmarks/Wolfgang.TryPattern.Benchmarks.csproj similarity index 80% rename from benchmarks/TryPattern.Benchmarks/TryPattern.Benchmarks.csproj rename to benchmarks/Wolfgang.TryPattern.Benchmarks/Wolfgang.TryPattern.Benchmarks.csproj index 36bd704..d41901f 100644 --- a/benchmarks/TryPattern.Benchmarks/TryPattern.Benchmarks.csproj +++ b/benchmarks/Wolfgang.TryPattern.Benchmarks/Wolfgang.TryPattern.Benchmarks.csproj @@ -1,18 +1,18 @@ - - - - Exe - net8.0 - enable - enable - - + + + + Exe + net8.0 + enable + enable + + - - + + - - - - + + + + diff --git a/examples/Wolfgang.TryPattern.Example1/Program.cs b/examples/Wolfgang.TryPattern.Example1/Program.cs new file mode 100644 index 0000000..3751555 --- /dev/null +++ b/examples/Wolfgang.TryPattern.Example1/Program.cs @@ -0,0 +1,2 @@ +// See https://aka.ms/new-console-template for more information +Console.WriteLine("Hello, World!"); diff --git a/examples/Wolfgang.TryPattern.Example1/Wolfgang.TryPattern.Example1.csproj b/examples/Wolfgang.TryPattern.Example1/Wolfgang.TryPattern.Example1.csproj new file mode 100644 index 0000000..0d1e157 --- /dev/null +++ b/examples/Wolfgang.TryPattern.Example1/Wolfgang.TryPattern.Example1.csproj @@ -0,0 +1,14 @@ + + + + Exe + net10.0 + enable + enable + + + + + + + diff --git a/src/.placeholder b/src/.placeholder deleted file mode 100644 index e69de29..0000000 diff --git a/src/TryPattern/Try.cs b/src/TryPattern/Try.cs deleted file mode 100644 index ce4e729..0000000 --- a/src/TryPattern/Try.cs +++ /dev/null @@ -1,108 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace TryPattern -{ - /// - /// Provides methods to execute actions and functions with automatic exception handling. - /// - public static class Try - { - /// - /// Executes an action and swallows any exceptions that occur. - /// - /// The action to execute. - public static void Action(Action action) - { - if (action == null) - { - throw new ArgumentNullException(nameof(action)); - } - - try - { - action(); - } - catch - { - // Swallow exception - } - } - - /// - /// Executes an asynchronous action and swallows any exceptions that occur. - /// - /// The asynchronous action to execute. - /// A task representing the asynchronous operation. - public static async Task ActionAsync(Func action) - { - if (action == null) - { - throw new ArgumentNullException(nameof(action)); - } - - try - { - await action().ConfigureAwait(false); - } - catch - { - // Swallow exception - } - } - - /// - /// Executes a function and returns its result, or the default value of T if an exception occurs. - /// - /// The return type of the function. - /// The function to execute. - /// The result of the function, or default(T) if an exception occurs. -#if NET5_0_OR_GREATER - public static T? Function(Func function) -#else - public static T Function(Func function) -#endif - { - if (function == null) - { - throw new ArgumentNullException(nameof(function)); - } - - try - { - return function(); - } - catch - { - return default; - } - } - - /// - /// Executes an asynchronous function and returns its result, or the default value of T if an exception occurs. - /// - /// The return type of the function. - /// The asynchronous function to execute. - /// A task representing the asynchronous operation that returns the result of the function, or default(T) if an exception occurs. -#if NET5_0_OR_GREATER - public static async Task FunctionAsync(Func> function) -#else - public static async Task FunctionAsync(Func> function) -#endif - { - if (function == null) - { - throw new ArgumentNullException(nameof(function)); - } - - try - { - return await function().ConfigureAwait(false); - } - catch - { - return default; - } - } - } -} diff --git a/src/TryPattern/TryPattern.csproj b/src/TryPattern/TryPattern.csproj deleted file mode 100644 index 0651437..0000000 --- a/src/TryPattern/TryPattern.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - - net462;net47;net471;net472;net48;net5.0;net6.0;net7.0;net8.0;net9.0 - latest - - - - enable - enable - - - diff --git a/src/Wolfgang.TryPattern/Try.cs b/src/Wolfgang.TryPattern/Try.cs new file mode 100644 index 0000000..e0424fd --- /dev/null +++ b/src/Wolfgang.TryPattern/Try.cs @@ -0,0 +1,122 @@ +using System; +using System.Threading.Tasks; + +namespace Wolfgang.TryPattern; + +/// +/// Provides methods to execute actions and functions with automatic exception handling. +/// +public static class Try +{ + /// + /// Executes an action and swallows any exceptions that occur. + /// + /// The action to execute. + public static TryActionResult Action(Action action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + try + { + action(); + return new TryActionResult(true, null); + } + catch (Exception ex) + { + return new TryActionResult(false, ex); + } + } + + + + /// + /// Executes an asynchronous action and swallows any exceptions that occur. + /// + /// The asynchronous action to execute. + /// A task representing the asynchronous operation. + public static async Task ActionAsync(Func action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + try + { + await action().ConfigureAwait(false); + return new TryActionResult(true, null); + } + catch(Exception ex) + { + return new TryActionResult(false, ex); + } + } + + + + /// + /// Executes a function and returns its result, or the default value of T if an exception occurs. + /// + /// The return type of the function. + /// The function to execute. + /// The result of the function, or default(T) if an exception occurs. +#if NET5_0_OR_GREATER + public static TryFuncResult Function(Func function) +#else + public static TryFuncResult Function(Func function) +#endif + { + if (function == null) + { + throw new ArgumentNullException(nameof(function)); + } + + try + { + var result = function(); + return new TryFuncResult(result); + } + catch (Exception ex) + { + return new TryFuncResult(false, default, ex); + } + } + + + + /// + /// Executes an asynchronous function and returns its result, or the default value of T if an exception occurs. + /// + /// The return type of the function. + /// The asynchronous function to execute. + /// A task representing the asynchronous operation that returns the result of the function, or default(T) if an exception occurs. +#if NET5_0_OR_GREATER + public static async Task> FunctionAsync(Func> function) +#else + public static async Task> FunctionAsync(Func> function) +#endif + { + if (function == null) + { + throw new ArgumentNullException(nameof(function)); + } + + + try + { + var result = await function().ConfigureAwait(false); + return new TryFuncResult(result); + } + catch (Exception ex) + { + return new TryFuncResult(false, default, ex); + } + } +} + + + + diff --git a/src/Wolfgang.TryPattern/TryActionResult.cs b/src/Wolfgang.TryPattern/TryActionResult.cs new file mode 100644 index 0000000..596f9b5 --- /dev/null +++ b/src/Wolfgang.TryPattern/TryActionResult.cs @@ -0,0 +1,72 @@ +using System; + +namespace Wolfgang.TryPattern; + + + +/// +/// The result of a Try Action execution. +/// +public record TryActionResult +{ + /// + /// Initializes a new instance of the TryActionResult class with the specified success status and + /// exception information. + /// + /// A value indicating whether the operation succeeded. Set to if the operation was + /// successful; otherwise, . + /// The exception that was thrown during the operation if it failed; otherwise, . + public TryActionResult + ( + bool succeeded, + Exception? exception + ) + { + Succeeded = succeeded; + Exception = exception; + } + + + /// + /// Initializes a new instance of the TryActionResult class for a successful operation. + /// + public TryActionResult() + : this(true, null) + { + } + + + + /// + /// Initializes a new instance of the TryActionResult class for a failed operation. + /// + /// The exception that caused the operation to fail. + public TryActionResult + ( + Exception exception + ) + : this(false, exception) + { + } + + + + /// + /// Gets a value indicating whether the operation completed successfully. + /// + public bool Succeeded { get; } + + + + /// + /// Gets a value indicating whether the operation did not succeed. + /// + public bool Failed => !Succeeded; + + + + /// + /// If the action failed, gets the exception that caused the current operation to fail. Otherwise, this value is null. + /// + public Exception? Exception { get; } +} \ No newline at end of file diff --git a/src/Wolfgang.TryPattern/TryFuncResult.cs b/src/Wolfgang.TryPattern/TryFuncResult.cs new file mode 100644 index 0000000..b009e22 --- /dev/null +++ b/src/Wolfgang.TryPattern/TryFuncResult.cs @@ -0,0 +1,98 @@ +using System; + +namespace Wolfgang.TryPattern; + +/// +/// The result of a Try Function execution. +/// +/// +/// The type of the result produced by the function. +/// +public record TryFuncResult +{ + + /// + /// Initializes a new instance of the TryFuncResult class with the specified success status, value and + /// exception information. + /// + /// A value indicating whether the operation succeeded. Set to if the operation was + /// successful; otherwise, . + /// The value produced by the operation if it succeeded; otherwise, the default value for the type. + /// The exception that was thrown during the operation if it failed; otherwise, . + public TryFuncResult + ( + bool succeeded, +#if NET5_0_OR_GREATER + T? value, +#else + T value, +#endif + Exception? exception + ) + { + Succeeded = succeeded; + Value = value; + Exception = exception; + } + + + + /// + /// Initializes a new instance of the TryFuncResult class for a successful operation. + /// + /// The value produced by the successful operation. + public TryFuncResult + ( +#if NET5_0_OR_GREATER + T? value +#else + T value +#endif + ) + : this(true, value, null) + { + } + + + + /// + /// Initializes a new instance of the TryFuncResult class for a failed operation. + /// + /// The exception that caused the operation to fail. + public TryFuncResult + ( + Exception? exception + ) + : this(false, default, exception) + { + + } + + + + /// + /// Gets a value indicating whether the operation completed successfully. + /// + public bool Succeeded { get; } + + + + /// + /// Gets a value indicating whether the operation did not complete successfully. + /// + public bool Failed => !Succeeded; + + + + /// + /// If the function failed, gets the exception that caused the current operation to fail. Otherwise, this value is null. + /// + public Exception? Exception { get; } + + + + /// + /// Gets the result value, if available. + /// + public T? Value { get; } +} \ No newline at end of file diff --git a/src/Wolfgang.TryPattern/Wolfgang.TryPattern.csproj b/src/Wolfgang.TryPattern/Wolfgang.TryPattern.csproj new file mode 100644 index 0000000..3af85f0 --- /dev/null +++ b/src/Wolfgang.TryPattern/Wolfgang.TryPattern.csproj @@ -0,0 +1,37 @@ + + + + + net462; + netstandard2.0; + net8.0; + net10.0 + + latest + enable + 0.1.0-rc1 + $(AssemblyName) + Chris Wolfgang + + Copyright 2025 Chris Wolfgang + https://github.com/Chris-Wolfgang/Try-Pattern + README.md + 1.0.0 + MIT + True + True + False + + + + enable + + + diff --git a/tests/.placeholder b/tests/.placeholder deleted file mode 100644 index e69de29..0000000 diff --git a/tests/TryPattern.Tests/TryFunctionAsyncTests.cs b/tests/TryPattern.Tests/TryFunctionAsyncTests.cs deleted file mode 100644 index fe0188f..0000000 --- a/tests/TryPattern.Tests/TryFunctionAsyncTests.cs +++ /dev/null @@ -1,144 +0,0 @@ -namespace TryPattern.Tests; - -public class TryFunctionAsyncTests -{ - [Fact] - public async Task FunctionAsync_WithNullFunction_ThrowsArgumentNullException() - { - // Arrange - Func>? nullFunction = null; - - // Act & Assert - await Assert.ThrowsAsync(() => Try.FunctionAsync(nullFunction!)); - } - - [Fact] - public async Task FunctionAsync_WithSuccessfulFunction_ReturnsResult() - { - // Arrange - const int expectedValue = 42; - Func> function = async () => - { - await Task.Delay(10); - return expectedValue; - }; - - // Act - var result = await Try.FunctionAsync(function); - - // Assert - Assert.Equal(expectedValue, result); - } - - [Fact] - public async Task FunctionAsync_WithStringFunction_ReturnsResult() - { - // Arrange - const string expectedValue = "Hello, Async World!"; - Func> function = async () => - { - await Task.Delay(10); - return expectedValue; - }; - - // Act - var result = await Try.FunctionAsync(function); - - // Assert - Assert.Equal(expectedValue, result); - } - - [Fact] - public async Task FunctionAsync_WithExceptionThrowingFunction_ReturnsDefault() - { - // Arrange - Func> function = async () => - { - await Task.Delay(10); - throw new InvalidOperationException("Test exception"); - }; - - // Act - var result = await Try.FunctionAsync(function); - - // Assert - Assert.Equal(default(int), result); - } - - [Fact] - public async Task FunctionAsync_WithExceptionThrowingStringFunction_ReturnsNull() - { - // Arrange - Func> function = async () => - { - await Task.Delay(10); - throw new InvalidOperationException("Test exception"); - }; - - // Act - var result = await Try.FunctionAsync(function); - - // Assert - Assert.Null(result); - } - - [Fact] - public async Task FunctionAsync_WithSynchronousException_ReturnsDefault() - { - // Arrange - Func> function = () => throw new InvalidOperationException("Synchronous exception"); - - // Act - var result = await Try.FunctionAsync(function); - - // Assert - Assert.Equal(default(int), result); - } - - [Fact] - public async Task FunctionAsync_WithComplexObject_ReturnsResult() - { - // Arrange - var expectedObject = new { Name = "Test", Value = 100 }; - Func> function = async () => - { - await Task.Delay(10); - return expectedObject; - }; - - // Act - var result = await Try.FunctionAsync(function); - - // Assert - Assert.Equal(expectedObject, result); - } - - [Fact] - public async Task FunctionAsync_WithMultipleCalls_HandlesEachIndependently() - { - // Arrange - var callCount = 0; - Func> successFunction = async () => - { - await Task.Delay(10); - return ++callCount; - }; - Func> failFunction = async () => - { - await Task.Delay(10); - callCount++; - throw new Exception(); - }; - - // Act - var result1 = await Try.FunctionAsync(successFunction); - var result2 = await Try.FunctionAsync(failFunction); - var result3 = await Try.FunctionAsync(successFunction); - - // Assert - Assert.Equal(1, result1); - Assert.Equal(0, result2); // Default int value - Assert.Equal(3, result3); - Assert.Equal(3, callCount); - } -} diff --git a/tests/TryPattern.Tests/TryFunctionTests.cs b/tests/TryPattern.Tests/TryFunctionTests.cs deleted file mode 100644 index 05984fd..0000000 --- a/tests/TryPattern.Tests/TryFunctionTests.cs +++ /dev/null @@ -1,106 +0,0 @@ -namespace TryPattern.Tests; - -public class TryFunctionTests -{ - [Fact] - public void Function_WithNullFunction_ThrowsArgumentNullException() - { - // Arrange - Func? nullFunction = null; - - // Act & Assert - Assert.Throws(() => Try.Function(nullFunction!)); - } - - [Fact] - public void Function_WithSuccessfulFunction_ReturnsResult() - { - // Arrange - const int expectedValue = 42; - Func function = () => expectedValue; - - // Act - var result = Try.Function(function); - - // Assert - Assert.Equal(expectedValue, result); - } - - [Fact] - public void Function_WithStringFunction_ReturnsResult() - { - // Arrange - const string expectedValue = "Hello, World!"; - Func function = () => expectedValue; - - // Act - var result = Try.Function(function); - - // Assert - Assert.Equal(expectedValue, result); - } - - [Fact] - public void Function_WithExceptionThrowingFunction_ReturnsDefault() - { - // Arrange - Func function = () => throw new InvalidOperationException("Test exception"); - - // Act - var result = Try.Function(function); - - // Assert - Assert.Equal(default(int), result); - } - - [Fact] - public void Function_WithExceptionThrowingStringFunction_ReturnsNull() - { - // Arrange - Func function = () => throw new InvalidOperationException("Test exception"); - - // Act - var result = Try.Function(function); - - // Assert - Assert.Null(result); - } - - [Fact] - public void Function_WithComplexObject_ReturnsResult() - { - // Arrange - var expectedObject = new { Name = "Test", Value = 100 }; - Func function = () => expectedObject; - - // Act - var result = Try.Function(function); - - // Assert - Assert.Equal(expectedObject, result); - } - - [Fact] - public void Function_WithMultipleCalls_HandlesEachIndependently() - { - // Arrange - var callCount = 0; - Func successFunction = () => ++callCount; - Func failFunction = () => - { - callCount++; - throw new Exception(); - }; - - // Act - var result1 = Try.Function(successFunction); - var result2 = Try.Function(failFunction); - var result3 = Try.Function(successFunction); - - // Assert - Assert.Equal(1, result1); - Assert.Equal(0, result2); // Default int value - Assert.Equal(3, result3); - Assert.Equal(3, callCount); - } -} diff --git a/tests/TryPattern.Tests/TryPattern.Tests.csproj b/tests/TryPattern.Tests/TryPattern.Tests.csproj index b8a75d3..1f8866f 100644 --- a/tests/TryPattern.Tests/TryPattern.Tests.csproj +++ b/tests/TryPattern.Tests/TryPattern.Tests.csproj @@ -1,27 +1,27 @@ - - - - net8.0 - enable - enable - - false - true - - - - - - - - - - - - - - - - - - + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + diff --git a/tests/TryPattern.Tests/TryActionAsyncTests.cs b/tests/Wolfgang.TryPattern.Tests/TryActionAsyncTests.cs similarity index 68% rename from tests/TryPattern.Tests/TryActionAsyncTests.cs rename to tests/Wolfgang.TryPattern.Tests/TryActionAsyncTests.cs index 25f258e..defafe3 100644 --- a/tests/TryPattern.Tests/TryActionAsyncTests.cs +++ b/tests/Wolfgang.TryPattern.Tests/TryActionAsyncTests.cs @@ -1,4 +1,4 @@ -namespace TryPattern.Tests; +namespace Wolfgang.TryPattern.Tests; public class TryActionAsyncTests { @@ -12,68 +12,90 @@ public async Task ActionAsync_WithNullAction_ThrowsArgumentNullException() await Assert.ThrowsAsync(() => Try.ActionAsync(nullAction!)); } + + [Fact] public async Task ActionAsync_WithSuccessfulAction_ExecutesAction() { // Arrange var executed = false; - Func action = async () => + var action = async () => { await Task.Delay(10); executed = true; }; // Act - await Try.ActionAsync(action); + var result = await Try.ActionAsync(action); // Assert Assert.True(executed); + Assert.True(result.Succeeded); + Assert.False(result.Failed); + Assert.Null(result.Exception); + } + + [Fact] public async Task ActionAsync_WithExceptionThrowingAction_SwallowsException() { // Arrange - Func action = async () => + var action = async () => { await Task.Delay(10); throw new InvalidOperationException("Test exception"); }; - // Act & Assert - Should not throw - var exception = await Record.ExceptionAsync(() => Try.ActionAsync(action)); - Assert.Null(exception); + // Act + var result = await Try.ActionAsync(action); + + // Assert + Assert.False(result.Succeeded); + Assert.True(result.Failed); + Assert.NotNull(result.Exception); } + + + [Fact] public async Task ActionAsync_WithSynchronousException_SwallowsException() { // Arrange Func action = () => throw new InvalidOperationException("Synchronous exception"); - // Act & Assert - Should not throw - var exception = await Record.ExceptionAsync(() => Try.ActionAsync(action)); - Assert.Null(exception); + // Act + var result = await Try.ActionAsync(action); + + // Assert + Assert.False(result.Succeeded); + Assert.True(result.Failed); + Assert.NotNull(result.Exception); } + + [Fact] public async Task ActionAsync_WithMultipleAsyncExceptions_SwallowsAllExceptions() { // Arrange var callCount = 0; - Func action1 = async () => + + var action1 = async () => { await Task.Delay(10); callCount++; throw new ArgumentException(); }; - Func action2 = async () => + var action2 = async () => { await Task.Delay(10); callCount++; throw new InvalidOperationException(); }; - Func action3 = async () => + var action3 = async () => { await Task.Delay(10); callCount++; @@ -84,8 +106,6 @@ public async Task ActionAsync_WithMultipleAsyncExceptions_SwallowsAllExceptions( await Try.ActionAsync(action1); await Try.ActionAsync(action2); await Try.ActionAsync(action3); - - // Assert - Assert.Equal(3, callCount); + Assert.Equal(3 ,callCount); } } diff --git a/tests/Wolfgang.TryPattern.Tests/TryActionResultTests.cs b/tests/Wolfgang.TryPattern.Tests/TryActionResultTests.cs new file mode 100644 index 0000000..9934ead --- /dev/null +++ b/tests/Wolfgang.TryPattern.Tests/TryActionResultTests.cs @@ -0,0 +1,78 @@ +namespace Wolfgang.TryPattern.Tests; + +public class TryActionResultTests +{ + + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Constructor_WithSucceededOnly_SetsPropertiesCorrectly(bool succeeded) + { + // Act + var result = new TryActionResult(succeeded, null); + // Assert + Assert.Equal(succeeded, result.Succeeded); + Assert.Null(result.Exception); + } + + + + + [Fact] + + public void Constructor_WithSucceededAndException_SetsPropertiesCorrectly() + { + // Arrange + var exception = new Exception("Test exception"); + // Act + var result = new TryActionResult(true, exception); + // Assert + Assert.Equal(exception, result.Exception); + } + + + + [Fact] + public void Constructor_WithFailedOnly_SetsPropertiesCorrectly() + { + // Act + var result = new TryActionResult(false, null); + // Assert + Assert.False(result.Succeeded); + Assert.Null(result.Exception); + } + + + + [Fact] + public void Constructor_with_no_parameters_sets_properties_correctly() + { + // Arrange + + // Act + var result = new TryActionResult(); + + // Assert + Assert.True(result.Succeeded); + Assert.False(result.Failed); + Assert.Null(result.Exception); + } + + + + [Fact] + public void Constructor_with_Exception_sets_properties_correctly() + { + // Arrange + + // Act + var result = new TryActionResult(new Exception()); + + // Assert + Assert.False(result.Succeeded); + Assert.True(result.Failed); + Assert.NotNull(result.Exception); + } + +} diff --git a/tests/TryPattern.Tests/TryActionTests.cs b/tests/Wolfgang.TryPattern.Tests/TryActionTests.cs similarity index 73% rename from tests/TryPattern.Tests/TryActionTests.cs rename to tests/Wolfgang.TryPattern.Tests/TryActionTests.cs index 99c6a9d..b768ec6 100644 --- a/tests/TryPattern.Tests/TryActionTests.cs +++ b/tests/Wolfgang.TryPattern.Tests/TryActionTests.cs @@ -1,4 +1,4 @@ -namespace TryPattern.Tests; +namespace Wolfgang.TryPattern.Tests; public class TryActionTests { @@ -12,6 +12,8 @@ public void Action_WithNullAction_ThrowsArgumentNullException() Assert.Throws(() => Try.Action(nullAction!)); } + + [Fact] public void Action_WithSuccessfulAction_ExecutesAction() { @@ -20,39 +22,50 @@ public void Action_WithSuccessfulAction_ExecutesAction() Action action = () => executed = true; // Act - Try.Action(action); + var result = Try.Action(action); // Assert Assert.True(executed); + Assert.True(result.Succeeded); + Assert.False(result.Failed); + Assert.Null(result.Exception); } + + [Fact] public void Action_WithExceptionThrowingAction_SwallowsException() { // Arrange Action action = () => throw new InvalidOperationException("Test exception"); - // Act & Assert - Should not throw - var exception = Record.Exception(() => Try.Action(action)); - Assert.Null(exception); + // Act + var result = Try.Action(action); + + // Assert + Assert.False(result.Succeeded); + Assert.True(result.Failed); + Assert.NotNull(result.Exception); } + + [Fact] public void Action_WithMultipleExceptions_SwallowsAllExceptions() { // Arrange var callCount = 0; - Action action1 = () => + var action1 = () => { callCount++; throw new ArgumentException(); }; - Action action2 = () => + var action2 = () => { callCount++; throw new InvalidOperationException(); }; - Action action3 = () => + var action3 = () => { callCount++; throw new NullReferenceException(); diff --git a/tests/Wolfgang.TryPattern.Tests/TryFuncResultTests.cs b/tests/Wolfgang.TryPattern.Tests/TryFuncResultTests.cs new file mode 100644 index 0000000..fc26d88 --- /dev/null +++ b/tests/Wolfgang.TryPattern.Tests/TryFuncResultTests.cs @@ -0,0 +1,184 @@ +namespace Wolfgang.TryPattern.Tests; + +public class TryFuncResultTests +{ + + + [Fact] + public void Constructor_WithSucceededAndValue_SetsPropertiesCorrectly() + { + // Arrange + const int expectedValue = 42; + + // Act + var result = new TryFuncResult(true, expectedValue, null); + + // Assert + Assert.True(result.Succeeded); + Assert.False(result.Failed); + Assert.Equal(expectedValue, result.Value); + Assert.Null(result.Exception); + } + + + + [Fact] + public void Constructor_WithFailedAndException_SetsPropertiesCorrectly() + { + // Arrange + var exception = new Exception("Test exception"); + + // Act + var result = new TryFuncResult(false, default, exception); + + // Assert + Assert.False(result.Succeeded); + Assert.True(result.Failed); + Assert.Equal(0, result.Value); + Assert.Equal(exception, result.Exception); + } + + + + [Fact] + public void Constructor_WithSucceededOnly_SetsPropertiesCorrectly() + { + // Arrange + const int expectedValue = 100; + // Act + var result = new TryFuncResult(expectedValue); + // Assert + Assert.True(result.Succeeded); + Assert.False(result.Failed); + Assert.Equal(expectedValue, result.Value); + Assert.Null(result.Exception); + } + + + + [Fact] + public void Constructor_for_int_WithFailedOnly_SetsPropertiesCorrectly() + { + // Arrange + var exception = new Exception("Test exception"); + // Act + var result = new TryFuncResult(exception); + // Assert + Assert.False(result.Succeeded); + Assert.True(result.Failed); + Assert.Equal(0, result.Value); + Assert.Equal(exception, result.Exception); + } + + + + [Fact] + public void Constructor_for_DateTime_WithFailedOnly_SetsPropertiesCorrectly() + { + // Arrange + var exception = new Exception("Test exception"); + // Act + var result = new TryFuncResult(exception); + // Assert + Assert.False(result.Succeeded); + Assert.True(result.Failed); + Assert.Equal(new DateTime(), result.Value); + Assert.Equal(exception, result.Exception); + } + + + + [Fact] + public void Constructor_when_passed_object_sets_properties_correctly() + { + // Arrange + + var value = new object(); + + // Act + var result = new TryFuncResult(value); + // Assert + Assert.True(result.Succeeded); + Assert.False(result.Failed); + Assert.Equal(value, result.Value); + Assert.Null(result.Exception); + } + + + + [Fact] + public void Constructor_when_passed_int_sets_properties_correctly() + { + // Arrange + + var value = 3; + + // Act + var result = new TryFuncResult(value); + // Assert + Assert.True(result.Succeeded); + Assert.False(result.Failed); + Assert.Equal(value, result.Value); + Assert.Null(result.Exception); + } + + + + [Theory] + [InlineData(null)] + [InlineData(3)] + public void Constructor_when_passed_Nullable_int_sets_properties_correctly(int? value) + { + // Arrange + + // Act + var result = new TryFuncResult(value); + // Assert + Assert.True(result.Succeeded); + Assert.False(result.Failed); + Assert.Equal(value, result.Value); + Assert.Null(result.Exception); + } + + + + [Fact] + public void Constructor_when_passed_string_sets_properties_correctly() + { + // Arrange + + var value = "Hello World"; + + // Act + var result = new TryFuncResult(value); + + // Assert + Assert.True(result.Succeeded); + Assert.False(result.Failed); + Assert.Equal(value, result.Value); + Assert.Null(result.Exception); + } + + + + [Theory] + [InlineData(null)] + [InlineData("Hello World")] + public void Constructor_when_passed_Nullable_string_sets_properties_correctly(string? value) + { + // Arrange + + // Act + var result = new TryFuncResult(value); + + // Assert + Assert.True(result.Succeeded); + Assert.False(result.Failed); + Assert.Equal(value, result.Value); + Assert.Null(result.Exception); + } + + + + +} diff --git a/tests/Wolfgang.TryPattern.Tests/TryFunctionAsyncTests.cs b/tests/Wolfgang.TryPattern.Tests/TryFunctionAsyncTests.cs new file mode 100644 index 0000000..735a345 --- /dev/null +++ b/tests/Wolfgang.TryPattern.Tests/TryFunctionAsyncTests.cs @@ -0,0 +1,256 @@ +namespace Wolfgang.TryPattern.Tests; + +public class TryFunctionAsyncTests +{ + [Fact] + public async Task FunctionAsync_WithNullFunction_ThrowsArgumentNullException() + { + // Arrange + Func>? nullFunction = null; + + // Act & Assert + await Assert.ThrowsAsync(() => Try.FunctionAsync(nullFunction!)); + } + + + + [Fact] + public async Task FunctionAsyncInt_WithSuccessfulFunction_ReturnsResult() + { + // Arrange + const int expectedValue = 42; + Func> function = async () => + { + await Task.Delay(10); + return expectedValue; + }; + + // Act + var result = await Try.FunctionAsync(function); + + // Assert + Assert.True(result.Succeeded); + Assert.False(result.Failed); + Assert.Null(result.Exception); + Assert.Equal(expectedValue, result.Value); + } + + + + [Fact] + public async Task FunctionAsyncNullableInt_WithSuccessfulFunction_ReturnsResult() + { + // Arrange + int? expectedValue = 42; + Func> function = async () => + { + await Task.Delay(10); + return expectedValue; + }; + + // Act + var result = await Try.FunctionAsync(function); + + // Assert + Assert.True(result.Succeeded); + Assert.False(result.Failed); + Assert.Null(result.Exception); + Assert.Equal(expectedValue, result.Value); + } + + + + [Fact] + public async Task FunctionAsyncString_WithStringFunction_ReturnsResult() + { + // Arrange + const string expectedValue = "Hello, Async World!"; + Func> function = async () => + { + await Task.Delay(10); + return expectedValue; + }; + + // Act + var result = await Try.FunctionAsync(function); + + // Assert + Assert.True(result.Succeeded); + Assert.False(result.Failed); + Assert.Null(result.Exception); + Assert.Equal(expectedValue, result.Value); + } + + + + [Fact] + public async Task FunctionAsyncStringNullable_WithStringFunction_ReturnsResult() + { + // Arrange + var expectedValue = "Hello, Async World!"; + Func> function = async () => + { + await Task.Delay(10); + return expectedValue; + }; + + // Act + var result = await Try.FunctionAsync(function); + + // Assert + Assert.True(result.Succeeded); + Assert.False(result.Failed); + Assert.Null(result.Exception); + Assert.Equal(expectedValue, result.Value); + } + + + + [Fact] + public async Task FunctionAsyncInt_WithExceptionThrowingFunction_ReturnsDefault() + { + // Arrange + Func> function = async () => + { + await Task.Delay(10); + throw new InvalidOperationException("Test exception"); + }; + + // Act + var result = await Try.FunctionAsync(function); + + // Assert + Assert.False(result.Succeeded); + Assert.True(result.Failed); + Assert.NotNull(result.Exception); + Assert.Equal(0, result.Value); + } + + + + [Fact] + public async Task FunctionAsyncIntNullable_WithExceptionThrowingFunction_ReturnsDefault() + { + // Arrange + Func> function = async () => + { + await Task.Delay(10); + throw new InvalidOperationException("Test exception"); + }; + + // Act + var result = await Try.FunctionAsync(function); + + // Assert + Assert.False(result.Succeeded); + Assert.True(result.Failed); + Assert.NotNull(result.Exception); + Assert.Null(result.Value); + } + + + + [Fact] + public async Task FunctionAsync_WithExceptionThrowingStringFunction_ReturnsNull() + { + // Arrange + Func> function = async () => + { + await Task.Delay(10); + throw new InvalidOperationException("Test exception"); + }; + + // Act + var result = await Try.FunctionAsync(function); + + // Assert + Assert.False(result.Succeeded); + Assert.True(result.Failed); + Assert.NotNull(result.Exception); + Assert.Null(result.Value); + } + + + + [Fact] + public async Task FunctionAsyncInt_WithSynchronousException_ReturnsDefault() + { + // Arrange + Func> function = () => throw new InvalidOperationException("Synchronous exception"); + + // Act + var result = await Try.FunctionAsync(function); + + // Assert + Assert.Equal(0, result.Value); + } + + + + [Fact] + public async Task FunctionAsyncIntNullable_WithSynchronousException_ReturnsDefault() + { + // Arrange + Func> function = () => throw new InvalidOperationException("Synchronous exception"); + + // Act + var result = await Try.FunctionAsync(function); + + // Assert + Assert.Null(result.Value); + } + + + + [Fact] + public async Task FunctionAsync_WithComplexObject_ReturnsResult() + { + // Arrange + var expectedObject = new { Name = "Test", Value = 100 }; + Func> function = async () => + { + await Task.Delay(10); + return expectedObject; + }; + + // Act + var result = await Try.FunctionAsync(function); + + // Assert + Assert.True(result.Succeeded); + Assert.False(result.Failed); + Assert.Null(result.Exception); + Assert.Equal(expectedObject, result.Value); + } + + + + [Fact] + public async Task FunctionAsync_WithMultipleCalls_HandlesEachIndependently() + { + // Arrange + var callCount = 0; + Func> successFunction = async () => + { + await Task.Delay(10); + return ++callCount; + }; + Func> failFunction = async () => + { + await Task.Delay(10); + callCount++; + throw new Exception(); + }; + + // Act + var result1 = await Try.FunctionAsync(successFunction); + var result2 = await Try.FunctionAsync(failFunction); + var result3 = await Try.FunctionAsync(successFunction); + + // Assert + Assert.Equal(1, result1.Value); + Assert.Equal(0, result2.Value); // Default int value + Assert.Equal(3, result3.Value); + Assert.Equal(3, callCount); + } +} diff --git a/tests/Wolfgang.TryPattern.Tests/TryFunctionTests.cs b/tests/Wolfgang.TryPattern.Tests/TryFunctionTests.cs new file mode 100644 index 0000000..e93d5d3 --- /dev/null +++ b/tests/Wolfgang.TryPattern.Tests/TryFunctionTests.cs @@ -0,0 +1,192 @@ +namespace Wolfgang.TryPattern.Tests; + +public class TryFunctionTests +{ + [Fact] + public void Function_WithNullFunction_ThrowsArgumentNullException() + { + // Arrange + Func? nullFunction = null; + + // Act & Assert + Assert.Throws(() => Try.Function(nullFunction!)); + } + + + + [Fact] + public void Function_WithSuccessfulFunctionOfInt_ReturnsResult() + { + // Arrange + const int expectedValue = 42; + var function = () => expectedValue; + + // Act + var result = Try.Function(function); + + // Assert + Assert.True(result.Succeeded); + Assert.False(result.Failed); + Assert.Null(result.Exception); + Assert.Equal(expectedValue, result.Value); + } + + + + [Fact] + public void Function_WithSuccessfulFunctionOfNullableInt_ReturnsResult() + { + // Arrange + var expectedValue = 42; + var function = () => expectedValue; + + // Act + var result = Try.Function(function); + + // Assert + Assert.True(result.Succeeded); + Assert.False(result.Failed); + Assert.Null(result.Exception); + Assert.Equal(expectedValue, result.Value); + } + + + + [Fact] + public void Function_WithStringFunction_ReturnsResult() + { + // Arrange + const string expectedValue = "Hello, World!"; + var function = () => expectedValue; + + // Act + var result = Try.Function(function); + + // Assert + Assert.True(result.Succeeded); + Assert.False(result.Failed); + Assert.Null(result.Exception); + Assert.Equal(expectedValue, result.Value); + } + + + + [Fact] + public void Function_WithNullableStringFunction_ReturnsResult() + { + // Arrange + const string expectedValue = "Hello, World!"; + var function = () => expectedValue; + + // Act + var result = Try.Function(function); + + // Assert + Assert.True(result.Succeeded); + Assert.False(result.Failed); + Assert.Null(result.Exception); + Assert.Equal(expectedValue, result.Value); + } + + + + [Fact] + public void Function_WithObjectFunction_ReturnsResult() + { + // Arrange + var expectedValue = new object(); + var function = () => expectedValue; + + // Act + var result = Try.Function(function); + + // Assert + Assert.True(result.Succeeded); + Assert.False(result.Failed); + Assert.Null(result.Exception); + Assert.Equal(expectedValue, result.Value); + } + + + + [Fact] + public void FunctionInt_WithExceptionThrowingFunction_ReturnsDefault() + { + // Arrange + Func function = () => throw new InvalidOperationException("Test exception"); + + // Act + var result = Try.Function(function); + + // Assert + Assert.False(result.Succeeded); + Assert.True(result.Failed); + Assert.NotNull(result.Exception); + Assert.Equal("Test exception", result.Exception.Message); + Assert.Equal(0, result.Value); + } + + + + [Fact] + public void FunctionNullableInt_WithExceptionThrowingFunction_ReturnsDefault() + { + // Arrange + Func function = () => throw new InvalidOperationException("Test exception"); + + // Act + var result = Try.Function(function); + + // Assert + Assert.False(result.Succeeded); + Assert.True(result.Failed); + Assert.NotNull(result.Exception); + Assert.Equal("Test exception", result.Exception.Message); + Assert.Null(result.Value); + } + + + + [Fact] + public void Function_WithComplexObject_ReturnsResult() + { + // Arrange + var expectedObject = new { Name = "Test", Value = 100 }; + Func function = () => expectedObject; + + // Act + var result = Try.Function(function); + + // Assert + Assert.Equal(expectedObject, result.Value); + } + + + + [Fact] + public void Function_WithMultipleCalls_HandlesEachIndependently() + { + // Arrange + var callCount = 0; + var successFunction = () => ++callCount; + var failFunction = new Func + ( + () => + { + callCount++; + throw new Exception(); + } + ); + + // Act + var result1 = Try.Function(successFunction); + var result2 = Try.Function(failFunction); + var result3 = Try.Function(successFunction); + + // Assert + Assert.Equal(1, result1.Value); + Assert.Equal(0, result2.Value); // Default int value + Assert.Equal(3, result3.Value); + Assert.Equal(3, callCount); + } +} diff --git a/tests/Wolfgang.TryPattern.Tests/Wolfgang.TryPattern.Tests.csproj b/tests/Wolfgang.TryPattern.Tests/Wolfgang.TryPattern.Tests.csproj new file mode 100644 index 0000000..db5f210 --- /dev/null +++ b/tests/Wolfgang.TryPattern.Tests/Wolfgang.TryPattern.Tests.csproj @@ -0,0 +1,116 @@ + + + + + net462;net472;net48;net481; + netcoreapp3.1; + net5.0;net6.0;net7.0; + net8.0;net9.0;net10.0 + + 1.0.0 + latest + enable + enable + false + true + Copyright 2025 Chris Wolfgang + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + +