module Test.Reporter.Stdout
( report,
)
where
import qualified Control.Exception as Exception
import qualified Data.ByteString as BS
import qualified GHC.Stack as Stack
import qualified List
import qualified Maybe
import NriPrelude
import qualified Numeric
import qualified Platform.Internal
import qualified System.IO
import qualified Test.Internal as Internal
import Test.Reporter.Internal (black, green, grey, red, yellow)
import qualified Test.Reporter.Internal
import qualified Text
import Text.Colour (chunk)
import qualified Text.Colour
import qualified Text.Colour.Capabilities.FromEnv
import qualified Prelude
report :: System.IO.Handle -> Internal.SuiteResult -> Prelude.IO ()
report :: Handle -> SuiteResult -> IO ()
report Handle
handle SuiteResult
results = do
terminalCapabilities <- Handle -> IO TerminalCapabilities
Text.Colour.Capabilities.FromEnv.getTerminalCapabilitiesFromHandle Handle
handle
reportChunks <- renderReport results
Text.Colour.hPutChunksUtf8With terminalCapabilities handle reportChunks
System.IO.hFlush handle
renderReport :: Internal.SuiteResult -> Prelude.IO (List (Text.Colour.Chunk))
renderReport :: SuiteResult -> IO (List Chunk)
renderReport SuiteResult
results =
let elapsed :: Text
elapsed = SuiteResult -> Text
formatElapsedDuration SuiteResult
results
in case SuiteResult
results of
Internal.AllPassed [SingleTest TracingSpan]
passed ->
let amountPassed :: Int
amountPassed = [SingleTest TracingSpan] -> Int
forall a. List a -> Int
List.length [SingleTest TracingSpan]
passed
in List Chunk -> IO (List Chunk)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
Prelude.pure
[ Chunk -> Chunk
green (Chunk -> Chunk
Text.Colour.underline Chunk
"TEST RUN PASSED"),
Chunk
"\n\n",
Chunk -> Chunk
black (Chunk -> Chunk) -> Chunk -> Chunk
forall a b. (a -> b) -> a -> b
<| Text -> Chunk
chunk (Text -> Chunk) -> Text -> Chunk
forall a b. (a -> b) -> a -> b
<| Text
"Duration: " Text -> Text -> Text
forall appendable.
Semigroup appendable =>
appendable -> appendable -> appendable
++ Text
elapsed,
Chunk
"\n",
Chunk -> Chunk
black (Text -> Chunk
chunk (Text -> Chunk) -> Text -> Chunk
forall a b. (a -> b) -> a -> b
<| Text
"Passed: " Text -> Text -> Text
forall appendable.
Semigroup appendable =>
appendable -> appendable -> appendable
++ Int -> Text
Text.fromInt Int
amountPassed),
Chunk
"\n"
]
Internal.OnlysPassed [SingleTest TracingSpan]
passed [SingleTest NotRan]
skipped ->
let amountPassed :: Int
amountPassed = [SingleTest TracingSpan] -> Int
forall a. List a -> Int
List.length [SingleTest TracingSpan]
passed
amountSkipped :: Int
amountSkipped = [SingleTest NotRan] -> Int
forall a. List a -> Int
List.length [SingleTest NotRan]
skipped
in List Chunk -> IO (List Chunk)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
Prelude.pure (List Chunk -> IO (List Chunk)) -> List Chunk -> IO (List Chunk)
forall a b. (a -> b) -> a -> b
<|
List (List Chunk) -> List Chunk
forall a. List (List a) -> List a
List.concat
[ (SingleTest TracingSpan -> List Chunk)
-> [SingleTest TracingSpan] -> List Chunk
forall a b. (a -> List b) -> List a -> List b
List.concatMap
( \SingleTest TracingSpan
only ->
(Chunk -> Chunk) -> SingleTest TracingSpan -> List Chunk
forall a. (Chunk -> Chunk) -> SingleTest a -> List Chunk
prettyPath Chunk -> Chunk
yellow SingleTest TracingSpan
only
List Chunk -> List Chunk -> List Chunk
forall appendable.
Semigroup appendable =>
appendable -> appendable -> appendable
++ [ Chunk
"This test passed, but there is a `Test.only` in your test.\n",
Chunk
"I failed the test, because it's easy to forget to remove `Test.only`.\n",
Chunk
"\n\n"
]
)
[SingleTest TracingSpan]
passed,
[ Chunk -> Chunk
yellow (Chunk -> Chunk
Text.Colour.underline (Chunk
"TEST RUN INCOMPLETE")),
Chunk -> Chunk
yellow Chunk
" because there is an `only` in your tests.",
Chunk
"\n\n",
Chunk -> Chunk
black (Chunk -> Chunk) -> Chunk -> Chunk
forall a b. (a -> b) -> a -> b
<| Text -> Chunk
chunk (Text -> Chunk) -> Text -> Chunk
forall a b. (a -> b) -> a -> b
<| Text
"Duration: " Text -> Text -> Text
forall appendable.
Semigroup appendable =>
appendable -> appendable -> appendable
++ Text
elapsed,
Chunk
"\n",
Chunk -> Chunk
black (Text -> Chunk
chunk (Text -> Chunk) -> Text -> Chunk
forall a b. (a -> b) -> a -> b
<| Text
"Passed: " Text -> Text -> Text
forall appendable.
Semigroup appendable =>
appendable -> appendable -> appendable
++ Int -> Text
Text.fromInt Int
amountPassed),
Chunk
"\n",
Chunk -> Chunk
black (Text -> Chunk
chunk (Text -> Chunk) -> Text -> Chunk
forall a b. (a -> b) -> a -> b
<| Text
"Skipped: " Text -> Text -> Text
forall appendable.
Semigroup appendable =>
appendable -> appendable -> appendable
++ Int -> Text
Text.fromInt Int
amountSkipped),
Chunk
"\n"
]
]
Internal.PassedWithSkipped [SingleTest TracingSpan]
passed [SingleTest NotRan]
skipped ->
let amountPassed :: Int
amountPassed = [SingleTest TracingSpan] -> Int
forall a. List a -> Int
List.length [SingleTest TracingSpan]
passed
amountSkipped :: Int
amountSkipped = [SingleTest NotRan] -> Int
forall a. List a -> Int
List.length [SingleTest NotRan]
skipped
in List Chunk -> IO (List Chunk)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
Prelude.pure (List Chunk -> IO (List Chunk)) -> List Chunk -> IO (List Chunk)
forall a b. (a -> b) -> a -> b
<|
List (List Chunk) -> List Chunk
forall a. List (List a) -> List a
List.concat
[ (SingleTest NotRan -> List Chunk)
-> [SingleTest NotRan] -> List Chunk
forall a b. (a -> List b) -> List a -> List b
List.concatMap
( \SingleTest NotRan
only ->
(Chunk -> Chunk) -> SingleTest NotRan -> List Chunk
forall a. (Chunk -> Chunk) -> SingleTest a -> List Chunk
prettyPath Chunk -> Chunk
yellow SingleTest NotRan
only
List Chunk -> List Chunk -> List Chunk
forall appendable.
Semigroup appendable =>
appendable -> appendable -> appendable
++ [ Chunk
"This test was skipped.",
Chunk
"\n\n"
]
)
[SingleTest NotRan]
skipped,
[ Chunk -> Chunk
yellow (Chunk -> Chunk
Text.Colour.underline Chunk
"TEST RUN INCOMPLETE"),
Chunk -> Chunk
yellow
( Text -> Chunk
chunk (Text -> Chunk) -> Text -> Chunk
forall a b. (a -> b) -> a -> b
<| case [SingleTest NotRan] -> Int
forall a. List a -> Int
List.length [SingleTest NotRan]
skipped of
Int
1 -> Text
" because 1 test was skipped"
Int
n -> Text
" because " Text -> Text -> Text
forall appendable.
Semigroup appendable =>
appendable -> appendable -> appendable
++ Int -> Text
Text.fromInt Int
n Text -> Text -> Text
forall appendable.
Semigroup appendable =>
appendable -> appendable -> appendable
++ Text
" tests were skipped"
),
Chunk
"\n\n",
Chunk -> Chunk
black (Chunk -> Chunk) -> Chunk -> Chunk
forall a b. (a -> b) -> a -> b
<| Text -> Chunk
chunk (Text -> Chunk) -> Text -> Chunk
forall a b. (a -> b) -> a -> b
<| Text
"Duration: " Text -> Text -> Text
forall appendable.
Semigroup appendable =>
appendable -> appendable -> appendable
++ Text
elapsed,
Chunk
"\n",
Chunk -> Chunk
black (Text -> Chunk
chunk (Text -> Chunk) -> Text -> Chunk
forall a b. (a -> b) -> a -> b
<| Text
"Passed: " Text -> Text -> Text
forall appendable.
Semigroup appendable =>
appendable -> appendable -> appendable
++ Int -> Text
Text.fromInt Int
amountPassed),
Chunk
"\n",
Chunk -> Chunk
black (Text -> Chunk
chunk (Text -> Chunk) -> Text -> Chunk
forall a b. (a -> b) -> a -> b
<| Text
"Skipped: " Text -> Text -> Text
forall appendable.
Semigroup appendable =>
appendable -> appendable -> appendable
++ Int -> Text
Text.fromInt Int
amountSkipped),
Chunk
"\n"
]
]
Internal.TestsFailed [SingleTest TracingSpan]
passed [SingleTest NotRan]
skipped [SingleTest FailedSpan]
failed -> do
let amountPassed :: Int
amountPassed = [SingleTest TracingSpan] -> Int
forall a. List a -> Int
List.length [SingleTest TracingSpan]
passed
let amountFailed :: Int
amountFailed = [SingleTest FailedSpan] -> Int
forall a. List a -> Int
List.length [SingleTest FailedSpan]
failed
let amountSkipped :: Int
amountSkipped = [SingleTest NotRan] -> Int
forall a. List a -> Int
List.length [SingleTest NotRan]
skipped
let failures :: List (SingleTest Failure)
failures = (SingleTest FailedSpan -> SingleTest Failure)
-> [SingleTest FailedSpan] -> List (SingleTest Failure)
forall a b. (a -> b) -> List a -> List b
List.map ((FailedSpan -> Failure)
-> SingleTest FailedSpan -> SingleTest Failure
forall (m :: * -> *) a value.
Functor m =>
(a -> value) -> m a -> m value
map (\(Internal.FailedSpan TracingSpan
_ Failure
failure) -> Failure
failure)) [SingleTest FailedSpan]
failed
srcLocs <- (SingleTest Failure -> IO (Maybe (SrcLoc, ByteString)))
-> List (SingleTest Failure) -> IO [Maybe (SrcLoc, ByteString)]
forall (t :: * -> *) (f :: * -> *) a b.
(Traversable t, Applicative f) =>
(a -> f b) -> t a -> f (t b)
forall (f :: * -> *) a b.
Applicative f =>
(a -> f b) -> [a] -> f [b]
Prelude.traverse SingleTest Failure -> IO (Maybe (SrcLoc, ByteString))
Test.Reporter.Internal.readSrcLoc List (SingleTest Failure)
failures
let failuresSrcs = (Maybe (SrcLoc, ByteString) -> List Chunk)
-> [Maybe (SrcLoc, ByteString)] -> List (List Chunk)
forall a b. (a -> b) -> List a -> List b
List.map Maybe (SrcLoc, ByteString) -> List Chunk
renderFailureInFile [Maybe (SrcLoc, ByteString)]
srcLocs
Prelude.pure <|
List.concat
[ List.concat <|
List.map2
( \List Chunk
srcLines SingleTest Failure
test ->
(Chunk -> Chunk) -> SingleTest Failure -> List Chunk
forall a. (Chunk -> Chunk) -> SingleTest a -> List Chunk
prettyPath Chunk -> Chunk
red SingleTest Failure
test
List Chunk -> List Chunk -> List Chunk
forall appendable.
Semigroup appendable =>
appendable -> appendable -> appendable
++ List Chunk
srcLines
List Chunk -> List Chunk -> List Chunk
forall appendable.
Semigroup appendable =>
appendable -> appendable -> appendable
++ [SingleTest Failure -> Chunk
testFailure SingleTest Failure
test, Chunk
"\n\n"]
)
failuresSrcs
failures,
[ red (Text.Colour.underline "TEST RUN FAILED"),
"\n\n",
black <| chunk <| "Duration: " ++ elapsed,
"\n",
black (chunk <| "Passed: " ++ Text.fromInt amountPassed),
"\n"
],
if amountSkipped == 0
then []
else
[ black (chunk <| "Skipped: " ++ Text.fromInt amountSkipped),
"\n"
],
[black (chunk <| "Failed: " ++ Text.fromInt amountFailed), "\n"]
]
SuiteResult
Internal.NoTestsInSuite ->
List Chunk -> IO (List Chunk)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
Prelude.pure
[ Chunk -> Chunk
yellow (Chunk -> Chunk
Text.Colour.underline Chunk
"TEST RUN INCOMPLETE"),
Chunk -> Chunk
yellow Chunk
" because the test suite is empty.",
Chunk
"\n"
]
renderFailureInFile :: Maybe (Stack.SrcLoc, BS.ByteString) -> List Text.Colour.Chunk
renderFailureInFile :: Maybe (SrcLoc, ByteString) -> List Chunk
renderFailureInFile Maybe (SrcLoc, ByteString)
maybeSrcLoc =
case Maybe (SrcLoc, ByteString)
maybeSrcLoc of
Just (SrcLoc
loc, ByteString
src) -> SrcLoc -> ByteString -> List Chunk
Test.Reporter.Internal.renderSrcLoc SrcLoc
loc ByteString
src
Maybe (SrcLoc, ByteString)
Nothing -> []
prettyPath :: (Text.Colour.Chunk -> Text.Colour.Chunk) -> Internal.SingleTest a -> List Text.Colour.Chunk
prettyPath :: forall a. (Chunk -> Chunk) -> SingleTest a -> List Chunk
prettyPath Chunk -> Chunk
style SingleTest a
test =
let loc :: SrcLoc
loc = SingleTest a -> SrcLoc
forall a. SingleTest a -> SrcLoc
Internal.loc SingleTest a
test
in List (List Chunk) -> List Chunk
forall a. List (List a) -> List a
List.concat
[ [ Chunk -> Chunk
grey (Chunk -> Chunk) -> Chunk -> Chunk
forall a b. (a -> b) -> a -> b
<|
Text -> Chunk
chunk
( Text
"↓ "
Text -> Text -> Text
forall appendable.
Semigroup appendable =>
appendable -> appendable -> appendable
++ String -> Text
Text.fromList (SrcLoc -> String
Stack.srcLocFile SrcLoc
loc)
Text -> Text -> Text
forall appendable.
Semigroup appendable =>
appendable -> appendable -> appendable
++ Text
":"
Text -> Text -> Text
forall appendable.
Semigroup appendable =>
appendable -> appendable -> appendable
++ Int -> Text
Text.fromInt (Int -> Int
forall a b. (Integral a, Num b) => a -> b
Prelude.fromIntegral (SrcLoc -> Int
Stack.srcLocStartLine SrcLoc
loc))
Text -> Text -> Text
forall appendable.
Semigroup appendable =>
appendable -> appendable -> appendable
++ Text
"\n"
)
],
[ Chunk -> Chunk
grey
( Text -> Chunk
chunk (Text -> Chunk) -> Text -> Chunk
forall a b. (a -> b) -> a -> b
<|
(Text -> Text) -> [Text] -> Text
forall m a. Monoid m => (a -> m) -> [a] -> m
forall (t :: * -> *) m a.
(Foldable t, Monoid m) =>
(a -> m) -> t a -> m
Prelude.foldMap
(\Text
text -> Text
"↓ " Text -> Text -> Text
forall appendable.
Semigroup appendable =>
appendable -> appendable -> appendable
++ Text
text Text -> Text -> Text
forall appendable.
Semigroup appendable =>
appendable -> appendable -> appendable
++ Text
"\n")
(SingleTest a -> [Text]
forall a. SingleTest a -> [Text]
Internal.describes SingleTest a
test)
),
Chunk -> Chunk
style (Text -> Chunk
chunk (Text
"✗ " Text -> Text -> Text
forall appendable.
Semigroup appendable =>
appendable -> appendable -> appendable
++ SingleTest a -> Text
forall a. SingleTest a -> Text
Internal.name SingleTest a
test)),
Chunk
"\n"
]
]
testFailure :: Internal.SingleTest Internal.Failure -> Text.Colour.Chunk
testFailure :: SingleTest Failure -> Chunk
testFailure SingleTest Failure
test =
Text -> Chunk
chunk (Text -> Chunk) -> Text -> Chunk
forall a b. (a -> b) -> a -> b
<|
case SingleTest Failure -> Failure
forall a. SingleTest a -> a
Internal.body SingleTest Failure
test of
Internal.FailedAssertion Text
msg SrcLoc
_ -> Text
msg
Internal.ThrewException SomeException
exception ->
Text
"Test threw an exception\n"
Text -> Text -> Text
forall appendable.
Semigroup appendable =>
appendable -> appendable -> appendable
++ String -> Text
Text.fromList (SomeException -> String
forall e. Exception e => e -> String
Exception.displayException SomeException
exception)
Failure
Internal.TookTooLong -> Text
"Test timed out"
Internal.TestRunnerMessedUp Text
msg ->
Text
"Test runner encountered an unexpected error:\n"
Text -> Text -> Text
forall appendable.
Semigroup appendable =>
appendable -> appendable -> appendable
++ Text
msg
Text -> Text -> Text
forall appendable.
Semigroup appendable =>
appendable -> appendable -> appendable
++ Text
"\n"
Text -> Text -> Text
forall appendable.
Semigroup appendable =>
appendable -> appendable -> appendable
++ Text
"This is a bug.\n\n"
Text -> Text -> Text
forall appendable.
Semigroup appendable =>
appendable -> appendable -> appendable
++ Text
"If you have some time to report the bug it would be much appreciated!\n"
Text -> Text -> Text
forall appendable.
Semigroup appendable =>
appendable -> appendable -> appendable
++ Text
"You can do so here: https://github.com/NoRedInk/haskell-libraries/issues"
formatElapsedDuration :: Internal.SuiteResult -> Text
formatElapsedDuration :: SuiteResult -> Text
formatElapsedDuration SuiteResult
result =
SuiteResult
result SuiteResult
-> (SuiteResult -> List TracingSpan) -> List TracingSpan
forall a b. a -> (a -> b) -> b
|> SuiteResult -> List TracingSpan
resultSpans List TracingSpan -> (List TracingSpan -> Float) -> Float
forall a b. a -> (a -> b) -> b
|> List TracingSpan -> Float
elapsedMilliseconds Float -> (Float -> Text) -> Text
forall a b. a -> (a -> b) -> b
|> Float -> Text
formatElapsedMilliseconds
elapsedMilliseconds :: List Platform.Internal.TracingSpan -> Float
elapsedMilliseconds :: List TracingSpan -> Float
elapsedMilliseconds List TracingSpan
spans =
let startTime :: MonotonicTime
startTime =
List TracingSpan
spans
List TracingSpan
-> (List TracingSpan -> List MonotonicTime) -> List MonotonicTime
forall a b. a -> (a -> b) -> b
|> (TracingSpan -> MonotonicTime)
-> List TracingSpan -> List MonotonicTime
forall a b. (a -> b) -> List a -> List b
List.map TracingSpan -> MonotonicTime
Platform.Internal.started
List MonotonicTime
-> (List MonotonicTime -> Maybe MonotonicTime)
-> Maybe MonotonicTime
forall a b. a -> (a -> b) -> b
|> List MonotonicTime -> Maybe MonotonicTime
forall a. Ord a => List a -> Maybe a
List.minimum
Maybe MonotonicTime
-> (Maybe MonotonicTime -> MonotonicTime) -> MonotonicTime
forall a b. a -> (a -> b) -> b
|> MonotonicTime -> Maybe MonotonicTime -> MonotonicTime
forall a. a -> Maybe a -> a
Maybe.withDefault MonotonicTime
0
finishTime :: MonotonicTime
finishTime =
List TracingSpan
spans
List TracingSpan
-> (List TracingSpan -> List MonotonicTime) -> List MonotonicTime
forall a b. a -> (a -> b) -> b
|> (TracingSpan -> MonotonicTime)
-> List TracingSpan -> List MonotonicTime
forall a b. (a -> b) -> List a -> List b
List.map TracingSpan -> MonotonicTime
Platform.Internal.finished
List MonotonicTime
-> (List MonotonicTime -> Maybe MonotonicTime)
-> Maybe MonotonicTime
forall a b. a -> (a -> b) -> b
|> List MonotonicTime -> Maybe MonotonicTime
forall a. Ord a => List a -> Maybe a
List.maximum
Maybe MonotonicTime
-> (Maybe MonotonicTime -> MonotonicTime) -> MonotonicTime
forall a b. a -> (a -> b) -> b
|> MonotonicTime -> Maybe MonotonicTime -> MonotonicTime
forall a. a -> Maybe a -> a
Maybe.withDefault MonotonicTime
0
in MonotonicTime
finishTime MonotonicTime -> MonotonicTime -> MonotonicTime
forall number. Num number => number -> number -> number
- MonotonicTime
startTime MonotonicTime -> (MonotonicTime -> Float) -> Float
forall a b. a -> (a -> b) -> b
|> MonotonicTime -> Float
forall a b. (Integral a, Num b) => a -> b
Prelude.fromIntegral Float -> (Float -> Float) -> Float
forall a b. a -> (a -> b) -> b
|> (Float -> Float -> Float
/ Float
1000)
resultSpans :: Internal.SuiteResult -> List Platform.Internal.TracingSpan
resultSpans :: SuiteResult -> List TracingSpan
resultSpans SuiteResult
result =
case SuiteResult
result of
Internal.AllPassed [SingleTest TracingSpan]
passed ->
(SingleTest TracingSpan -> TracingSpan)
-> [SingleTest TracingSpan] -> List TracingSpan
forall a b. (a -> b) -> List a -> List b
List.map SingleTest TracingSpan -> TracingSpan
forall a. SingleTest a -> a
Internal.body [SingleTest TracingSpan]
passed
Internal.OnlysPassed [SingleTest TracingSpan]
passed [SingleTest NotRan]
_skipped ->
(SingleTest TracingSpan -> TracingSpan)
-> [SingleTest TracingSpan] -> List TracingSpan
forall a b. (a -> b) -> List a -> List b
List.map SingleTest TracingSpan -> TracingSpan
forall a. SingleTest a -> a
Internal.body [SingleTest TracingSpan]
passed
Internal.PassedWithSkipped [SingleTest TracingSpan]
passed [SingleTest NotRan]
_skipped ->
(SingleTest TracingSpan -> TracingSpan)
-> [SingleTest TracingSpan] -> List TracingSpan
forall a b. (a -> b) -> List a -> List b
List.map SingleTest TracingSpan -> TracingSpan
forall a. SingleTest a -> a
Internal.body [SingleTest TracingSpan]
passed
Internal.TestsFailed [SingleTest TracingSpan]
passed [SingleTest NotRan]
_skipped [SingleTest FailedSpan]
failed -> do
(SingleTest TracingSpan -> TracingSpan)
-> [SingleTest TracingSpan] -> List TracingSpan
forall a b. (a -> b) -> List a -> List b
List.map SingleTest TracingSpan -> TracingSpan
forall a. SingleTest a -> a
Internal.body [SingleTest TracingSpan]
passed
List TracingSpan -> List TracingSpan -> List TracingSpan
forall appendable.
Semigroup appendable =>
appendable -> appendable -> appendable
++ ([SingleTest FailedSpan]
failed [SingleTest FailedSpan]
-> ([SingleTest FailedSpan] -> List FailedSpan) -> List FailedSpan
forall a b. a -> (a -> b) -> b
|> (SingleTest FailedSpan -> FailedSpan)
-> [SingleTest FailedSpan] -> List FailedSpan
forall a b. (a -> b) -> List a -> List b
List.map SingleTest FailedSpan -> FailedSpan
forall a. SingleTest a -> a
Internal.body List FailedSpan
-> (List FailedSpan -> List TracingSpan) -> List TracingSpan
forall a b. a -> (a -> b) -> b
|> (FailedSpan -> TracingSpan) -> List FailedSpan -> List TracingSpan
forall a b. (a -> b) -> List a -> List b
List.map (\(Internal.FailedSpan TracingSpan
span Failure
_) -> TracingSpan
span))
SuiteResult
Internal.NoTestsInSuite ->
[]
formatElapsedMilliseconds :: Float -> Text
formatElapsedMilliseconds :: Float -> Text
formatElapsedMilliseconds Float
ms =
if Float
ms Float -> Float -> Bool
forall comparable.
Ord comparable =>
comparable -> comparable -> Bool
< Float
1000
then Int -> Text
Text.fromInt (Float -> Int
round Float
ms) Text -> Text -> Text
forall appendable.
Semigroup appendable =>
appendable -> appendable -> appendable
++ Text
" ms"
else String -> Text
Text.fromList (Maybe Int -> Float -> ShowS
forall a. RealFloat a => Maybe Int -> a -> ShowS
Numeric.showFFloat (Int -> Maybe Int
forall a. a -> Maybe a
Just Int
2) (Float
ms Float -> Float -> Float
/ Float
1000) String
" s")