From b9761f6dee4293c25d23687ff062e9c32d00355c Mon Sep 17 00:00:00 2001 From: Patrick Stevens <3138005+Smaug123@users.noreply.github.com> Date: Tue, 8 Oct 2024 08:35:02 +0100 Subject: [PATCH] Add InterfaceMock attribute (#17) --- .github/workflows/dotnet.yaml | 45 +++++++++++++++++++ .../Attributes.fs | 15 +++++++ .../README.md | 6 +++ .../SurfaceBaseline.txt | 5 +++ ...pet.Plugin.InterfaceMock.Attributes.fsproj | 30 +++++++++++++ .../version.json | 11 +++++ ...ippet.Plugin.InterfaceMock.Consumer.fsproj | 2 +- .../README.md | 5 +++ .../TestSurface.fs | 26 +++++++++++ ...e.Whippet.Plugin.InterfaceMock.Test.fsproj | 15 ++++--- WoofWare.Whippet.sln | 6 +++ 11 files changed, 159 insertions(+), 7 deletions(-) create mode 100644 Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Attributes/Attributes.fs create mode 100644 Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Attributes/README.md create mode 100644 Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Attributes/SurfaceBaseline.txt create mode 100644 Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Attributes/WoofWare.Whippet.Plugin.InterfaceMock.Attributes.fsproj create mode 100644 Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Attributes/version.json create mode 100644 Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Test/TestSurface.fs diff --git a/.github/workflows/dotnet.yaml b/.github/workflows/dotnet.yaml index c3f5e65..dbd1cf4 100644 --- a/.github/workflows/dotnet.yaml +++ b/.github/workflows/dotnet.yaml @@ -190,6 +190,11 @@ jobs: with: name: nuget-package-httpclient path: Plugins/HttpClient/WoofWare.Whippet.Plugin.HttpClient/bin/Release/WoofWare.Whippet.Plugin.HttpClient.*.nupkg + - name: Upload NuGet artifact (interfacemock attrs) + uses: actions/upload-artifact@v4 + with: + name: nuget-package-interfacemock-attrs + path: Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Attributes/bin/Release/WoofWare.Whippet.Plugin.InterfaceMock.Attributes.*.nupkg - name: Upload NuGet artifact (interfacemock plugin) uses: actions/upload-artifact@v4 with: @@ -272,6 +277,14 @@ jobs: - name: Check NuGet contents # Verify that there is exactly one nupkg in the artifact that would be NuGet published run: if [[ $(find packed-httpclient -maxdepth 1 -name 'WoofWare.Whippet.Plugin.HttpClient.*.nupkg' -printf c | wc -c) -ne "1" ]]; then exit 1; fi + - name: Download NuGet artifact (interfacemock attrs) + uses: actions/download-artifact@v4 + with: + name: nuget-package-interfacemock-attrs + path: packed-interfacemock-attrs + - name: Check NuGet contents + # Verify that there is exactly one nupkg in the artifact that would be NuGet published + run: if [[ $(find packed-interfacemock-attrs -maxdepth 1 -name 'WoofWare.Whippet.Plugin.InterfaceMock.Attributes.*.nupkg' -printf c | wc -c) -ne "1" ]]; then exit 1; fi - name: Download NuGet artifact (interfacemock plugin) uses: actions/download-artifact@v4 with: @@ -612,3 +625,35 @@ jobs: nupkg-dir: packed/ dotnet: ${{ steps.dotnet-identify.outputs.dotnet }} + nuget-publish-interfacemock-attrs: + runs-on: ubuntu-latest + if: ${{ !github.event.repository.fork && github.ref == 'refs/heads/main' }} + needs: [all-required-checks-complete] + environment: main-deploy + permissions: + id-token: write + attestations: write + contents: read + steps: + - uses: actions/checkout@v4 + - name: Install Nix + uses: cachix/install-nix-action@v29 + with: + extra_nix_config: | + access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} + - name: Download NuGet artifact + uses: actions/download-artifact@v4 + with: + name: nuget-package-interfacemock-attrs + path: packed + - name: Identify `dotnet` + id: dotnet-identify + run: nix develop --command bash -c 'echo "dotnet=$(which dotnet)" >> $GITHUB_OUTPUT' + - name: Publish to NuGet + id: publish-success + uses: G-Research/common-actions/publish-nuget@2b7dc49cb14f3344fbe6019c14a31165e258c059 + with: + package-name: WoofWare.Whippet.Plugin.InterfaceMock.Attributes + nuget-key: ${{ secrets.NUGET_API_KEY }} + nupkg-dir: packed/ + dotnet: ${{ steps.dotnet-identify.outputs.dotnet }} diff --git a/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Attributes/Attributes.fs b/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Attributes/Attributes.fs new file mode 100644 index 0000000..bd02b6a --- /dev/null +++ b/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Attributes/Attributes.fs @@ -0,0 +1,15 @@ +namespace WoofWare.Whippet.Plugin.InterfaceMock + +/// Attribute indicating an interface type for which the "Interface Mock" Whippet +/// generator should apply during build. +/// This generator creates a record which implements the interface, +/// but where each method is represented as a record field, so you can use +/// record update syntax to easily specify partially-implemented mock objects. +/// You may optionally specify `isInternal = false` to get a mock with the public visibility modifier. +type InterfaceMockAttribute (isInternal : bool) = + inherit System.Attribute () + /// The default value of `isInternal`, the optional argument to the InterfaceMockAttribute constructor. + static member DefaultIsInternal = true + + /// Shorthand for the "isExtensionMethod = false" constructor; see documentation there for details. + new () = InterfaceMockAttribute InterfaceMockAttribute.DefaultIsInternal diff --git a/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Attributes/README.md b/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Attributes/README.md new file mode 100644 index 0000000..6125d03 --- /dev/null +++ b/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Attributes/README.md @@ -0,0 +1,6 @@ +# WoofWare.Whippet.Plugin.InterfaceMock.Attributes + +This is a very slim runtime dependency which consumers of WoofWare.Whippet.Plugin.InterfaceMock may optionally take. +This dependency contains attributes which control that source generator, +although you may instead omit this dependency and control the generator entirely through configuration in consumer's `.fsproj`. +Please see WoofWare.Whippet.Plugin.InterfaceMock's README for further information. diff --git a/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Attributes/SurfaceBaseline.txt b/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Attributes/SurfaceBaseline.txt new file mode 100644 index 0000000..c699ef4 --- /dev/null +++ b/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Attributes/SurfaceBaseline.txt @@ -0,0 +1,5 @@ +WoofWare.Whippet.Plugin.InterfaceMock.InterfaceMockAttribute inherit System.Attribute +WoofWare.Whippet.Plugin.InterfaceMock.InterfaceMockAttribute..ctor [constructor]: bool +WoofWare.Whippet.Plugin.InterfaceMock.InterfaceMockAttribute..ctor [constructor]: unit +WoofWare.Whippet.Plugin.InterfaceMock.InterfaceMockAttribute.DefaultIsInternal [static property]: [read-only] bool +WoofWare.Whippet.Plugin.InterfaceMock.InterfaceMockAttribute.get_DefaultIsInternal [static method]: unit -> bool \ No newline at end of file diff --git a/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Attributes/WoofWare.Whippet.Plugin.InterfaceMock.Attributes.fsproj b/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Attributes/WoofWare.Whippet.Plugin.InterfaceMock.Attributes.fsproj new file mode 100644 index 0000000..33e106d --- /dev/null +++ b/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Attributes/WoofWare.Whippet.Plugin.InterfaceMock.Attributes.fsproj @@ -0,0 +1,30 @@ + + + + netstandard2.1 + true + Patrick Stevens + Copyright (c) Patrick Stevens 2024 + Attributes to accompany the WoofWare.Whippet.Plugin.InterfaceMock source generator, to indicate what you want your types to be doing. + git + https://github.com/Smaug123/WoofWare.Whippet + MIT + README.md + fsharp;source-generator;source-gen;whippet;mock + true + FS3559 + WoofWare.Whippet.Plugin.InterfaceMock.Attributes + + + + + + + + True + / + README.md + + + + diff --git a/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Attributes/version.json b/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Attributes/version.json new file mode 100644 index 0000000..790e212 --- /dev/null +++ b/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Attributes/version.json @@ -0,0 +1,11 @@ +{ + "version": "0.1", + "publicReleaseRefSpec": [ + "^refs/heads/main$" + ], + "pathFilters": [ + "./", + ":/global.json", + ":/Directory.Build.props" + ] +} diff --git a/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Consumer/WoofWare.Whippet.Plugin.InterfaceMock.Consumer.fsproj b/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Consumer/WoofWare.Whippet.Plugin.InterfaceMock.Consumer.fsproj index 7cc005c..5339467 100644 --- a/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Consumer/WoofWare.Whippet.Plugin.InterfaceMock.Consumer.fsproj +++ b/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Consumer/WoofWare.Whippet.Plugin.InterfaceMock.Consumer.fsproj @@ -36,7 +36,7 @@ - + diff --git a/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock/README.md b/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock/README.md index a410c0d..fb30ac6 100644 --- a/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock/README.md +++ b/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock/README.md @@ -80,3 +80,8 @@ You may supply an `isInternal : bool` argument: By default, we make the resulting record type at most internal (never public), since this is intended only to be used in tests; but you can instead make it public by setting the `false` boolean. + +Instead of configuring the client with `InterfaceMock`, +you may choose to add an attribute called `InterfaceMock` (with an optional "isInternal" argument) +to any type you wish to use as an input. +You may use `WoofWare.Whippet.Plugin.InterfaceMock.Attributes` to provide this attribute, or you may define it yourself. diff --git a/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Test/TestSurface.fs b/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Test/TestSurface.fs new file mode 100644 index 0000000..0731047 --- /dev/null +++ b/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Test/TestSurface.fs @@ -0,0 +1,26 @@ +namespace WoofWare.Whippet.Plugin.InterfaceMock.Test + +open NUnit.Framework +open WoofWare.Whippet.Plugin.InterfaceMock +open ApiSurface + +[] +module TestAttributeSurface = + let assembly = typeof.Assembly + + [] + let ``Ensure API surface has not been modified`` () = ApiSurface.assertIdentical assembly + + (* + [] + let ``Check version against remote`` () = + MonotonicVersion.validate assembly "WoofWare.Whippet.Plugin.InterfaceMock.Attributes" + *) + + [] + let ``Update API surface`` () = + ApiSurface.writeAssemblyBaseline assembly + + [] + let ``Ensure public API is fully documented`` () = + DocCoverage.assertFullyDocumented assembly diff --git a/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Test/WoofWare.Whippet.Plugin.InterfaceMock.Test.fsproj b/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Test/WoofWare.Whippet.Plugin.InterfaceMock.Test.fsproj index a428a3d..309d41d 100644 --- a/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Test/WoofWare.Whippet.Plugin.InterfaceMock.Test.fsproj +++ b/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Test/WoofWare.Whippet.Plugin.InterfaceMock.Test.fsproj @@ -8,18 +8,21 @@ - - + + + - - - - + + + + + + diff --git a/WoofWare.Whippet.sln b/WoofWare.Whippet.sln index 95036ea..b663186 100644 --- a/WoofWare.Whippet.sln +++ b/WoofWare.Whippet.sln @@ -42,6 +42,8 @@ Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "WoofWare.Whippet.Plugin.Int EndProject Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "WoofWare.Whippet.Plugin.InterfaceMock.Test", "Plugins\InterfaceMock\WoofWare.Whippet.Plugin.InterfaceMock\WoofWare.Whippet.Plugin.InterfaceMock.Test\WoofWare.Whippet.Plugin.InterfaceMock.Test.fsproj", "{1B0D4C01-66CD-4E91-9DA5-9ED4F3319AFA}" EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "WoofWare.Whippet.Plugin.InterfaceMock.Attributes", "Plugins\InterfaceMock\WoofWare.Whippet.Plugin.InterfaceMock.Attributes\WoofWare.Whippet.Plugin.InterfaceMock.Attributes.fsproj", "{E73EA066-2725-42CD-BE7D-39264C24AA97}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -132,5 +134,9 @@ Global {1B0D4C01-66CD-4E91-9DA5-9ED4F3319AFA}.Debug|Any CPU.Build.0 = Debug|Any CPU {1B0D4C01-66CD-4E91-9DA5-9ED4F3319AFA}.Release|Any CPU.ActiveCfg = Release|Any CPU {1B0D4C01-66CD-4E91-9DA5-9ED4F3319AFA}.Release|Any CPU.Build.0 = Release|Any CPU + {E73EA066-2725-42CD-BE7D-39264C24AA97}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E73EA066-2725-42CD-BE7D-39264C24AA97}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E73EA066-2725-42CD-BE7D-39264C24AA97}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E73EA066-2725-42CD-BE7D-39264C24AA97}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal