{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE ImplicitParams #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeSynonymInstances #-}
module Test.Tasty.Checklist
(
withChecklist
, CanCheck
, check
, discardCheck
, checkValues
, DerivedVal(Val, Got, Observe)
, CheckResult
, ChecklistFailures
, TestShow(testShow)
, testShowList
, multiLineDiff
)
where
import Control.Exception ( evaluate )
import Control.Monad ( join, unless )
import Control.Monad.Catch
import Control.Monad.IO.Class ( MonadIO, liftIO )
import Data.IORef
import qualified Data.List as List
import qualified Data.Map as Map
import qualified Data.Parameterized.Context as Ctx
import Data.Text ( Text )
import qualified Data.Text as T
import System.IO ( hFlush, hPutStrLn, stdout, stderr )
data ChecklistFailures = ChecklistFailures Text [CheckResult]
data CheckResult = CheckFailed CheckName (Maybe InputAsText) FailureMessage
| CheckMessage Text
newtype CheckName = CheckName { CheckName -> Text
checkName :: Text }
newtype InputAsText = InputAsText { InputAsText -> Text
inputAsText :: Text } deriving (InputAsText -> InputAsText -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: InputAsText -> InputAsText -> Bool
$c/= :: InputAsText -> InputAsText -> Bool
== :: InputAsText -> InputAsText -> Bool
$c== :: InputAsText -> InputAsText -> Bool
Eq, Eq InputAsText
InputAsText -> InputAsText -> Bool
InputAsText -> InputAsText -> Ordering
InputAsText -> InputAsText -> InputAsText
forall a.
Eq a
-> (a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
min :: InputAsText -> InputAsText -> InputAsText
$cmin :: InputAsText -> InputAsText -> InputAsText
max :: InputAsText -> InputAsText -> InputAsText
$cmax :: InputAsText -> InputAsText -> InputAsText
>= :: InputAsText -> InputAsText -> Bool
$c>= :: InputAsText -> InputAsText -> Bool
> :: InputAsText -> InputAsText -> Bool
$c> :: InputAsText -> InputAsText -> Bool
<= :: InputAsText -> InputAsText -> Bool
$c<= :: InputAsText -> InputAsText -> Bool
< :: InputAsText -> InputAsText -> Bool
$c< :: InputAsText -> InputAsText -> Bool
compare :: InputAsText -> InputAsText -> Ordering
$ccompare :: InputAsText -> InputAsText -> Ordering
Ord)
newtype FailureMessage = FailureMessage { FailureMessage -> Text
failureMessage :: Text }
instance Exception ChecklistFailures
instance Show CheckResult where
show :: CheckResult -> String
show (CheckFailed CheckName
what Maybe InputAsText
onValue FailureMessage
msg) =
let chknm :: String
chknm = if forall (t :: * -> *) a. Foldable t => t a -> Int
length (Text -> [Text]
T.lines (CheckName -> Text
checkName CheckName
what)) forall a. Ord a => a -> a -> Bool
> Int
1
then String
"check: " forall a. Semigroup a => a -> a -> a
<> Text -> String
T.unpack (CheckName -> Text
checkName CheckName
what)
else String
"check '" forall a. Semigroup a => a -> a -> a
<> Text -> String
T.unpack (CheckName -> Text
checkName CheckName
what) forall a. Semigroup a => a -> a -> a
<> String
"'"
chkmsg :: String
chkmsg = if Text -> Bool
T.null (FailureMessage -> Text
failureMessage FailureMessage
msg)
then String
""
else String
" with: " forall a. Semigroup a => a -> a -> a
<> Text -> String
T.unpack (FailureMessage -> Text
failureMessage FailureMessage
msg)
chkval :: String
chkval = case Maybe InputAsText
onValue of
Maybe InputAsText
Nothing -> String
""
Just InputAsText
i -> String
"\n using: " forall a. Semigroup a => a -> a -> a
<> Text -> String
T.unpack (InputAsText -> Text
inputAsText InputAsText
i)
in String
"Failed " forall a. Semigroup a => a -> a -> a
<> String
chknm forall a. Semigroup a => a -> a -> a
<> String
chkmsg forall a. Semigroup a => a -> a -> a
<> String
chkval
show (CheckMessage Text
txt) = String
"-- " forall a. Semigroup a => a -> a -> a
<> Text -> String
T.unpack Text
txt
instance Show ChecklistFailures where
show :: ChecklistFailures -> String
show (ChecklistFailures Text
topMsg [CheckResult]
fails) =
let isMessage :: CheckResult -> Bool
isMessage = \case
CheckMessage Text
_ -> Bool
True
CheckResult
_ -> Bool
False
checkCnt :: Int
checkCnt = forall (t :: * -> *) a. Foldable t => t a -> Int
length forall a b. (a -> b) -> a -> b
$ forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not forall b c a. (b -> c) -> (a -> b) -> a -> c
. CheckResult -> Bool
isMessage) [CheckResult]
fails
in String
"ERROR: " forall a. Semigroup a => a -> a -> a
<> Text -> String
T.unpack Text
topMsg forall a. Semigroup a => a -> a -> a
<> String
"\n "
forall a. Semigroup a => a -> a -> a
<> forall a. Show a => a -> String
show Int
checkCnt forall a. Semigroup a => a -> a -> a
<> String
" checks failed in this checklist:\n -"
forall a. Semigroup a => a -> a -> a
<> forall a. [a] -> [[a]] -> [a]
List.intercalate String
"\n -" (forall a. Show a => a -> String
show forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [CheckResult]
fails)
type CanCheck = (?checker :: IORef [CheckResult])
withChecklist :: (MonadIO m, MonadMask m)
=> Text -> (CanCheck => m a) -> m a
withChecklist :: forall (m :: * -> *) a.
(MonadIO m, MonadMask m) =>
Text -> (CanCheck => m a) -> m a
withChecklist Text
topMsg CanCheck => m a
t = do
IORef [CheckResult]
checks <- forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO forall a b. (a -> b) -> a -> b
$ forall a. a -> IO (IORef a)
newIORef forall a. Monoid a => a
mempty
a
r <- (let ?checker = IORef [CheckResult]
checks in CanCheck => m a
t)
forall (m :: * -> *) a b. MonadCatch m => m a -> m b -> m a
`onException` (forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO forall a b. (a -> b) -> a -> b
$
do [CheckResult]
cs <- forall a. [a] -> [a]
List.reverse forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall a. IORef a -> IO a
readIORef IORef [CheckResult]
checks
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (forall (t :: * -> *) a. Foldable t => t a -> Bool
null [CheckResult]
cs) forall a b. (a -> b) -> a -> b
$ do
Handle -> IO ()
hFlush Handle
stdout
Handle -> String -> IO ()
hPutStrLn Handle
stderr String
""
let pfx :: String
pfx = String
" WARN "
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (Handle -> String -> IO ()
hPutStrLn Handle
stderr forall b c a. (b -> c) -> (a -> b) -> a -> c
. (String
pfx forall a. Semigroup a => a -> a -> a
<>) forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. Show a => a -> String
show) [CheckResult]
cs
Handle -> IO ()
hFlush Handle
stderr
)
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO forall a b. (a -> b) -> a -> b
$ do
[CheckResult]
collected <- forall a. [a] -> [a]
List.reverse forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall a. IORef a -> IO a
readIORef IORef [CheckResult]
checks
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (forall (t :: * -> *) a. Foldable t => t a -> Bool
null [CheckResult]
collected) forall a b. (a -> b) -> a -> b
$
forall (m :: * -> *) e a. (MonadThrow m, Exception e) => e -> m a
throwM (Text -> [CheckResult] -> ChecklistFailures
ChecklistFailures Text
topMsg [CheckResult]
collected)
forall (m :: * -> *) a. Monad m => a -> m a
return a
r
check :: (CanCheck, TestShow a, MonadIO m)
=> Text -> (a -> Bool) -> a -> m ()
check :: forall a (m :: * -> *).
(CanCheck, TestShow a, MonadIO m) =>
Text -> (a -> Bool) -> a -> m ()
check = forall (m :: * -> *) a.
(CanCheck, MonadIO m) =>
(a -> String)
-> Maybe InputAsText -> Text -> (a -> Bool) -> a -> m ()
checkShow forall v. TestShow v => v -> String
testShow forall a. Maybe a
Nothing
checkShow :: (CanCheck, MonadIO m)
=> (a -> String)
-> Maybe InputAsText
-> Text -> (a -> Bool) -> a -> m ()
checkShow :: forall (m :: * -> *) a.
(CanCheck, MonadIO m) =>
(a -> String)
-> Maybe InputAsText -> Text -> (a -> Bool) -> a -> m ()
checkShow a -> String
showit Maybe InputAsText
failInput Text
what a -> Bool
eval a
val = do
Bool
r <- forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO forall a b. (a -> b) -> a -> b
$ forall a. a -> IO a
evaluate (a -> Bool
eval a
val)
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless Bool
r forall a b. (a -> b) -> a -> b
$ do
let failtxt :: FailureMessage
failtxt = Text -> FailureMessage
FailureMessage forall a b. (a -> b) -> a -> b
$ String -> Text
T.pack forall a b. (a -> b) -> a -> b
$ a -> String
showit a
val
let chk :: CheckResult
chk = CheckName -> Maybe InputAsText -> FailureMessage -> CheckResult
CheckFailed (Text -> CheckName
CheckName Text
what) Maybe InputAsText
failInput FailureMessage
failtxt
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO forall a b. (a -> b) -> a -> b
$ forall a. IORef a -> (a -> a) -> IO ()
modifyIORef CanCheck
?checker (CheckResult
chkforall a. a -> [a] -> [a]
:)
discardCheck :: (CanCheck, MonadIO m) => Text -> m ()
discardCheck :: forall (m :: * -> *). (CanCheck, MonadIO m) => Text -> m ()
discardCheck Text
what = do
let isCheck :: Text -> CheckResult -> Bool
isCheck Text
n (CheckFailed CheckName
n' Maybe InputAsText
_ FailureMessage
_) = Text
n forall a. Eq a => a -> a -> Bool
== CheckName -> Text
checkName CheckName
n'
isCheck Text
_ (CheckMessage Text
_) = Bool
False
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO forall a b. (a -> b) -> a -> b
$ forall a. IORef a -> (a -> a) -> IO ()
modifyIORef CanCheck
?checker (forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> CheckResult -> Bool
isCheck Text
what))
checkValues :: CanCheck
=> TestShow dType
=> dType -> Ctx.Assignment (DerivedVal dType) idx -> IO ()
checkValues :: forall dType (idx :: Ctx (*)).
(CanCheck, TestShow dType) =>
dType -> Assignment (DerivedVal dType) idx -> IO ()
checkValues dType
got Assignment (DerivedVal dType) idx
expF = do
forall (m :: * -> *) a. Monad m => m (m a) -> m a
join forall a b. (a -> b) -> a -> b
$ forall a. a -> IO a
evaluate forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall {k} (m :: * -> *) (ctx :: Ctx k) (f :: k -> *).
Applicative m =>
(forall (tp :: k). Index ctx tp -> f tp -> m ())
-> Assignment f ctx -> m ()
Ctx.traverseWithIndex_ (forall dType (idx :: Ctx (*)) valType.
(CanCheck, TestShow dType) =>
dType -> Index idx valType -> DerivedVal dType valType -> IO ()
chkValue dType
got) Assignment (DerivedVal dType) idx
expF
let groupByInp :: t CheckResult -> [CheckResult]
groupByInp t CheckResult
chks =
let gmap :: Map (Maybe InputAsText) [CheckResult]
gmap = forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
foldr CheckResult
-> Map (Maybe InputAsText) [CheckResult]
-> Map (Maybe InputAsText) [CheckResult]
insByInp forall a. Monoid a => a
mempty t CheckResult
chks
insByInp :: CheckResult
-> Map (Maybe InputAsText) [CheckResult]
-> Map (Maybe InputAsText) [CheckResult]
insByInp = \case
c :: CheckResult
c@(CheckFailed CheckName
_ Maybe InputAsText
mbi FailureMessage
_) -> forall k a. Ord k => (a -> a -> a) -> k -> a -> Map k a -> Map k a
Map.insertWith forall a. Semigroup a => a -> a -> a
(<>) Maybe InputAsText
mbi [CheckResult
c]
CheckMessage Text
_ -> forall a. a -> a
id
addGroup :: (Maybe InputAsText, [CheckResult])
-> [CheckResult] -> [CheckResult]
addGroup (Maybe InputAsText
mbi,[CheckResult]
gchks) =
let newChks :: [CheckResult]
newChks = CheckResult -> CheckResult
dropInput forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [CheckResult]
gchks
dropInput :: CheckResult -> CheckResult
dropInput (CheckFailed CheckName
nm Maybe InputAsText
_ FailureMessage
fmsg) =
if forall a. a -> Maybe a
Just (FailureMessage -> Text
failureMessage FailureMessage
fmsg) forall a. Eq a => a -> a -> Bool
== (InputAsText -> Text
inputAsText forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Maybe InputAsText
mbi)
then CheckName -> Maybe InputAsText -> FailureMessage -> CheckResult
CheckFailed CheckName
nm forall a. Maybe a
Nothing
forall a b. (a -> b) -> a -> b
$ Text -> FailureMessage
FailureMessage Text
"<< ^^ above input ^^ >>"
else CheckName -> Maybe InputAsText -> FailureMessage -> CheckResult
CheckFailed CheckName
nm forall a. Maybe a
Nothing FailureMessage
fmsg
dropInput i :: CheckResult
i@(CheckMessage Text
_) = CheckResult
i
grpTitle :: Text
grpTitle = forall b a. b -> (a -> b) -> Maybe a -> b
maybe Text
"<no input identified>"
((Text
"Input for below: " forall a. Semigroup a => a -> a -> a
<>) forall b c a. (b -> c) -> (a -> b) -> a -> c
. InputAsText -> Text
inputAsText)
Maybe InputAsText
mbi
in (forall a. Semigroup a => a -> a -> a
<> ([CheckResult]
newChks forall a. Semigroup a => a -> a -> a
<> [Text -> CheckResult
CheckMessage Text
grpTitle]))
in forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
foldr (Maybe InputAsText, [CheckResult])
-> [CheckResult] -> [CheckResult]
addGroup forall a. Monoid a => a
mempty forall a b. (a -> b) -> a -> b
$ forall k a. Map k a -> [(k, a)]
Map.toList Map (Maybe InputAsText) [CheckResult]
gmap
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO forall a b. (a -> b) -> a -> b
$ forall a. IORef a -> (a -> a) -> IO ()
modifyIORef CanCheck
?checker forall {t :: * -> *}. Foldable t => t CheckResult -> [CheckResult]
groupByInp
chkValue :: CanCheck
=> TestShow dType
=> dType -> Ctx.Index idx valType -> DerivedVal dType valType -> IO ()
chkValue :: forall dType (idx :: Ctx (*)) valType.
(CanCheck, TestShow dType) =>
dType -> Index idx valType -> DerivedVal dType valType -> IO ()
chkValue dType
got Index idx valType
_idx =
let ti :: Maybe InputAsText
ti = forall a. a -> Maybe a
Just forall a b. (a -> b) -> a -> b
$ Text -> InputAsText
InputAsText forall a b. (a -> b) -> a -> b
$ String -> Text
T.pack forall a b. (a -> b) -> a -> b
$ forall v. TestShow v => v -> String
testShow dType
got
in \case
(Val Text
txt dType -> valType
fld valType
v) ->
let msg :: Text
msg = Text
txt
forall a. Semigroup a => a -> a -> a
<> Text
"\n"
forall a. Semigroup a => a -> a -> a
<> Text
" " forall a. Semigroup a => a -> a -> a
<> Text
"expected: " forall a. Semigroup a => a -> a -> a
<> Text
tv forall a. Semigroup a => a -> a -> a
<> Text
"\n"
forall a. Semigroup a => a -> a -> a
<> Text
" " forall a. Semigroup a => a -> a -> a
<> Text
"failed"
tv :: Text
tv = String -> Text
T.pack (forall v. TestShow v => v -> String
testShow valType
v)
in forall (m :: * -> *) a.
(CanCheck, MonadIO m) =>
(a -> String)
-> Maybe InputAsText -> Text -> (a -> Bool) -> a -> m ()
checkShow forall v. TestShow v => v -> String
testShow Maybe InputAsText
ti Text
msg (valType
v forall a. Eq a => a -> a -> Bool
==) forall a b. (a -> b) -> a -> b
$ dType -> valType
fld dType
got
(Observe Text
txt dType -> valType
fld valType
v valType -> valType -> String
observationReport) ->
let msg :: Text
msg = Text
txt forall a. Semigroup a => a -> a -> a
<> Text
" observation failure"
in forall (m :: * -> *) a.
(CanCheck, MonadIO m) =>
(a -> String)
-> Maybe InputAsText -> Text -> (a -> Bool) -> a -> m ()
checkShow (valType -> valType -> String
observationReport valType
v) Maybe InputAsText
ti Text
msg (valType
v forall a. Eq a => a -> a -> Bool
==) forall a b. (a -> b) -> a -> b
$ dType -> valType
fld dType
got
(Got Text
txt dType -> Bool
fld) -> forall (m :: * -> *) a.
(CanCheck, MonadIO m) =>
(a -> String)
-> Maybe InputAsText -> Text -> (a -> Bool) -> a -> m ()
checkShow forall v. TestShow v => v -> String
testShow Maybe InputAsText
ti Text
txt forall a. a -> a
id forall a b. (a -> b) -> a -> b
$ dType -> Bool
fld dType
got
data DerivedVal i d where
Val :: (TestShow d, Eq d) => Text -> (i -> d) -> d -> DerivedVal i d
Got :: Text -> (i -> Bool) -> DerivedVal i Bool
Observe :: (Eq d) => Text -> (i -> d) -> d -> (d -> d -> String) -> DerivedVal i d
class TestShow v where
testShow :: v -> String
default testShow :: Show v => v -> String
testShow = forall a. Show a => a -> String
show
instance TestShow ()
instance TestShow Bool
instance TestShow Int
instance TestShow Integer
instance TestShow Float
instance TestShow Char
instance TestShow String
instance (TestShow a, TestShow b) => TestShow (a,b) where
testShow :: (a, b) -> String
testShow (a
a,b
b) = String
"(" forall a. Semigroup a => a -> a -> a
<> forall v. TestShow v => v -> String
testShow a
a forall a. Semigroup a => a -> a -> a
<> String
", " forall a. Semigroup a => a -> a -> a
<> forall v. TestShow v => v -> String
testShow b
b forall a. Semigroup a => a -> a -> a
<> String
")"
instance (TestShow a, TestShow b, TestShow c) => TestShow (a,b,c) where
testShow :: (a, b, c) -> String
testShow (a
a,b
b,c
c) = String
"(" forall a. Semigroup a => a -> a -> a
<> forall v. TestShow v => v -> String
testShow a
a forall a. Semigroup a => a -> a -> a
<> String
", " forall a. Semigroup a => a -> a -> a
<> forall v. TestShow v => v -> String
testShow b
b forall a. Semigroup a => a -> a -> a
<> String
", " forall a. Semigroup a => a -> a -> a
<> forall v. TestShow v => v -> String
testShow c
c forall a. Semigroup a => a -> a -> a
<> String
")"
testShowList :: TestShow v => [v] -> String
testShowList :: forall v. TestShow v => [v] -> String
testShowList [v]
l = String
"[ " forall a. Semigroup a => a -> a -> a
<> (forall a. [a] -> [[a]] -> [a]
List.intercalate String
", " (forall v. TestShow v => v -> String
testShow forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [v]
l)) forall a. Semigroup a => a -> a -> a
<> String
" ]"
multiLineDiff :: T.Text -> T.Text -> String
multiLineDiff :: Text -> Text -> String
multiLineDiff Text
expected Text
actual =
let dl :: (a, a) -> a
dl (a
e,a
a) = if a
e forall a. Eq a => a -> a -> Bool
== a
a then forall {a}. (Semigroup a, IsString a) => a -> a
db a
e else forall {a}. (Semigroup a, IsString a) => a -> a -> a
de a
" ↱" a
e forall a. Semigroup a => a -> a -> a
<> a
"\n " forall a. Semigroup a => a -> a -> a
<> forall {a}. (Semigroup a, IsString a) => a -> a -> a
da a
" ↳" a
a
db :: a -> a
db a
b = a
"| > " forall a. Semigroup a => a -> a -> a
<> a
b
de :: a -> a -> a
de a
m a
e = a
"|" forall a. Semigroup a => a -> a -> a
<> a
m forall a. Semigroup a => a -> a -> a
<> a
"expect> " forall a. Semigroup a => a -> a -> a
<> a
e
da :: a -> a -> a
da a
m a
a = a
"|" forall a. Semigroup a => a -> a -> a
<> a
m forall a. Semigroup a => a -> a -> a
<> a
"actual> " forall a. Semigroup a => a -> a -> a
<> a
a
el :: [Text]
el = Text -> Text
visible forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Text -> [Text]
T.lines Text
expected
al :: [Text]
al = Text -> Text
visible forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Text -> [Text]
T.lines Text
actual
visible :: Text -> Text
visible = Text -> Text -> Text -> Text
T.replace Text
" " Text
"␠"
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Text -> Text -> Text
T.replace Text
"\n" Text
""
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Text -> Text -> Text
T.replace Text
"\t" Text
"␉"
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Text -> Text -> Text
T.replace Text
"\012" Text
"␍"
addnum :: Int -> T.Text -> T.Text
addnum :: Int -> Text -> Text
addnum Int
n Text
l = let nt :: Text
nt = String -> Text
T.pack (forall a. Show a => a -> String
show Int
n)
nl :: Int
nl = Text -> Int
T.length Text
nt
in Int -> Text -> Text
T.take (Int
4 forall a. Num a => a -> a -> a
- Int
nl) Text
" " forall a. Semigroup a => a -> a -> a
<> Text
nt forall a. Semigroup a => a -> a -> a
<> Text
l
ll :: [a] -> Text
ll = String -> Text
T.pack forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. Show a => a -> String
show forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (t :: * -> *) a. Foldable t => t a -> Int
length
tl :: Text -> Text
tl = String -> Text
T.pack forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. Show a => a -> String
show forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Int
T.length
banner :: Text
banner = Text
"MISMATCH between "
forall a. Semigroup a => a -> a -> a
<> forall {a}. [a] -> Text
ll [Text]
el forall a. Semigroup a => a -> a -> a
<> Text
"l/" forall a. Semigroup a => a -> a -> a
<> Text -> Text
tl Text
expected forall a. Semigroup a => a -> a -> a
<> Text
"c expected and "
forall a. Semigroup a => a -> a -> a
<> forall {a}. [a] -> Text
ll [Text]
al forall a. Semigroup a => a -> a -> a
<> Text
"l/" forall a. Semigroup a => a -> a -> a
<> Text -> Text
tl Text
actual forall a. Semigroup a => a -> a -> a
<> Text
"c actual"
diffReport :: [Text]
diffReport = forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (forall a b c. (a -> b -> c) -> (a, b) -> c
uncurry Int -> Text -> Text
addnum) forall a b. (a -> b) -> a -> b
$
forall a b. [a] -> [b] -> [(a, b)]
zip [Int
1..] forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat forall a b. (a -> b) -> a -> b
$
[ forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap forall {a}. (Eq a, Semigroup a, IsString a) => (a, a) -> a
dl forall a b. (a -> b) -> a -> b
$ forall a b. [a] -> [b] -> [(a, b)]
zip [Text]
el [Text]
al
, forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (forall {a}. (Semigroup a, IsString a) => a -> a -> a
de Text
"∌ ") forall a b. (a -> b) -> a -> b
$ forall a. Int -> [a] -> [a]
drop (forall (t :: * -> *) a. Foldable t => t a -> Int
length [Text]
al) [Text]
el
, forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (forall {a}. (Semigroup a, IsString a) => a -> a -> a
da Text
"∹ ") forall a b. (a -> b) -> a -> b
$ forall a. Int -> [a] -> [a]
drop (forall (t :: * -> *) a. Foldable t => t a -> Int
length [Text]
el) [Text]
al
]
forall a. Semigroup a => a -> a -> a
<> if [Text]
el forall a. Eq a => a -> a -> Bool
== [Text]
al
then let maxlen :: Int
maxlen = forall a. Ord a => a -> a -> a
max (Text -> Int
T.length Text
expected) (Text -> Int
T.length Text
actual)
end :: Text -> Text
end Text
x = Int -> Text -> Text
T.drop (Int
maxlen forall a. Num a => a -> a -> a
- Int
5) Text
x
in [ [ forall {a}. (Semigroup a, IsString a) => a -> a -> a
de Text
"∌ ending " forall a b. (a -> b) -> a -> b
$ Text -> Text
visible forall a b. (a -> b) -> a -> b
$ Text -> Text
end Text
expected ]
, [ forall {a}. (Semigroup a, IsString a) => a -> a -> a
da Text
"∹ ending " forall a b. (a -> b) -> a -> b
$ Text -> Text
visible forall a b. (a -> b) -> a -> b
$ Text -> Text
end Text
actual ]
]
else forall a. Monoid a => a
mempty
details :: [Text]
details = Text
banner forall a. a -> [a] -> [a]
: [Text]
diffReport
in if Text
expected forall a. Eq a => a -> a -> Bool
== Text
actual then String
"<no difference>" else Text -> String
T.unpack ([Text] -> Text
T.unlines [Text]
details)