From 86240905d3d4df27cf9bf15516dc9e9a5ff2b179 Mon Sep 17 00:00:00 2001 From: Anthony Lloyd Date: Tue, 19 Jun 2018 23:12:17 +0100 Subject: [PATCH 01/10] add flushable logger --- Expecto/Expecto.fs | 22 ++++++++++++++++------ Expecto/Logging.fs | 4 ++++ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/Expecto/Expecto.fs b/Expecto/Expecto.fs index 17eb6716..2a752e38 100644 --- a/Expecto/Expecto.fs +++ b/Expecto/Expecto.fs @@ -1026,21 +1026,29 @@ module Impl = } evalTests config test + let private flush (l:Logger) = + let d = l :?> IDisposable + if isNull d |> not then d.Dispose() + /// Runs tests, returns error code let runEvalWithCancel (ct:CancellationToken) config test = async { do! config.printer.beforeRun test let w = Stopwatch.StartNew() - let! results = evalTests config test + let! results = evalTestsWithCancel ct config test w.Stop() - let testSummary = { results = results - duration = w.Elapsed - maxMemory = 0L - memoryLimit = 0L - timedOut = [] } + let testSummary = { + results = results + duration = w.Elapsed + maxMemory = 0L + memoryLimit = 0L + timedOut = [] + } do! config.printer.summary config testSummary + flush logger + return testSummary.errorCode } @@ -1165,6 +1173,8 @@ module Impl = do! config.printer.summary config testSummary + flush logger + return testSummary.errorCode } diff --git a/Expecto/Logging.fs b/Expecto/Logging.fs index 0d896e1f..078eef58 100644 --- a/Expecto/Logging.fs +++ b/Expecto/Logging.fs @@ -12,6 +12,7 @@ /// /// Changes: /// Changed namespace to Expecto.Logging and file name to Logging.fs - Anthony Lloyd - 11 Jun 2018 +/// Add IFlushable - Anthony Lloyd - 19 Jun 2018 /// namespace Expecto.Logging @@ -293,6 +294,9 @@ module LoggerEx = // TODO: timeXXX functions +type IFlushable = + abstract Flush : unit -> unit + type LoggingConfig = { /// The `timestamp` function should preferably be monotonic and not 'jumpy' /// or take much time to call. From 609241401503279404659c6a7d69aec38a5da7e5 Mon Sep 17 00:00:00 2001 From: Anthony Lloyd Date: Tue, 19 Jun 2018 23:32:18 +0100 Subject: [PATCH 02/10] add flushable logger --- Expecto/Expecto.fs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Expecto/Expecto.fs b/Expecto/Expecto.fs index 2a752e38..b0196b59 100644 --- a/Expecto/Expecto.fs +++ b/Expecto/Expecto.fs @@ -1027,8 +1027,7 @@ module Impl = evalTests config test let private flush (l:Logger) = - let d = l :?> IDisposable - if isNull d |> not then d.Dispose() + if l :? IFlushable then (l :?> IFlushable).Flush() /// Runs tests, returns error code let runEvalWithCancel (ct:CancellationToken) config test = From 6ea7792e854ad35eab8f2bcb3156e00fc1fe73be Mon Sep 17 00:00:00 2001 From: Anthony Lloyd Date: Wed, 20 Jun 2018 23:35:39 +0100 Subject: [PATCH 03/10] add ansi logger --- Expecto/Expecto.fs | 2 +- Expecto/Logging.fs | 71 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 71 insertions(+), 2 deletions(-) diff --git a/Expecto/Expecto.fs b/Expecto/Expecto.fs index b0196b59..f4ef8f53 100644 --- a/Expecto/Expecto.fs +++ b/Expecto/Expecto.fs @@ -1649,7 +1649,7 @@ module Tests = let runTestsWithCancel (ct:CancellationToken) config (tests:Test) = Global.initialiseIfDefault { Global.defaultConfig with - getLogger = fun name -> LiterateConsoleTarget(name, config.verbosity, consoleSemaphore = Global.semaphore()) :> Logger } + getLogger = fun name -> ANSIConsoleLogger(name, config.verbosity, consoleSemaphore = Global.semaphore()) :> Logger } config.logName |> Option.iter setLogName if config.failOnFocusedTests && passesFocusTestCheck config tests |> not then 1 diff --git a/Expecto/Logging.fs b/Expecto/Logging.fs index 078eef58..0d8d01ea 100644 --- a/Expecto/Logging.fs +++ b/Expecto/Logging.fs @@ -17,6 +17,7 @@ namespace Expecto.Logging open System +open System.Text /// The log level denotes how 'important' the gauge or event message is. [] @@ -940,6 +941,70 @@ type CombiningTarget(name, otherLoggers: Logger list) = |> Async.Parallel |> Async.Ignore // Async +type ANSIConsoleLogger(name, minLevel, ?consoleSemaphore) = + let sem = defaultArg consoleSemaphore (obj()) + let options = Literate.LiterateOptions.create() + let tokenise = LiterateTokenisation.tokeniseMessage + let buffer = StringBuilder() + + let colorForWhite = + if Console.BackgroundColor = ConsoleColor.White then "\x1B[90m" else "\x1B[97m" + let colorReset = "\x1B[0m" + + let colorANSI = function + | ConsoleColor.White -> colorForWhite + | ConsoleColor.Gray -> "\x1B[31m" + | ConsoleColor.DarkGray -> "\x1B[90m" + | ConsoleColor.Yellow -> "\x1B[93m" + | ConsoleColor.Red -> "\x1B[91m" + | ConsoleColor.Blue -> "\x1B[33m" + | ConsoleColor.Magenta -> "\x1B[95m" + | ConsoleColor.Cyan -> "\x1B[96m" + | ConsoleColor.Green -> "\x1B[92m" + | _ -> "\x1B[334m" + + let atomicallyWriteColouredTextToConsole sem (parts: (string * ConsoleColor) list) = + lock sem <| fun _ -> + let mutable currentColour = Console.ForegroundColor + parts |> List.iter (fun (text, colour) -> + if currentColour <> colour then + colorANSI colour |> buffer.Append |> ignore + currentColour <- colour + buffer.Append text |> ignore + ) + buffer.Append colorReset |> ignore + + let atomicallyWriteTextToConsole sem (parts: (string * ConsoleColor) list) = + lock sem <| fun _ -> + parts |> List.iter (fun (text, _) -> + buffer.Append text |> ignore + ) + + let textToBuffer = + if Console.IsOutputRedirected then atomicallyWriteTextToConsole + else atomicallyWriteColouredTextToConsole + + let writeColourisedThenNewLine message = + [ yield! tokenise options message + yield Environment.NewLine, Literate.Text ] + |> List.map (fun (s, t) -> s, options.theme(t)) + |> textToBuffer sem + + interface Logger with + member __.name = name + member __.logWithAck level msgFactory = + if level >= minLevel then writeColourisedThenNewLine (msgFactory level) + async.Return() + member __.log level msgFactory = + if level >= minLevel then writeColourisedThenNewLine (msgFactory level) + async.Return() + interface IFlushable with + member __.Flush() = + lock sem (fun _ -> + buffer.ToString() |> Console.Write + buffer.Clear() |> ignore + ) + module Global = /// This is the global semaphore for colourising the console output. Ensure /// that the same semaphore is used across libraries by using the Logary @@ -949,7 +1014,7 @@ module Global = /// The global default configuration, which logs to Console at Info level. let defaultConfig = { timestamp = fun () -> DateTimeOffset.timestamp DateTimeOffset.UtcNow - getLogger = fun name -> LiterateConsoleTarget(name, Info) :> Logger + getLogger = fun name -> ANSIConsoleLogger(name, Info) :> Logger consoleSemaphore = consoleSemaphore } let private config = @@ -984,6 +1049,10 @@ module Global = member x.logWithAck level msgFactory = withLogger (fun logger -> logger.logWithAck level (msgFactory >> ensureName)) + interface IFlushable with + member __.Flush() = + if logger :? IFlushable then (logger :?> IFlushable).Flush() + let internal getStaticLogger (name: string []) = Flyweight name From 2cd03c786a109c5d19fc6c9985de7de9f2fd34c4 Mon Sep 17 00:00:00 2001 From: Anthony Lloyd Date: Wed, 27 Jun 2018 23:40:58 +0100 Subject: [PATCH 04/10] progress indicator --- Expecto.Tests/Tests.fs | 2 +- Expecto/Expecto.fs | 56 +++++++++++++++++++++++++++++--------- Expecto/Expecto.fsproj | 1 + Expecto/Logging.fs | 26 ++++++++++++------ Expecto/Progress.fs | 61 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 124 insertions(+), 22 deletions(-) create mode 100644 Expecto/Progress.fs diff --git a/Expecto.Tests/Tests.fs b/Expecto.Tests/Tests.fs index fcee2e44..28e512dc 100644 --- a/Expecto.Tests/Tests.fs +++ b/Expecto.Tests/Tests.fs @@ -1300,7 +1300,7 @@ let cancel = use ct = new CancellationTokenSource() let! _ = Async.StartChild(async { do! Async.Sleep 50 ct.Cancel() }) - let! results = evalTestsWithCancel ct.Token config t + let! results = evalTestsWithCancel ct.Token config t false results |> List.iter (fun (_,r) -> let d = int r.duration.TotalMilliseconds Expect.isLessThan d 1000 "cancel length" diff --git a/Expecto/Expecto.fs b/Expecto/Expecto.fs index f4ef8f53..ebc36e19 100644 --- a/Expecto/Expecto.fs +++ b/Expecto/Expecto.fs @@ -396,6 +396,11 @@ module Impl = let mutable logger = Log.create "Expecto" let setLogName name = logger <- Log.create name + let flush (l:Logger) = + if l :? IFlushable then + ProgressIndicator.pause (fun () -> + (l :?> IFlushable).Flush() + ) type TestResult = | Passed @@ -953,9 +958,14 @@ module Impl = config.parallelWorkers /// Evaluates tests. - let evalTestsWithCancel (ct:CancellationToken) config test = + let evalTestsWithCancel (ct:CancellationToken) config test progressStarted = async { + let tests = Test.toTestCodeList test + let testLength = List.length tests + + let testsCompleted = ref 0 + let evalTestAsync (test:FlatTest) = let beforeEach (test:FlatTest) = @@ -966,10 +976,14 @@ module Impl = let! result = execTestAsync ct config test do! beforeAsync do! TestPrinters.printResult config test result + + if progressStarted then + Interlocked.Increment(testsCompleted) + testLength/200 + / testLength |> ProgressIndicator.update + return test,result } - let tests = Test.toTestCodeList test let inline cons xs x = x::xs if not config.``parallel`` || @@ -1015,7 +1029,7 @@ module Impl = /// Evaluates tests. let evalTests config test = - evalTestsWithCancel CancellationToken.None config test + evalTestsWithCancel CancellationToken.None config test false let evalTestsSilent test = let config = @@ -1026,16 +1040,17 @@ module Impl = } evalTests config test - let private flush (l:Logger) = - if l :? IFlushable then (l :?> IFlushable).Flush() - /// Runs tests, returns error code let runEvalWithCancel (ct:CancellationToken) config test = async { do! config.printer.beforeRun test + ProgressIndicator.text "Expecto Running... " + let progressStarted = ProgressIndicator.start() + + let w = Stopwatch.StartNew() - let! results = evalTestsWithCancel ct config test + let! results = evalTestsWithCancel ct config test progressStarted w.Stop() let testSummary = { results = results @@ -1046,6 +1061,8 @@ module Impl = } do! config.printer.summary config testSummary + if progressStarted then ProgressIndicator.stop() + flush logger return testSummary.errorCode @@ -1059,6 +1076,9 @@ module Impl = async { do! config.printer.beforeRun test + ProgressIndicator.text "Expecto Running... " + let progressStarted = ProgressIndicator.start() + let tests = Test.toTestCodeList test |> List.filter (fun t -> Option.isNone t.shouldSkipEvaluation) @@ -1075,11 +1095,13 @@ module Impl = let next = List.length tests |> rand.Next List.item next tests - let finishTimestamp = - lazy + let totalTicks = config.stress.Value.TotalSeconds * float Stopwatch.Frequency |> int64 - |> (+) (Stopwatch.GetTimestamp()) + + let finishTime = + lazy + totalTicks |> (+) (Stopwatch.GetTimestamp()) let asyncRun foldRunner (runningTests:ResizeArray<_>, results, @@ -1108,7 +1130,7 @@ module Impl = Async.Start(async { let finishMilliseconds = - max (finishTimestamp.Value - Stopwatch.GetTimestamp()) 0L + max (finishTime.Value - Stopwatch.GetTimestamp()) 0L * 1000L / Stopwatch.Frequency let timeout = int finishMilliseconds + int config.stressTimeout.TotalMilliseconds @@ -1117,7 +1139,13 @@ module Impl = }, cancel.Token) Seq.takeWhile (fun test -> - if Stopwatch.GetTimestamp() < finishTimestamp.Value + let now = Stopwatch.GetTimestamp() + + if progressStarted then + 100 - int((finishTime.Value - now + totalTicks / 200L) / totalTicks) + |> ProgressIndicator.update + + if now < finishTime.Value && not ct.IsCancellationRequested then runningTests.Add test true @@ -1143,7 +1171,7 @@ module Impl = |> asyncRun Async.foldSequentiallyWithCancel initial |> Async.bind (fun (runningTests,results,maxMemory) -> if maxMemory > memoryLimit || - Stopwatch.GetTimestamp() > finishTimestamp.Value then + Stopwatch.GetTimestamp() > finishTime.Value then async.Return (runningTests,results,maxMemory) else let parallel = @@ -1172,6 +1200,8 @@ module Impl = do! config.printer.summary config testSummary + if progressStarted then ProgressIndicator.stop() + flush logger return testSummary.errorCode diff --git a/Expecto/Expecto.fsproj b/Expecto/Expecto.fsproj index fa7bf7cf..4da25247 100644 --- a/Expecto/Expecto.fsproj +++ b/Expecto/Expecto.fsproj @@ -15,6 +15,7 @@ + diff --git a/Expecto/Logging.fs b/Expecto/Logging.fs index 0d8d01ea..11aa0461 100644 --- a/Expecto/Logging.fs +++ b/Expecto/Logging.fs @@ -990,14 +990,6 @@ type ANSIConsoleLogger(name, minLevel, ?consoleSemaphore) = |> List.map (fun (s, t) -> s, options.theme(t)) |> textToBuffer sem - interface Logger with - member __.name = name - member __.logWithAck level msgFactory = - if level >= minLevel then writeColourisedThenNewLine (msgFactory level) - async.Return() - member __.log level msgFactory = - if level >= minLevel then writeColourisedThenNewLine (msgFactory level) - async.Return() interface IFlushable with member __.Flush() = lock sem (fun _ -> @@ -1005,6 +997,24 @@ type ANSIConsoleLogger(name, minLevel, ?consoleSemaphore) = buffer.Clear() |> ignore ) + interface Logger with + member __.name = name + member x.logWithAck level msgFactory = + if level >= minLevel then + writeColourisedThenNewLine (msgFactory level) + Expecto.ProgressIndicator.pause (fun () -> + (x :> IFlushable).Flush() + ) + async.Return() + member x.log level msgFactory = + if level >= minLevel then + writeColourisedThenNewLine (msgFactory level) + if level=Error then + Expecto.ProgressIndicator.pause (fun () -> + (x :> IFlushable).Flush() + ) + async.Return() + module Global = /// This is the global semaphore for colourising the console output. Ensure /// that the same semaphore is used across libraries by using the Logary diff --git a/Expecto/Progress.fs b/Expecto/Progress.fs new file mode 100644 index 00000000..f692d600 --- /dev/null +++ b/Expecto/Progress.fs @@ -0,0 +1,61 @@ +namespace Expecto + +open System +open System.Threading + +module internal ProgressIndicator = + + let private hideCursor = "\x1B[?25l" + let private showCursor = "\x1B[?25h" + let private animation = @"|/-\" + + let mutable private textValue = String.Empty + + let private percentValue = ref 0 + let private isRunning = ref false + + let text s = textValue <- s + + let update percent = + percentValue := max percent 0 |> min 100 + + let start() = + lock isRunning (fun () -> + if !isRunning then false + else + isRunning := true + if not Console.IsOutputRedirected then + hideCursor + textValue |> stdout.Write + Thread(fun () -> + while true do + lock isRunning (fun () -> + if !isRunning then + let p = !percentValue + let a = animation.[p % animation.Length] + let percent = + if p=100 then [|'1';'0';'0';'%';' ';a|] + elif p<10 then [|' ';' ';char(48+p);'%';' ';a|] + else [|' ';char(48+p/10);char(48+p%10);'%';' ';a|] + String percent + "\b\b\b\b\b\b" |> stdout.Write + ) + Thread.Sleep 250 + ).Start() + true + ) + + let stop() = + lock isRunning (fun() -> + if !isRunning then + isRunning := false + if not Console.IsOutputRedirected then + let lineLength = textValue.Length + 7 + let erase = String('\b', lineLength) + erase + String(' ', lineLength) + erase + showCursor |> stdout.Write + ) + + let pause f = + lock isRunning (fun () -> + if !isRunning then + stop(); f(); start() |> ignore + else f() + ) \ No newline at end of file From a99e865433488b671caddde5b78168aae7149ce4 Mon Sep 17 00:00:00 2001 From: Anthony Lloyd Date: Fri, 29 Jun 2018 23:24:17 +0100 Subject: [PATCH 05/10] fix progress, still a bit more --- Expecto/Progress.fs | 29 +++++++++++++++++++---------- build.fsx | 8 ++++++++ 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/Expecto/Progress.fs b/Expecto/Progress.fs index f692d600..62d9031e 100644 --- a/Expecto/Progress.fs +++ b/Expecto/Progress.fs @@ -10,11 +10,14 @@ module internal ProgressIndicator = let private animation = @"|/-\" let mutable private textValue = String.Empty - + let mutable private eraseLine = String.Empty let private percentValue = ref 0 let private isRunning = ref false - let text s = textValue <- s + let text s = + textValue <- s + let lineLength = textValue.Length + 7 + eraseLine <- String('\b', lineLength) let update percent = percentValue := max percent 0 |> min 100 @@ -25,18 +28,21 @@ module internal ProgressIndicator = else isRunning := true if not Console.IsOutputRedirected then - hideCursor + textValue |> stdout.Write + hideCursor |> stdout.Write Thread(fun () -> - while true do + let start = DateTime.UtcNow + while !isRunning do lock isRunning (fun () -> if !isRunning then let p = !percentValue - let a = animation.[p % animation.Length] + let t = (DateTime.UtcNow - start).TotalSeconds + let a = animation.[int t % animation.Length] let percent = if p=100 then [|'1';'0';'0';'%';' ';a|] elif p<10 then [|' ';' ';char(48+p);'%';' ';a|] else [|' ';char(48+p/10);char(48+p%10);'%';' ';a|] - String percent + "\b\b\b\b\b\b" |> stdout.Write + eraseLine + textValue + String percent + eraseLine |> stdout.Write + Console.Out.Flush() ) Thread.Sleep 250 ).Start() @@ -48,9 +54,8 @@ module internal ProgressIndicator = if !isRunning then isRunning := false if not Console.IsOutputRedirected then - let lineLength = textValue.Length + 7 - let erase = String('\b', lineLength) - erase + String(' ', lineLength) + erase + showCursor |> stdout.Write + eraseLine + String(' ', eraseLine.Length) + eraseLine + showCursor |> stdout.Write + Console.Out.Flush() ) let pause f = @@ -58,4 +63,8 @@ module internal ProgressIndicator = if !isRunning then stop(); f(); start() |> ignore else f() - ) \ No newline at end of file + ) + +// TODO +// 123/199 tests +// test output, where can I be \ No newline at end of file diff --git a/build.fsx b/build.fsx index 17ef9e18..713cc3f7 100644 --- a/build.fsx +++ b/build.fsx @@ -63,8 +63,15 @@ let build project = ToolPath = dotnetExePath Configuration = configuration Project = project + AdditionalArgs = ["--no-dependencies"] }) +Target "BuildExpecto" (fun _ -> + build "Expecto/Expecto.fsproj" + build "Expecto.Hopac/Expecto.Hopac.fsproj" + build "Expecto.FsCheck/Expecto.FsCheck.fsproj" +) + Target "BuildBenchmarkDotNet" (fun _ -> build "Expecto.BenchmarkDotNet/Expecto.BenchmarkDotNet.fsproj" ) @@ -150,6 +157,7 @@ Target "All" ignore ==> "InstallDotNetCore" ==> "AssemblyInfo" ==> "ProjectVersion" +==> "BuildExpecto" ==> "BuildBenchmarkDotNet" ==> "BuildTest" ==> "RunTest" From 0f42d6b29d81e9f83463a05cc3a21041bdbec537 Mon Sep 17 00:00:00 2001 From: Anthony Lloyd Date: Mon, 2 Jul 2018 23:18:50 +0100 Subject: [PATCH 06/10] output writer rework --- Expecto/Expect.fs | 2 +- Expecto/Expecto.fs | 23 +++++---- Expecto/Logging.fs | 4 -- Expecto/Progress.fs | 123 +++++++++++++++++++++++++++++++++++--------- 4 files changed, 112 insertions(+), 40 deletions(-) diff --git a/Expecto/Expect.fs b/Expecto/Expect.fs index 9ab8b998..90d3488f 100644 --- a/Expecto/Expect.fs +++ b/Expecto/Expect.fs @@ -587,7 +587,7 @@ let streamsEqual (s1 : IO.Stream) (s2 : IO.Stream) message = /// subset of the functions. Statistical test to 99.99% confidence level. let isFasterThanSub (f1:Performance.Measurer<_,_>->'a) (f2:Performance.Measurer<_,_>->'a) format = let toString (s:SampleStatistics) = - sprintf "%.4f \u00B1 %.4f ms" s.mean s.meanStandardError + sprintf "%.4f ± %.4f ms" s.mean s.meanStandardError match Performance.timeCompare f1 f2 with | Performance.ResultNotTheSame (r1, r2)-> diff --git a/Expecto/Expecto.fs b/Expecto/Expecto.fs index ebc36e19..4955dec8 100644 --- a/Expecto/Expecto.fs +++ b/Expecto/Expecto.fs @@ -394,13 +394,11 @@ module Impl = open Helpers open Mono.Cecil + let ansiOutputWriter = new ANSIOutputWriter() let mutable logger = Log.create "Expecto" let setLogName name = logger <- Log.create name - let flush (l:Logger) = - if l :? IFlushable then - ProgressIndicator.pause (fun () -> - (l :?> IFlushable).Flush() - ) + let flushLogger() = + ansiOutputWriter.Flush() type TestResult = | Passed @@ -978,8 +976,8 @@ module Impl = do! TestPrinters.printResult config test result if progressStarted then - Interlocked.Increment(testsCompleted) + testLength/200 - / testLength |> ProgressIndicator.update + Fraction (Interlocked.Increment testsCompleted, testLength) + |> ProgressIndicator.update return test,result } @@ -1063,7 +1061,7 @@ module Impl = if progressStarted then ProgressIndicator.stop() - flush logger + flushLogger() return testSummary.errorCode } @@ -1143,6 +1141,7 @@ module Impl = if progressStarted then 100 - int((finishTime.Value - now + totalTicks / 200L) / totalTicks) + |> Percent |> ProgressIndicator.update if now < finishTime.Value @@ -1202,7 +1201,7 @@ module Impl = if progressStarted then ProgressIndicator.stop() - flush logger + flushLogger() return testSummary.errorCode } @@ -1673,13 +1672,15 @@ module Tests = | _ -> None ) - /// Runs tests with the supplied config. /// Returns 0 if all tests passed, otherwise 1 let runTestsWithCancel (ct:CancellationToken) config (tests:Test) = Global.initialiseIfDefault { Global.defaultConfig with - getLogger = fun name -> ANSIConsoleLogger(name, config.verbosity, consoleSemaphore = Global.semaphore()) :> Logger } + getLogger = fun name -> + LiterateConsoleTarget(name, config.verbosity, + outputWriter = ansiOutputWriter.TextToOutput, + consoleSemaphore = Global.semaphore()) :> Logger } config.logName |> Option.iter setLogName if config.failOnFocusedTests && passesFocusTestCheck config tests |> not then 1 diff --git a/Expecto/Logging.fs b/Expecto/Logging.fs index 11aa0461..531532f8 100644 --- a/Expecto/Logging.fs +++ b/Expecto/Logging.fs @@ -10,10 +10,6 @@ /// Original Source: /// https://github.com/logary/logary/blob/996bdf92713f406b17c6cd7284e4d674f49e3ff6/src/Logary.Facade/Facade.fs /// -/// Changes: -/// Changed namespace to Expecto.Logging and file name to Logging.fs - Anthony Lloyd - 11 Jun 2018 -/// Add IFlushable - Anthony Lloyd - 19 Jun 2018 -/// namespace Expecto.Logging open System diff --git a/Expecto/Progress.fs b/Expecto/Progress.fs index 62d9031e..bd8dcc18 100644 --- a/Expecto/Progress.fs +++ b/Expecto/Progress.fs @@ -2,49 +2,69 @@ namespace Expecto open System open System.Threading +open System.IO +open System.Text -module internal ProgressIndicator = +type private FuncTextWriter(encoding:Encoding, write:string->unit) = + inherit TextWriter() + override __.Encoding = encoding + override __.Write (s:string) = write s + override __.WriteLine (s:string) = s + "\n" |> write + override __.WriteLine() = write "\n" + +type Progress = + | Percent of int + | Fraction of int * int +module internal ProgressIndicator = + let originalStdout = stdout let private hideCursor = "\x1B[?25l" let private showCursor = "\x1B[?25h" let private animation = @"|/-\" let mutable private textValue = String.Empty - let mutable private eraseLine = String.Empty - let private percentValue = ref 0 + let private progressValue = Percent 0 |> ref let private isRunning = ref false + let private isEnabled = not Console.IsOutputRedirected let text s = textValue <- s - let lineLength = textValue.Length + 7 - eraseLine <- String('\b', lineLength) - let update percent = - percentValue := max percent 0 |> min 100 + let update progress = + progressValue := + match progress with + | Percent p -> max p 0 |> min 100 |> Percent + | f -> f let start() = lock isRunning (fun () -> if !isRunning then false else isRunning := true - if not Console.IsOutputRedirected then + if isEnabled then hideCursor |> stdout.Write Thread(fun () -> - let start = DateTime.UtcNow - while !isRunning do - lock isRunning (fun () -> - if !isRunning then - let p = !percentValue - let t = (DateTime.UtcNow - start).TotalSeconds - let a = animation.[int t % animation.Length] - let percent = + let start = DateTime.UtcNow + while !isRunning do + lock isRunning (fun () -> + if !isRunning then + let t = (DateTime.UtcNow - start).TotalSeconds + let a = animation.[int t % animation.Length] + let progress = + match !progressValue with + | Percent p -> if p=100 then [|'1';'0';'0';'%';' ';a|] elif p<10 then [|' ';' ';char(48+p);'%';' ';a|] else [|' ';char(48+p/10);char(48+p%10);'%';' ';a|] - eraseLine + textValue + String percent + eraseLine |> stdout.Write - Console.Out.Flush() - ) - Thread.Sleep 250 + |> String + | Fraction (n,d) -> + let ns, ds = string n, string d + String(' ',ds.Length-ns.Length) + ns + "/" + ds + " " + string a + textValue + progress + String('\b', textValue.Length + progress.Length) + |> originalStdout.Write + Console.Out.Flush() + ) + Thread.Sleep 1000 ).Start() true ) @@ -53,8 +73,9 @@ module internal ProgressIndicator = lock isRunning (fun() -> if !isRunning then isRunning := false - if not Console.IsOutputRedirected then - eraseLine + String(' ', eraseLine.Length) + eraseLine + showCursor |> stdout.Write + if isEnabled then + String(' ', 80) + String('\b', 80) + showCursor + |> originalStdout.Write Console.Out.Flush() ) @@ -65,6 +86,60 @@ module internal ProgressIndicator = else f() ) +type ANSIOutputWriter() = + let buffer = StringBuilder() + let colorForWhite = + if Console.BackgroundColor = ConsoleColor.White then "\x1B[90m" else "\x1B[97m" + let colorReset = "\x1B[0m" + + let colorANSI = function + | ConsoleColor.White -> colorForWhite + | ConsoleColor.Gray -> "\x1B[31m" + | ConsoleColor.DarkGray -> "\x1B[90m" + | ConsoleColor.Yellow -> "\x1B[93m" + | ConsoleColor.Red -> "\x1B[91m" + | ConsoleColor.Blue -> "\x1B[33m" + | ConsoleColor.Magenta -> "\x1B[95m" + | ConsoleColor.Cyan -> "\x1B[96m" + | ConsoleColor.Green -> "\x1B[92m" + | _ -> "\x1B[334m" + + let foregroundColor = Console.ForegroundColor + + let textToOutput (parts: (string * ConsoleColor) list) = + lock buffer <| fun _ -> + let mutable currentColour = foregroundColor + parts |> List.iter (fun (text, colour) -> + if currentColour <> colour then + colorANSI colour |> buffer.Append |> ignore + currentColour <- colour + buffer.Append text |> ignore + ) + buffer.Append colorReset |> ignore + + let originalStdout = + let orig = ProgressIndicator.originalStdout + let textToOutput s = textToOutput [s, foregroundColor] + let tw = new FuncTextWriter(orig.Encoding, textToOutput) + Console.SetOut tw + orig + + member __.TextToOutput (sem:obj) (parts: (string * ConsoleColor) list) = + lock sem <| fun _ -> + textToOutput parts + + member __.Flush() = + lock buffer <| fun _ -> + ProgressIndicator.pause <| fun _ -> + buffer.ToString() |> originalStdout.Write + buffer.Clear() |> ignore + + interface IDisposable with + member __.Dispose() = + Console.SetOut originalStdout + // TODO -// 123/199 tests -// test output, where can I be \ No newline at end of file +// stderr +// flush on error and printfn +// stdout dispose etc +// fix colours \ No newline at end of file From 3cc6fbe03c95db2b43cb282b1fcf4eecaf0f1f2c Mon Sep 17 00:00:00 2001 From: Anthony Lloyd Date: Tue, 3 Jul 2018 22:50:27 +0100 Subject: [PATCH 07/10] vtmode, progress etc --- Expecto/Expecto.fs | 72 ++++++++++++------------ Expecto/Logging.fs | 83 +--------------------------- Expecto/Progress.fs | 131 ++++++++++++++++++++++++++++---------------- README.md | 8 --- 4 files changed, 120 insertions(+), 174 deletions(-) diff --git a/Expecto/Expecto.fs b/Expecto/Expecto.fs index 4955dec8..57cf0ede 100644 --- a/Expecto/Expecto.fs +++ b/Expecto/Expecto.fs @@ -394,11 +394,8 @@ module Impl = open Helpers open Mono.Cecil - let ansiOutputWriter = new ANSIOutputWriter() let mutable logger = Log.create "Expecto" let setLogName name = logger <- Log.create name - let flushLogger() = - ansiOutputWriter.Flush() type TestResult = | Passed @@ -622,33 +619,29 @@ module Impl = >> setField "reason" m) failed = fun n m d -> - logger.logWithAck LogLevel.Error ( - eventX "{testName} failed in {duration}. {message}" - >> setField "testName" n - >> setField "duration" d - >> setField "message" m) + async { + do! logger.logWithAck LogLevel.Error ( + eventX "{testName} failed in {duration}. {message}" + >> setField "testName" n + >> setField "duration" d + >> setField "message" m) + ANSIOutputWriter.Flush() + } exn = fun n e d -> - logger.logWithAck LogLevel.Error ( - eventX "{testName} errored in {duration}" - >> setField "testName" n - >> setField "duration" d - >> addExn e) + async { + do! logger.logWithAck LogLevel.Error ( + eventX "{testName} errored in {duration}" + >> setField "testName" n + >> setField "duration" d + >> addExn e) + ANSIOutputWriter.Flush() + } + - summary = fun config summary -> + summary = fun _config summary -> let spirit = - if summary.successful then - if Console.OutputEncoding.WebName = "utf-8" - && not config.mySpiritIsWeak then - "ᕙ໒( ˵ ಠ ╭͜ʖ╮ ಠೃ ˵ )७ᕗ" - else - "Success!" - else - if Console.OutputEncoding.WebName = "utf-8" - && not config.mySpiritIsWeak then - "( ರ Ĺ̯ ರೃ )" - else - "" + if summary.successful then "Success!" else String.Empty let commonAncestor = let rec loop ancestor (descendants : string list) = match descendants with @@ -855,7 +848,7 @@ module Impl = /// FsCheck end size (default: 100 for testing and 10,000 for /// stress testing). fsCheckEndSize: int option - /// Turn off spirits. + /// Depricated. Will be removed on next major release. mySpiritIsWeak: bool /// Allows duplicate test names. allowDuplicateNames: bool @@ -869,10 +862,11 @@ module Impl = filter = id failOnFocusedTests = false printer = - if Environment.GetEnvironmentVariable "TEAMCITY_PROJECT_NAME" <> null then - TestPrinters.teamCityPrinter TestPrinters.defaultPrinter - else + let tc = Environment.GetEnvironmentVariable "TEAMCITY_PROJECT_NAME" + if isNull tc then TestPrinters.defaultPrinter + else + TestPrinters.teamCityPrinter TestPrinters.defaultPrinter verbosity = Info logName = None locate = fun _ -> SourceLocation.empty @@ -1059,9 +1053,9 @@ module Impl = } do! config.printer.summary config testSummary - if progressStarted then ProgressIndicator.stop() - - flushLogger() + if progressStarted then + ProgressIndicator.stop() + ANSIOutputWriter.Close() return testSummary.errorCode } @@ -1199,9 +1193,9 @@ module Impl = do! config.printer.summary config testSummary - if progressStarted then ProgressIndicator.stop() - - flushLogger() + if progressStarted then + ProgressIndicator.stop() + ANSIOutputWriter.Close() return testSummary.errorCode } @@ -1363,6 +1357,7 @@ module Impl = eventX "It was requested that no focused tests exist, but yet there are {count} focused tests found." >> setField "count" focused.Length) |> Async.StartImmediate + ANSIOutputWriter.Flush() false [] @@ -1618,7 +1613,7 @@ module Tests = | FsCheck_Max_Tests n -> fun o -> {o with fsCheckMaxTests = n } | FsCheck_Start_Size n -> fun o -> {o with fsCheckStartSize = n } | FsCheck_End_Size n -> fun o -> {o with fsCheckEndSize = Some n } - | My_Spirit_Is_Weak -> fun o -> { o with mySpiritIsWeak = true } + | My_Spirit_Is_Weak -> id | Allow_Duplicate_Names -> fun o -> { o with allowDuplicateNames = true } let parsed = @@ -1679,7 +1674,7 @@ module Tests = { Global.defaultConfig with getLogger = fun name -> LiterateConsoleTarget(name, config.verbosity, - outputWriter = ansiOutputWriter.TextToOutput, + outputWriter = ANSIOutputWriter.TextToOutput, consoleSemaphore = Global.semaphore()) :> Logger } config.logName |> Option.iter setLogName if config.failOnFocusedTests && passesFocusTestCheck config tests |> not then @@ -1696,6 +1691,7 @@ module Tests = eventX "Found duplicated test names, these names are: {duplicates}" >> setField "duplicates" duplicates.Value ) |> Async.RunSynchronously + ANSIOutputWriter.Flush() 1 /// Runs tests with the supplied config. /// Returns 0 if all tests passed, otherwise 1 diff --git a/Expecto/Logging.fs b/Expecto/Logging.fs index 531532f8..85e3bde7 100644 --- a/Expecto/Logging.fs +++ b/Expecto/Logging.fs @@ -291,9 +291,6 @@ module LoggerEx = // TODO: timeXXX functions -type IFlushable = - abstract Flush : unit -> unit - type LoggingConfig = { /// The `timestamp` function should preferably be monotonic and not 'jumpy' /// or take much time to call. @@ -937,80 +934,6 @@ type CombiningTarget(name, otherLoggers: Logger list) = |> Async.Parallel |> Async.Ignore // Async -type ANSIConsoleLogger(name, minLevel, ?consoleSemaphore) = - let sem = defaultArg consoleSemaphore (obj()) - let options = Literate.LiterateOptions.create() - let tokenise = LiterateTokenisation.tokeniseMessage - let buffer = StringBuilder() - - let colorForWhite = - if Console.BackgroundColor = ConsoleColor.White then "\x1B[90m" else "\x1B[97m" - let colorReset = "\x1B[0m" - - let colorANSI = function - | ConsoleColor.White -> colorForWhite - | ConsoleColor.Gray -> "\x1B[31m" - | ConsoleColor.DarkGray -> "\x1B[90m" - | ConsoleColor.Yellow -> "\x1B[93m" - | ConsoleColor.Red -> "\x1B[91m" - | ConsoleColor.Blue -> "\x1B[33m" - | ConsoleColor.Magenta -> "\x1B[95m" - | ConsoleColor.Cyan -> "\x1B[96m" - | ConsoleColor.Green -> "\x1B[92m" - | _ -> "\x1B[334m" - - let atomicallyWriteColouredTextToConsole sem (parts: (string * ConsoleColor) list) = - lock sem <| fun _ -> - let mutable currentColour = Console.ForegroundColor - parts |> List.iter (fun (text, colour) -> - if currentColour <> colour then - colorANSI colour |> buffer.Append |> ignore - currentColour <- colour - buffer.Append text |> ignore - ) - buffer.Append colorReset |> ignore - - let atomicallyWriteTextToConsole sem (parts: (string * ConsoleColor) list) = - lock sem <| fun _ -> - parts |> List.iter (fun (text, _) -> - buffer.Append text |> ignore - ) - - let textToBuffer = - if Console.IsOutputRedirected then atomicallyWriteTextToConsole - else atomicallyWriteColouredTextToConsole - - let writeColourisedThenNewLine message = - [ yield! tokenise options message - yield Environment.NewLine, Literate.Text ] - |> List.map (fun (s, t) -> s, options.theme(t)) - |> textToBuffer sem - - interface IFlushable with - member __.Flush() = - lock sem (fun _ -> - buffer.ToString() |> Console.Write - buffer.Clear() |> ignore - ) - - interface Logger with - member __.name = name - member x.logWithAck level msgFactory = - if level >= minLevel then - writeColourisedThenNewLine (msgFactory level) - Expecto.ProgressIndicator.pause (fun () -> - (x :> IFlushable).Flush() - ) - async.Return() - member x.log level msgFactory = - if level >= minLevel then - writeColourisedThenNewLine (msgFactory level) - if level=Error then - Expecto.ProgressIndicator.pause (fun () -> - (x :> IFlushable).Flush() - ) - async.Return() - module Global = /// This is the global semaphore for colourising the console output. Ensure /// that the same semaphore is used across libraries by using the Logary @@ -1020,7 +943,7 @@ module Global = /// The global default configuration, which logs to Console at Info level. let defaultConfig = { timestamp = fun () -> DateTimeOffset.timestamp DateTimeOffset.UtcNow - getLogger = fun name -> ANSIConsoleLogger(name, Info) :> Logger + getLogger = fun name -> LiterateConsoleTarget(name, Info) :> Logger consoleSemaphore = consoleSemaphore } let private config = @@ -1055,10 +978,6 @@ module Global = member x.logWithAck level msgFactory = withLogger (fun logger -> logger.logWithAck level (msgFactory >> ensureName)) - interface IFlushable with - member __.Flush() = - if logger :? IFlushable then (logger :?> IFlushable).Flush() - let internal getStaticLogger (name: string []) = Flyweight name diff --git a/Expecto/Progress.fs b/Expecto/Progress.fs index bd8dcc18..789b2bdb 100644 --- a/Expecto/Progress.fs +++ b/Expecto/Progress.fs @@ -4,6 +4,9 @@ open System open System.Threading open System.IO open System.Text +open System.Runtime.InteropServices + +#nowarn "9" type private FuncTextWriter(encoding:Encoding, write:string->unit) = inherit TextWriter() @@ -18,9 +21,13 @@ type Progress = module internal ProgressIndicator = let originalStdout = stdout + let originalStderr = stderr let private hideCursor = "\x1B[?25l" let private showCursor = "\x1B[?25h" let private animation = @"|/-\" + + let private color = "\x1b[30;1m" + let private colorReset = "\x1B[0m" let mutable private textValue = String.Empty let private progressValue = Percent 0 |> ref @@ -60,9 +67,10 @@ module internal ProgressIndicator = | Fraction (n,d) -> let ns, ds = string n, string d String(' ',ds.Length-ns.Length) + ns + "/" + ds + " " + string a - textValue + progress + String('\b', textValue.Length + progress.Length) + color + textValue + progress + + String('\b', textValue.Length + progress.Length) + colorReset |> originalStdout.Write - Console.Out.Flush() + originalStdout.Flush() ) Thread.Sleep 1000 ).Start() @@ -76,7 +84,7 @@ module internal ProgressIndicator = if isEnabled then String(' ', 80) + String('\b', 80) + showCursor |> originalStdout.Write - Console.Out.Flush() + originalStdout.Flush() ) let pause f = @@ -86,27 +94,34 @@ module internal ProgressIndicator = else f() ) -type ANSIOutputWriter() = - let buffer = StringBuilder() - let colorForWhite = - if Console.BackgroundColor = ConsoleColor.White then "\x1B[90m" else "\x1B[97m" - let colorReset = "\x1B[0m" - - let colorANSI = function +module internal ANSIOutputWriter = + let private colorForWhite = + if Console.BackgroundColor = ConsoleColor.White then "\x1B[30m" + else "\x1B[37;1m" + let private colorReset = "\x1B[0m" + let private colorANSI = function + | ConsoleColor.Black -> "\x1b[30m" + | ConsoleColor.DarkBlue -> "\x1b[34m" + | ConsoleColor.DarkGreen -> "\x1b[32m" + | ConsoleColor.DarkCyan -> "\x1b[36m" + | ConsoleColor.DarkRed -> "\x1b[31m" + | ConsoleColor.DarkMagenta -> "\x1b[35m" + | ConsoleColor.DarkYellow -> "\x1b[33m" + | ConsoleColor.Gray -> "\x1b[37m" + | ConsoleColor.DarkGray -> "\x1b[30;1m" + | ConsoleColor.Blue -> "\x1b[34;1m" + | ConsoleColor.Green -> "\x1b[32;1m" + | ConsoleColor.Cyan -> "\x1b[36;1m" + | ConsoleColor.Red -> "\x1b[31;1m" + | ConsoleColor.Magenta -> "\x1b[35;1m" + | ConsoleColor.Yellow -> "\x1b[33;1m" | ConsoleColor.White -> colorForWhite - | ConsoleColor.Gray -> "\x1B[31m" - | ConsoleColor.DarkGray -> "\x1B[90m" - | ConsoleColor.Yellow -> "\x1B[93m" - | ConsoleColor.Red -> "\x1B[91m" - | ConsoleColor.Blue -> "\x1B[33m" - | ConsoleColor.Magenta -> "\x1B[95m" - | ConsoleColor.Cyan -> "\x1B[96m" - | ConsoleColor.Green -> "\x1B[92m" - | _ -> "\x1B[334m" - - let foregroundColor = Console.ForegroundColor - - let textToOutput (parts: (string * ConsoleColor) list) = + | _ -> "" + + let private foregroundColor = Console.ForegroundColor + + let private buffer = StringBuilder() + let private textToOutput (parts: (string * ConsoleColor) list) = lock buffer <| fun _ -> let mutable currentColour = foregroundColor parts |> List.iter (fun (text, colour) -> @@ -117,29 +132,53 @@ type ANSIOutputWriter() = ) buffer.Append colorReset |> ignore - let originalStdout = - let orig = ProgressIndicator.originalStdout - let textToOutput s = textToOutput [s, foregroundColor] - let tw = new FuncTextWriter(orig.Encoding, textToOutput) - Console.SetOut tw - orig - - member __.TextToOutput (sem:obj) (parts: (string * ConsoleColor) list) = - lock sem <| fun _ -> - textToOutput parts - - member __.Flush() = + let Flush() = lock buffer <| fun _ -> ProgressIndicator.pause <| fun _ -> - buffer.ToString() |> originalStdout.Write + buffer.ToString() |> ProgressIndicator.originalStdout.Write buffer.Clear() |> ignore - - interface IDisposable with - member __.Dispose() = - Console.SetOut originalStdout - -// TODO -// stderr -// flush on error and printfn -// stdout dispose etc -// fix colours \ No newline at end of file + let Close() = + Flush() + Console.SetOut ProgressIndicator.originalStdout + Console.SetError ProgressIndicator.originalStderr + + module WindowsConsole = + open Microsoft.FSharp.NativeInterop + [] + extern void* private GetStdHandle(int _nStdHandle) + [] + extern bool private GetConsoleMode(void* _hConsoleHandle, int* _lpMode) + [] + extern bool private SetConsoleMode(void* _hConsoleHandle, int _lpMode) + let enableVTMode() = + let INVALID_HANDLE_VALUE = nativeint -1 + let STD_OUTPUT_HANDLE = -11 + let ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 + let handle = GetStdHandle(STD_OUTPUT_HANDLE) + if handle <> INVALID_HANDLE_VALUE then + let mode = NativePtr.stackalloc 1 + if GetConsoleMode(handle, mode) then + let value = NativePtr.read mode + let value = value ||| ENABLE_VIRTUAL_TERMINAL_PROCESSING + SetConsoleMode(handle, value) |> ignore + do +#if NETFRAMEWORK + WindowsConsole.enableVTMode() +#else + if RuntimeInformation.IsOSPlatform OSPlatform.Windows then + WindowsConsole.enableVTMode() +#endif + ProgressIndicator.originalStdout.Flush() + let encoding = ProgressIndicator.originalStdout.Encoding + let std s = textToOutput [s, foregroundColor] + new FuncTextWriter(encoding, std) + |> Console.SetOut + let errorEncoding = ProgressIndicator.originalStderr.Encoding + let errorToOutput s = + textToOutput [s, ConsoleColor.Red] + Flush() + new FuncTextWriter(errorEncoding, errorToOutput) + |> Console.SetError + let TextToOutput (sem:obj) (parts: (string * ConsoleColor) list) = + lock sem <| fun _ -> + textToOutput parts \ No newline at end of file diff --git a/README.md b/README.md index 979cb5d3..10444287 100644 --- a/README.md +++ b/README.md @@ -1085,8 +1085,6 @@ ExpectoConfig record, that looks like: /// FsCheck end size (default: 100 for testing and 10,000 for /// stress testing). fsCheckEndSize: int option - /// Turn off spirits. - mySpiritIsWeak: bool /// Allows duplicate test names. allowDuplicateNames: bool } @@ -1094,12 +1092,6 @@ ExpectoConfig record, that looks like: By doing a `let config = { defaultConfig with parallel = true }`, for example. -If you [don't](https://github.com/haf/expecto/pull/43) -[like](https://github.com/haf/expecto/issues/145#issuecomment-297032723) the -spirits appearing in the output, you can turn them off by setting -`mySpiritIsWeak = true` when you run Expecto, or by running with -`--my-spirit-is-weak` from the command line ( ರ Ĺ̯ ರೃ ). - ## Contributing Please review the [guidelines for contributing](./CONTRIBUTING.md) to Expecto. From e5d02af260af0bc7fe19cd8f29e4c4ca5ed80fd4 Mon Sep 17 00:00:00 2001 From: Anthony Lloyd Date: Tue, 3 Jul 2018 23:04:49 +0100 Subject: [PATCH 08/10] preprocessor fix --- Expecto/Progress.fs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Expecto/Progress.fs b/Expecto/Progress.fs index 789b2bdb..980954ef 100644 --- a/Expecto/Progress.fs +++ b/Expecto/Progress.fs @@ -162,11 +162,11 @@ module internal ANSIOutputWriter = let value = value ||| ENABLE_VIRTUAL_TERMINAL_PROCESSING SetConsoleMode(handle, value) |> ignore do -#if NETFRAMEWORK - WindowsConsole.enableVTMode() -#else +#if NETSTANDARD2_0 if RuntimeInformation.IsOSPlatform OSPlatform.Windows then WindowsConsole.enableVTMode() +#else + WindowsConsole.enableVTMode() #endif ProgressIndicator.originalStdout.Flush() let encoding = ProgressIndicator.originalStdout.Encoding From 0bc7f0eea740cb6f6ba8d1caff46dc62e4bda443 Mon Sep 17 00:00:00 2001 From: Anthony Lloyd Date: Tue, 3 Jul 2018 23:25:19 +0100 Subject: [PATCH 09/10] try no netXX --- Expecto/Progress.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Expecto/Progress.fs b/Expecto/Progress.fs index 980954ef..bed9cf06 100644 --- a/Expecto/Progress.fs +++ b/Expecto/Progress.fs @@ -166,7 +166,7 @@ module internal ANSIOutputWriter = if RuntimeInformation.IsOSPlatform OSPlatform.Windows then WindowsConsole.enableVTMode() #else - WindowsConsole.enableVTMode() + //WindowsConsole.enableVTMode() #endif ProgressIndicator.originalStdout.Flush() let encoding = ProgressIndicator.originalStdout.Encoding From a9fe44bdb6635596302b4f0df3aa2a3a1bfa8f68 Mon Sep 17 00:00:00 2001 From: Anthony Lloyd Date: Wed, 4 Jul 2018 08:51:22 +0100 Subject: [PATCH 10/10] net windows check, currentLength --- Expecto/Progress.fs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Expecto/Progress.fs b/Expecto/Progress.fs index bed9cf06..02e60bfa 100644 --- a/Expecto/Progress.fs +++ b/Expecto/Progress.fs @@ -37,6 +37,8 @@ module internal ProgressIndicator = let text s = textValue <- s + let mutable private currentLength = 0 + let update progress = progressValue := match progress with @@ -66,9 +68,11 @@ module internal ProgressIndicator = |> String | Fraction (n,d) -> let ns, ds = string n, string d - String(' ',ds.Length-ns.Length) + ns + "/" + ds + " " + string a - color + textValue + progress + - String('\b', textValue.Length + progress.Length) + colorReset + String(' ',ds.Length-ns.Length) + ns + "/" + ds + + " " + string a + currentLength <- textValue.Length + progress.Length + color + textValue + progress + + String('\b', currentLength) + colorReset |> originalStdout.Write originalStdout.Flush() ) @@ -82,7 +86,7 @@ module internal ProgressIndicator = if !isRunning then isRunning := false if isEnabled then - String(' ', 80) + String('\b', 80) + showCursor + String(' ', currentLength) + String('\b', currentLength) + showCursor |> originalStdout.Write originalStdout.Flush() ) @@ -166,7 +170,8 @@ module internal ANSIOutputWriter = if RuntimeInformation.IsOSPlatform OSPlatform.Windows then WindowsConsole.enableVTMode() #else - //WindowsConsole.enableVTMode() + if Environment.OSVersion.Platform = PlatformID.Win32NT then + WindowsConsole.enableVTMode() #endif ProgressIndicator.originalStdout.Flush() let encoding = ProgressIndicator.originalStdout.Encoding