mirror of
https://github.com/Smaug123/WoofWare.Expect
synced 2025-10-06 21:18:41 +00:00
Compare commits
3 Commits
WoofWare.E
...
WoofWare.E
Author | SHA1 | Date | |
---|---|---|---|
|
ebc24f85aa | ||
|
faacb4770c | ||
|
d21786ecd4 |
21
README.md
21
README.md
@@ -16,8 +16,7 @@ An [expect-testing](https://blog.janestreet.com/the-joy-of-expect-tests/) librar
|
|||||||
# Current status
|
# Current status
|
||||||
|
|
||||||
The basic mechanism works.
|
The basic mechanism works.
|
||||||
Snapshot updating is vibe-coded with Opus 4 and is purely text-based; I didn't want to use the F# compiler services because that's a pretty heavyweight dependency which should be confined to a separate test runner entity.
|
It's fairly well tested, but you will almost certainly be able to find ways to break it; try not to be too fancy with your syntax around the `snapshot` statement.
|
||||||
It's fairly well tested, but you will certainly be able to find ways to break it; try not to be too fancy with your syntax around the `snapshot` statement.
|
|
||||||
|
|
||||||
# How to use
|
# How to use
|
||||||
|
|
||||||
@@ -51,8 +50,17 @@ let ``With return! and snapshotThrows, you can see exceptions too`` () =
|
|||||||
snapshotThrows @"System.Exception: oh no"
|
snapshotThrows @"System.Exception: oh no"
|
||||||
return! (fun () -> failwith<int> "oh no")
|
return! (fun () -> failwith<int> "oh no")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let ``You can do lists more neatly with the snapshotList keyword`` () =
|
||||||
|
expect {
|
||||||
|
snapshotList [ "8" ; "9" ; "10" ; "11" ; "12" ]
|
||||||
|
return [ 8..12 ]
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
(JSON output for elements is not yet supported with `snapshotList`.)
|
||||||
|
|
||||||
You can adjust the formatting:
|
You can adjust the formatting:
|
||||||
|
|
||||||
```fsharp
|
```fsharp
|
||||||
@@ -64,6 +72,15 @@ let ``Overriding the formatting`` () =
|
|||||||
snapshot @"Int32"
|
snapshot @"Int32"
|
||||||
return 123
|
return 123
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let ``Overriding the formatting with lists`` () =
|
||||||
|
expect {
|
||||||
|
// these two lines *do* have to be in this order, for annoying reasons
|
||||||
|
snapshotList [ "8" ; "9" ; "0" ; "1" ; "2" ]
|
||||||
|
withFormat (fun x -> string<int> (x % 10))
|
||||||
|
return [ 8..12 ]
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
You can override the JSON serialisation if you find the snapshot format displeasing:
|
You can override the JSON serialisation if you find the snapshot format displeasing:
|
||||||
|
@@ -34,7 +34,7 @@ module MyModule =
|
|||||||
expect {
|
expect {
|
||||||
snapshot (* first comment *) (* second comment *)
|
snapshot (* first comment *) (* second comment *)
|
||||||
(* third comment on new line *)
|
(* third comment on new line *)
|
||||||
@""updated after many comments""
|
""updated after many comments""
|
||||||
|
|
||||||
return 123
|
return 123
|
||||||
}
|
}
|
||||||
@@ -100,7 +100,7 @@ module MyModule =
|
|||||||
|
|
||||||
let nestedComments () =
|
let nestedComments () =
|
||||||
expect {
|
expect {
|
||||||
snapshot (* outer (* inner *) comment *) @""updated after nested comments""
|
snapshot (* outer (* inner *) comment *) ""updated after nested comments""
|
||||||
return ""nested""
|
return ""nested""
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,7 +165,7 @@ module MyModule =
|
|||||||
|
|
||||||
let commentWithSpecialChars () =
|
let commentWithSpecialChars () =
|
||||||
expect {
|
expect {
|
||||||
snapshot (* comment with ""quotes"" and \ backslash *) @""updated after weird comment""
|
snapshot (* comment with ""quotes"" and \ backslash *) ""updated after weird comment""
|
||||||
return ""special""
|
return ""special""
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,7 +233,7 @@ module MyModule =
|
|||||||
snapshot
|
snapshot
|
||||||
|
|
||||||
|
|
||||||
@""updated after spaces""
|
""updated after spaces""
|
||||||
|
|
||||||
return ""whitespace""
|
return ""whitespace""
|
||||||
}
|
}
|
||||||
@@ -301,7 +301,7 @@ module MyModule =
|
|||||||
expect {
|
expect {
|
||||||
snapshotJson (* comment 1 *)
|
snapshotJson (* comment 1 *)
|
||||||
(* comment 2 *)
|
(* comment 2 *)
|
||||||
(* comment 3 *) @""updated after comments""
|
(* comment 3 *) ""updated after comments""
|
||||||
|
|
||||||
return 123
|
return 123
|
||||||
}
|
}
|
||||||
|
@@ -31,7 +31,7 @@ open WoofWare.Expect
|
|||||||
module MyModule =
|
module MyModule =
|
||||||
let emptyString () =
|
let emptyString () =
|
||||||
expect {
|
expect {
|
||||||
snapshot @""now has content""
|
snapshot ""now has content""
|
||||||
return """"
|
return """"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,7 +121,7 @@ module MyModule =
|
|||||||
|
|
||||||
let emptyVerbatim () =
|
let emptyVerbatim () =
|
||||||
expect {
|
expect {
|
||||||
snapshot @""now has content""
|
snapshot ""now has content""
|
||||||
return """"
|
return """"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,7 +211,7 @@ module MyModule =
|
|||||||
|
|
||||||
let emptyTripleQuote () =
|
let emptyTripleQuote () =
|
||||||
expect {
|
expect {
|
||||||
snapshot @""now has content""
|
snapshot ""now has content""
|
||||||
return """"
|
return """"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -301,7 +301,7 @@ module MyModule =
|
|||||||
|
|
||||||
let onlyWhitespace () =
|
let onlyWhitespace () =
|
||||||
expect {
|
expect {
|
||||||
snapshot @""now has content""
|
snapshot ""now has content""
|
||||||
return ""whitespace""
|
return ""whitespace""
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -575,7 +575,7 @@ module MyModule =
|
|||||||
let veryLongLine () =
|
let veryLongLine () =
|
||||||
expect {
|
expect {
|
||||||
snapshot
|
snapshot
|
||||||
@""this line is short though""
|
""this line is short though""
|
||||||
|
|
||||||
return ""long line""
|
return ""long line""
|
||||||
}
|
}
|
||||||
|
@@ -472,7 +472,7 @@ Sixth line""
|
|||||||
|
|
||||||
let windowsLineEndings () =
|
let windowsLineEndings () =
|
||||||
expect {
|
expect {
|
||||||
snapshot @""down with line endings""
|
snapshot ""down with line endings""
|
||||||
return ""crlf""
|
return ""crlf""
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
|
@@ -32,7 +32,7 @@ open WoofWare.Expect
|
|||||||
module MyModule =
|
module MyModule =
|
||||||
let foo () =
|
let foo () =
|
||||||
expect {
|
expect {
|
||||||
snapshot @""replacement""
|
snapshot ""replacement""
|
||||||
return 123
|
return 123
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
@@ -81,7 +81,7 @@ open WoofWare.Expect
|
|||||||
module MyModule =
|
module MyModule =
|
||||||
let foo () =
|
let foo () =
|
||||||
expect {
|
expect {
|
||||||
snapshot @""replacement""
|
snapshot ""replacement""
|
||||||
return 123
|
return 123
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
|
@@ -31,7 +31,7 @@ open WoofWare.Expect
|
|||||||
module MyModule =
|
module MyModule =
|
||||||
let emoji () =
|
let emoji () =
|
||||||
expect {
|
expect {
|
||||||
snapshot @""Updated with 🚀🌟✨ more emoji!""
|
snapshot ""Updated with 🚀🌟✨ more emoji!""
|
||||||
return 123
|
return 123
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,7 +172,7 @@ module MyModule =
|
|||||||
|
|
||||||
let arabicRTL () =
|
let arabicRTL () =
|
||||||
expect {
|
expect {
|
||||||
snapshot @""Updated Arabic: مرحبا بالعالم""
|
snapshot ""Updated Arabic: مرحبا بالعالم""
|
||||||
return ""rtl test""
|
return ""rtl test""
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,7 +241,7 @@ module MyModule =
|
|||||||
let combiningCharacters () =
|
let combiningCharacters () =
|
||||||
expect {
|
expect {
|
||||||
// Combining diacritics: e + ́ = é
|
// Combining diacritics: e + ́ = é
|
||||||
snapshot @""updated test with combining: é and ä!""
|
snapshot ""updated test with combining: é and ä!""
|
||||||
return ""combining""
|
return ""combining""
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,7 +309,7 @@ module MyModule =
|
|||||||
|
|
||||||
let mixedScripts () =
|
let mixedScripts () =
|
||||||
expect {
|
expect {
|
||||||
snapshotJson @""Updated mixed: English, русский, 日本語, العربية, emoji 🚀""
|
snapshotJson ""Updated mixed: English, русский, 日本語, العربية, emoji 🚀""
|
||||||
return [ ""multilingual"" ]
|
return [ ""multilingual"" ]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -377,7 +377,7 @@ module MyModule =
|
|||||||
|
|
||||||
let zeroWidthChars () =
|
let zeroWidthChars () =
|
||||||
expect {
|
expect {
|
||||||
snapshot @""Updated: Zerowidthspacetest"" // Contains U+200B
|
snapshot ""Updated: Zerowidthspacetest"" // Contains U+200B
|
||||||
return ""zwsp""
|
return ""zwsp""
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -445,7 +445,7 @@ module MyModule =
|
|||||||
|
|
||||||
let mathSymbols () =
|
let mathSymbols () =
|
||||||
expect {
|
expect {
|
||||||
snapshot @""Pretty vacuous, huh: ∀x∈ℝ, ∃y: x² + y² = 1 ⟹ |x| ≤ 1""
|
snapshot ""Pretty vacuous, huh: ∀x∈ℝ, ∃y: x² + y² = 1 ⟹ |x| ≤ 1""
|
||||||
return ""math""
|
return ""math""
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
|
32
WoofWare.Expect.Test/TestSnapshotList.fs
Normal file
32
WoofWare.Expect.Test/TestSnapshotList.fs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
namespace WoofWare.Expect.Test
|
||||||
|
|
||||||
|
open NUnit.Framework
|
||||||
|
open WoofWare.Expect
|
||||||
|
|
||||||
|
[<TestFixture>]
|
||||||
|
module TestSnapshotList =
|
||||||
|
[<OneTimeSetUp>]
|
||||||
|
let ``Prepare to bulk-update tests`` () =
|
||||||
|
// If you don't want to enter bulk-update mode, just replace this line with a no-op `()`.
|
||||||
|
// The `updateAllSnapshots` tear-down below will simply do nothing in that case.
|
||||||
|
// GlobalBuilderConfig.enterBulkUpdateMode ()
|
||||||
|
()
|
||||||
|
|
||||||
|
[<OneTimeTearDown>]
|
||||||
|
let ``Update all tests`` () =
|
||||||
|
GlobalBuilderConfig.updateAllSnapshots ()
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let ``simple list test`` () =
|
||||||
|
expect {
|
||||||
|
snapshotList [ "1" ; "2" ; "3" ]
|
||||||
|
return [ 1..3 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let ``list test with formatting`` () =
|
||||||
|
expect {
|
||||||
|
snapshotList [ "8" ; "9" ; "0" ; "1" ; "2" ]
|
||||||
|
withFormat (fun x -> string<int> (x % 10))
|
||||||
|
return [ 8..12 ]
|
||||||
|
}
|
@@ -16,6 +16,7 @@
|
|||||||
<Compile Include="TestDiff.fs" />
|
<Compile Include="TestDiff.fs" />
|
||||||
<Compile Include="TestDot.fs" />
|
<Compile Include="TestDot.fs" />
|
||||||
<Compile Include="TestExceptionThrowing.fs" />
|
<Compile Include="TestExceptionThrowing.fs" />
|
||||||
|
<Compile Include="TestSnapshotList.fs" />
|
||||||
<Compile Include="TestSurface.fs" />
|
<Compile Include="TestSurface.fs" />
|
||||||
<Compile Include="TestSnapshotFinding\TestSnapshotFinding.fs" />
|
<Compile Include="TestSnapshotFinding\TestSnapshotFinding.fs" />
|
||||||
<Compile Include="TestSnapshotFinding\TestUnicodeCharacters.fs" />
|
<Compile Include="TestSnapshotFinding\TestUnicodeCharacters.fs" />
|
||||||
|
213
WoofWare.Expect/AstWalker.fs
Normal file
213
WoofWare.Expect/AstWalker.fs
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
namespace WoofWare.Expect
|
||||||
|
|
||||||
|
// This file mostly courtesy of Claude 4 Opus.
|
||||||
|
|
||||||
|
open Fantomas.FCS.Diagnostics
|
||||||
|
open Fantomas.FCS.Syntax
|
||||||
|
open Fantomas.FCS.Text
|
||||||
|
|
||||||
|
type internal SnapshotLocation =
|
||||||
|
{
|
||||||
|
KeywordRange : Range
|
||||||
|
Keyword : string
|
||||||
|
ReplacementRange : Range
|
||||||
|
}
|
||||||
|
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module internal AstWalker =
|
||||||
|
|
||||||
|
let private snapshotSignifier =
|
||||||
|
[ "snapshot" ; "snapshotJson" ; "snapshotList" ; "snapshotThrows" ]
|
||||||
|
|> Set.ofList
|
||||||
|
|
||||||
|
/// Check if this is a call to snapshotList (or any other snapshot method we care about)
|
||||||
|
/// Returns the identifier that is the snapshot invocation, and its range.
|
||||||
|
let private isSnapshotCall (funcExpr : SynExpr) : (string * Range) option =
|
||||||
|
match funcExpr with
|
||||||
|
| SynExpr.Ident ident when snapshotSignifier.Contains ident.idText -> Some (ident.idText, ident.idRange)
|
||||||
|
| SynExpr.LongIdent (_, longIdent, _, _) ->
|
||||||
|
match longIdent.IdentsWithTrivia with
|
||||||
|
| [] -> None
|
||||||
|
| ids ->
|
||||||
|
match List.last ids with
|
||||||
|
| SynIdent.SynIdent (ident, _) ->
|
||||||
|
if snapshotSignifier.Contains ident.idText then
|
||||||
|
Some (ident.idText, ident.idRange)
|
||||||
|
else
|
||||||
|
None
|
||||||
|
| _ -> None
|
||||||
|
|
||||||
|
/// Extract the argument from a method application
|
||||||
|
let private getMethodArgument (expr : SynExpr) =
|
||||||
|
match expr with
|
||||||
|
| SynExpr.App (_, _, _, argExpr, _) -> Some argExpr
|
||||||
|
| _ -> None
|
||||||
|
|
||||||
|
/// Walk expressions looking for our target
|
||||||
|
let rec findSnapshotListCalls (targetLine : int) (methodName : string) (expr : SynExpr) : SnapshotLocation list =
|
||||||
|
match expr with
|
||||||
|
// Direct method application
|
||||||
|
| SynExpr.App (_, _, funcExpr, argExpr, range) ->
|
||||||
|
match isSnapshotCall funcExpr with
|
||||||
|
| Some (keyword, keywordRange) ->
|
||||||
|
if range.StartLine <= targetLine && targetLine <= range.EndLine then
|
||||||
|
match argExpr with
|
||||||
|
| SynExpr.ArrayOrList (isList, _, argRange) when isList ->
|
||||||
|
// It's a list literal
|
||||||
|
[
|
||||||
|
{
|
||||||
|
ReplacementRange = argRange
|
||||||
|
KeywordRange = keywordRange
|
||||||
|
Keyword = keyword
|
||||||
|
}
|
||||||
|
] // Text will be extracted separately
|
||||||
|
| SynExpr.ArrayOrListComputed (isArray, _inner, argRange) when not isArray ->
|
||||||
|
// It's a list comprehension
|
||||||
|
[
|
||||||
|
{
|
||||||
|
ReplacementRange = argRange
|
||||||
|
KeywordRange = keywordRange
|
||||||
|
Keyword = keyword
|
||||||
|
}
|
||||||
|
]
|
||||||
|
| _ ->
|
||||||
|
// It could be a variable reference or other expression
|
||||||
|
[
|
||||||
|
{
|
||||||
|
ReplacementRange = argExpr.Range
|
||||||
|
KeywordRange = keywordRange
|
||||||
|
Keyword = keyword
|
||||||
|
}
|
||||||
|
]
|
||||||
|
else
|
||||||
|
[]
|
||||||
|
| None ->
|
||||||
|
// Other app variations
|
||||||
|
findSnapshotListCalls targetLine methodName funcExpr
|
||||||
|
@ findSnapshotListCalls targetLine methodName argExpr
|
||||||
|
|
||||||
|
// Nested in paren
|
||||||
|
| SynExpr.Paren (innerExpr, _, _, _) -> findSnapshotListCalls targetLine methodName innerExpr
|
||||||
|
|
||||||
|
// Sequential expressions (e.g., in a do block)
|
||||||
|
| SynExpr.Sequential (_, _, expr1, expr2, _, _) ->
|
||||||
|
findSnapshotListCalls targetLine methodName expr1
|
||||||
|
@ findSnapshotListCalls targetLine methodName expr2
|
||||||
|
|
||||||
|
// Let bindings
|
||||||
|
| SynExpr.LetOrUse (_, _, bindings, bodyExpr, _, _) ->
|
||||||
|
let bindingResults =
|
||||||
|
bindings
|
||||||
|
|> List.collect (fun binding ->
|
||||||
|
match binding with
|
||||||
|
| SynBinding (expr = expr) -> findSnapshotListCalls targetLine methodName expr
|
||||||
|
)
|
||||||
|
|
||||||
|
bindingResults @ findSnapshotListCalls targetLine methodName bodyExpr
|
||||||
|
|
||||||
|
// Match expressions
|
||||||
|
| SynExpr.Match (_, _, clauses, _, _) ->
|
||||||
|
clauses
|
||||||
|
|> List.collect (fun (SynMatchClause (resultExpr = expr)) ->
|
||||||
|
findSnapshotListCalls targetLine methodName expr
|
||||||
|
)
|
||||||
|
|
||||||
|
// If/then/else
|
||||||
|
| SynExpr.IfThenElse (_, thenExpr, elseExprOpt, _, _, _, _) ->
|
||||||
|
let thenResults = findSnapshotListCalls targetLine methodName thenExpr
|
||||||
|
|
||||||
|
let elseResults =
|
||||||
|
match elseExprOpt with
|
||||||
|
| Some elseExpr -> findSnapshotListCalls targetLine methodName elseExpr
|
||||||
|
| None -> []
|
||||||
|
|
||||||
|
thenResults @ elseResults
|
||||||
|
|
||||||
|
// Lambda
|
||||||
|
| SynExpr.Lambda (body = bodyExpr) -> findSnapshotListCalls targetLine methodName bodyExpr
|
||||||
|
|
||||||
|
// Computation expression
|
||||||
|
| SynExpr.ComputationExpr (_, innerExpr, _) -> findSnapshotListCalls targetLine methodName innerExpr
|
||||||
|
|
||||||
|
// Default case - no results
|
||||||
|
| _ -> []
|
||||||
|
|
||||||
|
/// Walk a module or namespace looking for expressions
|
||||||
|
let rec findInModuleDecls (targetLine : int) (methodName : string) (decls : SynModuleDecl list) =
|
||||||
|
decls
|
||||||
|
|> List.collect (fun decl ->
|
||||||
|
match decl with
|
||||||
|
| SynModuleDecl.Let (_, bindings, _) ->
|
||||||
|
bindings
|
||||||
|
|> List.collect (fun binding ->
|
||||||
|
match binding with
|
||||||
|
| SynBinding (expr = expr) -> findSnapshotListCalls targetLine methodName expr
|
||||||
|
)
|
||||||
|
|
||||||
|
| SynModuleDecl.Expr (expr, _) -> findSnapshotListCalls targetLine methodName expr
|
||||||
|
|
||||||
|
| SynModuleDecl.NestedModule (decls = nestedDecls) -> findInModuleDecls targetLine methodName nestedDecls
|
||||||
|
|
||||||
|
| SynModuleDecl.Types (typeDefs, _) ->
|
||||||
|
typeDefs
|
||||||
|
|> List.collect (fun typeDef ->
|
||||||
|
match typeDef with
|
||||||
|
| SynTypeDefn (typeRepr = SynTypeDefnRepr.ObjectModel (members = members)) ->
|
||||||
|
members
|
||||||
|
|> List.collect (fun member' ->
|
||||||
|
match member' with
|
||||||
|
| SynMemberDefn.Member (memberBinding, _) ->
|
||||||
|
match memberBinding with
|
||||||
|
| SynBinding (expr = expr) -> findSnapshotListCalls targetLine methodName expr
|
||||||
|
| _ -> []
|
||||||
|
)
|
||||||
|
| _ -> []
|
||||||
|
)
|
||||||
|
|
||||||
|
| SynModuleDecl.HashDirective _
|
||||||
|
| SynModuleDecl.Attributes _
|
||||||
|
| SynModuleDecl.ModuleAbbrev _
|
||||||
|
| SynModuleDecl.Exception _
|
||||||
|
| SynModuleDecl.Open _ -> []
|
||||||
|
| SynModuleDecl.NamespaceFragment (SynModuleOrNamespace (decls = decls)) ->
|
||||||
|
findInModuleDecls targetLine methodName decls
|
||||||
|
)
|
||||||
|
|
||||||
|
/// Main function to find snapshot list locations
|
||||||
|
let findSnapshotList
|
||||||
|
(infoFilePath : string)
|
||||||
|
(lines : string[])
|
||||||
|
(lineNumber : int)
|
||||||
|
(methodName : string) // e.g., "snapshotList"
|
||||||
|
: SnapshotLocation
|
||||||
|
=
|
||||||
|
let sourceText = SourceText.ofString (String.concat "\n" lines)
|
||||||
|
|
||||||
|
// Parse the file
|
||||||
|
let parsedInput, diagnostics = Fantomas.FCS.Parse.parseFile false sourceText []
|
||||||
|
|
||||||
|
// Check for parse errors
|
||||||
|
if
|
||||||
|
diagnostics
|
||||||
|
|> List.exists (fun d -> d.Severity = FSharpDiagnosticSeverity.Error)
|
||||||
|
then
|
||||||
|
failwithf $"Parse errors in file %s{infoFilePath}: %A{diagnostics}"
|
||||||
|
|
||||||
|
// Walk the AST
|
||||||
|
let results =
|
||||||
|
match parsedInput with
|
||||||
|
| ParsedInput.ImplFile (ParsedImplFileInput (contents = modules)) ->
|
||||||
|
modules
|
||||||
|
|> List.collect (fun moduleOrNs ->
|
||||||
|
match moduleOrNs with
|
||||||
|
| SynModuleOrNamespace (decls = decls) -> findInModuleDecls lineNumber methodName decls
|
||||||
|
)
|
||||||
|
| ParsedInput.SigFile _ -> failwith "unexpected: signature files can't contain expressions"
|
||||||
|
|
||||||
|
// Find the closest match
|
||||||
|
results
|
||||||
|
|> Seq.filter (fun loc ->
|
||||||
|
loc.KeywordRange.StartLine <= lineNumber
|
||||||
|
&& lineNumber <= loc.KeywordRange.EndLine
|
||||||
|
)
|
||||||
|
|> Seq.exactlyOne
|
@@ -1,5 +1,6 @@
|
|||||||
namespace WoofWare.Expect
|
namespace WoofWare.Expect
|
||||||
|
|
||||||
|
open System.Collections.Generic
|
||||||
open System.IO
|
open System.IO
|
||||||
open System.Runtime.CompilerServices
|
open System.Runtime.CompilerServices
|
||||||
open System.Text.Json
|
open System.Text.Json
|
||||||
@@ -32,6 +33,61 @@ type ExpectBuilder (mode : Mode) =
|
|||||||
else
|
else
|
||||||
ExpectBuilder Mode.Assert
|
ExpectBuilder Mode.Assert
|
||||||
|
|
||||||
|
/// Combine two `ExpectStateListy`s. The first one is the "expected" snapshot; the second is the "actual".
|
||||||
|
member _.Bind<'U> (state : ExpectStateListy<'U>, f : unit -> ExpectStateListy<'U>) : ExpectStateListy<'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 ->
|
||||||
|
|
||||||
|
let formatter =
|
||||||
|
match state.Formatter, actual.Formatter with
|
||||||
|
| None, f -> f
|
||||||
|
| Some f, None -> Some f
|
||||||
|
| Some _, Some _ -> failwith "multiple formatters supplied for a single expect!"
|
||||||
|
|
||||||
|
{
|
||||||
|
Formatter = formatter
|
||||||
|
Snapshot = state.Snapshot
|
||||||
|
Actual = actual.Actual
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Combine an `ExpectStateListy` with an `ExpectState`. The first one is the "expected" snapshot; the second is
|
||||||
|
/// the "actual".
|
||||||
|
member _.Bind<'U, 'elt when 'U :> IEnumerable<'elt>>
|
||||||
|
(state : ExpectStateListy<'elt>, f : unit -> ExpectState<'U>)
|
||||||
|
: ExpectStateListy<'elt>
|
||||||
|
=
|
||||||
|
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 ->
|
||||||
|
|
||||||
|
let formatter : ((unit -> 'elt) -> string) option =
|
||||||
|
match state.Formatter, actual.Formatter with
|
||||||
|
| None, None -> None
|
||||||
|
| None, Some _ ->
|
||||||
|
failwith
|
||||||
|
"unexpectedly had a formatter supplied before the snapshotList keyword; I thought this was impossible"
|
||||||
|
| Some f, None -> Some f
|
||||||
|
| Some _, Some _ -> failwith "multiple formatters supplied for a single expect!"
|
||||||
|
|
||||||
|
{
|
||||||
|
Formatter = formatter
|
||||||
|
Snapshot = state.Snapshot
|
||||||
|
Actual = actual.Actual |> Option.map (fun f () -> Seq.cast<'elt> (f ()))
|
||||||
|
}
|
||||||
|
|
||||||
/// Combine two `ExpectState`s. The first one is the "expected" snapshot; the second is the "actual".
|
/// Combine two `ExpectState`s. The first one is the "expected" snapshot; the second is the "actual".
|
||||||
member _.Bind<'U> (state : ExpectState<'U>, f : unit -> ExpectState<'U>) : ExpectState<'U> =
|
member _.Bind<'U> (state : ExpectState<'U>, f : unit -> ExpectState<'U>) : ExpectState<'U> =
|
||||||
let actual = f ()
|
let actual = f ()
|
||||||
@@ -71,6 +127,40 @@ type ExpectBuilder (mode : Mode) =
|
|||||||
JsonDocOptions = jsonDocOptions
|
JsonDocOptions = jsonDocOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Express that the actual value's <c>ToString</c> should identically equal this string.</summary>
|
||||||
|
[<CustomOperation("snapshot", MaintainsVariableSpaceUsingBind = true)>]
|
||||||
|
member _.Snapshot<'a>
|
||||||
|
(
|
||||||
|
state : ExpectStateListy<'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
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Formatter = state.Formatter
|
||||||
|
JsonSerialiserOptions = None
|
||||||
|
JsonDocOptions = None
|
||||||
|
Snapshot = Some (SnapshotValue.Formatted snapshot, callerInfo)
|
||||||
|
Actual = None
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Express that the actual value's <c>ToString</c> should identically equal this string.</summary>
|
/// <summary>Express that the actual value's <c>ToString</c> should identically equal this string.</summary>
|
||||||
[<CustomOperation("snapshot", MaintainsVariableSpaceUsingBind = true)>]
|
[<CustomOperation("snapshot", MaintainsVariableSpaceUsingBind = true)>]
|
||||||
member _.Snapshot<'a>
|
member _.Snapshot<'a>
|
||||||
@@ -102,6 +192,46 @@ type ExpectBuilder (mode : Mode) =
|
|||||||
Actual = None
|
Actual = None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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>snapshotJson "123"</c> indicates the JSON integer 123.
|
||||||
|
/// </remarks>
|
||||||
|
[<CustomOperation("snapshotJson", MaintainsVariableSpaceUsingBind = true)>]
|
||||||
|
member this.SnapshotJson<'a>
|
||||||
|
(
|
||||||
|
state : ExpectStateListy<unit>,
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Formatter = None
|
||||||
|
JsonSerialiserOptions = None
|
||||||
|
JsonDocOptions = None
|
||||||
|
Snapshot = Some (SnapshotValue.Json snapshot, callerInfo)
|
||||||
|
Actual = None
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Express that the actual value, when converted to JSON, should result in a JSON document
|
/// Express that the actual value, when converted to JSON, should result in a JSON document
|
||||||
/// which matches the JSON document that is this string.
|
/// which matches the JSON document that is this string.
|
||||||
@@ -142,6 +272,45 @@ type ExpectBuilder (mode : Mode) =
|
|||||||
Actual = None
|
Actual = None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Express that the actual value, which is a sequence, should have elements which individually (in order) match
|
||||||
|
/// this snapshot list.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// For example, <c>snapshotList ["123" ; "456"]</c> indicates an exactly-two-element list <c>[123 ; 456]</c>.
|
||||||
|
/// </remarks>
|
||||||
|
[<CustomOperation("snapshotList", MaintainsVariableSpaceUsingBind = true)>]
|
||||||
|
member _.SnapshotList<'a>
|
||||||
|
(
|
||||||
|
state : ExpectStateListy<unit>,
|
||||||
|
snapshot : string list,
|
||||||
|
[<CallerMemberName>] ?memberName : string,
|
||||||
|
[<CallerLineNumber>] ?callerLine : int,
|
||||||
|
[<CallerFilePath>] ?filePath : string
|
||||||
|
)
|
||||||
|
: ExpectStateListy<'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
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Formatter = None
|
||||||
|
Snapshot = Some (snapshot, callerInfo)
|
||||||
|
Actual = None
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Expresses that the given expression throws during evaluation.
|
/// Expresses that the given expression throws during evaluation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -187,10 +356,54 @@ type ExpectBuilder (mode : Mode) =
|
|||||||
Actual = None
|
Actual = None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Expresses that the given expression throws during evaluation.
|
||||||
|
/// </summary>
|
||||||
|
/// <example>
|
||||||
|
/// <code>
|
||||||
|
/// expect {
|
||||||
|
/// snapshotThrows @"System.Exception: oh no"
|
||||||
|
/// return! (fun () -> failwith "oh no")
|
||||||
|
/// }
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
|
[<CustomOperation("snapshotThrows", MaintainsVariableSpaceUsingBind = true)>]
|
||||||
|
member _.SnapshotThrows<'a>
|
||||||
|
(
|
||||||
|
state : ExpectStateListy<'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
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Formatter = None
|
||||||
|
JsonSerialiserOptions = None
|
||||||
|
JsonDocOptions = None
|
||||||
|
Snapshot = Some (SnapshotValue.ThrowsException snapshot, callerInfo)
|
||||||
|
Actual = None
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Express that the <c>return</c> value of this builder should be formatted using this function, before
|
/// Express that the <c>return</c> value of this builder should be formatted using this function, before
|
||||||
/// comparing to the snapshot.
|
/// comparing to the snapshot.
|
||||||
/// this value.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// For example, <c>withFormat (fun x -> x.ToString ()) "123"</c> is equivalent to <c>snapshot "123"</c>.
|
/// For example, <c>withFormat (fun x -> x.ToString ()) "123"</c> is equivalent to <c>snapshot "123"</c>.
|
||||||
@@ -204,6 +417,23 @@ type ExpectBuilder (mode : Mode) =
|
|||||||
Formatter = Some (fun f -> f () |> formatter)
|
Formatter = Some (fun f -> f () |> formatter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Express that the <c>return</c> value of this builder should be formatted using this function, before
|
||||||
|
/// comparing to the snapshot.
|
||||||
|
/// In the case of <c>snapshotList</c>, this applies to the elements of the sequence, not to the sequence itself.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// For example, <c>withFormat (fun x -> x.ToString ()) "123"</c> is equivalent to <c>snapshot "123"</c>.
|
||||||
|
/// </remarks>
|
||||||
|
[<CustomOperation("withFormat", MaintainsVariableSpaceUsingBind = true)>]
|
||||||
|
member _.WithFormat<'T> (state : ExpectStateListy<'T>, formatter : 'T -> string) =
|
||||||
|
match state.Formatter with
|
||||||
|
| Some _ -> failwith "Please don't supply withFormat more than once"
|
||||||
|
| None ->
|
||||||
|
{ state with
|
||||||
|
Formatter = Some (fun f -> f () |> formatter)
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Express that these JsonSerializerOptions should be used to construct the JSON object to which the snapshot
|
/// Express that these JsonSerializerOptions should be used to construct the JSON object to which the snapshot
|
||||||
/// is to be compared (or, in write-out-the-snapshot mode, to construct the JSON object to be written out).
|
/// is to be compared (or, in write-out-the-snapshot mode, to construct the JSON object to be written out).
|
||||||
@@ -227,6 +457,42 @@ type ExpectBuilder (mode : Mode) =
|
|||||||
JsonSerialiserOptions = Some jsonOptions
|
JsonSerialiserOptions = Some jsonOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Express that these JsonSerializerOptions should be used to construct the JSON object to which the snapshot
|
||||||
|
/// is to be compared (or, in write-out-the-snapshot mode, to construct the JSON object to be written out).
|
||||||
|
/// </summary>
|
||||||
|
/// <example>
|
||||||
|
/// If you want your snapshots to be written out compactly, rather than the default indenting:
|
||||||
|
/// <code>
|
||||||
|
/// expect {
|
||||||
|
/// snapshotJson @"{""a"":3}"
|
||||||
|
/// withJsonSerializerOptions (JsonSerializerOptions (WriteIndented = false))
|
||||||
|
/// return Map.ofList ["a", 3]
|
||||||
|
/// }
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
|
[<CustomOperation("withJsonSerializerOptions", MaintainsVariableSpaceUsingBind = true)>]
|
||||||
|
member _.WithJsonSerializerOptions<'T> (state : ExpectStateListy<'T>, jsonOptions : JsonSerializerOptions) =
|
||||||
|
match state.Snapshot with
|
||||||
|
| Some _ ->
|
||||||
|
failwith
|
||||||
|
"I don't know how you've managed to do this; please raise an issue against github.com/Smaug123/WoofWare.Expect . Somehow withJsonSerializerOptions has already got a snapshot."
|
||||||
|
| None ->
|
||||||
|
|
||||||
|
match state.Actual with
|
||||||
|
| Some _ ->
|
||||||
|
failwith
|
||||||
|
"I don't know how you've managed to do this; please raise an issue against github.com/Smaug123/WoofWare.Expect . Somehow withJsonSerializerOptions has already got an expected-value."
|
||||||
|
| None ->
|
||||||
|
|
||||||
|
{
|
||||||
|
Formatter = state.Formatter
|
||||||
|
JsonSerialiserOptions = Some jsonOptions
|
||||||
|
JsonDocOptions = None
|
||||||
|
Snapshot = None
|
||||||
|
Actual = None
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Express that these JsonDocumentOptions should be used when parsing the snapshot string into a JSON object.
|
/// Express that these JsonDocumentOptions should be used when parsing the snapshot string into a JSON object.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -258,23 +524,21 @@ type ExpectBuilder (mode : Mode) =
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// MaintainsVariableSpaceUsingBind causes this to be used; it's a dummy representing "no snapshot and no assertion".
|
/// MaintainsVariableSpaceUsingBind causes this to be used; it's a dummy representing "no snapshot and no assertion".
|
||||||
member _.Return (() : unit) : ExpectState<'T> =
|
member _.Return (() : unit) : ExpectStateListy<'T> =
|
||||||
{
|
{
|
||||||
Formatter = None
|
Formatter = None
|
||||||
JsonSerialiserOptions = None
|
|
||||||
JsonDocOptions = None
|
|
||||||
Snapshot = None
|
Snapshot = None
|
||||||
Actual = None
|
Actual = None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Expresses the "actual value" component of the assertion "expected snapshot = actual value".
|
/// Expresses the "actual value" component of the assertion "expected snapshot = actual value".
|
||||||
member _.Return (value : 'T) : ExpectState<'T> =
|
member _.Return<'T> (value : 'T) : ExpectState<'T> =
|
||||||
{
|
{
|
||||||
Snapshot = None
|
Snapshot = None
|
||||||
Formatter = None
|
Formatter = None
|
||||||
JsonDocOptions = None
|
|
||||||
JsonSerialiserOptions = None
|
|
||||||
Actual = Some (fun () -> value)
|
Actual = Some (fun () -> value)
|
||||||
|
JsonSerialiserOptions = None
|
||||||
|
JsonDocOptions = None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Expresses the "actual value" component of the assertion "expected snapshot = actual value", but delayed behind
|
/// Expresses the "actual value" component of the assertion "expected snapshot = actual value", but delayed behind
|
||||||
@@ -291,6 +555,56 @@ type ExpectBuilder (mode : Mode) =
|
|||||||
/// Computation expression `Delay`.
|
/// Computation expression `Delay`.
|
||||||
member _.Delay (f : unit -> ExpectState<'T>) : unit -> ExpectState<'T> = f
|
member _.Delay (f : unit -> ExpectState<'T>) : unit -> ExpectState<'T> = f
|
||||||
|
|
||||||
|
/// Computation expression `Delay`.
|
||||||
|
member _.Delay (f : unit -> ExpectStateListy<'T>) : unit -> ExpectStateListy<'T> = f
|
||||||
|
|
||||||
|
/// Computation expression `Run`, which runs a `Delay`ed snapshot assertion, throwing if the assertion fails.
|
||||||
|
member _.Run (f : unit -> ExpectStateListy<'T>) : unit =
|
||||||
|
let state = f () |> CompletedListSnapshotGeneric.make
|
||||||
|
|
||||||
|
let lines = lazy File.ReadAllLines state.Caller.FilePath
|
||||||
|
|
||||||
|
let listSource =
|
||||||
|
lazy
|
||||||
|
AstWalker.findSnapshotList
|
||||||
|
state.Caller.FilePath
|
||||||
|
(lines.Force ())
|
||||||
|
state.Caller.LineNumber
|
||||||
|
state.Caller.MemberName
|
||||||
|
|
||||||
|
let raiseError expected actual =
|
||||||
|
let diff =
|
||||||
|
Diff.patienceLines (Array.ofList expected) (Array.ofList actual) |> Diff.format
|
||||||
|
|
||||||
|
match mode with
|
||||||
|
| Mode.Assert ->
|
||||||
|
if GlobalBuilderConfig.isBulkUpdateMode () then
|
||||||
|
GlobalBuilderConfig.registerTest (CompletedSnapshot.makeFromAst (listSource.Force ()) state)
|
||||||
|
else
|
||||||
|
$"snapshot mismatch! snapshot at %s{state.Caller.FilePath}:%i{state.Caller.LineNumber} (%s{state.Caller.MemberName}) diff:\n%s{diff}"
|
||||||
|
|> ExpectException
|
||||||
|
|> raise
|
||||||
|
| Mode.AssertMockingSource (mockSource, line) ->
|
||||||
|
$"snapshot mismatch! snapshot at %s{mockSource}:%i{line} (%s{state.Caller.MemberName}) diff:\n%s{diff}"
|
||||||
|
|> ExpectException
|
||||||
|
|> raise
|
||||||
|
| Mode.Update ->
|
||||||
|
let lines = lines.Force ()
|
||||||
|
let listSource = listSource.Force ()
|
||||||
|
let result = SnapshotUpdate.updateAtLocation listSource lines actual
|
||||||
|
File.writeAllLines result state.Caller.FilePath
|
||||||
|
failwith ("Snapshot successfully updated. Previous contents:\n" + String.concat "\n" lines)
|
||||||
|
|
||||||
|
match CompletedListSnapshotGeneric.passesAssertion state with
|
||||||
|
| None ->
|
||||||
|
match mode, GlobalBuilderConfig.isBulkUpdateMode () with
|
||||||
|
| Mode.Update, _
|
||||||
|
| _, true ->
|
||||||
|
failwith
|
||||||
|
"Snapshot assertion passed, but we are in snapshot-updating mode. Use the `expect` builder instead of `expect'` to assert the contents of a single snapshot; disable `GlobalBuilderConfig.bulkUpdate` to move back to assertion-checking mode."
|
||||||
|
| _ -> ()
|
||||||
|
| Some (expected, actual) -> raiseError expected actual
|
||||||
|
|
||||||
/// Computation expression `Run`, which runs a `Delay`ed snapshot assertion, throwing if the assertion fails.
|
/// Computation expression `Run`, which runs a `Delay`ed snapshot assertion, throwing if the assertion fails.
|
||||||
member _.Run (f : unit -> ExpectState<'T>) : unit =
|
member _.Run (f : unit -> ExpectState<'T>) : unit =
|
||||||
let state = f () |> CompletedSnapshotGeneric.make
|
let state = f () |> CompletedSnapshotGeneric.make
|
||||||
@@ -310,7 +624,7 @@ type ExpectBuilder (mode : Mode) =
|
|||||||
|> raise
|
|> raise
|
||||||
| Mode.Assert ->
|
| Mode.Assert ->
|
||||||
if GlobalBuilderConfig.isBulkUpdateMode () then
|
if GlobalBuilderConfig.isBulkUpdateMode () then
|
||||||
GlobalBuilderConfig.registerTest state
|
GlobalBuilderConfig.registerTest (CompletedSnapshot.makeGuess state)
|
||||||
else
|
else
|
||||||
let diff = Diff.patience snapshot actual
|
let diff = Diff.patience snapshot actual
|
||||||
|
|
||||||
|
@@ -43,8 +43,7 @@ module GlobalBuilderConfig =
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
let clearTests () = lock locker allTests.Clear
|
let clearTests () = lock locker allTests.Clear
|
||||||
|
|
||||||
let internal registerTest (s : CompletedSnapshotGeneric<'T>) : unit =
|
let internal registerTest (toAdd : CompletedSnapshot) : unit =
|
||||||
let toAdd = s |> CompletedSnapshot.make
|
|
||||||
lock locker (fun () -> allTests.Add toAdd)
|
lock locker (fun () -> allTests.Add toAdd)
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@@ -11,45 +11,50 @@ type Position = int<pos>
|
|||||||
|
|
||||||
/// A Patience diff is composed of a sequence of transformations to get from one string to another.
|
/// A Patience diff is composed of a sequence of transformations to get from one string to another.
|
||||||
/// This represents those transformations.
|
/// This represents those transformations.
|
||||||
type DiffOperation =
|
type DiffOperation<'line> =
|
||||||
/// This line is the same on both sides of the diff.
|
/// This line is the same on both sides of the diff.
|
||||||
/// On the left, it appears at position posA. On the right, at position posB.
|
/// On the left, it appears at position posA. On the right, at position posB.
|
||||||
| Match of posA : Position * posB : Position * line : string
|
| Match of posA : Position * posB : Position * line : 'line
|
||||||
/// Delete this line, which is at this position.
|
/// Delete this line, which is at this position.
|
||||||
| Delete of posA : Position * line : string
|
| Delete of posA : Position * line : 'line
|
||||||
/// Insert this line at the given position.
|
/// Insert this line at the given position.
|
||||||
| Insert of posB : Position * line : string
|
| Insert of posB : Position * line : 'line
|
||||||
|
|
||||||
|
/// The diff between two line-oriented streams. Normally the generic parameter will be `string`, indicating
|
||||||
|
/// that the thing being diffed was text.
|
||||||
|
type Diff'<'line> = private | Diff of DiffOperation<'line> list
|
||||||
|
|
||||||
/// The diff between two line-oriented pieces of text.
|
/// The diff between two line-oriented pieces of text.
|
||||||
type Diff = private | Diff of DiffOperation list
|
type Diff = Diff'<string>
|
||||||
|
|
||||||
/// A match between positions in two sequences
|
/// A match between positions in two sequences
|
||||||
type internal LineMatch =
|
type internal LineMatch<'line> =
|
||||||
{
|
{
|
||||||
PosA : Position
|
PosA : Position
|
||||||
PosB : Position
|
PosB : Position
|
||||||
Line : string
|
Line : 'line
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Result of finding unique lines in a sequence
|
/// Result of finding unique lines in a sequence
|
||||||
type internal UniqueLines =
|
type internal UniqueLines<'line when 'line : comparison> =
|
||||||
{
|
{
|
||||||
/// Map from line content to its position (only for unique lines)
|
/// Map from line content to its position (only for unique lines)
|
||||||
LinePositions : Map<string, Position>
|
LinePositions : Map<'line, Position>
|
||||||
/// All line counts (for verification)
|
/// All line counts (for verification)
|
||||||
LineCounts : Map<string, int>
|
LineCounts : Map<'line, int>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The diff between two line-oriented pieces of text.
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
module Diff =
|
module Diff =
|
||||||
/// Find lines that appear exactly once in a sequence
|
/// Find lines that appear exactly once in a sequence
|
||||||
let private findUniqueLines (lines : string array) : UniqueLines =
|
let private findUniqueLines (lines : 'line array) : UniqueLines<'line> =
|
||||||
let positions = Dictionary<string, Position> ()
|
let positions = Dictionary<'line, Position> ()
|
||||||
let counts = Dictionary<string, int> ()
|
let counts = Dictionary<'line, int> ()
|
||||||
|
|
||||||
lines
|
lines
|
||||||
|> Array.iteri (fun i line ->
|
|> Array.iteri (fun i line ->
|
||||||
if counts.ContainsKey (line) then
|
if counts.ContainsKey line then
|
||||||
counts.[line] <- counts.[line] + 1
|
counts.[line] <- counts.[line] + 1
|
||||||
else
|
else
|
||||||
counts.[line] <- 1
|
counts.[line] <- 1
|
||||||
@@ -70,7 +75,7 @@ module Diff =
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Find longest increasing subsequence based on B positions
|
/// Find longest increasing subsequence based on B positions
|
||||||
let private longestIncreasingSubsequence (matches : LineMatch array) : LineMatch list =
|
let private longestIncreasingSubsequence (matches : LineMatch<'line> array) : LineMatch<'line> list =
|
||||||
let n = matches.Length
|
let n = matches.Length
|
||||||
|
|
||||||
if n = 0 then
|
if n = 0 then
|
||||||
@@ -103,9 +108,8 @@ module Diff =
|
|||||||
|
|
||||||
reconstruct endIndex []
|
reconstruct endIndex []
|
||||||
|
|
||||||
/// Simple Myers diff implementation. You probably want to use `patience` instead, for more human-readable diffs.
|
let private myers' (a : 'line array) (b : 'line array) : DiffOperation<'line> list =
|
||||||
let myers (a : string array) (b : string array) : Diff =
|
let rec diffHelper (i : int) (j : int) (acc : DiffOperation<'line> list) =
|
||||||
let rec diffHelper (i : int) (j : int) (acc : DiffOperation list) =
|
|
||||||
match i < a.Length, j < b.Length with
|
match i < a.Length, j < b.Length with
|
||||||
| false, false -> List.rev acc
|
| false, false -> List.rev acc
|
||||||
| true, false ->
|
| true, false ->
|
||||||
@@ -146,11 +150,14 @@ module Diff =
|
|||||||
// No close match, just delete and insert
|
// No close match, just delete and insert
|
||||||
diffHelper (i + 1) j (Delete (i * 1<pos>, a.[i]) :: acc)
|
diffHelper (i + 1) j (Delete (i * 1<pos>, a.[i]) :: acc)
|
||||||
|
|
||||||
diffHelper 0 0 [] |> Diff
|
diffHelper 0 0 []
|
||||||
|
|
||||||
|
/// Simple Myers diff implementation. You probably want to use `patience` instead, for more human-readable diffs.
|
||||||
|
let myers (a : string array) (b : string array) : Diff = myers' a b |> Diff
|
||||||
|
|
||||||
/// Patience diff: a human-readable line-based diff.
|
/// Patience diff: a human-readable line-based diff.
|
||||||
/// Operates on lines of string; the function `patience` will split on lines for you.
|
/// Operates on lines of string; the function `patience` will split on lines for you.
|
||||||
let rec patienceLines (a : string array) (b : string array) : Diff =
|
let rec patienceLines (a : 'line array) (b : 'line array) : Diff'<'line> =
|
||||||
// Handle empty sequences
|
// Handle empty sequences
|
||||||
match a.Length, b.Length with
|
match a.Length, b.Length with
|
||||||
| 0, 0 -> [] |> Diff
|
| 0, 0 -> [] |> Diff
|
||||||
@@ -177,7 +184,7 @@ module Diff =
|
|||||||
|
|
||||||
if Set.isEmpty commonUniques then
|
if Set.isEmpty commonUniques then
|
||||||
// No unique common lines, fall back to Myers
|
// No unique common lines, fall back to Myers
|
||||||
myers a b
|
myers' a b |> Diff
|
||||||
else
|
else
|
||||||
// Build matches for unique common lines
|
// Build matches for unique common lines
|
||||||
let matches =
|
let matches =
|
||||||
@@ -196,7 +203,7 @@ module Diff =
|
|||||||
let anchorMatches = longestIncreasingSubsequence matches |> List.toArray
|
let anchorMatches = longestIncreasingSubsequence matches |> List.toArray
|
||||||
|
|
||||||
// Build diff imperatively
|
// Build diff imperatively
|
||||||
let result = ResizeArray<DiffOperation> ()
|
let result = ResizeArray<DiffOperation<_>> ()
|
||||||
let mutable prevA = 0<pos>
|
let mutable prevA = 0<pos>
|
||||||
let mutable prevB = 0<pos>
|
let mutable prevB = 0<pos>
|
||||||
|
|
||||||
@@ -244,26 +251,32 @@ module Diff =
|
|||||||
patienceLines (a.Split '\n') (b.Split '\n')
|
patienceLines (a.Split '\n') (b.Split '\n')
|
||||||
|
|
||||||
/// Format the diff as a human-readable string, including line numbers at the left.
|
/// Format the diff as a human-readable string, including line numbers at the left.
|
||||||
let formatWithLineNumbers (Diff ops) : string =
|
let formatWithLineNumbers' (formatter : 'line -> string) (Diff ops) : string =
|
||||||
ops
|
ops
|
||||||
|> List.map (fun op ->
|
|> List.map (fun op ->
|
||||||
match op with
|
match op with
|
||||||
| Match (a, b, line) -> sprintf " %3d %3d %s" a b line
|
| Match (a, b, line) -> sprintf " %3d %3d %s" a b (formatter line)
|
||||||
| Delete (a, line) -> sprintf "- %3d %s" a line
|
| Delete (a, line) -> sprintf "- %3d %s" a (formatter line)
|
||||||
| Insert (b, line) -> sprintf "+ %3d %s" b line
|
| Insert (b, line) -> sprintf "+ %3d %s" b (formatter line)
|
||||||
|
)
|
||||||
|
|> String.concat "\n"
|
||||||
|
|
||||||
|
/// Format the diff as a human-readable string, including line numbers at the left.
|
||||||
|
let formatWithLineNumbers (d : Diff) : string = formatWithLineNumbers' id d
|
||||||
|
|
||||||
|
/// Format the diff as a human-readable string.
|
||||||
|
let format' (formatter : 'line -> string) (Diff ops) : string =
|
||||||
|
ops
|
||||||
|
|> List.map (fun op ->
|
||||||
|
match op with
|
||||||
|
| Match (_, _, line) -> " " + (formatter line)
|
||||||
|
| Delete (_, line) -> "- " + (formatter line)
|
||||||
|
| Insert (_, line) -> "+ " + (formatter line)
|
||||||
)
|
)
|
||||||
|> String.concat "\n"
|
|> String.concat "\n"
|
||||||
|
|
||||||
/// Format the diff as a human-readable string.
|
/// Format the diff as a human-readable string.
|
||||||
let format (Diff ops) : string =
|
let format (ops : Diff) : string = format' id ops
|
||||||
ops
|
|
||||||
|> List.map (fun op ->
|
|
||||||
match op with
|
|
||||||
| Match (_, _, line) -> sprintf " %s" line
|
|
||||||
| Delete (_, line) -> sprintf "- %s" line
|
|
||||||
| Insert (_, line) -> sprintf "+ %s" line
|
|
||||||
)
|
|
||||||
|> String.concat "\n"
|
|
||||||
|
|
||||||
/// Compute diff statistics
|
/// Compute diff statistics
|
||||||
type internal DiffStats =
|
type internal DiffStats =
|
||||||
@@ -274,7 +287,7 @@ module Diff =
|
|||||||
TotalOperations : int
|
TotalOperations : int
|
||||||
}
|
}
|
||||||
|
|
||||||
let internal computeStats (ops : DiffOperation list) : DiffStats =
|
let internal computeStats (ops : DiffOperation<'a> list) : DiffStats =
|
||||||
let counts =
|
let counts =
|
||||||
ops
|
ops
|
||||||
|> List.fold
|
|> List.fold
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
namespace WoofWare.Expect
|
namespace WoofWare.Expect
|
||||||
|
|
||||||
|
open System.Collections
|
||||||
open System.Text.Json
|
open System.Text.Json
|
||||||
open System.Text.Json.Serialization
|
open System.Text.Json.Serialization
|
||||||
|
|
||||||
@@ -34,6 +35,15 @@ type ExpectState<'T> =
|
|||||||
Actual : (unit -> 'T) option
|
Actual : (unit -> 'T) option
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The state accumulated by the `expect` builder. You should never find yourself interacting with this type.
|
||||||
|
type ExpectStateListy<'T> =
|
||||||
|
private
|
||||||
|
{
|
||||||
|
Formatter : ((unit -> 'T) -> string) option
|
||||||
|
Snapshot : (string list * CallerInfo) option
|
||||||
|
Actual : (unit -> 'T seq) option
|
||||||
|
}
|
||||||
|
|
||||||
/// The state accumulated by the `expect` builder. You should never find yourself interacting with this type.
|
/// The state accumulated by the `expect` builder. You should never find yourself interacting with this type.
|
||||||
type internal CompletedSnapshotGeneric<'T> =
|
type internal CompletedSnapshotGeneric<'T> =
|
||||||
private
|
private
|
||||||
@@ -126,18 +136,69 @@ module internal CompletedSnapshotGeneric =
|
|||||||
else
|
else
|
||||||
None
|
None
|
||||||
|
|
||||||
/// Represents a snapshot test that has failed and is awaiting update or report to the user.
|
type internal CompletedListSnapshotGeneric<'elt> =
|
||||||
type CompletedSnapshot =
|
private
|
||||||
internal
|
|
||||||
{
|
{
|
||||||
CallerInfo : CallerInfo
|
Expected : string list
|
||||||
Replacement : string
|
Format : 'elt -> string
|
||||||
|
Caller : CallerInfo
|
||||||
|
Actual : unit -> 'elt seq
|
||||||
}
|
}
|
||||||
|
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
module internal CompletedSnapshot =
|
module internal CompletedListSnapshotGeneric =
|
||||||
let make (s : CompletedSnapshotGeneric<'T>) =
|
let replacement (s : CompletedListSnapshotGeneric<'T>) =
|
||||||
|
s.Actual () |> unbox<IEnumerable> |> Seq.cast |> Seq.map s.Format |> Seq.toList
|
||||||
|
|
||||||
|
/// Returns None if the assertion passes, or Some (expected, actual) if the assertion fails.
|
||||||
|
let internal passesAssertion (state : CompletedListSnapshotGeneric<'T>) : (string list * string list) option =
|
||||||
|
let actual =
|
||||||
|
state.Actual ()
|
||||||
|
|> unbox<IEnumerable>
|
||||||
|
|> Seq.cast
|
||||||
|
|> Seq.map state.Format
|
||||||
|
|> Seq.toList
|
||||||
|
|
||||||
|
if state.Expected <> actual then
|
||||||
|
Some (state.Expected, actual)
|
||||||
|
else
|
||||||
|
None
|
||||||
|
|
||||||
|
let make (state : ExpectStateListy<'elt>) : CompletedListSnapshotGeneric<'elt> =
|
||||||
|
match state.Actual with
|
||||||
|
| None -> failwith "expected an assertion, but got none"
|
||||||
|
| Some actual ->
|
||||||
|
|
||||||
|
match state.Snapshot with
|
||||||
|
| None -> failwith "expected a snapshotList, but got none"
|
||||||
|
| Some (snapshot, caller) ->
|
||||||
|
|
||||||
|
let formatter =
|
||||||
|
match state.Formatter with
|
||||||
|
| Some f -> fun x -> f (fun () -> x)
|
||||||
|
| None -> fun x -> x.ToString ()
|
||||||
|
|
||||||
{
|
{
|
||||||
CallerInfo = s.Caller
|
Expected = snapshot
|
||||||
Replacement = CompletedSnapshotGeneric.replacement s
|
Format = formatter
|
||||||
|
Caller = caller
|
||||||
|
Actual = actual
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents a snapshot test that has failed and is awaiting update or report to the user.
|
||||||
|
type internal CompletedSnapshot =
|
||||||
|
| GuessString of CallerInfo * replacement : string
|
||||||
|
| Known of CallerInfo * replacement : string list * SnapshotLocation
|
||||||
|
|
||||||
|
member this.CallerInfo =
|
||||||
|
match this with
|
||||||
|
| CompletedSnapshot.GuessString (c, _) -> c
|
||||||
|
| CompletedSnapshot.Known (c, _, _) -> c
|
||||||
|
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module internal CompletedSnapshot =
|
||||||
|
let makeGuess (s : CompletedSnapshotGeneric<'T>) =
|
||||||
|
CompletedSnapshot.GuessString (s.Caller, CompletedSnapshotGeneric.replacement s)
|
||||||
|
|
||||||
|
let makeFromAst (source : SnapshotLocation) (s : CompletedListSnapshotGeneric<'elt>) =
|
||||||
|
CompletedSnapshot.Known (s.Caller, CompletedListSnapshotGeneric.replacement s, source)
|
||||||
|
@@ -203,11 +203,22 @@ module internal SnapshotUpdate =
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
let internal stringLiteral (content : string) =
|
||||||
|
if
|
||||||
|
(content.IndexOf '\n' < 0)
|
||||||
|
&& (content.IndexOf '\\' < 0)
|
||||||
|
&& (content.IndexOf '"' < 0)
|
||||||
|
then
|
||||||
|
// simple case where there's no escaping
|
||||||
|
"\"" + content + "\""
|
||||||
|
else
|
||||||
|
"@\"" + content.Replace ("\"", "\"\"") + "\""
|
||||||
|
|
||||||
/// Update the snapshot string with a new value; this doesn't edit the file on disk, but
|
/// Update the snapshot string with a new value; this doesn't edit the file on disk, but
|
||||||
/// instead returns the new contents.
|
/// instead returns the new contents.
|
||||||
/// We always write single-quoted @-strings for simplicity.
|
/// We always write single-quoted @-strings for simplicity.
|
||||||
let private updateSnapshot (lines : string[]) (info : StringLiteralInfo) (newContent : string) : string[] =
|
let private updateSnapshot (lines : string[]) (info : StringLiteralInfo) (newContent : string) : string[] =
|
||||||
let newString = "@\"" + newContent.Replace ("\"", "\"\"") + "\""
|
let newString = stringLiteral newContent
|
||||||
|
|
||||||
if info.StartLine = info.EndLine then
|
if info.StartLine = info.EndLine then
|
||||||
// Single line update
|
// Single line update
|
||||||
@@ -230,7 +241,7 @@ module internal SnapshotUpdate =
|
|||||||
|
|
||||||
let newLines =
|
let newLines =
|
||||||
if newContent.IndexOf '\n' >= 0 then
|
if newContent.IndexOf '\n' >= 0 then
|
||||||
let split = newContent.Replace("\"", "\"\"").Split ('\n')
|
let split = newContent.Replace("\"", "\"\"").Split '\n'
|
||||||
|
|
||||||
match split with
|
match split with
|
||||||
| [||] -> failwith "expected contents from split string"
|
| [||] -> failwith "expected contents from split string"
|
||||||
@@ -287,6 +298,27 @@ module internal SnapshotUpdate =
|
|||||||
|> Seq.sortByDescending fst
|
|> Seq.sortByDescending fst
|
||||||
|> Seq.fold (fun lines (lineNum, replacement) -> updateSnapshotAtLine lines lineNum replacement) fileLines
|
|> Seq.fold (fun lines (lineNum, replacement) -> updateSnapshotAtLine lines lineNum replacement) fileLines
|
||||||
|
|
||||||
|
let internal updateAtLocation (source : SnapshotLocation) (lines : string[]) (actual : string seq) =
|
||||||
|
let indent = String.replicate source.KeywordRange.StartColumn " "
|
||||||
|
|
||||||
|
[|
|
||||||
|
// Range's lines are one-indexed!
|
||||||
|
lines.[0 .. source.KeywordRange.EndLine - 2]
|
||||||
|
[|
|
||||||
|
lines.[source.KeywordRange.EndLine - 1].Substring (0, source.KeywordRange.EndColumn)
|
||||||
|
+ " ["
|
||||||
|
|]
|
||||||
|
actual |> Seq.map (fun s -> indent + " " + stringLiteral s) |> Array.ofSeq
|
||||||
|
[|
|
||||||
|
indent
|
||||||
|
+ "]"
|
||||||
|
+ lines.[source.ReplacementRange.EndLine - 1].Substring source.ReplacementRange.EndColumn
|
||||||
|
|]
|
||||||
|
lines.[source.ReplacementRange.EndLine ..]
|
||||||
|
|]
|
||||||
|
|> Array.concat
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Update every failed snapshot in the input, editing the files on disk.
|
/// Update every failed snapshot in the input, editing the files on disk.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -296,10 +328,19 @@ module internal SnapshotUpdate =
|
|||||||
|> Seq.iter (fun (callerFile, callers) ->
|
|> Seq.iter (fun (callerFile, callers) ->
|
||||||
let contents = System.IO.File.ReadAllLines callerFile
|
let contents = System.IO.File.ReadAllLines callerFile
|
||||||
|
|
||||||
let sources =
|
let newContents =
|
||||||
callers |> Seq.map (fun csc -> csc.CallerInfo.LineNumber, csc.Replacement)
|
callers
|
||||||
|
|> Seq.map (fun csc -> csc.CallerInfo.LineNumber, csc)
|
||||||
let newContents = updateAllLines contents sources
|
|> Seq.sortByDescending fst
|
||||||
|
|> Seq.fold
|
||||||
|
(fun lines (lineNum, replacement) ->
|
||||||
|
match replacement with
|
||||||
|
| CompletedSnapshot.GuessString (_, replacement) ->
|
||||||
|
updateSnapshotAtLine lines lineNum replacement
|
||||||
|
| CompletedSnapshot.Known (_, replacement, snapshotLocation) ->
|
||||||
|
updateAtLocation snapshotLocation lines replacement
|
||||||
|
)
|
||||||
|
contents
|
||||||
|
|
||||||
File.writeAllLines newContents callerFile
|
File.writeAllLines newContents callerFile
|
||||||
)
|
)
|
||||||
|
@@ -6,50 +6,50 @@ WoofWare.Expect.Builder.get_expect [static method]: unit -> WoofWare.Expect.Expe
|
|||||||
WoofWare.Expect.Builder.get_expect' [static method]: unit -> WoofWare.Expect.ExpectBuilder
|
WoofWare.Expect.Builder.get_expect' [static method]: unit -> WoofWare.Expect.ExpectBuilder
|
||||||
WoofWare.Expect.CallerInfo inherit obj, implements WoofWare.Expect.CallerInfo System.IEquatable, System.Collections.IStructuralEquatable, WoofWare.Expect.CallerInfo System.IComparable, System.IComparable, System.Collections.IStructuralComparable
|
WoofWare.Expect.CallerInfo inherit obj, implements WoofWare.Expect.CallerInfo System.IEquatable, System.Collections.IStructuralEquatable, WoofWare.Expect.CallerInfo System.IComparable, System.IComparable, System.Collections.IStructuralComparable
|
||||||
WoofWare.Expect.CallerInfo.Equals [method]: (WoofWare.Expect.CallerInfo, System.Collections.IEqualityComparer) -> bool
|
WoofWare.Expect.CallerInfo.Equals [method]: (WoofWare.Expect.CallerInfo, System.Collections.IEqualityComparer) -> bool
|
||||||
WoofWare.Expect.CompletedSnapshot inherit obj, implements WoofWare.Expect.CompletedSnapshot System.IEquatable, System.Collections.IStructuralEquatable, WoofWare.Expect.CompletedSnapshot System.IComparable, System.IComparable, System.Collections.IStructuralComparable
|
WoofWare.Expect.Diff'`1 inherit obj, implements 'line WoofWare.Expect.Diff' System.IEquatable, System.Collections.IStructuralEquatable, 'line WoofWare.Expect.Diff' System.IComparable, System.IComparable, System.Collections.IStructuralComparable
|
||||||
WoofWare.Expect.CompletedSnapshot.Equals [method]: (WoofWare.Expect.CompletedSnapshot, System.Collections.IEqualityComparer) -> bool
|
WoofWare.Expect.Diff'`1.Equals [method]: ('line WoofWare.Expect.Diff', System.Collections.IEqualityComparer) -> bool
|
||||||
WoofWare.Expect.Diff inherit obj, implements WoofWare.Expect.Diff System.IEquatable, System.Collections.IStructuralEquatable, WoofWare.Expect.Diff System.IComparable, System.IComparable, System.Collections.IStructuralComparable
|
|
||||||
WoofWare.Expect.Diff.Equals [method]: (WoofWare.Expect.Diff, System.Collections.IEqualityComparer) -> bool
|
|
||||||
WoofWare.Expect.DiffModule inherit obj
|
WoofWare.Expect.DiffModule inherit obj
|
||||||
WoofWare.Expect.DiffModule.format [static method]: WoofWare.Expect.Diff -> string
|
WoofWare.Expect.DiffModule.format [static method]: string WoofWare.Expect.Diff' -> string
|
||||||
WoofWare.Expect.DiffModule.formatWithLineNumbers [static method]: WoofWare.Expect.Diff -> string
|
WoofWare.Expect.DiffModule.format' [static method]: ('line -> string) -> 'line WoofWare.Expect.Diff' -> string
|
||||||
WoofWare.Expect.DiffModule.myers [static method]: string [] -> string [] -> WoofWare.Expect.Diff
|
WoofWare.Expect.DiffModule.formatWithLineNumbers [static method]: string WoofWare.Expect.Diff' -> string
|
||||||
WoofWare.Expect.DiffModule.patience [static method]: string -> string -> WoofWare.Expect.Diff
|
WoofWare.Expect.DiffModule.formatWithLineNumbers' [static method]: ('line -> string) -> 'line WoofWare.Expect.Diff' -> string
|
||||||
WoofWare.Expect.DiffModule.patienceLines [static method]: string [] -> string [] -> WoofWare.Expect.Diff
|
WoofWare.Expect.DiffModule.myers [static method]: string [] -> string [] -> string WoofWare.Expect.Diff'
|
||||||
WoofWare.Expect.DiffOperation inherit obj, implements WoofWare.Expect.DiffOperation System.IEquatable, System.Collections.IStructuralEquatable, WoofWare.Expect.DiffOperation System.IComparable, System.IComparable, System.Collections.IStructuralComparable - union type with 3 cases
|
WoofWare.Expect.DiffModule.patience [static method]: string -> string -> string WoofWare.Expect.Diff'
|
||||||
WoofWare.Expect.DiffOperation+Delete inherit WoofWare.Expect.DiffOperation
|
WoofWare.Expect.DiffModule.patienceLines [static method]: 'line [] -> 'line [] -> 'line WoofWare.Expect.Diff'
|
||||||
WoofWare.Expect.DiffOperation+Delete.get_line [method]: unit -> string
|
WoofWare.Expect.DiffOperation`1 inherit obj, implements 'line WoofWare.Expect.DiffOperation System.IEquatable, System.Collections.IStructuralEquatable, 'line WoofWare.Expect.DiffOperation System.IComparable, System.IComparable, System.Collections.IStructuralComparable - union type with 3 cases
|
||||||
WoofWare.Expect.DiffOperation+Delete.get_posA [method]: unit -> int
|
WoofWare.Expect.DiffOperation`1+Delete inherit 'line WoofWare.Expect.DiffOperation
|
||||||
WoofWare.Expect.DiffOperation+Delete.line [property]: [read-only] string
|
WoofWare.Expect.DiffOperation`1+Delete.get_line [method]: unit -> 'line
|
||||||
WoofWare.Expect.DiffOperation+Delete.posA [property]: [read-only] int
|
WoofWare.Expect.DiffOperation`1+Delete.get_posA [method]: unit -> int
|
||||||
WoofWare.Expect.DiffOperation+Insert inherit WoofWare.Expect.DiffOperation
|
WoofWare.Expect.DiffOperation`1+Delete.line [property]: [read-only] 'line
|
||||||
WoofWare.Expect.DiffOperation+Insert.get_line [method]: unit -> string
|
WoofWare.Expect.DiffOperation`1+Delete.posA [property]: [read-only] int
|
||||||
WoofWare.Expect.DiffOperation+Insert.get_posB [method]: unit -> int
|
WoofWare.Expect.DiffOperation`1+Insert inherit 'line WoofWare.Expect.DiffOperation
|
||||||
WoofWare.Expect.DiffOperation+Insert.line [property]: [read-only] string
|
WoofWare.Expect.DiffOperation`1+Insert.get_line [method]: unit -> 'line
|
||||||
WoofWare.Expect.DiffOperation+Insert.posB [property]: [read-only] int
|
WoofWare.Expect.DiffOperation`1+Insert.get_posB [method]: unit -> int
|
||||||
WoofWare.Expect.DiffOperation+Match inherit WoofWare.Expect.DiffOperation
|
WoofWare.Expect.DiffOperation`1+Insert.line [property]: [read-only] 'line
|
||||||
WoofWare.Expect.DiffOperation+Match.get_line [method]: unit -> string
|
WoofWare.Expect.DiffOperation`1+Insert.posB [property]: [read-only] int
|
||||||
WoofWare.Expect.DiffOperation+Match.get_posA [method]: unit -> int
|
WoofWare.Expect.DiffOperation`1+Match inherit 'line WoofWare.Expect.DiffOperation
|
||||||
WoofWare.Expect.DiffOperation+Match.get_posB [method]: unit -> int
|
WoofWare.Expect.DiffOperation`1+Match.get_line [method]: unit -> 'line
|
||||||
WoofWare.Expect.DiffOperation+Match.line [property]: [read-only] string
|
WoofWare.Expect.DiffOperation`1+Match.get_posA [method]: unit -> int
|
||||||
WoofWare.Expect.DiffOperation+Match.posA [property]: [read-only] int
|
WoofWare.Expect.DiffOperation`1+Match.get_posB [method]: unit -> int
|
||||||
WoofWare.Expect.DiffOperation+Match.posB [property]: [read-only] int
|
WoofWare.Expect.DiffOperation`1+Match.line [property]: [read-only] 'line
|
||||||
WoofWare.Expect.DiffOperation+Tags inherit obj
|
WoofWare.Expect.DiffOperation`1+Match.posA [property]: [read-only] int
|
||||||
WoofWare.Expect.DiffOperation+Tags.Delete [static field]: int = 1
|
WoofWare.Expect.DiffOperation`1+Match.posB [property]: [read-only] int
|
||||||
WoofWare.Expect.DiffOperation+Tags.Insert [static field]: int = 2
|
WoofWare.Expect.DiffOperation`1+Tags inherit obj
|
||||||
WoofWare.Expect.DiffOperation+Tags.Match [static field]: int = 0
|
WoofWare.Expect.DiffOperation`1+Tags.Delete [static field]: int = 1
|
||||||
WoofWare.Expect.DiffOperation.Equals [method]: (WoofWare.Expect.DiffOperation, System.Collections.IEqualityComparer) -> bool
|
WoofWare.Expect.DiffOperation`1+Tags.Insert [static field]: int = 2
|
||||||
WoofWare.Expect.DiffOperation.get_IsDelete [method]: unit -> bool
|
WoofWare.Expect.DiffOperation`1+Tags.Match [static field]: int = 0
|
||||||
WoofWare.Expect.DiffOperation.get_IsInsert [method]: unit -> bool
|
WoofWare.Expect.DiffOperation`1.Equals [method]: ('line WoofWare.Expect.DiffOperation, System.Collections.IEqualityComparer) -> bool
|
||||||
WoofWare.Expect.DiffOperation.get_IsMatch [method]: unit -> bool
|
WoofWare.Expect.DiffOperation`1.get_IsDelete [method]: unit -> bool
|
||||||
WoofWare.Expect.DiffOperation.get_Tag [method]: unit -> int
|
WoofWare.Expect.DiffOperation`1.get_IsInsert [method]: unit -> bool
|
||||||
WoofWare.Expect.DiffOperation.IsDelete [property]: [read-only] bool
|
WoofWare.Expect.DiffOperation`1.get_IsMatch [method]: unit -> bool
|
||||||
WoofWare.Expect.DiffOperation.IsInsert [property]: [read-only] bool
|
WoofWare.Expect.DiffOperation`1.get_Tag [method]: unit -> int
|
||||||
WoofWare.Expect.DiffOperation.IsMatch [property]: [read-only] bool
|
WoofWare.Expect.DiffOperation`1.IsDelete [property]: [read-only] bool
|
||||||
WoofWare.Expect.DiffOperation.NewDelete [static method]: (int, string) -> WoofWare.Expect.DiffOperation
|
WoofWare.Expect.DiffOperation`1.IsInsert [property]: [read-only] bool
|
||||||
WoofWare.Expect.DiffOperation.NewInsert [static method]: (int, string) -> WoofWare.Expect.DiffOperation
|
WoofWare.Expect.DiffOperation`1.IsMatch [property]: [read-only] bool
|
||||||
WoofWare.Expect.DiffOperation.NewMatch [static method]: (int, int, string) -> WoofWare.Expect.DiffOperation
|
WoofWare.Expect.DiffOperation`1.NewDelete [static method]: (int, 'line) -> 'line WoofWare.Expect.DiffOperation
|
||||||
WoofWare.Expect.DiffOperation.Tag [property]: [read-only] int
|
WoofWare.Expect.DiffOperation`1.NewInsert [static method]: (int, 'line) -> 'line WoofWare.Expect.DiffOperation
|
||||||
|
WoofWare.Expect.DiffOperation`1.NewMatch [static method]: (int, int, 'line) -> 'line WoofWare.Expect.DiffOperation
|
||||||
|
WoofWare.Expect.DiffOperation`1.Tag [property]: [read-only] int
|
||||||
WoofWare.Expect.Dot inherit obj
|
WoofWare.Expect.Dot inherit obj
|
||||||
WoofWare.Expect.Dot+IFileSystem - interface with 3 member(s)
|
WoofWare.Expect.Dot+IFileSystem - interface with 3 member(s)
|
||||||
WoofWare.Expect.Dot+IFileSystem.DeleteFile [method]: string -> unit
|
WoofWare.Expect.Dot+IFileSystem.DeleteFile [method]: string -> unit
|
||||||
@@ -72,18 +72,28 @@ WoofWare.Expect.ExpectBuilder inherit obj
|
|||||||
WoofWare.Expect.ExpectBuilder..ctor [constructor]: (string * int)
|
WoofWare.Expect.ExpectBuilder..ctor [constructor]: (string * int)
|
||||||
WoofWare.Expect.ExpectBuilder..ctor [constructor]: bool
|
WoofWare.Expect.ExpectBuilder..ctor [constructor]: bool
|
||||||
WoofWare.Expect.ExpectBuilder..ctor [constructor]: WoofWare.Expect.Mode
|
WoofWare.Expect.ExpectBuilder..ctor [constructor]: WoofWare.Expect.Mode
|
||||||
|
WoofWare.Expect.ExpectBuilder.Bind [method]: ('elt WoofWare.Expect.ExpectStateListy, unit -> #('elt seq) WoofWare.Expect.ExpectState) -> 'elt WoofWare.Expect.ExpectStateListy
|
||||||
WoofWare.Expect.ExpectBuilder.Bind [method]: ('U WoofWare.Expect.ExpectState, unit -> 'U WoofWare.Expect.ExpectState) -> 'U WoofWare.Expect.ExpectState
|
WoofWare.Expect.ExpectBuilder.Bind [method]: ('U WoofWare.Expect.ExpectState, unit -> 'U WoofWare.Expect.ExpectState) -> 'U WoofWare.Expect.ExpectState
|
||||||
|
WoofWare.Expect.ExpectBuilder.Bind [method]: ('U WoofWare.Expect.ExpectStateListy, unit -> 'U WoofWare.Expect.ExpectStateListy) -> 'U WoofWare.Expect.ExpectStateListy
|
||||||
WoofWare.Expect.ExpectBuilder.Delay [method]: (unit -> 'T WoofWare.Expect.ExpectState) -> (unit -> 'T WoofWare.Expect.ExpectState)
|
WoofWare.Expect.ExpectBuilder.Delay [method]: (unit -> 'T WoofWare.Expect.ExpectState) -> (unit -> 'T WoofWare.Expect.ExpectState)
|
||||||
|
WoofWare.Expect.ExpectBuilder.Delay [method]: (unit -> 'T WoofWare.Expect.ExpectStateListy) -> (unit -> 'T WoofWare.Expect.ExpectStateListy)
|
||||||
WoofWare.Expect.ExpectBuilder.Return [method]: 'T -> '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.Return [method]: unit -> 'T WoofWare.Expect.ExpectStateListy
|
||||||
WoofWare.Expect.ExpectBuilder.ReturnFrom [method]: (unit -> 'T) -> 'T WoofWare.Expect.ExpectState
|
WoofWare.Expect.ExpectBuilder.ReturnFrom [method]: (unit -> 'T) -> 'T WoofWare.Expect.ExpectState
|
||||||
WoofWare.Expect.ExpectBuilder.Run [method]: (unit -> 'T WoofWare.Expect.ExpectState) -> unit
|
WoofWare.Expect.ExpectBuilder.Run [method]: (unit -> 'T WoofWare.Expect.ExpectState) -> unit
|
||||||
|
WoofWare.Expect.ExpectBuilder.Run [method]: (unit -> 'T WoofWare.Expect.ExpectStateListy) -> unit
|
||||||
WoofWare.Expect.ExpectBuilder.Snapshot [method]: ('a WoofWare.Expect.ExpectState, string, string option, int option, string option) -> 'a WoofWare.Expect.ExpectState
|
WoofWare.Expect.ExpectBuilder.Snapshot [method]: ('a WoofWare.Expect.ExpectState, string, string option, int option, string option) -> 'a WoofWare.Expect.ExpectState
|
||||||
|
WoofWare.Expect.ExpectBuilder.Snapshot [method]: ('a WoofWare.Expect.ExpectStateListy, string, string option, int option, string option) -> 'a WoofWare.Expect.ExpectState
|
||||||
WoofWare.Expect.ExpectBuilder.SnapshotJson [method]: (unit WoofWare.Expect.ExpectState, string, string option, int option, string option) -> 'a WoofWare.Expect.ExpectState
|
WoofWare.Expect.ExpectBuilder.SnapshotJson [method]: (unit WoofWare.Expect.ExpectState, string, string option, int option, string option) -> 'a WoofWare.Expect.ExpectState
|
||||||
|
WoofWare.Expect.ExpectBuilder.SnapshotJson [method]: (unit WoofWare.Expect.ExpectStateListy, string, string option, int option, string option) -> 'a WoofWare.Expect.ExpectState
|
||||||
|
WoofWare.Expect.ExpectBuilder.SnapshotList [method]: (unit WoofWare.Expect.ExpectStateListy, string list, string option, int option, string option) -> 'a WoofWare.Expect.ExpectStateListy
|
||||||
WoofWare.Expect.ExpectBuilder.SnapshotThrows [method]: ('a WoofWare.Expect.ExpectState, string, string option, int option, string option) -> 'a WoofWare.Expect.ExpectState
|
WoofWare.Expect.ExpectBuilder.SnapshotThrows [method]: ('a WoofWare.Expect.ExpectState, string, string option, int option, string option) -> 'a WoofWare.Expect.ExpectState
|
||||||
|
WoofWare.Expect.ExpectBuilder.SnapshotThrows [method]: ('a WoofWare.Expect.ExpectStateListy, string, string option, int option, string option) -> 'a WoofWare.Expect.ExpectState
|
||||||
WoofWare.Expect.ExpectBuilder.WithFormat [method]: ('T WoofWare.Expect.ExpectState, 'T -> string) -> 'T WoofWare.Expect.ExpectState
|
WoofWare.Expect.ExpectBuilder.WithFormat [method]: ('T WoofWare.Expect.ExpectState, 'T -> string) -> 'T WoofWare.Expect.ExpectState
|
||||||
|
WoofWare.Expect.ExpectBuilder.WithFormat [method]: ('T WoofWare.Expect.ExpectStateListy, 'T -> string) -> 'T WoofWare.Expect.ExpectStateListy
|
||||||
WoofWare.Expect.ExpectBuilder.WithJsonDocOptions [method]: ('T WoofWare.Expect.ExpectState, System.Text.Json.JsonDocumentOptions) -> 'T WoofWare.Expect.ExpectState
|
WoofWare.Expect.ExpectBuilder.WithJsonDocOptions [method]: ('T WoofWare.Expect.ExpectState, System.Text.Json.JsonDocumentOptions) -> 'T WoofWare.Expect.ExpectState
|
||||||
WoofWare.Expect.ExpectBuilder.WithJsonSerializerOptions [method]: ('T WoofWare.Expect.ExpectState, System.Text.Json.JsonSerializerOptions) -> 'T WoofWare.Expect.ExpectState
|
WoofWare.Expect.ExpectBuilder.WithJsonSerializerOptions [method]: ('T WoofWare.Expect.ExpectState, System.Text.Json.JsonSerializerOptions) -> 'T WoofWare.Expect.ExpectState
|
||||||
|
WoofWare.Expect.ExpectBuilder.WithJsonSerializerOptions [method]: ('T WoofWare.Expect.ExpectStateListy, System.Text.Json.JsonSerializerOptions) -> 'T WoofWare.Expect.ExpectState
|
||||||
WoofWare.Expect.ExpectException inherit System.Exception, implements System.Collections.IStructuralEquatable
|
WoofWare.Expect.ExpectException inherit System.Exception, implements System.Collections.IStructuralEquatable
|
||||||
WoofWare.Expect.ExpectException..ctor [constructor]: string
|
WoofWare.Expect.ExpectException..ctor [constructor]: string
|
||||||
WoofWare.Expect.ExpectException..ctor [constructor]: unit
|
WoofWare.Expect.ExpectException..ctor [constructor]: unit
|
||||||
@@ -91,6 +101,7 @@ WoofWare.Expect.ExpectException.Equals [method]: (System.Exception, System.Colle
|
|||||||
WoofWare.Expect.ExpectException.Equals [method]: System.Exception -> bool
|
WoofWare.Expect.ExpectException.Equals [method]: System.Exception -> bool
|
||||||
WoofWare.Expect.ExpectException.Message [property]: [read-only] string
|
WoofWare.Expect.ExpectException.Message [property]: [read-only] string
|
||||||
WoofWare.Expect.ExpectState`1 inherit obj
|
WoofWare.Expect.ExpectState`1 inherit obj
|
||||||
|
WoofWare.Expect.ExpectStateListy`1 inherit obj
|
||||||
WoofWare.Expect.GlobalBuilderConfig inherit obj
|
WoofWare.Expect.GlobalBuilderConfig inherit obj
|
||||||
WoofWare.Expect.GlobalBuilderConfig.clearTests [static method]: unit -> unit
|
WoofWare.Expect.GlobalBuilderConfig.clearTests [static method]: unit -> unit
|
||||||
WoofWare.Expect.GlobalBuilderConfig.enterBulkUpdateMode [static method]: unit -> unit
|
WoofWare.Expect.GlobalBuilderConfig.enterBulkUpdateMode [static method]: unit -> unit
|
||||||
|
@@ -21,6 +21,7 @@
|
|||||||
<Compile Include="File.fs" />
|
<Compile Include="File.fs" />
|
||||||
<Compile Include="Diff.fs" />
|
<Compile Include="Diff.fs" />
|
||||||
<Compile Include="Dot.fs" />
|
<Compile Include="Dot.fs" />
|
||||||
|
<Compile Include="AstWalker.fs" />
|
||||||
<Compile Include="Domain.fs" />
|
<Compile Include="Domain.fs" />
|
||||||
<Compile Include="SnapshotUpdate.fs" />
|
<Compile Include="SnapshotUpdate.fs" />
|
||||||
<Compile Include="Config.fs" />
|
<Compile Include="Config.fs" />
|
||||||
@@ -39,8 +40,9 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<!-- FSharp.SystemTextJson requires at least this version -->
|
<!-- FSharp.SystemTextJson requires at least this version -->
|
||||||
<PackageReference Update="FSharp.Core" Version="4.7.0" />
|
<PackageReference Update="FSharp.Core" Version="8.0.100" />
|
||||||
<PackageReference Include="FSharp.SystemTextJson" Version="1.4.36" />
|
<PackageReference Include="FSharp.SystemTextJson" Version="1.4.36" />
|
||||||
|
<PackageReference Include="Fantomas.FCS" Version="7.0.3" />
|
||||||
<!-- Needed for DeepEquals -->
|
<!-- Needed for DeepEquals -->
|
||||||
<PackageReference Include="System.Text.Json" Version="9.0.0" />
|
<PackageReference Include="System.Text.Json" Version="9.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "0.6",
|
"version": "0.8",
|
||||||
"publicReleaseRefSpec": [
|
"publicReleaseRefSpec": [
|
||||||
"^refs/heads/main$"
|
"^refs/heads/main$"
|
||||||
],
|
],
|
||||||
|
@@ -9,6 +9,11 @@
|
|||||||
"version": "7.0.2",
|
"version": "7.0.2",
|
||||||
"hash": "sha256-BAaENIm/ksTiXrUImRgKoIXTGIlgsX7ch6ayoFjhJXA="
|
"hash": "sha256-BAaENIm/ksTiXrUImRgKoIXTGIlgsX7ch6ayoFjhJXA="
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"pname": "Fantomas.FCS",
|
||||||
|
"version": "7.0.3",
|
||||||
|
"hash": "sha256-BmCUq+ZQ3b25nrMBTc5tcxdO2soryEjNx9Fn/FJpi1c="
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"pname": "fsharp-analyzers",
|
"pname": "fsharp-analyzers",
|
||||||
"version": "0.31.0",
|
"version": "0.31.0",
|
||||||
@@ -16,8 +21,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pname": "FSharp.Core",
|
"pname": "FSharp.Core",
|
||||||
"version": "4.7.0",
|
"version": "8.0.100",
|
||||||
"hash": "sha256-7aa4bga9XWLkq7J5KXv8Bilf1KGum77lSUqp+ooYIUg="
|
"hash": "sha256-FCjhq+W603ibz9XAA9iH5K6gJhX02/pMHyge6dHb4xs="
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pname": "FSharp.Core",
|
"pname": "FSharp.Core",
|
||||||
@@ -59,11 +64,26 @@
|
|||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"hash": "sha256-FeM40ktcObQJk4nMYShB61H/E8B7tIKfl9ObJ0IOcCM="
|
"hash": "sha256-FeM40ktcObQJk4nMYShB61H/E8B7tIKfl9ObJ0IOcCM="
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"pname": "Microsoft.NETCore.Platforms",
|
||||||
|
"version": "1.1.1",
|
||||||
|
"hash": "sha256-8hLiUKvy/YirCWlFwzdejD2Db3DaXhHxT7GSZx/znJg="
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"pname": "Microsoft.NETCore.Platforms",
|
"pname": "Microsoft.NETCore.Platforms",
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"hash": "sha256-IEvBk6wUXSdyCnkj6tHahOJv290tVVT8tyemYcR0Yro="
|
"hash": "sha256-IEvBk6wUXSdyCnkj6tHahOJv290tVVT8tyemYcR0Yro="
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"pname": "Microsoft.NETCore.Targets",
|
||||||
|
"version": "1.1.0",
|
||||||
|
"hash": "sha256-0AqQ2gMS8iNlYkrD+BxtIg7cXMnr9xZHtKAuN4bjfaQ="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "Microsoft.NETCore.Targets",
|
||||||
|
"version": "1.1.3",
|
||||||
|
"hash": "sha256-WLsf1NuUfRWyr7C7Rl9jiua9jximnVvzy6nk2D2bVRc="
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"pname": "Microsoft.Testing.Extensions.Telemetry",
|
"pname": "Microsoft.Testing.Extensions.Telemetry",
|
||||||
"version": "1.5.3",
|
"version": "1.5.3",
|
||||||
@@ -159,11 +179,31 @@
|
|||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"hash": "sha256-7jZM4qAbIzne3AcdFfMbvbgogqpxvVe6q2S7Ls8xQy0="
|
"hash": "sha256-7jZM4qAbIzne3AcdFfMbvbgogqpxvVe6q2S7Ls8xQy0="
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"pname": "runtime.any.System.Runtime",
|
||||||
|
"version": "4.3.0",
|
||||||
|
"hash": "sha256-qwhNXBaJ1DtDkuRacgHwnZmOZ1u9q7N8j0cWOLYOELM="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "runtime.native.System",
|
||||||
|
"version": "4.3.0",
|
||||||
|
"hash": "sha256-ZBZaodnjvLXATWpXXakFgcy6P+gjhshFXmglrL5xD5Y="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "runtime.unix.System.Private.Uri",
|
||||||
|
"version": "4.3.0",
|
||||||
|
"hash": "sha256-c5tXWhE/fYbJVl9rXs0uHh3pTsg44YD1dJvyOA0WoMs="
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"pname": "System.Buffers",
|
"pname": "System.Buffers",
|
||||||
"version": "4.5.1",
|
"version": "4.5.1",
|
||||||
"hash": "sha256-wws90sfi9M7kuCPWkv1CEYMJtCqx9QB/kj0ymlsNaxI="
|
"hash": "sha256-wws90sfi9M7kuCPWkv1CEYMJtCqx9QB/kj0ymlsNaxI="
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"pname": "System.Buffers",
|
||||||
|
"version": "4.6.0",
|
||||||
|
"hash": "sha256-c2QlgFB16IlfBms5YLsTCFQ/QeKoS6ph1a9mdRkq/Jc="
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"pname": "System.Collections.Immutable",
|
"pname": "System.Collections.Immutable",
|
||||||
"version": "8.0.0",
|
"version": "8.0.0",
|
||||||
@@ -174,6 +214,11 @@
|
|||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"hash": "sha256-6mW3N6FvcdNH/pB58pl+pFSCGWgyaP4hfVtC/SMWDV4="
|
"hash": "sha256-6mW3N6FvcdNH/pB58pl+pFSCGWgyaP4hfVtC/SMWDV4="
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"pname": "System.Diagnostics.DiagnosticSource",
|
||||||
|
"version": "8.0.1",
|
||||||
|
"hash": "sha256-zmwHjcJgKcbkkwepH038QhcnsWMJcHys+PEbFGC0Jgo="
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"pname": "System.Formats.Asn1",
|
"pname": "System.Formats.Asn1",
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
@@ -209,16 +254,31 @@
|
|||||||
"version": "4.5.5",
|
"version": "4.5.5",
|
||||||
"hash": "sha256-EPQ9o1Kin7KzGI5O3U3PUQAZTItSbk9h/i4rViN3WiI="
|
"hash": "sha256-EPQ9o1Kin7KzGI5O3U3PUQAZTItSbk9h/i4rViN3WiI="
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"pname": "System.Memory",
|
||||||
|
"version": "4.6.0",
|
||||||
|
"hash": "sha256-OhAEKzUM6eEaH99DcGaMz2pFLG/q/N4KVWqqiBYUOFo="
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"pname": "System.Numerics.Vectors",
|
"pname": "System.Numerics.Vectors",
|
||||||
"version": "4.4.0",
|
"version": "4.6.0",
|
||||||
"hash": "sha256-auXQK2flL/JpnB/rEcAcUm4vYMCYMEMiWOCAlIaqu2U="
|
"hash": "sha256-fKS3uWQ2HmR69vNhDHqPLYNOt3qpjiWQOXZDHvRE1HU="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pname": "System.Private.Uri",
|
||||||
|
"version": "4.3.0",
|
||||||
|
"hash": "sha256-fVfgcoP4AVN1E5wHZbKBIOPYZ/xBeSIdsNF+bdukIRM="
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pname": "System.Reflection.Metadata",
|
"pname": "System.Reflection.Metadata",
|
||||||
"version": "8.0.0",
|
"version": "8.0.0",
|
||||||
"hash": "sha256-dQGC30JauIDWNWXMrSNOJncVa1umR1sijazYwUDdSIE="
|
"hash": "sha256-dQGC30JauIDWNWXMrSNOJncVa1umR1sijazYwUDdSIE="
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"pname": "System.Runtime",
|
||||||
|
"version": "4.3.1",
|
||||||
|
"hash": "sha256-R9T68AzS1PJJ7v6ARz9vo88pKL1dWqLOANg4pkQjkA0="
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"pname": "System.Runtime.CompilerServices.Unsafe",
|
"pname": "System.Runtime.CompilerServices.Unsafe",
|
||||||
"version": "4.5.3",
|
"version": "4.5.3",
|
||||||
@@ -229,6 +289,11 @@
|
|||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"hash": "sha256-bEG1PnDp7uKYz/OgLOWs3RWwQSVYm+AnPwVmAmcgp2I="
|
"hash": "sha256-bEG1PnDp7uKYz/OgLOWs3RWwQSVYm+AnPwVmAmcgp2I="
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"pname": "System.Runtime.CompilerServices.Unsafe",
|
||||||
|
"version": "6.1.0",
|
||||||
|
"hash": "sha256-NyqqpRcHumzSxpsgRDguD5SGwdUNHBbo0OOdzLTIzCU="
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"pname": "System.Security.AccessControl",
|
"pname": "System.Security.AccessControl",
|
||||||
"version": "4.5.0",
|
"version": "4.5.0",
|
||||||
|
Reference in New Issue
Block a user