1
1
{-# LANGUAGE ParallelListComp #-}
2
2
3
- {-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}
4
-
5
3
-- | This accompanies the format-page.md documentation as a sanity check
6
4
-- and a precise reference. It is intended to demonstrate that the page
7
5
-- format works. It is also used as a reference implementation for tests of
@@ -17,10 +15,12 @@ module FormatPage (
17
15
-- * Page types
18
16
Key (.. ),
19
17
Operation (.. ),
18
+ opHasBlobRef ,
20
19
Value (.. ),
21
20
BlobRef (.. ),
22
21
PageSerialised ,
23
- PageIntermediate ,
22
+ PageIntermediate (.. ),
23
+ PageSizesOffsets (.. ),
24
24
25
25
-- * Page size
26
26
PageSize (.. ),
@@ -30,26 +30,31 @@ module FormatPage (
30
30
31
31
-- * Encoding and decoding
32
32
DiskPageSize (.. ),
33
+ diskPageSizeBytes ,
33
34
encodePage ,
34
35
decodePage ,
35
36
serialisePage ,
36
37
deserialisePage ,
38
+ fromBitmap ,
39
+ toBitmap ,
37
40
38
41
-- * Overflow pages
39
42
pageOverflowPrefixSuffixLen ,
40
43
pageDiskPages ,
41
44
pageSerialisedChunks ,
42
45
43
- -- * Tests and generators
44
- tests ,
45
- -- ** Generators and shrinkers
46
+ -- * Generators and shrinkers
47
+ PageContentFits (.. ),
46
48
genPageContentFits ,
49
+ PageContentMaybeOverfull (.. ),
47
50
genPageContentMaybeOverfull ,
51
+ PageContentSingle (.. ),
48
52
genPageContentSingle ,
49
53
genPageContentNearFull ,
50
54
genPageContentMedium ,
51
55
MinKeySize (.. ),
52
56
noMinKeySize ,
57
+ maxKeySize ,
53
58
orderdKeyOps ,
54
59
shrinkKeyOps ,
55
60
shrinkOrderedKeyOps ,
@@ -72,9 +77,6 @@ import Control.Exception (assert)
72
77
import Control.Monad
73
78
74
79
import Test.QuickCheck hiding ((.&.) )
75
- import Test.Tasty
76
- import Test.Tasty.QuickCheck (testProperty )
77
-
78
80
79
81
-------------------------------------------------------------------------------
80
82
-- Page content types
@@ -868,132 +870,6 @@ shrinkOpaqueByteString bs =
868
870
| BS. null prefix -> []
869
871
| otherwise -> [ BS. init prefix <> BSC. cons ' ' spaces ]
870
872
871
- -------------------------------------------------------------------------------
872
- -- Tests
873
- --
874
-
875
- tests :: TestTree
876
- tests = testGroup " FormatPage"
877
- [ testProperty " to/from bitmap" prop_toFromBitmap
878
- , testProperty " maxKeySize" prop_maxKeySize
879
-
880
- , let dpgsz = DiskPage4k in
881
- testGroup " size distribution"
882
- [ testProperty " genPageContentFits" $
883
- checkCoverage $
884
- coverTable " page size in bytes"
885
- [(" 0 <= n < 512" ,10 )
886
- ,(" 3k < n <= 4k" , 5 )] $
887
- coverTable " page size in disk pages"
888
- [(" 1 page" , 50 )
889
- ,(" 2 pages" , 0.5 )
890
- ,(" 3+ pages" , 0.5 )] $
891
- forAll (genPageContentFits dpgsz noMinKeySize) $
892
- prop_size_distribution dpgsz (property False )
893
-
894
- , testProperty " genPageContentMaybeOverfull" $
895
- checkCoverage $
896
- forAll (genPageContentMaybeOverfull dpgsz noMinKeySize) $
897
- prop_size_distribution dpgsz
898
- (cover 10 True " over-full" (property True ))
899
-
900
- , testProperty " genPageContentSingle" $
901
- checkCoverage $
902
- coverTable " page size in disk pages"
903
- [(" 1 page" , 10 )
904
- ,(" 2 pages" , 2 )
905
- ,(" 3+ pages" , 2 )] $
906
- forAll ((: [] ) <$> genPageContentSingle dpgsz noMinKeySize) $
907
- prop_size_distribution dpgsz (property False )
908
- ]
909
- , testProperty " size 0" prop_size0
910
- , testProperty " size 1" prop_size1
911
- , testProperty " size 2" prop_size2
912
- , testProperty " size 3" prop_size3
913
- , testProperty " encode/decode" prop_encodeDecode
914
- , testProperty " serialise/deserialise" prop_serialiseDeserialise
915
- , testProperty " encode/serialise/deserialise/decode"
916
- prop_encodeSerialiseDeserialiseDecode
917
- , testProperty " overflow pages" prop_overflowPages
918
- ]
919
-
920
- prop_toFromBitmap :: [Bool ] -> Bool
921
- prop_toFromBitmap bits =
922
- bits == take (length bits) (roundTrip bits)
923
- where
924
- roundTrip = fromBitmap . toBitmap
925
-
926
- prop_size_distribution :: DiskPageSize
927
- -> Property -- ^ over-full sub-property
928
- -> [(Key , Operation )]
929
- -> Property
930
- prop_size_distribution dpgsz propOverfull p =
931
- case calcPageSize dpgsz p of
932
- Nothing -> propOverfull
933
- Just PageSize {pageSizeElems, pageSizeBlobs, pageSizeBytes} ->
934
- tabulate " page size in elements"
935
- [ showNumElems pageSizeElems ] $
936
- tabulate " page number of blobs"
937
- [ showNumElems pageSizeBlobs ] $
938
- tabulate " page size in bytes"
939
- [ showPageSizeBytes pageSizeBytes ] $
940
- tabulate " page size in disk pages"
941
- [ showPageSizeDiskPages pageSizeBytes ] $
942
- tabulate " key size in bytes"
943
- [ showKeyValueSizeBytes (BS. length k) | (Key k, _) <- p ] $
944
- tabulate " value size in bytes"
945
- [ showKeyValueSizeBytes (BS. length v)
946
- | (_, op) <- p
947
- , Value v <- case op of
948
- Insert v _ -> [v]
949
- Mupsert v -> [v]
950
- Delete -> []
951
- ] $
952
- property $ (if pageSizeElems > 1
953
- then pageSizeBytes <= dpgszBytes
954
- else True )
955
- && (pageSizeElems == length p)
956
- && (pageSizeBlobs == length (filter (opHasBlobRef . snd ) p))
957
- where
958
- dpgszBytes = diskPageSizeBytes dpgsz
959
-
960
- showNumElems :: Int -> String
961
- showNumElems n
962
- | n <= 1 = show n
963
- | n < 10 = " 1 < n < 10"
964
- | otherwise = nearest 10 n
965
-
966
- showPageSizeBytes :: Int -> String
967
- showPageSizeBytes n
968
- | n > 4096 = nearest4k n
969
- | n > 1024 = nearest1k n
970
- | otherwise = nearest 512 n
971
-
972
- showPageSizeDiskPages :: Int -> String
973
- showPageSizeDiskPages n
974
- | npgs == 1 = " 1 page"
975
- | npgs == 2 = " 2 pages"
976
- | otherwise = " 3+ pages"
977
- where
978
- npgs = (n + dpgszBytes - 1 ) `div` dpgszBytes
979
-
980
- showKeyValueSizeBytes :: Int -> String
981
- showKeyValueSizeBytes n
982
- | n < 20 = nearest 5 n
983
- | n < 100 = " 20 <= n < 100"
984
- | n < 1024 = nearest 100 n
985
- | otherwise = nearest1k n
986
-
987
- nearest :: Int -> Int -> String
988
- nearest m n = show ((n `div` m) * m) ++ " <= n < "
989
- ++ show ((n `div` m) * m + m)
990
-
991
- nearest1k , nearest4k :: Int -> String
992
- nearest1k n = show ((n- 1 ) `div` 1024 ) ++ " k < n <= "
993
- ++ show ((n- 1 ) `div` 1024 + 1 ) ++ " k"
994
- nearest4k n = show (((n- 1 ) `div` 4096 ) * 4 ) ++ " k < n <= "
995
- ++ show (((n- 1 ) `div` 4096 ) * 4 + 4 ) ++ " k"
996
-
997
873
-- | The maximum size of key that is guaranteed to always fit in an empty
998
874
-- 4k page. So this is a worst case maximum size: this size key will fit
999
875
-- irrespective of the corresponding operation, including the possibility
@@ -1006,95 +882,3 @@ pageSizeOverhead =
1006
882
(pageSizeBytes . fromJust . calcPageSize DiskPage4k )
1007
883
[(Key BS. empty, Insert (Value BS. empty) (Just (BlobRef 0 0 )))]
1008
884
-- the page size passed to calcPageSize here is irrelevant
1009
-
1010
- prop_maxKeySize :: Bool
1011
- prop_maxKeySize = maxKeySize DiskPage4k == 4052
1012
-
1013
- -- | The 'calcPageSize' and 'calcPageSizeOffsets' (used by 'encodePage') had
1014
- -- better agree with each other!
1015
- --
1016
- -- The 'calcPageSize' uses the incremental 'PageSize' API, to work out the page
1017
- -- size, element by element, while 'calcPageSizeOffsets' is a bulk operation
1018
- -- used by 'encodePage'. It's critical that they agree on how many elements can
1019
- -- fit into a page.
1020
- --
1021
- prop_size0 :: PageContentMaybeOverfull -> Bool
1022
- prop_size0 (PageContentMaybeOverfull dpgsz p) =
1023
- case (calcPageSize dpgsz p, encodePage dpgsz p) of
1024
- (Nothing , Nothing ) -> True
1025
- (Nothing , Just {}) -> False -- they disagree!
1026
- (Just {}, Nothing ) -> False -- they disagree!
1027
- (Just PageSize {.. },
1028
- Just PageIntermediate {pageSizesOffsets = PageSizesOffsets {.. }, .. }) ->
1029
- pageSizeElems == fromIntegral pageNumKeys
1030
- && pageSizeBlobs == fromIntegral pageNumBlobs
1031
- && pageSizeBytes == fromIntegral sizePageUsed
1032
- && pageSizeDisk == pageDiskPageSize
1033
- && pageSizeDisk == dpgsz
1034
-
1035
- prop_size1 :: PageContentFits -> Bool
1036
- prop_size1 (PageContentFits dpgsz p) =
1037
- sizePageUsed > 0
1038
- && sizePageUsed + sizePagePadding == sizePageDiskPage
1039
- && if pageNumKeys p' == 1
1040
- then fromIntegral sizePageDiskPage `mod` diskPageSizeBytes dpgsz == 0
1041
- else fromIntegral sizePageDiskPage == diskPageSizeBytes dpgsz
1042
- where
1043
- Just p' = encodePage dpgsz p
1044
- PageSizesOffsets {.. } = pageSizesOffsets p'
1045
-
1046
- prop_size2 :: PageContentFits -> Bool
1047
- prop_size2 (PageContentFits dpgsz p) =
1048
- BS. length (serialisePage p')
1049
- == fromIntegral (sizePageDiskPage (pageSizesOffsets p'))
1050
- where
1051
- Just p' = encodePage dpgsz p
1052
-
1053
- prop_size3 :: PageContentFits -> Bool
1054
- prop_size3 (PageContentFits dpgsz p) =
1055
- case (calcPageSize dpgsz p, encodePage dpgsz p) of
1056
- (Just PageSize {pageSizeBytes}, Just p') ->
1057
- pageSizeBytes == (fromIntegral . sizePageUsed . pageSizesOffsets) p'
1058
- _ -> False
1059
-
1060
- prop_encodeDecode :: PageContentFits -> Property
1061
- prop_encodeDecode (PageContentFits dpgsz p) =
1062
- p === decodePage p'
1063
- where
1064
- Just p' = encodePage dpgsz p
1065
-
1066
- prop_serialiseDeserialise :: PageContentFits -> Bool
1067
- prop_serialiseDeserialise (PageContentFits dpgsz p) =
1068
- p' == roundTrip p'
1069
- where
1070
- Just p' = encodePage dpgsz p
1071
- roundTrip = deserialisePage dpgsz . serialisePage
1072
-
1073
- prop_encodeSerialiseDeserialiseDecode :: PageContentFits -> Bool
1074
- prop_encodeSerialiseDeserialiseDecode (PageContentFits dpgsz p) =
1075
- p == roundTrip p'
1076
- where
1077
- Just p' = encodePage dpgsz p
1078
- roundTrip = decodePage . deserialisePage dpgsz . serialisePage
1079
-
1080
- prop_overflowPages :: PageContentSingle -> Property
1081
- prop_overflowPages (PageContentSingle dpgsz k op) =
1082
- label (" pages " ++ show (length ps)) $
1083
- all ((== diskPageSizeBytes dpgsz) . BS. length ) ps
1084
- .&&. pageDiskPages p === length ps
1085
- .&&. case pageOverflowPrefixSuffixLen p of
1086
- Nothing -> length ps === 1
1087
- Just (prefixlen, suffixlen) ->
1088
- prefixlen + suffixlen === BS. length (unValue v)
1089
- .&&. Value (BS. drop (dpgszBytes - prefixlen) (head ps)
1090
- <> BS. take suffixlen (BS. concat (drop 1 ps)))
1091
- === v
1092
- where
1093
- Just p = encodePage dpgsz [(k, op)]
1094
- ps = pageSerialisedChunks dpgsz (serialisePage p)
1095
- v = case op of
1096
- Insert v' _ -> v'
1097
- Mupsert v' -> v'
1098
- Delete -> error " unexpected"
1099
- dpgszBytes = diskPageSizeBytes dpgsz
1100
-
0 commit comments