mirror of
https://github.com/Smaug123/WoofWare.Expect
synced 2025-10-05 20:48:40 +00:00
Initial commit
This commit is contained in:
18
.config/dotnet-tools.json
Normal file
18
.config/dotnet-tools.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"isRoot": true,
|
||||||
|
"tools": {
|
||||||
|
"fantomas": {
|
||||||
|
"version": "7.0.2",
|
||||||
|
"commands": [
|
||||||
|
"fantomas"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"fsharp-analyzers": {
|
||||||
|
"version": "0.31.0",
|
||||||
|
"commands": [
|
||||||
|
"fsharp-analyzers"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
40
.editorconfig
Normal file
40
.editorconfig
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
root=true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset=utf-8
|
||||||
|
trim_trailing_whitespace=true
|
||||||
|
insert_final_newline=true
|
||||||
|
indent_style=space
|
||||||
|
indent_size=4
|
||||||
|
|
||||||
|
# ReSharper properties
|
||||||
|
resharper_xml_indent_size=2
|
||||||
|
resharper_xml_max_line_length=100
|
||||||
|
resharper_xml_tab_width=2
|
||||||
|
|
||||||
|
[*.{csproj,fsproj,sqlproj,targets,props,ts,tsx,css,json}]
|
||||||
|
indent_style=space
|
||||||
|
indent_size=2
|
||||||
|
|
||||||
|
[*.{fs,fsi}]
|
||||||
|
fsharp_bar_before_discriminated_union_declaration=true
|
||||||
|
fsharp_space_before_uppercase_invocation=true
|
||||||
|
fsharp_space_before_class_constructor=true
|
||||||
|
fsharp_space_before_member=true
|
||||||
|
fsharp_space_before_colon=true
|
||||||
|
fsharp_space_before_semicolon=true
|
||||||
|
fsharp_multiline_bracket_style=aligned
|
||||||
|
fsharp_newline_between_type_definition_and_members=true
|
||||||
|
fsharp_align_function_signature_to_indentation=true
|
||||||
|
fsharp_alternative_long_member_definitions=true
|
||||||
|
fsharp_multi_line_lambda_closing_newline=true
|
||||||
|
fsharp_experimental_keep_indent_in_branch=true
|
||||||
|
fsharp_max_value_binding_width=80
|
||||||
|
fsharp_max_record_width=0
|
||||||
|
max_line_length=120
|
||||||
|
end_of_line=lf
|
||||||
|
|
||||||
|
[*.{appxmanifest,build,dtd,nuspec,xaml,xamlx,xoml,xsd}]
|
||||||
|
indent_style=space
|
||||||
|
indent_size=2
|
||||||
|
tab_width=2
|
1
.fantomasignore
Normal file
1
.fantomasignore
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.direnv/
|
175
.github/workflows/dotnet.yaml
vendored
Normal file
175
.github/workflows/dotnet.yaml
vendored
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
# yaml-language-server: $schema=https://raw.githubusercontent.com/SchemaStore/schemastore/master/src/schemas/json/github-workflow.json
|
||||||
|
name: .NET
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ main ]
|
||||||
|
|
||||||
|
env:
|
||||||
|
DOTNET_NOLOGO: true
|
||||||
|
DOTNET_CLI_TELEMETRY_OPTOUT: true
|
||||||
|
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
|
||||||
|
NUGET_XMLDOC_MODE: ''
|
||||||
|
DOTNET_MULTILEVEL_LOOKUP: 0
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0 # so that NerdBank.GitVersioning has access to history
|
||||||
|
- name: Install Nix
|
||||||
|
uses: cachix/install-nix-action@v30
|
||||||
|
with:
|
||||||
|
extra_nix_config: |
|
||||||
|
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Restore dependencies
|
||||||
|
run: nix develop --command dotnet restore
|
||||||
|
- name: Build
|
||||||
|
run: nix develop --command dotnet build --no-restore --configuration Release
|
||||||
|
- name: Test
|
||||||
|
run: nix develop --command dotnet test
|
||||||
|
|
||||||
|
build-nix:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Install Nix
|
||||||
|
uses: cachix/install-nix-action@v30
|
||||||
|
with:
|
||||||
|
extra_nix_config: |
|
||||||
|
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Build
|
||||||
|
run: nix build
|
||||||
|
- name: Reproducibility check
|
||||||
|
run: nix build --rebuild
|
||||||
|
|
||||||
|
check-dotnet-format:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Install Nix
|
||||||
|
uses: cachix/install-nix-action@v30
|
||||||
|
with:
|
||||||
|
extra_nix_config: |
|
||||||
|
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Run Fantomas
|
||||||
|
run: nix run .#fantomas -- --check .
|
||||||
|
|
||||||
|
check-nix-format:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Install Nix
|
||||||
|
uses: cachix/install-nix-action@v30
|
||||||
|
with:
|
||||||
|
extra_nix_config: |
|
||||||
|
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Run Alejandra
|
||||||
|
run: nix develop --command alejandra --check .
|
||||||
|
|
||||||
|
flake-check:
|
||||||
|
name: Check flake
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@master
|
||||||
|
- name: Install Nix
|
||||||
|
uses: cachix/install-nix-action@v30
|
||||||
|
with:
|
||||||
|
extra_nix_config: |
|
||||||
|
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Flake check
|
||||||
|
run: nix flake check
|
||||||
|
|
||||||
|
nuget-pack:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0 # so that NerdBank.GitVersioning has access to history
|
||||||
|
- name: Install Nix
|
||||||
|
uses: cachix/install-nix-action@v31
|
||||||
|
with:
|
||||||
|
extra_nix_config: |
|
||||||
|
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Restore dependencies
|
||||||
|
run: nix develop --command dotnet restore
|
||||||
|
- name: Build
|
||||||
|
run: nix develop --command dotnet build --no-restore --configuration Release
|
||||||
|
- name: Pack
|
||||||
|
run: nix develop --command dotnet pack --configuration Release
|
||||||
|
- name: Upload NuGet artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: nuget-package
|
||||||
|
path: WoofWare.Expect/bin/Release/WoofWare.Expect.*.nupkg
|
||||||
|
|
||||||
|
expected-pack:
|
||||||
|
needs: [nuget-pack]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Download NuGet artifact (plugin)
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: nuget-package
|
||||||
|
path: packed
|
||||||
|
- name: Check NuGet contents
|
||||||
|
# Verify that there is exactly one nupkg in the artifact that would be NuGet published
|
||||||
|
run: if [[ $(find packed -maxdepth 1 -name 'WoofWare.Expect.*.nupkg' -printf c | wc -c) -ne "1" ]]; then exit 1; fi
|
||||||
|
|
||||||
|
github-release-dry-run:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [nuget-pack]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Download NuGet artifact
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: nuget-package
|
||||||
|
path: packed
|
||||||
|
- name: Compute package path
|
||||||
|
id: compute-path
|
||||||
|
run: |
|
||||||
|
find . -maxdepth 1 -type f -name 'WoofWare.Expect.*.nupkg' -exec sh -c 'echo "output=$(basename "$1")" >> $GITHUB_OUTPUT' shell {} \;
|
||||||
|
- name: Compute tag name
|
||||||
|
id: compute-tag
|
||||||
|
env:
|
||||||
|
NUPKG_PATH: ${{ steps.compute-path.outputs.output }}
|
||||||
|
run: echo "output=$(basename "$NUPKG_PATH" .nupkg)" >> $GITHUB_OUTPUT
|
||||||
|
- name: Tag and release
|
||||||
|
uses: G-Research/common-actions/github-release@19d7281a0f9f83e13c78f99a610dbc80fc59ba3b
|
||||||
|
with:
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
target-commitish: ${{ github.sha }}
|
||||||
|
tag: ${{ steps.compute-tag.outputs.output }}
|
||||||
|
binary-contents: ${{ steps.compute-path.outputs.output }}
|
||||||
|
dry-run: true
|
||||||
|
|
||||||
|
linkcheck:
|
||||||
|
name: Check links
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@master
|
||||||
|
- name: Install Nix
|
||||||
|
uses: cachix/install-nix-action@v31
|
||||||
|
with:
|
||||||
|
extra_nix_config: |
|
||||||
|
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Run link checker
|
||||||
|
run: nix develop --command markdown-link-check README.md
|
||||||
|
|
||||||
|
all-required-checks-complete:
|
||||||
|
if: ${{ always() }}
|
||||||
|
needs: [check-dotnet-format, check-nix-format, build, build-nix, flake-check, expected-pack, linkcheck]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: G-Research/common-actions/check-required-lite@2b7dc49cb14f3344fbe6019c14a31165e258c059
|
||||||
|
with:
|
||||||
|
needs-context: ${{ toJSON(needs) }}
|
16
.gitignore
vendored
Normal file
16
.gitignore
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
bin/
|
||||||
|
obj/
|
||||||
|
/packages/
|
||||||
|
riderModule.iml
|
||||||
|
/_ReSharper.Caches/
|
||||||
|
.idea/
|
||||||
|
*.sln.DotSettings.user
|
||||||
|
.DS_Store
|
||||||
|
result
|
||||||
|
.analyzerpackages/
|
||||||
|
analysis.sarif
|
||||||
|
.direnv/
|
||||||
|
.venv/
|
||||||
|
.vs/
|
||||||
|
|
||||||
|
.fake
|
19
Directory.Build.props
Normal file
19
Directory.Build.props
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<Project>
|
||||||
|
<PropertyGroup>
|
||||||
|
<DebugType Condition=" '$(DebugType)' == '' ">embedded</DebugType>
|
||||||
|
<Deterministic>true</Deterministic>
|
||||||
|
<NetCoreTargetingPackRoot>[UNDEFINED]</NetCoreTargetingPackRoot>
|
||||||
|
<DisableImplicitLibraryPacksFolder>true</DisableImplicitLibraryPacksFolder>
|
||||||
|
<DisableImplicitNuGetFallbackFolder>true</DisableImplicitNuGetFallbackFolder>
|
||||||
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
|
<DebugType>embedded</DebugType>
|
||||||
|
<WarnOn>FS3388,FS3559</WarnOn>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Nerdbank.GitVersioning" Version="3.8.38-alpha" PrivateAssets="all"/>
|
||||||
|
<SourceLinkGitHubHost Include="github.com" ContentUrl="https://raw.githubusercontent.com"/>
|
||||||
|
</ItemGroup>
|
||||||
|
<PropertyGroup Condition="'$(GITHUB_ACTION)' != ''">
|
||||||
|
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 Patrick Stevens
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
40
README.md
Normal file
40
README.md
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# WoofWare.Expect
|
||||||
|
|
||||||
|
An [expect-testing](https://blog.janestreet.com/the-joy-of-expect-tests/) library for F#.
|
||||||
|
(Also known as "snapshot testing".)
|
||||||
|
|
||||||
|
# Current status
|
||||||
|
|
||||||
|
Basic mechanism works, but I haven't yet decided how the ergonomic updating of the input text will work.
|
||||||
|
Ideally it would edit the input AST, but I don't yet know if that's viable.
|
||||||
|
|
||||||
|
# How to use
|
||||||
|
|
||||||
|
See [the tests](./WoofWare.Expect.Test/SimpleTest.fs).
|
||||||
|
|
||||||
|
```fsharp
|
||||||
|
[<Test>]
|
||||||
|
let ``This test fails: JSON documents are not equal`` () =
|
||||||
|
expect {
|
||||||
|
snapshotJson "123"
|
||||||
|
return 124
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let ``This test passes: JSON documents are equal`` () =
|
||||||
|
expect {
|
||||||
|
snapshotJson " 123 "
|
||||||
|
return 123
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let ``This test fails: plain text comparison of ToString`` () =
|
||||||
|
expect {
|
||||||
|
snapshot " 123 "
|
||||||
|
return 123
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# Licence
|
||||||
|
|
||||||
|
MIT.
|
37
WoofWare.Expect.Test/SimpleTest.fs
Normal file
37
WoofWare.Expect.Test/SimpleTest.fs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
namespace WoofWare.Expect.Test
|
||||||
|
|
||||||
|
open System
|
||||||
|
open WoofWare.Expect
|
||||||
|
open NUnit.Framework
|
||||||
|
|
||||||
|
[<TestFixture>]
|
||||||
|
module SimpleTest =
|
||||||
|
[<Test>]
|
||||||
|
let ``JSON is resilient to whitespace changes`` () =
|
||||||
|
expect {
|
||||||
|
snapshotJson "123 "
|
||||||
|
return 123
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let ``Example of a failing test`` () =
|
||||||
|
expect {
|
||||||
|
snapshot
|
||||||
|
"snapshot mismatch! snapshot at filepath.fs:32 (Example of a failing test) was:
|
||||||
|
|
||||||
|
- 123
|
||||||
|
|
||||||
|
actual was:
|
||||||
|
|
||||||
|
+ 124"
|
||||||
|
|
||||||
|
return
|
||||||
|
Assert
|
||||||
|
.Throws<Exception>(fun () ->
|
||||||
|
expectWithMockedFilePath "filepath.fs" {
|
||||||
|
snapshot "123"
|
||||||
|
return 124
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.Message
|
||||||
|
}
|
24
WoofWare.Expect.Test/TestSurface.fs
Normal file
24
WoofWare.Expect.Test/TestSurface.fs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
namespace WoofWare.Expect.Test
|
||||||
|
|
||||||
|
open ApiSurface
|
||||||
|
open NUnit.Framework
|
||||||
|
|
||||||
|
[<TestFixture>]
|
||||||
|
module TestSurface =
|
||||||
|
let assembly = typeof<WoofWare.Expect.ExpectBuilder>.Assembly
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let ``Ensure API surface has not been modified`` () = ApiSurface.assertIdentical assembly
|
||||||
|
|
||||||
|
[<Test ; Explicit>]
|
||||||
|
let ``Update API surface`` () =
|
||||||
|
ApiSurface.writeAssemblyBaseline assembly
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let ``Ensure public API is fully documented`` () =
|
||||||
|
DocCoverage.assertFullyDocumented assembly
|
||||||
|
|
||||||
|
[<Test ; Explicit "Not yet published">]
|
||||||
|
// https://github.com/nunit/nunit3-vs-adapter/issues/876
|
||||||
|
let ``EnsureVersionIsMonotonic`` () =
|
||||||
|
MonotonicVersion.validate assembly "WoofWare.Expect"
|
25
WoofWare.Expect.Test/WoofWare.Expect.Test.fsproj
Normal file
25
WoofWare.Expect.Test/WoofWare.Expect.Test.fsproj
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<LangVersion>latest</LangVersion>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="SimpleTest.fs" />
|
||||||
|
<Compile Include="TestSurface.fs" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="ApiSurface" Version="4.1.20" />
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1"/>
|
||||||
|
<PackageReference Include="NUnit" Version="4.3.2"/>
|
||||||
|
<PackageReference Include="NUnit3TestAdapter" Version="5.0.0"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\WoofWare.Expect\WoofWare.Expect.fsproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
22
WoofWare.Expect.sln
Normal file
22
WoofWare.Expect.sln
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "WoofWare.Expect", "WoofWare.Expect\WoofWare.Expect.fsproj", "{01AADDA1-6A03-4C25-86DC-0FD1DB7A2187}"
|
||||||
|
EndProject
|
||||||
|
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "WoofWare.Expect.Test", "WoofWare.Expect.Test\WoofWare.Expect.Test.fsproj", "{980C24F9-41E8-4A8E-8F02-7D3B7C4A3FDD}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{01AADDA1-6A03-4C25-86DC-0FD1DB7A2187}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{01AADDA1-6A03-4C25-86DC-0FD1DB7A2187}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{01AADDA1-6A03-4C25-86DC-0FD1DB7A2187}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{01AADDA1-6A03-4C25-86DC-0FD1DB7A2187}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{980C24F9-41E8-4A8E-8F02-7D3B7C4A3FDD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{980C24F9-41E8-4A8E-8F02-7D3B7C4A3FDD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{980C24F9-41E8-4A8E-8F02-7D3B7C4A3FDD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{980C24F9-41E8-4A8E-8F02-7D3B7C4A3FDD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
203
WoofWare.Expect/Library.fs
Normal file
203
WoofWare.Expect/Library.fs
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
namespace WoofWare.Expect
|
||||||
|
|
||||||
|
open System.Runtime.CompilerServices
|
||||||
|
open System.Text.Json
|
||||||
|
open System.Text.Json.Serialization
|
||||||
|
|
||||||
|
type private CallerInfo =
|
||||||
|
{
|
||||||
|
MemberName : string
|
||||||
|
FilePath : string
|
||||||
|
LineNumber : int
|
||||||
|
}
|
||||||
|
|
||||||
|
type private SnapshotValue =
|
||||||
|
| BareString of string
|
||||||
|
| Json of string
|
||||||
|
|
||||||
|
/// The state accumulated by the `expect` builder. You should never find yourself interacting with this type.
|
||||||
|
type ExpectState<'T> =
|
||||||
|
private
|
||||||
|
{
|
||||||
|
Snapshot : (SnapshotValue * CallerInfo) option
|
||||||
|
Actual : 'T option
|
||||||
|
}
|
||||||
|
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module private Text =
|
||||||
|
let predent (c : char) (s : string) =
|
||||||
|
s.Split '\n' |> Seq.map (sprintf "%c %s" c) |> String.concat "\n"
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The builder which powers WoofWare.Expect.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>You're not expected to construct this explicitly; it's a computation expression, available as <c>Builder.expect</c>.</remarks>
|
||||||
|
/// <param name="filePathOverride">Override the file paths reported in snapshots, so that your tests can be fully stable even on failure. (You almost certainly don't want to set this.)</param>
|
||||||
|
type ExpectBuilder (?filePathOverride : string) =
|
||||||
|
/// Combine two `ExpectState`s. The first one is the "expected" snapshot; the second is the "actual".
|
||||||
|
member _.Bind (state : ExpectState<unit>, f : unit -> ExpectState<'U>) : ExpectState<'U> =
|
||||||
|
let actual = f ()
|
||||||
|
|
||||||
|
match state.Actual with
|
||||||
|
| Some _ -> failwith "somehow came in with an Actual"
|
||||||
|
| None ->
|
||||||
|
|
||||||
|
match actual.Snapshot with
|
||||||
|
| Some _ -> failwith "somehow Actual came through with a Snapshot"
|
||||||
|
| None ->
|
||||||
|
|
||||||
|
// Pass through the state structure when there's no actual value
|
||||||
|
{
|
||||||
|
Snapshot = state.Snapshot
|
||||||
|
Actual = actual.Actual
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Express that the actual value's <c>ToString</c> should identically equal this string.</summary>
|
||||||
|
[<CustomOperation("snapshot", MaintainsVariableSpaceUsingBind = true)>]
|
||||||
|
member _.Snapshot
|
||||||
|
(
|
||||||
|
state : ExpectState<'a>,
|
||||||
|
snapshot : string,
|
||||||
|
[<CallerMemberName>] ?memberName : string,
|
||||||
|
[<CallerLineNumber>] ?callerLine : int,
|
||||||
|
[<CallerFilePath>] ?filePath : string
|
||||||
|
)
|
||||||
|
: ExpectState<'a>
|
||||||
|
=
|
||||||
|
match state.Snapshot with
|
||||||
|
| Some _ -> failwith "snapshot can only be specified once"
|
||||||
|
| None ->
|
||||||
|
let memberName = defaultArg memberName "<unknown method>"
|
||||||
|
let filePath = defaultArg filePath "<unknown file>"
|
||||||
|
let lineNumber = defaultArg callerLine -1
|
||||||
|
|
||||||
|
let callerInfo =
|
||||||
|
{
|
||||||
|
MemberName = memberName
|
||||||
|
FilePath = filePath
|
||||||
|
LineNumber = lineNumber
|
||||||
|
}
|
||||||
|
|
||||||
|
{ state with
|
||||||
|
Snapshot = Some (SnapshotValue.BareString snapshot, callerInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Express that the actual value, when converted to JSON, should result in a JSON document
|
||||||
|
/// which matches the JSON document that is this string.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// For example, <c>snapshot "123"</c> indicates the JSON integer 123.
|
||||||
|
/// </remarks>
|
||||||
|
[<CustomOperation("snapshotJson", MaintainsVariableSpaceUsingBind = true)>]
|
||||||
|
member _.SnapshotJson
|
||||||
|
(
|
||||||
|
state : ExpectState<'a>,
|
||||||
|
snapshot : string,
|
||||||
|
[<CallerMemberName>] ?memberName : string,
|
||||||
|
[<CallerLineNumber>] ?callerLine : int,
|
||||||
|
[<CallerFilePath>] ?filePath : string
|
||||||
|
)
|
||||||
|
: ExpectState<'a>
|
||||||
|
=
|
||||||
|
match state.Snapshot with
|
||||||
|
| Some _ -> failwith "snapshot can only be specified once"
|
||||||
|
| None ->
|
||||||
|
let memberName = defaultArg memberName "<unknown method>"
|
||||||
|
let filePath = defaultArg filePath "<unknown file>"
|
||||||
|
let lineNumber = defaultArg callerLine -1
|
||||||
|
|
||||||
|
let callerInfo =
|
||||||
|
{
|
||||||
|
MemberName = memberName
|
||||||
|
FilePath = filePath
|
||||||
|
LineNumber = lineNumber
|
||||||
|
}
|
||||||
|
|
||||||
|
{ state with
|
||||||
|
Snapshot = Some (SnapshotValue.Json snapshot, callerInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// MaintainsVariableSpaceUsingBind causes this to be used; it's a dummy representing "no snapshot and no assertion".
|
||||||
|
member _.Return (() : unit) : ExpectState<'T> =
|
||||||
|
{
|
||||||
|
Snapshot = None
|
||||||
|
Actual = None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Expresses the "actual value" component of the assertion "expected snapshot = actual value".
|
||||||
|
member _.Return (value : 'T) : ExpectState<'T> =
|
||||||
|
{
|
||||||
|
Snapshot = None
|
||||||
|
Actual = Some value
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computation expression `Delay`.
|
||||||
|
member _.Delay (f : unit -> ExpectState<'T>) : unit -> ExpectState<'T> = f
|
||||||
|
|
||||||
|
/// Computation expression `Run`, which runs a `Delay`ed snapshot assertion, throwing if the assertion fails.
|
||||||
|
member _.Run (f : unit -> ExpectState<'T>) : unit =
|
||||||
|
let state = f ()
|
||||||
|
|
||||||
|
let options = JsonFSharpOptions.Default().ToJsonSerializerOptions ()
|
||||||
|
|
||||||
|
match state.Snapshot, state.Actual with
|
||||||
|
| Some (snapshot, source), Some actual ->
|
||||||
|
match snapshot with
|
||||||
|
| SnapshotValue.Json snapshot ->
|
||||||
|
let canonicalSnapshot = JsonDocument.Parse snapshot
|
||||||
|
|
||||||
|
let canonicalActual =
|
||||||
|
JsonSerializer.Serialize (actual, options) |> JsonDocument.Parse
|
||||||
|
|
||||||
|
if not (JsonElement.DeepEquals (canonicalActual.RootElement, canonicalSnapshot.RootElement)) then
|
||||||
|
failwithf
|
||||||
|
"snapshot mismatch! snapshot at %s:%i (%s) was:\n\n%s\n\nactual was:\n\n%s"
|
||||||
|
(filePathOverride |> Option.defaultValue source.FilePath)
|
||||||
|
source.LineNumber
|
||||||
|
source.MemberName
|
||||||
|
(canonicalSnapshot.RootElement.ToString () |> Text.predent '-')
|
||||||
|
(canonicalActual.RootElement.ToString () |> Text.predent '-')
|
||||||
|
|
||||||
|
| SnapshotValue.BareString snapshot ->
|
||||||
|
let actual = actual.ToString ()
|
||||||
|
|
||||||
|
if actual <> snapshot then
|
||||||
|
failwithf
|
||||||
|
"snapshot mismatch! snapshot at %s:%i (%s) was:\n\n%s\n\nactual was:\n\n%s"
|
||||||
|
(filePathOverride |> Option.defaultValue source.FilePath)
|
||||||
|
source.LineNumber
|
||||||
|
source.MemberName
|
||||||
|
(snapshot |> Text.predent '-')
|
||||||
|
(actual |> Text.predent '+')
|
||||||
|
|
||||||
|
| None, _ -> failwith "Must specify snapshot"
|
||||||
|
| _, None -> failwith "Must specify actual value with 'return'"
|
||||||
|
|
||||||
|
/// Module containing the `expect` builder.
|
||||||
|
[<AutoOpen>]
|
||||||
|
module Builder =
|
||||||
|
/// <summary>The primary WoofWare.Expect builder.</summary>
|
||||||
|
///
|
||||||
|
/// <remarks>
|
||||||
|
/// You are expected to use this like so:
|
||||||
|
///
|
||||||
|
/// <code>
|
||||||
|
/// expect {
|
||||||
|
/// snapshot "123"
|
||||||
|
/// return 124
|
||||||
|
/// }
|
||||||
|
/// </code>
|
||||||
|
///
|
||||||
|
/// (That example expectation will fail, because the actual value 124 does not snapshot to the expected snapshot "123".)
|
||||||
|
/// </remarks>
|
||||||
|
let expect = ExpectBuilder ()
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This is the `expect` builder, but it mocks out the filepath reported on failure.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// You probably don't want to use this; use `expect` instead.
|
||||||
|
/// The point of the mocked builder is to allow fully predictable testing of the WoofWare.Expect library itself.
|
||||||
|
/// </remarks>
|
||||||
|
let expectWithMockedFilePath (path : string) = ExpectBuilder path
|
15
WoofWare.Expect/SurfaceBaseline.txt
Normal file
15
WoofWare.Expect/SurfaceBaseline.txt
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
WoofWare.Expect.Builder inherit obj
|
||||||
|
WoofWare.Expect.Builder.expect [static property]: [read-only] WoofWare.Expect.ExpectBuilder
|
||||||
|
WoofWare.Expect.Builder.expectWithMockedFilePath [static method]: string -> WoofWare.Expect.ExpectBuilder
|
||||||
|
WoofWare.Expect.Builder.get_expect [static method]: unit -> WoofWare.Expect.ExpectBuilder
|
||||||
|
WoofWare.Expect.ExpectBuilder inherit obj
|
||||||
|
WoofWare.Expect.ExpectBuilder..ctor [constructor]: string option
|
||||||
|
WoofWare.Expect.ExpectBuilder.Bind [method]: (unit WoofWare.Expect.ExpectState, unit -> 'U WoofWare.Expect.ExpectState) -> 'U WoofWare.Expect.ExpectState
|
||||||
|
WoofWare.Expect.ExpectBuilder.Delay [method]: (unit -> 'T WoofWare.Expect.ExpectState) -> (unit -> 'T WoofWare.Expect.ExpectState)
|
||||||
|
WoofWare.Expect.ExpectBuilder.Return [method]: 'T -> 'T WoofWare.Expect.ExpectState
|
||||||
|
WoofWare.Expect.ExpectBuilder.Return [method]: unit -> 'T WoofWare.Expect.ExpectState
|
||||||
|
WoofWare.Expect.ExpectBuilder.Run [method]: (unit -> 'T WoofWare.Expect.ExpectState) -> unit
|
||||||
|
WoofWare.Expect.ExpectBuilder.Snapshot [method]: ('a WoofWare.Expect.ExpectState, string, string option, int option, string option) -> 'a WoofWare.Expect.ExpectState
|
||||||
|
WoofWare.Expect.ExpectBuilder.SnapshotJson [method]: ('a WoofWare.Expect.ExpectState, string, string option, int option, string option) -> 'a WoofWare.Expect.ExpectState
|
||||||
|
WoofWare.Expect.ExpectState`1 inherit obj, implements 'T WoofWare.Expect.ExpectState System.IEquatable, System.Collections.IStructuralEquatable, 'T WoofWare.Expect.ExpectState System.IComparable, System.IComparable, System.Collections.IStructuralComparable
|
||||||
|
WoofWare.Expect.ExpectState`1.Equals [method]: ('T WoofWare.Expect.ExpectState, System.Collections.IEqualityComparer) -> bool
|
40
WoofWare.Expect/WoofWare.Expect.fsproj
Normal file
40
WoofWare.Expect/WoofWare.Expect.fsproj
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
|
<Authors>Patrick Stevens</Authors>
|
||||||
|
<Copyright>Copyright (c) Patrick Stevens 2025</Copyright>
|
||||||
|
<Description>Snapshot/expect testing framework for F#</Description>
|
||||||
|
<RepositoryType>git</RepositoryType>
|
||||||
|
<RepositoryUrl>https://github.com/Smaug123/WoofWare.Expect</RepositoryUrl>
|
||||||
|
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||||
|
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||||
|
<PackageIcon>logo.png</PackageIcon>
|
||||||
|
<PackageTags>fsharp;snapshot;expect;test;testing;jest</PackageTags>
|
||||||
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="Library.fs"/>
|
||||||
|
<None Include="..\README.md">
|
||||||
|
<Pack>True</Pack>
|
||||||
|
<PackagePath>\</PackagePath>
|
||||||
|
</None>
|
||||||
|
<None Include="..\logo.png">
|
||||||
|
<Pack>True</Pack>
|
||||||
|
<PackagePath>\</PackagePath>
|
||||||
|
</None>
|
||||||
|
<EmbeddedResource Include="SurfaceBaseline.txt"/>
|
||||||
|
<EmbeddedResource Include="version.json"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<!-- FSharp.SystemTextJson requires at least this version -->
|
||||||
|
<PackageReference Update="FSharp.Core" Version="4.7.0" />
|
||||||
|
<PackageReference Include="FSharp.SystemTextJson" Version="1.4.36" />
|
||||||
|
<!-- Needed for DeepEquals -->
|
||||||
|
<PackageReference Include="System.Text.Json" Version="9.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
12
WoofWare.Expect/version.json
Normal file
12
WoofWare.Expect/version.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"version": "0.1",
|
||||||
|
"publicReleaseRefSpec": [
|
||||||
|
"^refs/heads/main$"
|
||||||
|
],
|
||||||
|
"pathFilters": [
|
||||||
|
":/WoofWare.Expect/",
|
||||||
|
":/README.md",
|
||||||
|
":/Directory.Build.props",
|
||||||
|
":/LICENSE"
|
||||||
|
]
|
||||||
|
}
|
61
flake.lock
generated
Normal file
61
flake.lock
generated
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"flake-utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1731533236,
|
||||||
|
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1749903597,
|
||||||
|
"narHash": "sha256-jp0D4vzBcRKwNZwfY4BcWHemLGUs4JrS3X9w5k/JYDA=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "41da1e3ea8e23e094e5e3eeb1e6b830468a7399e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixpkgs-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
71
flake.nix
Normal file
71
flake.nix
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
{
|
||||||
|
description = "Expect testing framework";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
|
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = {
|
||||||
|
nixpkgs,
|
||||||
|
flake-utils,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
flake-utils.lib.eachDefaultSystem (system: let
|
||||||
|
pkgs = nixpkgs.legacyPackages.${system};
|
||||||
|
pname = "WoofWare.Expect";
|
||||||
|
dotnet-sdk = pkgs.dotnetCorePackages.sdk_9_0;
|
||||||
|
dotnet-runtime = pkgs.dotnetCorePackages.runtime_9_0;
|
||||||
|
version = "0.1";
|
||||||
|
dotnetTool = dllOverride: toolName: toolVersion: hash:
|
||||||
|
pkgs.stdenvNoCC.mkDerivation rec {
|
||||||
|
name = toolName;
|
||||||
|
version = toolVersion;
|
||||||
|
nativeBuildInputs = [pkgs.makeWrapper];
|
||||||
|
src = pkgs.fetchNuGet {
|
||||||
|
pname = name;
|
||||||
|
version = version;
|
||||||
|
hash = hash;
|
||||||
|
installPhase = ''mkdir -p $out/bin && cp -r tools/net*/any/* $out/bin'';
|
||||||
|
};
|
||||||
|
installPhase = let
|
||||||
|
dll =
|
||||||
|
if isNull dllOverride
|
||||||
|
then name
|
||||||
|
else dllOverride;
|
||||||
|
in
|
||||||
|
# fsharp-analyzers requires the .NET SDK at runtime, so we use that instead of dotnet-runtime.
|
||||||
|
''
|
||||||
|
runHook preInstall
|
||||||
|
mkdir -p "$out/lib"
|
||||||
|
cp -r ./bin/* "$out/lib"
|
||||||
|
makeWrapper "${dotnet-sdk}/bin/dotnet" "$out/bin/${name}" --set DOTNET_HOST_PATH "${dotnet-sdk}/bin/dotnet" --add-flags "$out/lib/${dll}.dll"
|
||||||
|
runHook postInstall
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
packages = let
|
||||||
|
deps = builtins.fromJSON (builtins.readFile ./nix/deps.json);
|
||||||
|
in {
|
||||||
|
fantomas = dotnetTool null "fantomas" (builtins.fromJSON (builtins.readFile ./.config/dotnet-tools.json)).tools.fantomas.version (builtins.head (builtins.filter (elem: elem.pname == "fantomas") deps)).hash;
|
||||||
|
fsharp-analyzers = dotnetTool "FSharp.Analyzers.Cli" "fsharp-analyzers" (builtins.fromJSON (builtins.readFile ./.config/dotnet-tools.json)).tools.fsharp-analyzers.version (builtins.head (builtins.filter (elem: elem.pname == "fsharp-analyzers") deps)).hash;
|
||||||
|
default = pkgs.buildDotnetModule {
|
||||||
|
inherit pname version dotnet-sdk dotnet-runtime;
|
||||||
|
name = "WoofWare.Expect";
|
||||||
|
src = ./.;
|
||||||
|
projectFile = "./WoofWare.Expect/WoofWare.Expect.fsproj";
|
||||||
|
testProjectFile = "./WoofWare.Expect.Test/WoofWare.Expect.Test.fsproj";
|
||||||
|
nugetDeps = ./nix/deps.json; # `nix build .#default.fetch-deps && ./result nix/deps.json`
|
||||||
|
doCheck = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
devShell = pkgs.mkShell {
|
||||||
|
buildInputs = [dotnet-sdk];
|
||||||
|
packages = [
|
||||||
|
pkgs.alejandra
|
||||||
|
pkgs.nodePackages.markdown-link-check
|
||||||
|
pkgs.shellcheck
|
||||||
|
];
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
267
nix/deps.json
Normal file
267
nix/deps.json
Normal file
@@ -0,0 +1,267 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"pname": "ApiSurface",
|
||||||
|
"version": "4.1.20",
|
||||||
|
"hash": "sha256-koWgO9FC9ax+Ij56ug8kxeyknl0yhLqnNLOUdxtqqo4="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "fantomas",
|
||||||
|
"version": "7.0.2",
|
||||||
|
"hash": "sha256-BAaENIm/ksTiXrUImRgKoIXTGIlgsX7ch6ayoFjhJXA="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "fsharp-analyzers",
|
||||||
|
"version": "0.31.0",
|
||||||
|
"hash": "sha256-PoAvaXbXsmvVw870UsnqdD20HoBHO7u4bzoaz5DXfzM="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "FSharp.Core",
|
||||||
|
"version": "4.7.0",
|
||||||
|
"hash": "sha256-7aa4bga9XWLkq7J5KXv8Bilf1KGum77lSUqp+ooYIUg="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "FSharp.Core",
|
||||||
|
"version": "9.0.202",
|
||||||
|
"hash": "sha256-64Gub0qemmCoMa1tDus6TeTuB1+5sHfE6KD2j4o84mA="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "FSharp.SystemTextJson",
|
||||||
|
"version": "1.4.36",
|
||||||
|
"hash": "sha256-zZEhjP0mdc5E3fBPS4/lqD7sxoaoT5SOspP546RWYdc="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "Microsoft.ApplicationInsights",
|
||||||
|
"version": "2.22.0",
|
||||||
|
"hash": "sha256-mUQ63atpT00r49ca50uZu2YCiLg3yd6r3HzTryqcuEA="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "Microsoft.Bcl.AsyncInterfaces",
|
||||||
|
"version": "9.0.0",
|
||||||
|
"hash": "sha256-BsXNOWEgfFq3Yz7VTtK6m/ov4/erRqyBzieWSIpmc1U="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "Microsoft.CodeCoverage",
|
||||||
|
"version": "17.14.1",
|
||||||
|
"hash": "sha256-f8QytG8GvRoP47rO2KEmnDLxIpyesaq26TFjDdW40Gs="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "Microsoft.NET.Test.Sdk",
|
||||||
|
"version": "17.14.1",
|
||||||
|
"hash": "sha256-mZUzDFvFp7x1nKrcnRd0hhbNu5g8EQYt8SKnRgdhT/A="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "Microsoft.NETCore.Platforms",
|
||||||
|
"version": "1.1.0",
|
||||||
|
"hash": "sha256-FeM40ktcObQJk4nMYShB61H/E8B7tIKfl9ObJ0IOcCM="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "Microsoft.NETCore.Platforms",
|
||||||
|
"version": "2.0.0",
|
||||||
|
"hash": "sha256-IEvBk6wUXSdyCnkj6tHahOJv290tVVT8tyemYcR0Yro="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "Microsoft.Testing.Extensions.Telemetry",
|
||||||
|
"version": "1.5.3",
|
||||||
|
"hash": "sha256-bIXwPSa3jkr2b6xINOqMUs6/uj/r4oVFM7xq3uVIZDU="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "Microsoft.Testing.Extensions.TrxReport.Abstractions",
|
||||||
|
"version": "1.5.3",
|
||||||
|
"hash": "sha256-IfMRfcyaIKEMRtx326ICKtinDBEfGw/Sv8ZHawJ96Yc="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "Microsoft.Testing.Extensions.VSTestBridge",
|
||||||
|
"version": "1.5.3",
|
||||||
|
"hash": "sha256-XpM/yFjhLSsuzyDV+xKubs4V1zVVYiV05E0+N4S1h0g="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "Microsoft.Testing.Platform",
|
||||||
|
"version": "1.5.3",
|
||||||
|
"hash": "sha256-y61Iih6w5D79dmrj2V675mcaeIiHoj1HSa1FRit2BLM="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "Microsoft.Testing.Platform.MSBuild",
|
||||||
|
"version": "1.5.3",
|
||||||
|
"hash": "sha256-YspvjE5Jfi587TAfsvfDVJXNrFOkx1B3y1CKV6m7YLY="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "Microsoft.TestPlatform.ObjectModel",
|
||||||
|
"version": "17.12.0",
|
||||||
|
"hash": "sha256-3XBHBSuCxggAIlHXmKNQNlPqMqwFlM952Av6RrLw1/w="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "Microsoft.TestPlatform.ObjectModel",
|
||||||
|
"version": "17.14.1",
|
||||||
|
"hash": "sha256-QMf6O+w0IT+16Mrzo7wn+N20f3L1/mDhs/qjmEo1rYs="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "Microsoft.TestPlatform.TestHost",
|
||||||
|
"version": "17.14.1",
|
||||||
|
"hash": "sha256-1cxHWcvHRD7orQ3EEEPPxVGEkTpxom1/zoICC9SInJs="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "Nerdbank.GitVersioning",
|
||||||
|
"version": "3.8.38-alpha",
|
||||||
|
"hash": "sha256-gPMrVbjOZxXoofczF/pn6eVkLhjVSJIyQrLO2oljrDc="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "NETStandard.Library",
|
||||||
|
"version": "2.0.3",
|
||||||
|
"hash": "sha256-Prh2RPebz/s8AzHb2sPHg3Jl8s31inv9k+Qxd293ybo="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "Newtonsoft.Json",
|
||||||
|
"version": "13.0.3",
|
||||||
|
"hash": "sha256-hy/BieY4qxBWVVsDqqOPaLy1QobiIapkbrESm6v2PHc="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "NuGet.Common",
|
||||||
|
"version": "6.13.2",
|
||||||
|
"hash": "sha256-ASLa/Jigg5Eop0ZrXPl98RW2rxnJRC7pbbxhuV74hFw="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "NuGet.Configuration",
|
||||||
|
"version": "6.13.2",
|
||||||
|
"hash": "sha256-z8VW1YdRDanyyRTDYRvRkSv/XPR3c/hMM1y8cNNjx0Y="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "NuGet.Frameworks",
|
||||||
|
"version": "6.13.2",
|
||||||
|
"hash": "sha256-caDyc+WgYOo43AUTjtbP0MyvYDb6JweEKDdIul61Cac="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "NuGet.Packaging",
|
||||||
|
"version": "6.13.2",
|
||||||
|
"hash": "sha256-lhO+SFwIYZ4aPHxIGm5ubkkE2a5Ve2xgtroRbNh7hpw="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "NuGet.Protocol",
|
||||||
|
"version": "6.13.2",
|
||||||
|
"hash": "sha256-5lnAHHZjy7A4vgv65AeBAs64mSNpuoUjxW3HnrMpuzY="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "NuGet.Versioning",
|
||||||
|
"version": "6.13.2",
|
||||||
|
"hash": "sha256-gmpyBpKnt+GHqgx/2uFKp+J2csbxEAy1E7WdVT117sw="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "NUnit",
|
||||||
|
"version": "4.3.2",
|
||||||
|
"hash": "sha256-0RWe8uFoxYp6qhPlDDEghOMcKJgyw2ybvEoAqBLebeE="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "NUnit3TestAdapter",
|
||||||
|
"version": "5.0.0",
|
||||||
|
"hash": "sha256-7jZM4qAbIzne3AcdFfMbvbgogqpxvVe6q2S7Ls8xQy0="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "System.Buffers",
|
||||||
|
"version": "4.5.1",
|
||||||
|
"hash": "sha256-wws90sfi9M7kuCPWkv1CEYMJtCqx9QB/kj0ymlsNaxI="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "System.Collections.Immutable",
|
||||||
|
"version": "8.0.0",
|
||||||
|
"hash": "sha256-F7OVjKNwpqbUh8lTidbqJWYi476nsq9n+6k0+QVRo3w="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "System.Diagnostics.DiagnosticSource",
|
||||||
|
"version": "5.0.0",
|
||||||
|
"hash": "sha256-6mW3N6FvcdNH/pB58pl+pFSCGWgyaP4hfVtC/SMWDV4="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "System.Formats.Asn1",
|
||||||
|
"version": "6.0.0",
|
||||||
|
"hash": "sha256-KaMHgIRBF7Nf3VwOo+gJS1DcD+41cJDPWFh+TDQ8ee8="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "System.Formats.Asn1",
|
||||||
|
"version": "8.0.1",
|
||||||
|
"hash": "sha256-may/Wg+esmm1N14kQTG4ESMBi+GQKPp0ZrrBo/o6OXM="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "System.IO.Abstractions",
|
||||||
|
"version": "4.2.13",
|
||||||
|
"hash": "sha256-nkC/PiqE6+c1HJ2yTwg3x+qdBh844Z8n3ERWDW8k6Gg="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "System.IO.FileSystem.AccessControl",
|
||||||
|
"version": "4.5.0",
|
||||||
|
"hash": "sha256-ck44YBQ0M+2Im5dw0VjBgFD1s0XuY54cujrodjjSBL8="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "System.IO.Pipelines",
|
||||||
|
"version": "9.0.0",
|
||||||
|
"hash": "sha256-vb0NrPjfEao3kfZ0tavp2J/29XnsQTJgXv3/qaAwwz0="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "System.Memory",
|
||||||
|
"version": "4.5.5",
|
||||||
|
"hash": "sha256-EPQ9o1Kin7KzGI5O3U3PUQAZTItSbk9h/i4rViN3WiI="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "System.Numerics.Vectors",
|
||||||
|
"version": "4.4.0",
|
||||||
|
"hash": "sha256-auXQK2flL/JpnB/rEcAcUm4vYMCYMEMiWOCAlIaqu2U="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "System.Reflection.Metadata",
|
||||||
|
"version": "8.0.0",
|
||||||
|
"hash": "sha256-dQGC30JauIDWNWXMrSNOJncVa1umR1sijazYwUDdSIE="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "System.Runtime.CompilerServices.Unsafe",
|
||||||
|
"version": "4.5.3",
|
||||||
|
"hash": "sha256-lnZMUqRO4RYRUeSO8HSJ9yBHqFHLVbmenwHWkIU20ak="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "System.Runtime.CompilerServices.Unsafe",
|
||||||
|
"version": "6.0.0",
|
||||||
|
"hash": "sha256-bEG1PnDp7uKYz/OgLOWs3RWwQSVYm+AnPwVmAmcgp2I="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "System.Security.AccessControl",
|
||||||
|
"version": "4.5.0",
|
||||||
|
"hash": "sha256-AFsKPb/nTk2/mqH/PYpaoI8PLsiKKimaXf+7Mb5VfPM="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "System.Security.Cryptography.Pkcs",
|
||||||
|
"version": "6.0.4",
|
||||||
|
"hash": "sha256-2e0aRybote+OR66bHaNiYpF//4fCiaO3zbR2e9GABUI="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "System.Security.Cryptography.ProtectedData",
|
||||||
|
"version": "4.4.0",
|
||||||
|
"hash": "sha256-Ri53QmFX8I8UH0x4PikQ1ZA07ZSnBUXStd5rBfGWFOE="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "System.Security.Principal.Windows",
|
||||||
|
"version": "4.5.0",
|
||||||
|
"hash": "sha256-BkUYNguz0e4NJp1kkW7aJBn3dyH9STwB5N8XqnlCsmY="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "System.Text.Encodings.Web",
|
||||||
|
"version": "9.0.0",
|
||||||
|
"hash": "sha256-WGaUklQEJywoGR2jtCEs5bxdvYu5SHaQchd6s4RE5x0="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "System.Text.Json",
|
||||||
|
"version": "6.0.10",
|
||||||
|
"hash": "sha256-UijYh0dxFjFinMPSTJob96oaRkNm+Wsa+7Ffg6mRnsc="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "System.Text.Json",
|
||||||
|
"version": "8.0.5",
|
||||||
|
"hash": "sha256-yKxo54w5odWT6nPruUVsaX53oPRe+gKzGvLnnxtwP68="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "System.Text.Json",
|
||||||
|
"version": "9.0.0",
|
||||||
|
"hash": "sha256-aM5Dh4okLnDv940zmoFAzRmqZre83uQBtGOImJpoIqk="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "System.Threading.Tasks.Extensions",
|
||||||
|
"version": "4.5.4",
|
||||||
|
"hash": "sha256-owSpY8wHlsUXn5xrfYAiu847L6fAKethlvYx97Ri1ng="
|
||||||
|
}
|
||||||
|
]
|
Reference in New Issue
Block a user