mirror of
https://github.com/Smaug123/WoofWare.Expect
synced 2025-10-04 20:18:39 +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