{-# LANGUAGE MultiWayIf #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PackageImports #-} {-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} -- | -- Module : Main -- Copyright : (c) 2020 Emily Pillmore -- License : BSD-style -- -- Maintainer : Emily Pillmore -- Stability : Experimental -- Portability : portable -- -- This module contains the test implementation for the `base16` package -- module Main ( main , tests ) where import Data.Bifunctor (second) import qualified Data.ByteString as BS import Data.ByteString.Internal (c2w) import qualified Data.ByteString.Lazy as LBS import qualified Data.ByteString.Short as SBS import "base16" Data.ByteString.Base16 as B16 import qualified "base16-bytestring" Data.ByteString.Base16 as Bos import qualified Data.Text as T import qualified Data.Text.Encoding as T import Data.Text.Encoding.Base16.Error (Base16Error(..)) import qualified Data.Text.Lazy as TL import qualified Data.Text.Lazy.Encoding as TL import qualified Data.Text.Short as TS import Data.Word import Internal import Test.Tasty import Test.Tasty.HUnit import Test.Tasty.QuickCheck (testProperty) main :: IO () main = defaultMain tests tests :: TestTree tests = testGroup "Base16 Tests" [ mkTree b16 [ mkPropTree , mkUnitTree ] , mkTree lb16 [ mkPropTree , mkUnitTree ] , mkTree sb16 [ mkPropTree , mkUnitTree ] , mkTree t16 [ mkPropTree , mkUnitTree , mkDecodeTree T.decodeUtf8' b16 ] , mkTree tl16 [ mkPropTree , mkUnitTree , mkDecodeTree TL.decodeUtf8' lb16 ] , mkTree ts16 [ mkPropTree , mkUnitTree , mkDecodeTree (second TS.fromText . T.decodeUtf8' . SBS.fromShort) sb16 ] ] -- ---------------------------------------------------------------- -- -- Test tree generation -- | Make a test tree for a given label -- mkTree :: forall a b proxy . Harness a b => proxy a -> [proxy a -> TestTree] -> TestTree mkTree a = testGroup (label @a) . fmap ($ a) -- | Make a test group with some name, lifting a test tree up to the correct -- type information via some Harness -- mkTests :: forall a b proxy . Harness a b => String -> [proxy a -> TestTree] -> proxy a -> TestTree mkTests context ts = testGroup context . (<*>) ts . pure -- | Make property tests for a given harness instance -- mkPropTree :: forall a b proxy. Harness a b => proxy a -> TestTree mkPropTree = mkTests "Property Tests" [ prop_roundtrip , prop_correctness , const prop_bos_coherence ] -- | Make unit tests for a given harness instance -- mkUnitTree :: forall a b proxy . Harness a b => proxy a -> TestTree mkUnitTree = mkTests "Unit tests" [ rfcVectors , lenientTests ] -- | Make unit tests for textual 'decode*With' functions -- mkDecodeTree :: forall t a b c e proxy . ( TextHarness a b c , Harness t c , Show e ) => (c -> Either e b) -> proxy t -> proxy a -> TestTree mkDecodeTree utf8 t = mkTests "Decoding tests" [ decodeWithVectors utf8 t ] -- ---------------------------------------------------------------- -- -- Property tests prop_roundtrip :: forall a b proxy. Harness a b => proxy a -> TestTree prop_roundtrip _ = testGroup "prop_roundtrip" [ testProperty "prop_std_roundtrip" $ \(bs :: b) -> Right (encode bs) == decode (encode (encode bs)) , testProperty "prop_std_lenient_roundtrip" $ \(bs :: b) -> encode bs == lenient (encode (encode bs)) ] prop_correctness :: forall a b proxy. Harness a b => proxy a -> TestTree prop_correctness _ = testGroup "prop_validity" [ testProperty "prop_std_valid" $ \(bs :: b) -> validate (encode bs) , testProperty "prop_std_correct" $ \(bs :: b) -> correct (encode bs) ] -- | just a sanity check against `base16-bytestring` -- prop_bos_coherence :: TestTree prop_bos_coherence = testGroup "prop_bos_coherence" [ testProperty "prop_std_bos_coherence" $ \bs -> Right bs == B16.decodeBase16 (B16.encodeBase16' bs) && Right bs == (Right (fst $ Bos.decode $ Bos.encode bs) :: Either T.Text BS.ByteString) ] -- ---------------------------------------------------------------- -- -- Unit tests rfcVectors :: forall a b proxy. Harness a b => proxy a -> TestTree rfcVectors _ = testGroup "RFC 4648 Test Vectors" [ testGroup "lower-case" [ testCaseB16 "" "" , testCaseB16 "f" "66" , testCaseB16 "fo" "666f" , testCaseB16 "foo" "666f6f" , testCaseB16 "foob" "666f6f62" , testCaseB16 "fooba" "666f6f6261" , testCaseB16 "foobar" "666f6f626172" ] , testGroup "upper-case" [ testCaseB16 "" "" , testCaseB16 "f" "66" , testCaseB16 "fo" "666F" , testCaseB16 "foo" "666F6F" , testCaseB16 "foob" "666F6F62" , testCaseB16 "fooba" "666F6F6261" , testCaseB16 "foobar" "666F6F626172" ] , testGroup "mixed-case" [ testCaseB16 "" "" , testCaseB16 "f" "66" , testCaseB16 "fo" "666F" , testCaseB16 "foo" "666F6f" , testCaseB16 "foob" "666F6f62" , testCaseB16 "fooba" "666F6f6261" , testCaseB16 "foobar" "666F6f626172" ] ] where testCaseB16 s t = testCaseSteps (show $ if s == "" then "empty" else s) $ \step -> do step "encode is sound" lower t @=? encode @a s step "decode is sound" Right s @=? decode (encode s) -- | Unit test trees for the `decode*With` family of text-valued functions -- decodeWithVectors :: forall t a b c e proxy . ( TextHarness a c b , Harness t b , Show e ) => (b -> Either e c) -- ^ utf8 -> proxy t -- ^ witness to the bytestring-ey dictionaries -> proxy a -- ^ witness to the text dictionaries -> TestTree decodeWithVectors utf8 _ _ = testGroup "DecodeWith* unit tests" [ testGroup "decodeWith negative tests" [ testCase "decodeWith non-utf8 inputs on decodeUtf8" $ do case decodeWith_ @a utf8 "\1079743" of Left (DecodeError _) -> return () _ -> assertFailure "decoding phase" , testCase "decodeWith valid utf8 inputs on decodeUtf8" $ do case decodeWith_ @a utf8 (encode @t "\1079743") of Left (ConversionError _) -> return () _ -> assertFailure "conversion phase" ] , testGroup "decodeWith positive tests" [ testCase "decodeWith utf8 inputs on decodeUtf8" $ do a <- either (assertFailure . show) pure $ decode @a "666f6f626172" b <- either (assertFailure . show) pure $ decodeWith_ @a utf8 "666f6f626172" a @=? b ] ] lenientTests :: forall a b proxy. Harness a b => proxy a -> TestTree lenientTests _ = testGroup "Lenient Tests" [ testCaseB16 "" "" , testCaseB16 "f" "6+6" , testCaseB16 "fo" "6$6+6|f" , testCaseB16 "foo" "==========6$$66()*F6f" , testCaseB16 "foob" "66^%$&^6f6F62" , testCaseB16 "fooba" "666f()*#@6F#)(@*)6()*)2()61" , testCaseB16 "foobar" "6@6@6@f@6@f@6@2@6@1@7@2++++++++++++++++++++++++" ] where testCaseB16 s t = testCaseSteps (show $ if s == "" then "empty" else s) $ \step -> do let t0 = decode (encode @a s) t1 = lenient @a t step "compare decoding" t0 @=? Right t1