{-# LANGUAGE UndecidableInstances #-} module BitRecordsSpec (spec) where import Data.Bits import Data.ByteString.Builder import Data.Proxy import Data.Type.BitRecords import Data.Type.Equality () import Data.Kind.Extra import GHC.TypeLits import Prelude hiding ((.), id) import Test.Hspec import Test.QuickCheck (property, Arbitrary(..), choose) import Test.TypeSpecCrazy import Text.Printf basicsSpec :: Spec basicsSpec = do describe "Maybe, Lists, Either, Bool, 'True, 'False, FlagJust, FlagNothing" $ do let checkFlagJust :: "Type Level Bool, 'True, 'False, FlagJust, FlagNothing Accessors" ################################################################## "The record size works" ~~~~~~~~~~~~~~~~~~~~~~~~ 1 `ShouldBe` FieldWidth (FlagJust 'Nothing) -* 1 `ShouldBe` FieldWidth (FlagJust ('Just "Blah")) -* 1 `ShouldBe` FieldWidth (FlagNothing ('Just "Blah")) -- TODO reenable tests -- -* 32 `ShouldBe` SizeInBits (ToBitRecord ('Just FieldU32)) -- -* 0 `ShouldBe` SizeInBits (ToBitRecord '[]) -- -* 10 `ShouldBe` SizeInBits (ToBitRecord '[Field 10]) -- -* 25 `ShouldBe` SizeInBits (ToBitRecord '[Field 10, Field 15]) -- -* 1 `ShouldBe` SizeInBits (ToBitRecord Bool) -- -* 1 `ShouldBe` SizeInBits (ToBitRecord 'True) -- -* 1 `ShouldBe` SizeInBits (ToBitRecord 'False) checkFlagJust = Valid runIO $ print checkFlagJust describe "bitBuffer64Printer" $ do describe "Just x" $ it "writes x" $ bitBuffer64Printer (Proxy :: Proxy (OptionalRecord ('Just ('BitRecordMember (Flag := 'True))))) `shouldBe` "<< 80 >>" describe "Nothing" $ it "writes nothing" $ bitBuffer64Printer (Proxy :: Proxy (OptionalRecord ('Nothing))) `shouldBe` "<< >>" -- describe "'[]" $ it "writes nothing" $ -- bitBuffer64Printer (Proxy :: Proxy (BitRecordOfList (Fun1 RecordField) ('[]))) `shouldBe` "<< >>" -- describe "'[x1, x2]" $ it "writes x1 then x2" $ -- bitBuffer64Printer (Proxy :: Proxy (BitRecordOfList (Fun1 RecordField) ('[FieldU8 := 1, FieldU8 := 2]))) `shouldBe` "<< 01 02 >>" describe "'True" $ it "writes a single bit with a 1" $ bitBuffer64Printer (Proxy :: Proxy (RecordField (Flag := 'True))) `shouldBe` "<< 80 >>" describe "'False" $ it "writes a single bit with a 0" $ bitBuffer64Printer (Proxy :: Proxy (RecordField (Flag := 'False))) `shouldBe` "<< 00 >>" describe "@: labelled fields" $ do it "writes them ..." $ let fld = Proxy @(From (RecordField ( "foo" @: FlagJust 'Nothing ))) in bitBuffer64Printer fld `shouldBe` "<< 00 >>" describe "FlagJust" $ do it "writes a single bit '1' for a 'Just ...' parameter" $ bitBuffer64Printer (Proxy :: Proxy (RecordField (FlagJust ('Just "test")))) `shouldBe` "<< 80 >>" it "writes a single bit '0' for a 'Nothing' parameter" $ bitBuffer64Printer (Proxy :: Proxy (RecordField (FlagJust 'Nothing))) `shouldBe` "<< 00 >>" describe "FlagNothing" $ do it "writes a single bit '0' for a 'Just ...' parameter" $ bitBuffer64Printer (Proxy :: Proxy (RecordField (FlagNothing ('Just "test")))) `shouldBe` "<< 00 >>" it "writes a single bit '1' for a 'Nothing' parameter" $ bitBuffer64Printer (Proxy :: Proxy (RecordField (FlagNothing 'Nothing))) `shouldBe` "<< 80 >>" -- TODO reenable showRecord tests -- describe "showRecord" $ do -- describe "Maybe" $ do -- it "prints 'Just x'" $ -- showRecord (Proxy :: Proxy (ToBitRecord ('Just 'True))) `shouldBe` "Field:\n Demote Rep: Bool\n Bits: 1\n Static Value: 'True" -- it "prints nothing for 'Nothing'" $ -- showRecord (Proxy :: Proxy (ToBitRecord ('Nothing))) `shouldBe` "" -- describe "List" $ do -- it "prints '[x]'" $ -- showRecord (Proxy :: Proxy (ToBitRecord ('[ 'True ]))) `shouldBe` "Field:\n Demote Rep: Bool\n Bits: 1\n Static Value: 'True" -- it "prints '[x1, x2, x3]'" $ -- showRecord (Proxy :: Proxy (ToBitRecord ('[ Bool, Bool, Bool ]))) `shouldBe` "Field:\n Demote Rep: Bool\n Bits: 1\nField:\n Demote Rep: Bool\n Bits: 1\nField:\n Demote Rep: Bool\n Bits: 1" -- it "prints nothing for '[]'" $ -- showRecord (Proxy :: Proxy (ToBitRecord ('[]))) `shouldBe` "" -- describe "Bool" $ do -- it "prints 'True" $ -- showRecord (Proxy :: Proxy (ToBitRecord ('True))) `shouldBe` "Field:\n Demote Rep: Bool\n Bits: 1\n Static Value: 'True" -- it "prints 'False" $ -- showRecord (Proxy :: Proxy (ToBitRecord ('False))) `shouldBe` "Field:\n Demote Rep: Bool\n Bits: 1\n Static Value: 'False" -- it "prints a Bool" $ -- showRecord (Proxy :: Proxy (ToBitRecord Bool)) `shouldBe` "Field:\n Demote Rep: Bool\n Bits: 1" -- describe "FlagJust" $ do -- it "prints a 'FlagJust 'Just ..'" $ -- showRecord (Proxy :: Proxy (ToBitRecord (FlagJust ('Just "123")))) `shouldBe` "Field:\n Demote Rep: Bool\n Bits: 1\n Static Value: 'True" -- it "prints a 'FlagJust 'Nothing'" $ -- showRecord (Proxy :: Proxy (ToBitRecord (FlagJust 'Nothing))) `shouldBe` "Field:\n Demote Rep: Bool\n Bits: 1\n Static Value: 'False" -- describe "FlagNothing" $ do -- it "prints 'FlagNothing 'Just ..'" $ -- showRecord (Proxy :: Proxy (ToBitRecord (FlagNothing ('Just "123")))) `shouldBe` "Field:\n Demote Rep: Bool\n Bits: 1\n Static Value: 'False" -- it "prints a 'FlagNothing 'Nothing'" $ -- showRecord (Proxy :: Proxy (ToBitRecord (FlagNothing 'Nothing))) `shouldBe` "Field:\n Demote Rep: Bool\n Bits: 1\n Static Value: 'True" #ifdef COMPLEXTESTS arraySpec :: SpecWith () arraySpec = describe "RecArray" $ do describe "level record accessors" $ do let checkArrayRec :: "BitRecord accessors involving RecArray" ####################################### "The record size works" ~~~~~~~~~~~~~~~~~~~~~~~~ 1 `ShouldBe` SizeInBits (From (RecArray ('BitRecordMember Flag) 1)) -* 91 `ShouldBe` SizeInBits (From (("foo" @: Flag .+. FieldU8) ^^ 10) :+. Flag) -* 91 `ShouldBe` SizeInBits (From (RecArray ("foo" @: Flag .+. FieldU8) 10) :+. Flag) checkArrayRec = Valid runIO $ print checkArrayRec describe "showRecord" $ it "appends its body n times" $ let expected = "utf-8(40) := <> [5 Bytes]\nutf-8(40) := <> [5 Bytes]\nutf-8(40) := <> [5 Bytes]\nutf-8(40) := <> [5 Bytes]\nutf-8(40) := <> [5 Bytes]" actual = showARecord (Proxy @ (('BitRecordMember [utf8|hello|] ^^ 5))) in actual `shouldBe` expected describe "bitBuffer64Printer" $ it "writes its contents n times to the builder" $ let actual = bitBuffer64Printer (Proxy :: Proxy (('BitRecordMember (Field 24 := 0x010203) ^^ 4))) expected = "<< 01 02 03 01 02 03 01 02 03 01 02 03 >>" in actual `shouldBe` expected sizedSpec = describe "Sized" $ do describe "TypeChecks" $ let checkSized :: "Sized" ######### "SizedString" ~~~~~~~~~~~~~~~ 88 `ShouldBe` FieldWidth [utf8|Hello World|] -* 104 `ShouldBe` SizeInBits (From (RecordField [utf8|Heλλo World|])) -/- "Sized BitRecord Members" ~~~~~~~~~~~~~~~~~~~~~~~~ 8 `ShouldBe` SizeInBits (From (Sized8 'EmptyBitRecord)) -* 9 `ShouldBe` SizeInBits (From (Sized8 ('BitRecordMember Flag))) -* 0 `ShouldBe` SizeInBytes 'EmptyBitRecord -* 1 `ShouldBe` SizeInBytes ('BitRecordMember Flag) -/- "SizedField" ~~~~~~~~~~~~ 9 `ShouldBe` SizeInBits (From (SizedField8 Flag)) -* 1 `ShouldBe` SizeInBytes Flag -- TODO add more Sized tests, especially for SizedField checkSized = Valid in runIO $ print checkSized describe "showRecord" $ do describe "SizedString" $ it "renders a string containing wide utf-8 characters to a header containing the number of chars and the actual string" $ showARecord (Proxy :: Proxy (RecordField [utf8|Heλλo World!|])) `shouldBe` "utf-8(112) := <> [14 Bytes]" describe "Sized SizeField16 SizedString" $ it "renders the number bytes not chars as the size field value" $ showARecord (Proxy :: Proxy (SizedField16 [utf8|Heλλo World!|])) `shouldBe` "size: U16 := hex: 000e (dec: 14)\nutf-8(112) := <> [14 Bytes]" describe "bitBuffer64Printer" $ do describe "no length prefix" $ it "renders no size prefix and the string as UTF-8 bytes" $ bitBuffer64Printer (Proxy :: Proxy (RecordField [utf8|ABC|])) `shouldBe` "<< 41 42 43 >>" describe "8-bit length prefix" $ it "renders a single byte size prefix and the string as UTF-8 bytes" $ bitBuffer64Printer (Proxy :: Proxy (SizedField8 [utf8|ABC|])) `shouldBe` "<< 03 41 42 43 >>" describe "16-bit length prefix" $ it "renders a big endian 16 bit size prefix and the string as UTF-8 bytes" $ bitBuffer64Printer (Proxy :: Proxy (SizedField16 [utf8|ABC|])) `shouldBe` "<< 00 03 41 42 43 >>" describe "32-bit length prefix" $ it "renders a big endian 32 bit size prefix and the string as UTF-8 bytes" $ bitBuffer64Printer (Proxy :: Proxy (SizedField32 [utf82|ABC|])) `shouldBe` "<< 00 00 00 03 41 42 43 >>" describe "64-bit length prefix" $ it "renders a big endian 64 bit size prefix and the string as UTF-8 bytes" $ bitBuffer64Printer (Proxy :: Proxy (SizedField64 [utf8|ABC|])) `shouldBe` "<< 00 00 00 00 00 00 00 03 41 42 43 >>" type TestRecAligned = "bar" @: Field 8 .+: Field 8 := 0 .+: "baz" @: Field 8 .+: Field 32 := 0 .+: "foo" @: Field 8 .+: Field 8 := 0 .+: "oof" @: Field 8 .+: Field 8 := 0 .+. "rab" @: Field 8 checkTestRecAligned :: Expect '[ ShouldBe 96 (SizeInBits TestRecAligned) ] checkTestRecAligned = Valid type TestRecUnAligned = "bar" @: Field 8 .+: Field 8 := 0 .+: "baz" @: Field 7 .+: Field 32 := 0 .+: "foo" @: Field 8 .+. Field 8 := 0xfe checkTestRecUnAligned :: Expect '[ ShouldBe 71 (SizeInBits TestRecUnAligned) ] checkTestRecUnAligned = Valid testTakeLastN :: "Taking the last n elements of a list" ####################################### TakeLastN 0 '[1,2,3] `ShouldBe` ('[] :: [Nat]) -* TakeLastN 1 '[1,2,3] `ShouldBe` '[3] -* TakeLastN 2 '[1,2,3] `ShouldBe` '[2,3] -* TakeLastN 5 '[1,2,3] `ShouldBe` '[1,2,3] testTakeLastN = Valid testRem :: Expect '[ Mod 0 3 `ShouldBe` 0 , Mod 1 3 `ShouldBe` 1 , Mod 2 3 `ShouldBe` 2 , Mod 3 3 `ShouldBe` 0 , Mod 4 3 `ShouldBe` 1 , Mod 5 3 `ShouldBe` 2 , Mod 6 3 `ShouldBe` 0 ] testRem = Valid testRemPow2 :: "ModPow2" ######### "Remainder of '1'" ~~~~~~~~~~~~~~~~~~ It "1 `ModPow2` 1 is 1" (Is 1 (ModPow2 1 1)) -* It "1 `ModPow2` 8 is 1" (Is 1 (ModPow2 1 8)) -/- "Remainder of '3916441'" ~~~~~~~~~~~~~~~~~~~~~~~~~~~ It " `ModPow2` 1 is 1" (Is 1 (ModPow2 3916441 1)) -* It " `ModPow2` 4 is 9" (Is 9 (ModPow2 3916441 4)) -* It " `ModPow2` 8 is 153" (Is 153 (ModPow2 3916441 8)) testRemPow2 = Valid testDiv :: Expect '[ Div 0 3 `ShouldBe` 0 , Div 1 3 `ShouldBe` 0 , Div 2 3 `ShouldBe` 0 , Div 3 3 `ShouldBe` 1 , Div 4 3 `ShouldBe` 1 , Div 5 3 `ShouldBe` 1 , Div 6 3 `ShouldBe` 2 , Div 144 13 `ShouldBe` 11 -- , Div 512 128 `ShouldBe` 11 ] testDiv = Valid testNatBits :: "Type Level bit operations" ########################### "TestHighBit" ~~~~~~~~~~~~ ShouldBeFalse (TestHighBit 127 8) -* ShouldBeFalse (TestHighBit 127 7) -* ShouldBeTrue (TestHighBit 127 6) -* ShouldBeFalse (TestHighBit 32 6) -* ShouldBeTrue (TestHighBit 32 5) -* ShouldBeFalse (TestHighBit 16 5) -* ShouldBeTrue (TestHighBit 16 4) -* ShouldBeFalse (TestHighBit 8 4) -* ShouldBeTrue (TestHighBit 8 3) -* ShouldBeFalse (TestHighBit 4 3) -* ShouldBeTrue (TestHighBit 4 2) -* ShouldBeFalse (TestHighBit 2 2) -* ShouldBeTrue (TestHighBit 2 1) -* ShouldBeFalse (TestHighBit 0 1) -* ShouldBeTrue (TestHighBit 1 0) -/- "ToBits" ~~~~~~~~~ It "returns the empty list for a zero bit length" (ToBits 1023 0 `ShouldBe` ('[] :: [Bool]) ) -* It "returns [] for a single unset bit" (ShouldBe ('[] :: [Bool]) (ToBits 0 1)) -* It "returns [True] for a single set bit" (ShouldBe '[ 'True] (ToBits 1 1)) -* It "returns [True, False] when getting two bits from 0x2" (ShouldBe '[ 'True, 'False] (ToBits 0x2 2)) -* It "returns the list of bits in correct order" (ShouldBe '[ 'True, 'True, 'True, 'True , 'False, 'False, 'False, 'False] (ToBits 0xf0 8)) -* It "returns no leading 'False (i.e. it omits leading zero bits)" (ShouldBe '[ 'True, 'True, 'True, 'True] (ToBits 0x0000000f 32)) -/- "FromBits" ~~~~~~~~~ It "returns 0 for '[]" (ShouldBe 0 (FromBits ('[] :: [Bool]))) -* It "returns 0 for [False]" (ShouldBe 0 (FromBits '[ 'False])) -* It "returns 1 for [True]" (ShouldBe 1 (FromBits '[ 'True])) -* It "returns 2 for [True, False]" (ShouldBe 2 (FromBits '[ 'True, 'False])) -* It "returns 4 for [True, False, False]" (ShouldBe 4 (FromBits '[ 'True, 'False, 'False])) -* It "returns 5 for [True, False, True]" (ShouldBe 5 (FromBits '[ 'True, 'False, 'True])) -/- "ShiftBitsR" ~~~~~~~~~~~~ It "returns the input bits for n == 0" (ShouldBe '[ 'True, 'False] (ShiftBitsR ['True, 'False] 0)) -* It "returns '[] when shifting [True] 1 bits" (ShouldBe ('[] :: [Bool]) (ShiftBitsR '[ 'True ] 1)) -* It "returns '[True] when shifting [True, True] 1 bits" (ShouldBe '[ 'True] (ShiftBitsR '[ 'True, 'True ] 1)) -* It "returns (ToBits 12 8) when shifting (ToBits 97 8) 3 bits to the right" (ShouldBe (ToBits 12 8) (ShiftBitsR (ToBits 97 8) 3)) -/- "GetMostSignificantBitIndex" ~~~~~~~~~~~~~~~ It "returns 1 for 0" (ShouldBe 1 (GetMostSignificantBitIndex 64 0)) -* It "returns 1 for 1" (ShouldBe 1 (GetMostSignificantBitIndex 64 1)) -* It "returns 1 for 2" (ShouldBe 1 (GetMostSignificantBitIndex 64 2)) -* It "returns 1 for 3" (ShouldBe 1 (GetMostSignificantBitIndex 64 3)) -* It "returns 2 for 4" (ShouldBe 2 (GetMostSignificantBitIndex 64 4)) -* It "returns 2 for 5" (ShouldBe 2 (GetMostSignificantBitIndex 64 4)) -* It "returns 8 for 511" (ShouldBe 8 (GetMostSignificantBitIndex 64 511)) -* It "returns 63 for (2^64 - 1)" (ShouldBe 63 (GetMostSignificantBitIndex 64 (2^64 - 1))) -/- "ShiftR" ~~~~~~~~~ It "returns '0' when shifting '42' 6 bits to the right" (ShouldBe 0 (ShiftR 64 42 6)) -* It "returns 2 when shifting 512 8 bits to the right" (ShouldBe 2 (ShiftR 64 512 8)) testNatBits = Valid #endif spec :: Spec spec = do basicsSpec #ifdef COMPLEXTESTS sizedSpec arraySpec describe "The Set of Type Functions" $ it "is sound" $ do print (Valid :: Expect (SizeInBits (Flag .+. Field 7) `Is` 8)) print testTakeLastN print testRem print testRemPow2 print testDiv print testNatBits print checkTestRecAligned print checkTestRecUnAligned describe "showARecord" $ do it "prints (Field 4 .+. (Field 4 := 0x96)) to \"<..>0110\"" $ let actual = showRecord (Proxy :: Proxy (Field 4 .+. Field 4 := 0x96)) in actual `shouldBe` "bits(4)\nbits(4) := 10010110 (hex: 96 dec: 150)" describe "StaticLazybytestringbuilder" $ do it "writes (and flushes) bits" $ let rec = Proxy rec :: Proxy TestRecUnAligned actualB :: Builder actualB = runBitBuilderHoley (toFunctionBuilder rec) 1 3 7 actual = printBuilder actualB in actual `shouldBe` "<< 01 00 06 00 00 00 00 0f fc >>" describe "Formatting sub-byte fields" $ do it "only the addressed bits are copied to the output" $ property $ \value -> let rec = Proxy rec :: Proxy (Field 4 := 0 .+. "here" @: Field 4) actualB :: Builder actualB = runBitBuilderHoley (toFunctionBuilder rec) value actual = printBuilder actualB expected = printf "<< %.2x >>" (value .&. 0xf) in actual `shouldBe` expected it "renders (Flag := 0 .+. Field 7 := 130) to << 02 >>" $ let rec = Proxy rec :: Proxy (Flag := 'False .+. Field 7 := 130) actual = printBuilder b where b = runBitBuilderHoley (toFunctionBuilder rec) in actual `shouldBe` "<< 02 >>" describe "ByteStringBuilder" $ describe "runBitBuilderHoley" $ it "0x01020304050607 to << 00 01 02 03 04 05 06 07 >>" $ let expected = "<< 00 01 02 03 04 05 06 07 >>" actual = printBuilder (runBitBuilderHoley (toFunctionBuilder (bitBuffer64 64 0x01020304050607))) in actual `shouldBe` expected #endif instance (KnownNat n, n <= 64) => Arbitrary (B n) where arbitrary = do let h = 2^(n'-1) - 1 n' = fromIntegral (natVal (Proxy @n)) :: Int x <- choose (0, h + h + 1) return (B x)