packages feed

mono-hash-0.1.0.0: test/Main.hs

{-# LANGUAGE TypeApplications #-}

module Main (main) where

import Data.Bits (Bits, shiftL, shiftR, (.&.))
import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as BSC
import qualified Data.ByteString.Lazy as BSL
import qualified Data.ByteString.Lazy.Char8 as BSLC
import Data.Hash.Mono.FNV (fnv1, fnv1a)
import Data.Hash.Mono.HalfSipHash
import Data.Hash.Mono.SipHash
import Data.Int (Int16, Int32, Int64, Int8)
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import Data.WideWord.Word128 (Word128)
import Data.WideWord.Word256 (Word256)
import Data.Word (Word16, Word32, Word64, Word8)
import qualified Test.Data.ByteFoldable
import qualified Test.Data.FNV
import qualified Test.Data.HalfSipHash
import qualified Test.Data.SipHash
import Test.Tasty (TestTree, defaultMain, testGroup)
import Test.Tasty.HUnit (assertBool, testCase, (@?=))

main :: IO ()
main = defaultMain tests

{- | Code points spanning the one- to four-byte UTF-8 ranges, including the
boundaries between them.
-}
charEncodingInput :: String
charEncodingInput = "\x00\x7f\x80\x7ff\x800\xffff\x10000\x1234\x1F600\x10FFFF"

-- | UTF-8 bytes of a 'Char'; lone surrogates use the 3-byte WTF-8 form.
charBytes :: Char -> [Word8]
charBytes c
    | n < 0x80 = [fromIntegral n]
    | n < 0x800 = [byte 0xc0 6, byte 0x80 0]
    | n < 0x10000 = [byte 0xe0 12, byte 0x80 6, byte 0x80 0]
    | otherwise = [byte 0xf0 18, byte 0x80 12, byte 0x80 6, byte 0x80 0]
  where
    n = fromEnum c
    byte mask shift = fromIntegral (mask + ((n `shiftR` shift) .&. 0x3f))

-- | UTF-8 bytes of a 'String'.
utf8 :: String -> BS.ByteString
utf8 = TE.encodeUtf8 . T.pack

-- | Big-endian bytes of a fixed-width word.
wordBytes :: (Bits a, Integral a) => Int -> a -> [Word8]
wordBytes width w =
    [fromIntegral (w `shiftR` (8 * i)) | i <- [width - 1, width - 2 .. 0]]

packWords :: (Bits a, Integral a) => Int -> [a] -> BS.ByteString
packWords width = BS.pack . concatMap (wordBytes width)

word16Samples :: [Word16]
word16Samples = [0x0102, 0x0304, 0xfffe]

word32Samples :: [Word32]
word32Samples = [0x01020304, 0x05060708, 0xdeadbeef]

word64Samples :: [Word64]
word64Samples = [0x0102030405060708, 0x1112131415161718, 0xdeadbeefcafed00d]

-- | Boundary values for the signed element types.
int8Samples :: [Int8]
int8Samples = [minBound, -1, 0, 1, maxBound]

int16Samples :: [Int16]
int16Samples = [minBound, -1, 0, 1, maxBound]

int32Samples :: [Int32]
int32Samples = [minBound, -1, 0, 1, maxBound]

int64Samples :: [Int64]
int64Samples = [minBound, -1, 0, 1, maxBound]

-- | Big-endian two's-complement encoding of the signed sample lists.
int8SampleBytes :: BS.ByteString
int8SampleBytes = BS.pack [0x80, 0xff, 0x00, 0x01, 0x7f]

int16SampleBytes :: BS.ByteString
int16SampleBytes =
    BS.pack [0x80, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x7f, 0xff]

int32SampleBytes :: BS.ByteString
int32SampleBytes =
    BS.pack
        [ 0x80
        , 0x00
        , 0x00
        , 0x00
        , 0xff
        , 0xff
        , 0xff
        , 0xff
        , 0x00
        , 0x00
        , 0x00
        , 0x00
        , 0x00
        , 0x00
        , 0x00
        , 0x01
        , 0x7f
        , 0xff
        , 0xff
        , 0xff
        ]

int64SampleBytes :: BS.ByteString
int64SampleBytes =
    BS.pack
        [ 0x80
        , 0x00
        , 0x00
        , 0x00
        , 0x00
        , 0x00
        , 0x00
        , 0x00
        , 0xff
        , 0xff
        , 0xff
        , 0xff
        , 0xff
        , 0xff
        , 0xff
        , 0xff
        , 0x00
        , 0x00
        , 0x00
        , 0x00
        , 0x00
        , 0x00
        , 0x00
        , 0x00
        , 0x00
        , 0x00
        , 0x00
        , 0x00
        , 0x00
        , 0x00
        , 0x00
        , 0x01
        , 0x7f
        , 0xff
        , 0xff
        , 0xff
        , 0xff
        , 0xff
        , 0xff
        , 0xff
        ]

word128Sample :: Word128
word128Sample =
    (fromIntegral (0x0102030405060708 :: Word64) `shiftL` 64)
        + fromIntegral (0x1112131415161718 :: Word64)

word256Sample :: Word256
word256Sample =
    (fromIntegral (0x2122232425262728 :: Word64) `shiftL` 192)
        + (fromIntegral (0x3132333435363738 :: Word64) `shiftL` 128)
        + (fromIntegral (0x4142434445464748 :: Word64) `shiftL` 64)
        + fromIntegral (0x5152535455565758 :: Word64)

tests :: TestTree
tests =
    testGroup
        "Tests"
        [ goldenSamples
        , inputEncoding
        , encodingCollisions
        , Test.Data.ByteFoldable.tests
        ]

{- | Hashes must match the reference vectors in 'Test.Data.SipHash',
'Test.Data.HalfSipHash', and 'Test.Data.FNV'.
-}
goldenSamples :: TestTree
goldenSamples =
    testGroup
        "Golden Samples"
        [ testGroup
            "FNV"
            [ testCase "FNV1  Word32 String matches UTF-8 bytes" $ fmap (fnv1 @Word32) (Test.Data.FNV.inputs @String) @?= fmap (fnv1 @Word32 . utf8) (Test.Data.FNV.inputs @String)
            , testCase "FNV1a Word32 String matches UTF-8 bytes" $ fmap (fnv1a @Word32) (Test.Data.FNV.inputs @String) @?= fmap (fnv1a @Word32 . utf8) (Test.Data.FNV.inputs @String)
            , testCase "FNV1  Word64 String matches UTF-8 bytes" $ fmap (fnv1 @Word64) (Test.Data.FNV.inputs @String) @?= fmap (fnv1 @Word64 . utf8) (Test.Data.FNV.inputs @String)
            , testCase "FNV1a Word64 String matches UTF-8 bytes" $ fmap (fnv1a @Word64) (Test.Data.FNV.inputs @String) @?= fmap (fnv1a @Word64 . utf8) (Test.Data.FNV.inputs @String)
            , testCase "FNV1  Word32 Text matches UTF-8 bytes" $ fmap (fnv1 @Word32) (Test.Data.FNV.inputs @Text) @?= fmap (fnv1 @Word32 . TE.encodeUtf8) (Test.Data.FNV.inputs @Text)
            , testCase "FNV1a Word32 Text matches UTF-8 bytes" $ fmap (fnv1a @Word32) (Test.Data.FNV.inputs @Text) @?= fmap (fnv1a @Word32 . TE.encodeUtf8) (Test.Data.FNV.inputs @Text)
            , testCase "FNV1  Word64 Text matches UTF-8 bytes" $ fmap (fnv1 @Word64) (Test.Data.FNV.inputs @Text) @?= fmap (fnv1 @Word64 . TE.encodeUtf8) (Test.Data.FNV.inputs @Text)
            , testCase "FNV1a Word64 Text matches UTF-8 bytes" $ fmap (fnv1a @Word64) (Test.Data.FNV.inputs @Text) @?= fmap (fnv1a @Word64 . TE.encodeUtf8) (Test.Data.FNV.inputs @Text)
            , testCase "FNV1a Word32 non-ASCII String" $ fmap (fnv1a @Word32) Test.Data.FNV.utf8Inputs @?= Test.Data.FNV.fnv1aVectorsForWord32Utf8
            , testCase "FNV1a Word32 non-ASCII Text" $ fmap ((fnv1a @Word32) . T.pack) Test.Data.FNV.utf8Inputs @?= Test.Data.FNV.fnv1aVectorsForWord32Utf8
            , testCase "FNV1a Word64 non-ASCII String" $ fmap (fnv1a @Word64) Test.Data.FNV.utf8Inputs @?= Test.Data.FNV.fnv1aVectorsForWord64Utf8
            , testCase "FNV1a Word64 non-ASCII Text" $ fmap ((fnv1a @Word64) . T.pack) Test.Data.FNV.utf8Inputs @?= Test.Data.FNV.fnv1aVectorsForWord64Utf8
            , testCase "FNV1  Word32 ByteString" $ fmap (fnv1 @Word32) (Test.Data.FNV.inputs @BS.ByteString) @?= Test.Data.FNV.fnv1VectorsForWord32
            , testCase "FNV1a Word32 ByteString" $ fmap (fnv1a @Word32) (Test.Data.FNV.inputs @BS.ByteString) @?= Test.Data.FNV.fnv1aVectorsForWord32
            , testCase "FNV1  Word64 ByteString" $ fmap (fnv1 @Word64) (Test.Data.FNV.inputs @BS.ByteString) @?= Test.Data.FNV.fnv1VectorsForWord64
            , testCase "FNV1a Word64 ByteString" $ fmap (fnv1a @Word64) (Test.Data.FNV.inputs @BS.ByteString) @?= Test.Data.FNV.fnv1aVectorsForWord64
            , testCase "FNV1  Word32 Lazy ByteString" $ fmap (fnv1 @Word32) (Test.Data.FNV.inputs @BSL.ByteString) @?= Test.Data.FNV.fnv1VectorsForWord32
            , testCase "FNV1a Word32 Lazy ByteString" $ fmap (fnv1a @Word32) (Test.Data.FNV.inputs @BSL.ByteString) @?= Test.Data.FNV.fnv1aVectorsForWord32
            , testCase "FNV1  Word64 Lazy ByteString" $ fmap (fnv1 @Word64) (Test.Data.FNV.inputs @BSL.ByteString) @?= Test.Data.FNV.fnv1VectorsForWord64
            , testCase "FNV1a Word64 Lazy ByteString" $ fmap (fnv1a @Word64) (Test.Data.FNV.inputs @BSL.ByteString) @?= Test.Data.FNV.fnv1aVectorsForWord64
            , testCase "FNV1  Word32 ByteString Char8" $ fmap (fnv1 @Word32) (Test.Data.FNV.inputs @BSC.ByteString) @?= Test.Data.FNV.fnv1VectorsForWord32
            , testCase "FNV1a Word32 ByteString Char8" $ fmap (fnv1a @Word32) (Test.Data.FNV.inputs @BSC.ByteString) @?= Test.Data.FNV.fnv1aVectorsForWord32
            , testCase "FNV1  Word64 ByteString Char8" $ fmap (fnv1 @Word64) (Test.Data.FNV.inputs @BSC.ByteString) @?= Test.Data.FNV.fnv1VectorsForWord64
            , testCase "FNV1a Word64 ByteString Char8" $ fmap (fnv1a @Word64) (Test.Data.FNV.inputs @BSC.ByteString) @?= Test.Data.FNV.fnv1aVectorsForWord64
            , testCase "FNV1  Word32 Lazy ByteString Char8" $ fmap (fnv1 @Word32) (Test.Data.FNV.inputs @BSLC.ByteString) @?= Test.Data.FNV.fnv1VectorsForWord32
            , testCase "FNV1a Word32 Lazy ByteString Char8" $ fmap (fnv1a @Word32) (Test.Data.FNV.inputs @BSLC.ByteString) @?= Test.Data.FNV.fnv1aVectorsForWord32
            , testCase "FNV1  Word64 Lazy ByteString Char8" $ fmap (fnv1 @Word64) (Test.Data.FNV.inputs @BSLC.ByteString) @?= Test.Data.FNV.fnv1VectorsForWord64
            , testCase "FNV1a Word64 Lazy ByteString Char8" $ fmap (fnv1a @Word64) (Test.Data.FNV.inputs @BSLC.ByteString) @?= Test.Data.FNV.fnv1aVectorsForWord64
            ]
        , testGroup
            "SipHash"
            [ testCase "SipHash-2-4 Word64 String" $ fmap (sipHash @Word64 2 4 Test.Data.SipHash.key) (Test.Data.SipHash.inputs @String) @?= Test.Data.SipHash.sipHash24VectorsForWord64
            , testCase "SipHash-2-4 Word64 Text" $ fmap (sipHash @Word64 2 4 Test.Data.SipHash.key) (Test.Data.SipHash.inputs @Text) @?= Test.Data.SipHash.sipHash24VectorsForWord64
            , testCase "SipHash-2-4 Word64 ByteString" $ fmap (sipHash @Word64 2 4 Test.Data.SipHash.key) (Test.Data.SipHash.inputs @BS.ByteString) @?= Test.Data.SipHash.sipHash24VectorsForWord64
            , testCase "SipHash-2-4 Word64 Lazy ByteString" $ fmap (sipHash @Word64 2 4 Test.Data.SipHash.key) (Test.Data.SipHash.inputs @BSL.ByteString) @?= Test.Data.SipHash.sipHash24VectorsForWord64
            , testCase "SipHash-2-4 Word64 ByteString Char8" $ fmap (sipHash @Word64 2 4 Test.Data.SipHash.key) (Test.Data.SipHash.inputs @BSC.ByteString) @?= Test.Data.SipHash.sipHash24VectorsForWord64
            , testCase "SipHash-2-4 Word64 Lazy ByteString Char8" $ fmap (sipHash @Word64 2 4 Test.Data.SipHash.key) (Test.Data.SipHash.inputs @BSLC.ByteString) @?= Test.Data.SipHash.sipHash24VectorsForWord64
            , testCase "SipHash-2-4 Word128 String" $ fmap (sipHash @Word128 2 4 Test.Data.SipHash.key) (Test.Data.SipHash.inputs @String) @?= Test.Data.SipHash.sipHash24VectorsForWord128
            , testCase "SipHash-2-4 Word128 Text" $ fmap (sipHash @Word128 2 4 Test.Data.SipHash.key) (Test.Data.SipHash.inputs @Text) @?= Test.Data.SipHash.sipHash24VectorsForWord128
            , testCase "SipHash-2-4 Word128 ByteString" $ fmap (sipHash @Word128 2 4 Test.Data.SipHash.key) (Test.Data.SipHash.inputs @BS.ByteString) @?= Test.Data.SipHash.sipHash24VectorsForWord128
            , testCase "SipHash-2-4 Word128 Lazy ByteString" $ fmap (sipHash @Word128 2 4 Test.Data.SipHash.key) (Test.Data.SipHash.inputs @BSL.ByteString) @?= Test.Data.SipHash.sipHash24VectorsForWord128
            , testCase "SipHash-2-4 Word128 ByteString Char8" $ fmap (sipHash @Word128 2 4 Test.Data.SipHash.key) (Test.Data.SipHash.inputs @BSC.ByteString) @?= Test.Data.SipHash.sipHash24VectorsForWord128
            , testCase "SipHash-2-4 Word128 Lazy ByteString Char8" $ fmap (sipHash @Word128 2 4 Test.Data.SipHash.key) (Test.Data.SipHash.inputs @BSLC.ByteString) @?= Test.Data.SipHash.sipHash24VectorsForWord128
            , testCase "SipHash-2-4 Word64 boundary lengths" $ fmap (sipHash @Word64 2 4 Test.Data.SipHash.key) Test.Data.SipHash.boundaryInputs @?= Test.Data.SipHash.boundaryVectorsForWord64
            ]
        , testGroup
            "HalfSipHash"
            [ testCase "HalfSipHash-2-4 Word32 String" $ fmap (halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key) (Test.Data.HalfSipHash.inputs @String) @?= Test.Data.HalfSipHash.halfSipHash24VectorsForWord32
            , testCase "HalfSipHash-2-4 Word32 Text" $ fmap (halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key) (Test.Data.HalfSipHash.inputs @Text) @?= Test.Data.HalfSipHash.halfSipHash24VectorsForWord32
            , testCase "HalfSipHash-2-4 Word32 ByteString" $ fmap (halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key) (Test.Data.HalfSipHash.inputs @BS.ByteString) @?= Test.Data.HalfSipHash.halfSipHash24VectorsForWord32
            , testCase "HalfSipHash-2-4 Word32 Lazy ByteString" $ fmap (halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key) (Test.Data.HalfSipHash.inputs @BSL.ByteString) @?= Test.Data.HalfSipHash.halfSipHash24VectorsForWord32
            , testCase "HalfSipHash-2-4 Word32 ByteString Char8" $ fmap (halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key) (Test.Data.HalfSipHash.inputs @BSC.ByteString) @?= Test.Data.HalfSipHash.halfSipHash24VectorsForWord32
            , testCase "HalfSipHash-2-4 Word32 Lazy ByteString Char8" $ fmap (halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key) (Test.Data.HalfSipHash.inputs @BSLC.ByteString) @?= Test.Data.HalfSipHash.halfSipHash24VectorsForWord32
            , testCase "HalfSipHash-2-4 Word64 String" $ fmap (halfSipHash @Word64 2 4 Test.Data.HalfSipHash.key) (Test.Data.HalfSipHash.inputs @String) @?= Test.Data.HalfSipHash.halfSipHash24VectorsForWord64
            , testCase "HalfSipHash-2-4 Word64 Text" $ fmap (halfSipHash @Word64 2 4 Test.Data.HalfSipHash.key) (Test.Data.HalfSipHash.inputs @Text) @?= Test.Data.HalfSipHash.halfSipHash24VectorsForWord64
            , testCase "HalfSipHash-2-4 Word64 ByteString" $ fmap (halfSipHash @Word64 2 4 Test.Data.HalfSipHash.key) (Test.Data.HalfSipHash.inputs @BS.ByteString) @?= Test.Data.HalfSipHash.halfSipHash24VectorsForWord64
            , testCase "HalfSipHash-2-4 Word64 Lazy ByteString" $ fmap (halfSipHash @Word64 2 4 Test.Data.HalfSipHash.key) (Test.Data.HalfSipHash.inputs @BSL.ByteString) @?= Test.Data.HalfSipHash.halfSipHash24VectorsForWord64
            , testCase "HalfSipHash-2-4 Word64 ByteString Char8" $ fmap (halfSipHash @Word64 2 4 Test.Data.HalfSipHash.key) (Test.Data.HalfSipHash.inputs @BSC.ByteString) @?= Test.Data.HalfSipHash.halfSipHash24VectorsForWord64
            , testCase "HalfSipHash-2-4 Word64 Lazy ByteString Char8" $ fmap (halfSipHash @Word64 2 4 Test.Data.HalfSipHash.key) (Test.Data.HalfSipHash.inputs @BSLC.ByteString) @?= Test.Data.HalfSipHash.halfSipHash24VectorsForWord64
            , testCase "HalfSipHash-2-4 Word32 boundary lengths" $ fmap (halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key) Test.Data.HalfSipHash.boundaryInputs @?= Test.Data.HalfSipHash.boundaryVectorsForWord32
            ]
        ]

{- | Every element type must feed the hash the same bytes its explicit
byte encoding does, so typed inputs and byte inputs agree.
-}
inputEncoding :: TestTree
inputEncoding =
    testGroup
        "Input Encoding"
        [ testGroup
            "FNV"
            [ testCase "FNV1a Word64 Char encoding matches bytes" $ do
                let bytes = BS.pack (concatMap charBytes charEncodingInput)
                bytes @?= TE.encodeUtf8 (T.pack charEncodingInput)
                fnv1a @Word64 charEncodingInput @?= fnv1a @Word64 bytes
                fnv1a @Word64 (T.pack charEncodingInput) @?= fnv1a @Word64 bytes
                fnv1a @Word64 "\xD800" @?= fnv1a @Word64 (BS.pack [0xed, 0xa0, 0x80])
            , testCase "FNV1a Word64 word elements match bytes" $ do
                fnv1a @Word64 [word128Sample] @?= fnv1a @Word64 (BS.pack (wordBytes 16 word128Sample))
                fnv1a @Word64 [word256Sample] @?= fnv1a @Word64 (BS.pack (wordBytes 32 word256Sample))
            , testCase "FNV1 Word64 small word elements match bytes" $ do
                fnv1 @Word64 word16Samples @?= fnv1 @Word64 (packWords 2 word16Samples)
                fnv1 @Word64 word32Samples @?= fnv1 @Word64 (packWords 4 word32Samples)
                fnv1 @Word64 word64Samples @?= fnv1 @Word64 (packWords 8 word64Samples)
            , testCase "FNV1a Word64 small word elements match bytes" $ do
                fnv1a @Word64 word16Samples @?= fnv1a @Word64 (packWords 2 word16Samples)
                fnv1a @Word64 word32Samples @?= fnv1a @Word64 (packWords 4 word32Samples)
                fnv1a @Word64 word64Samples @?= fnv1a @Word64 (packWords 8 word64Samples)
            , testCase "FNV1a Word64 signed elements match two's complement bytes" $ do
                fnv1a @Word64 int8Samples @?= fnv1a @Word64 int8SampleBytes
                fnv1a @Word64 int16Samples @?= fnv1a @Word64 int16SampleBytes
                fnv1a @Word64 int32Samples @?= fnv1a @Word64 int32SampleBytes
                fnv1a @Word64 int64Samples @?= fnv1a @Word64 int64SampleBytes
            ]
        , testGroup
            "SipHash"
            [ testCase "SipHash-2-4 Word64 Char encoding matches bytes" $ do
                let bytes = BS.pack (concatMap charBytes charEncodingInput)
                bytes @?= TE.encodeUtf8 (T.pack charEncodingInput)
                sipHash @Word64 2 4 Test.Data.SipHash.key charEncodingInput @?= sipHash @Word64 2 4 Test.Data.SipHash.key bytes
                sipHash @Word64 2 4 Test.Data.SipHash.key (T.pack charEncodingInput) @?= sipHash @Word64 2 4 Test.Data.SipHash.key bytes
            , testCase "SipHash-2-4 Word64 word elements match bytes" $ do
                sipHash @Word64 2 4 Test.Data.SipHash.key [word128Sample] @?= sipHash @Word64 2 4 Test.Data.SipHash.key (BS.pack (wordBytes 16 word128Sample))
                sipHash @Word64 2 4 Test.Data.SipHash.key [word256Sample] @?= sipHash @Word64 2 4 Test.Data.SipHash.key (BS.pack (wordBytes 32 word256Sample))
            , testCase "SipHash-2-4 Word64 small word elements match bytes" $ do
                sipHash @Word64 2 4 Test.Data.SipHash.key word16Samples @?= sipHash @Word64 2 4 Test.Data.SipHash.key (packWords 2 word16Samples)
                sipHash @Word64 2 4 Test.Data.SipHash.key word32Samples @?= sipHash @Word64 2 4 Test.Data.SipHash.key (packWords 4 word32Samples)
                sipHash @Word64 2 4 Test.Data.SipHash.key word64Samples @?= sipHash @Word64 2 4 Test.Data.SipHash.key (packWords 8 word64Samples)
            , testCase "SipHash-2-4 Word64 signed elements match two's complement bytes" $ do
                sipHash @Word64 2 4 Test.Data.SipHash.key int8Samples @?= sipHash @Word64 2 4 Test.Data.SipHash.key int8SampleBytes
                sipHash @Word64 2 4 Test.Data.SipHash.key int16Samples @?= sipHash @Word64 2 4 Test.Data.SipHash.key int16SampleBytes
                sipHash @Word64 2 4 Test.Data.SipHash.key int32Samples @?= sipHash @Word64 2 4 Test.Data.SipHash.key int32SampleBytes
                sipHash @Word64 2 4 Test.Data.SipHash.key int64Samples @?= sipHash @Word64 2 4 Test.Data.SipHash.key int64SampleBytes
            ]
        , testGroup
            "HalfSipHash"
            [ testCase "HalfSipHash-2-4 Word32 Char encoding matches bytes" $ do
                let bytes = BS.pack (concatMap charBytes charEncodingInput)
                bytes @?= TE.encodeUtf8 (T.pack charEncodingInput)
                halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key charEncodingInput @?= halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key bytes
                halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key (T.pack charEncodingInput) @?= halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key bytes
            , testCase "HalfSipHash-2-4 Word32 word elements match bytes" $ do
                halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key [word128Sample] @?= halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key (BS.pack (wordBytes 16 word128Sample))
                halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key [word256Sample] @?= halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key (BS.pack (wordBytes 32 word256Sample))
            , testCase "HalfSipHash-2-4 Word32 small word elements match bytes" $ do
                halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key word16Samples @?= halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key (packWords 2 word16Samples)
                halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key word32Samples @?= halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key (packWords 4 word32Samples)
                halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key word64Samples @?= halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key (packWords 8 word64Samples)
            , testCase "HalfSipHash-2-4 Word32 signed elements match two's complement bytes" $ do
                halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key int8Samples @?= halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key int8SampleBytes
                halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key int16Samples @?= halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key int16SampleBytes
                halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key int32Samples @?= halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key int32SampleBytes
                halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key int64Samples @?= halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key int64SampleBytes
            ]
        ]

{- | Distinct texts that the old, non-prefix-free 'Char' encoding mapped to
the same bytes must hash differently.
-}
encodingCollisions :: TestTree
encodingCollisions =
    testGroup
        "Encoding Collisions"
        [ testGroup
            "FNV"
            [ testCase "FNV1a Word64 String" $ checkCollisions (fnv1a @Word64) id
            , testCase "FNV1a Word64 Text" $ checkCollisions (fnv1a @Word64) T.pack
            ]
        , testGroup
            "SipHash"
            [ testCase "SipHash-2-4 Word64 String" $ checkCollisions (sipHash @Word64 2 4 Test.Data.SipHash.key) id
            , testCase "SipHash-2-4 Word64 Text" $ checkCollisions (sipHash @Word64 2 4 Test.Data.SipHash.key) T.pack
            ]
        , testGroup
            "HalfSipHash"
            [ testCase "HalfSipHash-2-4 Word32 String" $ checkCollisions (halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key) id
            , testCase "HalfSipHash-2-4 Word32 Text" $ checkCollisions (halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key) T.pack
            ]
        ]

checkCollisions :: (Eq a, Show a) => (t -> a) -> (String -> t) -> IO ()
checkCollisions h mk =
    mapM_
        (\(label, x, y) -> assertBool label (h (mk x) /= h (mk y)))
        collisionPairs

collisionPairs :: [(String, String, String)]
collisionPairs =
    [ ("U+0100 and U+0001 U+0000 must not collide", "\x0100", "\x01\x00")
    , ("U+1234 and U+0012 U+0034 must not collide", "\x1234", "\x12\x34")
    , ("U+1F600 and U+0001 U+00F6 U+0000 must not collide", "\x1F600", "\x01\xF6\x00")
    ]