Skip to content

Commit 6d9198f

Browse files
committed
refactor: reorganize test reporter structure and improve logging functionality
1 parent ed767eb commit 6d9198f

File tree

1 file changed

+95
-91
lines changed

1 file changed

+95
-91
lines changed

Sprout.fs

Lines changed: 95 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
module Sprout
2-
open System.Diagnostics
32

43
let mutable info: string -> unit = ignore
54
let mutable debug: string -> unit = ignore
@@ -36,12 +35,6 @@ type ItBuilder(name: string) =
3635
let it name = ItBuilder name
3736
let 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-
4538
type Describe = {
4639
Name: string
4740
TestCases: It list
@@ -97,101 +90,111 @@ type TestResult =
9790
| Pending of Path * string
9891

9992
type 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

153155
type 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
}
162162
with
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

173171
type 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>]
236243
module 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

Comments
 (0)