@@ -4,173 +4,8 @@ module internal ReportFormatter
44
55open Hedgehog
66open Hedgehog.Xunit
7- open System.Text
8-
9- // ========================================
10- // Constants and Formatting
11- // ========================================
12-
13- let private indent = " " // 2 spaces to align with xUnit's output format
14- let private printValue = Hedgehog.FSharp.ValueFormatting.printValue
15-
16- // ========================================
17- // Report Formatting
18- // ========================================
19-
20- /// Filters exception string to show only user code stack trace.
21- /// When we rethrow using ExceptionDispatchInfo.Capture().Throw() to preserve the original stack trace,
22- /// it adds a "--- End of stack trace from previous location ---" marker and appends Hedgehog's
23- /// internal frames as the exception propagates. We remove everything from that marker onwards
24- /// to show only the user's code in the test failure report.
25- let private filterExceptionStackTrace ( exceptionEntry : string ) : string =
26- match exceptionEntry.IndexOf( " --- End of stack trace from previous location ---" ) with
27- | - 1 -> exceptionEntry // No marker found, return as-is
28- | idx -> exceptionEntry.Substring( 0 , idx) .TrimEnd()
29-
30- // ========================================
31- // Journal Entry Groups
32- // ========================================
33-
34- type private JournalEntryGroup =
35- | ParametersGroup of ( string * obj ) list
36- | GeneratedGroup of obj list
37- | CounterexamplesGroup of string list
38- | TextsGroup of string list
39- | CancellationsGroup of string list
40- | ExceptionsGroup of exn list
41-
42- let private classifyJournalLine ( line : JournalLine ) : JournalEntryGroup =
43- match line with
44- | TestParameter ( name, value) -> ParametersGroup [( name, value)]
45- | GeneratedValue value -> GeneratedGroup [ value]
46- | Counterexample msg -> CounterexamplesGroup [ msg]
47- | Text msg -> TextsGroup [ msg]
48- | Cancellation msg -> CancellationsGroup [ msg]
49- | Exception exn -> ExceptionsGroup [ exn]
50-
51- let private groupKey ( group : JournalEntryGroup ) : int =
52- match group with
53- | ParametersGroup _ -> 0
54- | GeneratedGroup _ -> 1
55- | CounterexamplesGroup _ -> 2
56- | TextsGroup _ -> 3
57- | CancellationsGroup _ -> 4
58- | ExceptionsGroup _ -> 5
59-
60- let private mergeGroups ( groups : JournalEntryGroup list ) : JournalEntryGroup =
61- match groups with
62- | [] -> failwith " Cannot merge empty group list"
63- | ParametersGroup _ :: _ ->
64- groups |> List.collect ( function ParametersGroup items -> items | _ -> []) |> ParametersGroup
65- | GeneratedGroup _ :: _ ->
66- groups |> List.collect ( function GeneratedGroup items -> items | _ -> []) |> GeneratedGroup
67- | CounterexamplesGroup _ :: _ ->
68- groups |> List.collect ( function CounterexamplesGroup items -> items | _ -> []) |> CounterexamplesGroup
69- | TextsGroup _ :: _ ->
70- groups |> List.collect ( function TextsGroup items -> items | _ -> []) |> TextsGroup
71- | CancellationsGroup _ :: _ ->
72- groups |> List.collect ( function CancellationsGroup items -> items | _ -> []) |> CancellationsGroup
73- | ExceptionsGroup _ :: _ ->
74- groups |> List.collect ( function ExceptionsGroup items -> items | _ -> []) |> ExceptionsGroup
75-
76- // ========================================
77- // Group Rendering Functions
78- // ========================================
79-
80- let private renderParameters ( sb : StringBuilder ) ( parameters : ( string * obj ) list ) : unit =
81- sb.AppendLine() .AppendLine( " Test parameters:" ) |> ignore
82- parameters |> List.iter ( fun ( name , value ) ->
83- sb.AppendIndentedLine( indent, $" %s {name} = %s {printValue value}" ) |> ignore)
84-
85- let private renderGenerated ( sb : StringBuilder ) ( values : obj list ) : unit =
86- sb.AppendLine() .AppendLine( " Generated values:" ) |> ignore
87- values |> List.iter ( fun value ->
88- sb.AppendIndentedLine( indent, printValue value) |> ignore)
89-
90- let private renderCounterexamples ( sb : StringBuilder ) ( messages : string list ) : unit =
91- sb.AppendLine() .AppendLine( " Counterexamples:" ) |> ignore
92- messages |> List.iter ( fun msg -> sb.AppendIndentedLine( indent, msg) |> ignore)
93-
94- let private renderTexts ( sb : StringBuilder ) ( messages : string list ) : unit =
95- sb.AppendLine() |> ignore
96- messages |> List.iter ( fun msg -> sb.AppendLine( msg) |> ignore)
97-
98- let private renderCancellations ( sb : StringBuilder ) ( messages : string list ) : unit =
99- sb.AppendLine() |> ignore
100- messages |> List.iter ( fun msg -> sb.AppendLine( msg) |> ignore)
101-
102- let private renderExceptions ( sb : StringBuilder ) ( exceptions : exn list ) : unit =
103- exceptions |> List.iter ( fun exn ->
104- let exceptionString = string ( Exceptions.unwrap exn)
105- let filteredEntry = filterExceptionStackTrace exceptionString
106- sb.AppendLine() .AppendLine( " Actual exception:" ) .AppendLine( filteredEntry) |> ignore)
107-
108- let private formatFailureForXunit ( failure : FailureData ) ( report : Report ) : string =
109- let sb = StringBuilder()
110-
111- let renderTests ( tests : int < tests >) =
112- sprintf " %d test%s " ( int tests) ( if int tests = 1 then " " else " s" )
113-
114- let renderAndShrinks ( shrinks : int < shrinks >) =
115- if int shrinks = 0 then
116- " "
117- else
118- sprintf " and %d shrink%s " ( int shrinks) ( if int shrinks = 1 then " " else " s" )
119-
120- let renderAndDiscards ( discards : int < discards >) =
121- if int discards = 0 then
122- " "
123- else
124- sprintf " and %d discard%s " ( int discards) ( if int discards = 1 then " " else " s" )
125-
126- // Header
127- sb.AppendIndentedLine(
128- indent,
129- sprintf
130- " *** Failed! Falsifiable (after %s%s%s ):"
131- ( renderTests report.Tests)
132- ( renderAndShrinks failure.Shrinks)
133- ( renderAndDiscards report.Discards)
134- )
135- |> ignore
136-
137- // Recheck seed (if available)
138- match failure.RecheckInfo with
139- | Some recheckInfo ->
140- let serialized = RecheckData.serialize recheckInfo.Data
141- sb.AppendLine()
142- .AppendLine( " You can reproduce this failure with the following Recheck Seed:" )
143- .AppendIndentedLine( indent, $" \" %s {serialized}\" " ) |> ignore
144- | None -> ()
145-
146- // Evaluate journal entries and group consecutively by type
147- let journalLines = Journal.eval failure.Journal
148-
149- // Classify each journal line and group consecutive entries of the same type
150- let groups =
151- journalLines
152- |> Seq.map classifyJournalLine
153- |> Seq.groupConsecutiveBy groupKey
154- |> List.map ( fun ( _ , groupList ) -> mergeGroups groupList)
155-
156- // Render each group in order
157- groups |> List.iter ( fun group ->
158- match group with
159- | ParametersGroup parameters -> renderParameters sb parameters
160- | GeneratedGroup values -> renderGenerated sb values
161- | CounterexamplesGroup messages -> renderCounterexamples sb messages
162- | TextsGroup messages -> renderTexts sb messages
163- | CancellationsGroup messages -> renderCancellations sb messages
164- | ExceptionsGroup exceptions -> renderExceptions sb exceptions)
165-
166- sb.ToString()
167-
168- let private formatReportForXunit ( report : Report ) : string =
169- match report.Status with
170- | Failed failure -> formatFailureForXunit failure report
171- | _ -> Report.render report
1727
1738let tryRaise ( report : Report ) : unit =
1749 match report.Status with
175- | Failed _ -> report |> formatReportForXunit |> PropertyFailedException |> raise
10+ | Failed _ -> report |> Report.render |> PropertyFailedException |> raise
17611 | _ -> Report.tryRaise report
0 commit comments