11module Sprout
2- open System.Diagnostics
32
43let mutable info : string -> unit = ignore
54let mutable debug : string -> unit = ignore
@@ -36,12 +35,6 @@ type ItBuilder(name: string) =
3635let it name = ItBuilder name
3736let pending name = It.Pending name
3837
39- module AnsiColours =
40- let green = " \u001b [32m"
41- let red = " \u001b [31m"
42- let grey = " \u001b [90m"
43- let reset = " \u001b [0m"
44-
4538type Describe = {
4639 Name: string
4740 TestCases: It list
@@ -97,101 +90,111 @@ type TestResult =
9790 | Pending of Path * string
9891
9992type ITestReporter =
100- abstract BeginSuite : name : string * level : int -> unit
101- abstract ReportResult : result : TestResult * level : int -> unit
102- abstract EndSuite : name : string * level : int -> unit
103- abstract Info : message : string * indent : string * indentCount : int -> unit
104- abstract Debug : message : string * indent : string * indentCount : int -> unit
93+ abstract BeginSuite : name : string * path : Path -> unit
94+ abstract ReportResult : result : TestResult * path : Path -> unit
95+ abstract EndSuite : name : string * path : Path -> unit
96+ abstract Info : message : string * path : Path -> unit
97+ abstract Debug : message : string * path : Path -> unit
10598 abstract End : TestResult list -> unit
10699
107- type ConsoleReporter () =
108- let sw = Stopwatch.StartNew()
109- interface ITestReporter with
110- member _.BeginSuite ( name , level ) =
111- let indent = String.replicate ( level * 2 ) " "
112- printfn $" {indent}{AnsiColours.green}{name}{AnsiColours.reset}"
113-
114- member _.ReportResult ( result , level ) =
115- let indent = String.replicate ( level * 2 ) " "
116- match result with
117- | Passed (_, name) ->
118- printfn $" %s {indent}%s {AnsiColours.green} ✅ passed: %s {name}%s {AnsiColours.reset}"
119- | Failed (_, name, ex) ->
120- printfn $" %s {indent}%s {AnsiColours.red} ❌ failed: %s {name} - %s {ex.Message}%s {AnsiColours.reset}"
121- | Pending (_, name) ->
122- printfn $" %s {indent}%s {AnsiColours.grey} ❔ pending: %s {name}%s {AnsiColours.reset}"
123-
124- member _.EndSuite ( _ , _ ) = ()
125- member _.Debug ( message : string , indent : string , indentCount : int ): unit =
126- let indent = String.replicate ( indentCount * 2 ) indent
127- printfn $" %s {indent}%s {AnsiColours.grey}%s {message}%s {AnsiColours.reset}"
128- member _.Info ( message : string , indent : string , indentCount : int ): unit =
129- let indent = String.replicate ( indentCount * 2 ) indent
130- printfn $" %s {indent}%s {AnsiColours.grey}%s {message}%s {AnsiColours.reset}"
131- member _.End ( testResults : TestResult list ): unit =
132- printfn $" got %d {List.length testResults} test results"
133- testResults |> List.iter ( function
134- // | Passed (path, name) ->
135- // let pathString = String.concat " / " path.Value
136- // context.Log $"Test passed: %s{AnsiColours.green}%s{pathString} - %s{name}%s{AnsiColours.reset}"
137- | Failed ( path, name, ex) ->
138- let pathString = String.concat " / " path.Value
139- printfn $" Test failed: %s {AnsiColours.red}%s {pathString} / %s {name} - %s {ex.Message}%s {AnsiColours.reset}"
140- // | Pending (path, name) ->
141- // let pathString = String.concat " / " path.Value
142- // context.Log $"Test pending: %s{AnsiColours.grey}%s{pathString} / %s{name}%s{AnsiColours.reset}")
143- | _ -> ())
144-
145- // Count results
146- let passedCount = testResults |> List.filter ( function Passed _ -> true | _ -> false ) |> List.length
147- let failedCount = testResults |> List.filter ( function Failed _ -> true | _ -> false ) |> List.length
148- let pendingCount = testResults |> List.filter ( function Pending _ -> true | _ -> false ) |> List.length
149-
150- printfn $" Summary: {passedCount} passed, {failedCount} failed, {pendingCount} pending"
151- printfn $" Total time: %s {sw.Elapsed.ToString()}"
100+ module Reporters =
101+ open System.Diagnostics
102+
103+ module AnsiColours =
104+ let green = " \u001b [32m"
105+ let red = " \u001b [31m"
106+ let grey = " \u001b [90m"
107+ let white = " \u001b [37m"
108+ let reset = " \u001b [0m"
109+
110+ type ConsoleReporter () =
111+ let sw = Stopwatch.StartNew()
112+ let indent ( path : Path ) = String.replicate (( path.Length - 1 ) * 2 ) " "
113+ interface ITestReporter with
114+ member _.BeginSuite ( name , path ) =
115+ let indent = indent path
116+ printfn $" %s {indent}{AnsiColours.green}{name}{AnsiColours.reset}"
117+
118+ member _.ReportResult ( result , path ) =
119+ let indent = indent path
120+ match result with
121+ | Passed (_, name) ->
122+ printfn $" %s {indent}%s {AnsiColours.green} ✅ passed: %s {name}%s {AnsiColours.reset}"
123+ | Failed (_, name, ex) ->
124+ printfn $" %s {indent}%s {AnsiColours.red} ❌ failed: %s {name} - %s {ex.Message}%s {AnsiColours.reset}"
125+ | Pending (_, name) ->
126+ printfn $" %s {indent}%s {AnsiColours.grey} ❔ pending: %s {name}%s {AnsiColours.reset}"
127+
128+ member _.EndSuite ( _ , _ ) = ()
129+ member _.Debug ( message : string , path : Path ): unit =
130+ let indent = indent path
131+ printfn $" %s {indent}%s {AnsiColours.grey}%s {message}%s {AnsiColours.reset}"
132+ member _.Info ( message : string , path : Path ): unit =
133+ let indent = indent path
134+ printfn $" %s {indent}%s {AnsiColours.white}%s {message}%s {AnsiColours.reset}"
135+ member _.End ( testResults : TestResult list ): unit =
136+ let testFailures = testResults |> List.filter ( function Failed _ -> true | _ -> false )
137+ if not ( List.isEmpty testFailures) then
138+ printfn $" There were %d {List.length testFailures} test failures:"
139+ else
140+ printfn $" All tests passed!"
141+ testResults |> List.iter ( function
142+ | Failed ( path, name, ex) ->
143+ let pathString = String.concat " / " path.Value
144+ printfn $" - %s {AnsiColours.red}%s {pathString} / %s {name} - %s {ex.Message}%s {AnsiColours.reset}"
145+ | _ -> ())
146+
147+ // Count results
148+ let passedCount = testResults |> List.filter ( function Passed _ -> true | _ -> false ) |> List.length
149+ let failedCount = testResults |> List.filter ( function Failed _ -> true | _ -> false ) |> List.length
150+ let pendingCount = testResults |> List.filter ( function Pending _ -> true | _ -> false ) |> List.length
151+
152+ printfn $" Summary: {passedCount} passed, {failedCount} failed, {pendingCount} pending"
153+ printfn $" Total time: %s {sw.Elapsed.ToString()}"
152154
153155type TestContext = {
154156 Path: Path
155157 ParentBeforeHooks: HookFunction list
156158 ParentAfterHooks: HookFunction list
157159 Reporter: ITestReporter
158- Indent: string
159- IndentCount: int
160160 Log: string -> unit
161161}
162162with
163163 static member Empty = {
164164 Path = Path []
165165 ParentBeforeHooks = []
166166 ParentAfterHooks = []
167- Indent = " "
168- IndentCount = 2
169- Reporter = ConsoleReporter() :> ITestReporter
167+ Reporter = Reporters.ConsoleReporter() :> ITestReporter
170168 Log = printfn " %s "
171169 }
172170
173171type TestSuiteRunner = Describe -> TestContext -> unit
172+ type private LogLevel = Debug of string | Info of string
174173
175- let runTestCase path ( testCase : It ): TestResult =
176- match testCase.Body with
177- | Some body ->
178- try
179- body()
180- Passed ( path, testCase.Name)
181- with ex ->
182- Failed ( path, testCase.Name, ex)
183- | None ->
184- Pending ( path, testCase.Name)
185-
186- let rec doRunTestSuite ( suite : Describe ) ( context : TestContext ) =
174+ let private runTestCase path ( testCase : It ) beforeHooks afterHooks =
187175 // setup logging functions
188176 let info ' , debug' = info, debug
189177 use _ = { new System.IDisposable with
190178 member _.Dispose () = info <- info'; debug <- debug' }
191- info <- fun s -> context.Reporter.Info( s, context.Indent, context.IndentCount)
192- debug <- fun s -> context.Reporter.Debug( s, context.Indent, context.IndentCount)
193-
194- context.Reporter.BeginSuite( suite.Name, context.IndentCount)
179+ let logs = ResizeArray< LogLevel>()
180+ info <- fun s -> logs.Add ( Info s)
181+ debug <- fun s -> logs.Add ( Debug s)
182+ beforeHooks |> Seq.iter ( fun hookFunction -> hookFunction())
183+ let result =
184+ match testCase.Body with
185+ | Some body ->
186+ try
187+ body()
188+ Passed ( path, testCase.Name)
189+ with ex ->
190+ Failed ( path, testCase.Name, ex)
191+ | None ->
192+ Pending ( path, testCase.Name)
193+ afterHooks |> Seq.iter ( fun hookFunction -> hookFunction())
194+ result, logs
195+
196+ let rec private doRunTestSuite ( suite : Describe ) ( context : TestContext ): TestResult list =
197+ context.Reporter.BeginSuite( suite.Name, context.Path)
195198
196199 let beforeHooks , afterHooks =
197200 suite.Each
@@ -205,13 +208,15 @@ let rec doRunTestSuite (suite: Describe) (context: TestContext) =
205208
206209 let testResults = [
207210 for testCase in suite.TestCases do
208- beforeHooks |> Seq.iter ( fun hookFunction -> hookFunction())
209- runTestCase context.Path testCase
210- afterHooks |> Seq.iter ( fun hookFunction -> hookFunction())
211+ runTestCase context.Path testCase beforeHooks afterHooks
211212 ]
212213
213- for result in testResults do
214- context.Reporter.ReportResult( result, context.Path.Length)
214+ for result, logs in testResults do
215+ for log in logs do
216+ match log with
217+ | Info message -> context.Reporter.Info( message, context.Path)
218+ | Debug message -> context.Reporter.Debug( message, context.Path)
219+ context.Reporter.ReportResult( result, context.Path)
215220
216221 let childrenResults =
217222 suite.Children
@@ -224,23 +229,22 @@ let rec doRunTestSuite (suite: Describe) (context: TestContext) =
224229 doRunTestSuite
225230 child
226231 childContext)
227- testResults @ List.concat childrenResults
232+ let allResults = ( testResults |> List.map fst) @ List.concat childrenResults
233+ allResults
228234
229- let runTestSuite ( sb : Describe ) ( context : TestContext ) =
235+ let runTestSuiteWithContext ( sb : Describe ) ( context : TestContext ) =
230236 let testResults = doRunTestSuite sb { context with Path = Path ( context.Path.Value @ [ sb.Name]) }
231- context.Reporter.EndSuite( sb.Name, context.Path.Length )
237+ context.Reporter.EndSuite( sb.Name, context.Path)
232238 context.Reporter.End testResults
233- ()
239+
240+ let runTestSuite ( sb : Describe ) = runTestSuiteWithContext sb TestContext.Empty
234241
235242[<AutoOpen>]
236243module Constraints =
237244 let shouldEqual expected actual =
238245 if expected <> actual then
239246 failwithf " Expected %A but got %A " expected actual
240- else
241- info $" Expected %A {expected} and got %A {actual} - test passed"
247+
242248 let shouldNotEqual unexpected actual =
243249 if unexpected = actual then
244250 failwithf " Expected not to be %A but got %A " unexpected actual
245- else
246- info $" Expected not to be %A {unexpected} and got %A {actual} - test passed"
0 commit comments