lsm-tree-1.0.0.0: test/Test/Util/RawPage.hs
module Test.Util.RawPage (
assertEqualRawPages,
propEqualRawPages,
) where
import Control.Monad (unless)
import Data.Align (align)
import Data.List.Split (chunksOf)
import Data.These (These (..))
import Data.Word (Word8)
import qualified System.Console.ANSI as ANSI
import Database.LSMTree.Internal.BitMath (div16, mod16)
import qualified Database.LSMTree.Internal.RawBytes as RB
import Database.LSMTree.Internal.RawPage (RawPage, rawPageRawBytes)
import Test.Tasty.HUnit (Assertion, assertFailure)
import Test.Tasty.QuickCheck (Property, counterexample)
assertEqualRawPages :: RawPage -> RawPage -> Assertion
assertEqualRawPages a b = unless (a == b) $ do
assertFailure $ "unequal pages:\n" ++ ANSI.setSGRCode [ANSI.Reset] ++ compareBytes (RB.unpack (rawPageRawBytes a)) (RB.unpack (rawPageRawBytes b))
propEqualRawPages :: RawPage -> RawPage -> Property
propEqualRawPages a b = counterexample
(ANSI.setSGRCode [ANSI.Reset] ++ compareBytes (RB.unpack (rawPageRawBytes a)) (RB.unpack (rawPageRawBytes b)))
(a == b)
-- Print two bytestreams next to each other highlighting the differences.
compareBytes :: [Word8] -> [Word8] -> String
compareBytes xs ys = unlines $ go (grouping chunks)
where
go :: [Either [()] [([Word8], [Word8])]] -> [String]
go [] = []
go (Left _ : zs) = "..." : go zs
go (Right diff : zs) = map (uncurry showDiff) diff ++ go zs
showDiff :: [Word8] -> [Word8] -> String
showDiff = aux id id where
aux :: ShowS -> ShowS -> [Word8] -> [Word8] -> String
aux accl accr [] [] = accl . showString " " . accr $ ""
aux accl accr [] rs = accl . showString " " . accr . green (concatMapS showsWord8 rs) $ ""
aux accl accr ls [] = accl . red (concatMapS showsWord8 ls) . showString " " . accr $ ""
aux accl accr (l:ls) (r:rs)
| l == r = aux (accl . showsWord8 l) (accr . showsWord8 r) ls rs
| otherwise = aux (accl . red (showsWord8 l)) (accr . green (showsWord8 r)) ls rs
-- chunks are either equal, or not
chunks :: [Either () ([Word8], [Word8])]
chunks =
[ case b of
These x y
| x == y -> Left ()
| otherwise -> Right (x, y)
This x -> Right (x, [])
That y -> Right ([], y)
| b <- align (chunksOf 16 xs) (chunksOf 16 ys)
]
sgr :: [ANSI.SGR] -> ShowS
sgr cs = (ANSI.setSGRCode cs ++)
red :: ShowS -> ShowS
red s = sgr [ANSI.SetColor ANSI.Foreground ANSI.Vivid ANSI.Red] . s . sgr [ANSI.Reset]
green :: ShowS -> ShowS
green s = sgr [ANSI.SetColor ANSI.Foreground ANSI.Vivid ANSI.Green] . s . sgr [ANSI.Reset]
concatMapS :: (a -> ShowS) -> [a] -> ShowS
concatMapS f xs = \acc -> foldr (\a acc' -> f a acc') acc xs
showsWord8 :: Word8 -> ShowS
showsWord8 w = \acc -> hexdigit (div16 w) : hexdigit (mod16 w) : acc
grouping :: [Either a b] -> [Either [a] [b]]
grouping = foldr add []
where
add (Left x) [] = [Left [x]]
add (Right y) [] = [Right [y]]
add (Left x) (Left xs : rest) = Left (x : xs) : rest
add (Right y) rest@(Left _ : _) = Right [y] : rest
add (Left x) rest@(Right _ : _ ) = Left [x] : rest
add (Right y) (Right ys : rest) = Right (y:ys) : rest
hexdigit :: (Num a, Eq a) => a -> Char
hexdigit 0x0 = '0'
hexdigit 0x1 = '1'
hexdigit 0x2 = '2'
hexdigit 0x3 = '3'
hexdigit 0x4 = '4'
hexdigit 0x5 = '5'
hexdigit 0x6 = '6'
hexdigit 0x7 = '7'
hexdigit 0x8 = '8'
hexdigit 0x9 = '9'
hexdigit 0xA = 'a'
hexdigit 0xB = 'b'
hexdigit 0xC = 'c'
hexdigit 0xD = 'd'
hexdigit 0xE = 'e'
hexdigit 0xF = 'f'
hexdigit _ = '?'