Skip to content

Commit 3d24f3d

Browse files
committed
Move FormatPage tests into a dedicated test module
1 parent 0942319 commit 3d24f3d

File tree

5 files changed

+248
-234
lines changed

5 files changed

+248
-234
lines changed

lsm-tree.cabal

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -869,14 +869,12 @@ library prototypes
869869
ScheduledMerges
870870

871871
build-depends:
872-
, base <5
872+
, base <5
873873
, binary
874874
, bytestring
875875
, containers
876876
, contra-tracer
877877
, QuickCheck
878-
, tasty
879-
, tasty-quickcheck
880878
, transformers
881879

882880
test-suite prototypes-test
@@ -885,11 +883,13 @@ test-suite prototypes-test
885883
hs-source-dirs: test-prototypes
886884
main-is: Main.hs
887885
other-modules:
886+
Test.FormatPage
888887
Test.ScheduledMerges
889888
Test.ScheduledMergesQLS
890889

891890
build-depends:
892-
, base
891+
, base <5
892+
, bytestring
893893
, constraints
894894
, containers
895895
, contra-tracer

src-extras/Database/LSMTree/Extras/ReferenceImpl.hs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,8 @@ import qualified Database.LSMTree.Internal.RawBytes as RB
7676
import Database.LSMTree.Internal.RawOverflowPage
7777
import Database.LSMTree.Internal.RawPage
7878
import Database.LSMTree.Internal.Serialise
79-
import FormatPage
79+
import FormatPage hiding (PageContentFits, PageContentMaybeOverfull,
80+
PageContentSingle)
8081

8182
import Test.QuickCheck
8283

src-prototypes/FormatPage.hs

Lines changed: 11 additions & 227 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
{-# LANGUAGE ParallelListComp #-}
22

3-
{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}
4-
53
-- | This accompanies the format-page.md documentation as a sanity check
64
-- and a precise reference. It is intended to demonstrate that the page
75
-- format works. It is also used as a reference implementation for tests of
@@ -17,10 +15,12 @@ module FormatPage (
1715
-- * Page types
1816
Key (..),
1917
Operation (..),
18+
opHasBlobRef,
2019
Value (..),
2120
BlobRef (..),
2221
PageSerialised,
23-
PageIntermediate,
22+
PageIntermediate (..),
23+
PageSizesOffsets (..),
2424

2525
-- * Page size
2626
PageSize (..),
@@ -30,26 +30,31 @@ module FormatPage (
3030

3131
-- * Encoding and decoding
3232
DiskPageSize(..),
33+
diskPageSizeBytes,
3334
encodePage,
3435
decodePage,
3536
serialisePage,
3637
deserialisePage,
38+
fromBitmap,
39+
toBitmap,
3740

3841
-- * Overflow pages
3942
pageOverflowPrefixSuffixLen,
4043
pageDiskPages,
4144
pageSerialisedChunks,
4245

43-
-- * Tests and generators
44-
tests,
45-
-- ** Generators and shrinkers
46+
-- * Generators and shrinkers
47+
PageContentFits (..),
4648
genPageContentFits,
49+
PageContentMaybeOverfull (..),
4750
genPageContentMaybeOverfull,
51+
PageContentSingle (..),
4852
genPageContentSingle,
4953
genPageContentNearFull,
5054
genPageContentMedium,
5155
MinKeySize(..),
5256
noMinKeySize,
57+
maxKeySize,
5358
orderdKeyOps,
5459
shrinkKeyOps,
5560
shrinkOrderedKeyOps,
@@ -72,9 +77,6 @@ import Control.Exception (assert)
7277
import Control.Monad
7378

7479
import Test.QuickCheck hiding ((.&.))
75-
import Test.Tasty
76-
import Test.Tasty.QuickCheck (testProperty)
77-
7880

7981
-------------------------------------------------------------------------------
8082
-- Page content types
@@ -868,132 +870,6 @@ shrinkOpaqueByteString bs =
868870
| BS.null prefix -> []
869871
| otherwise -> [ BS.init prefix <> BSC.cons ' ' spaces ]
870872

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-
997873
-- | The maximum size of key that is guaranteed to always fit in an empty
998874
-- 4k page. So this is a worst case maximum size: this size key will fit
999875
-- irrespective of the corresponding operation, including the possibility
@@ -1006,95 +882,3 @@ pageSizeOverhead =
1006882
(pageSizeBytes . fromJust . calcPageSize DiskPage4k)
1007883
[(Key BS.empty, Insert (Value BS.empty) (Just (BlobRef 0 0)))]
1008884
-- 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-

test-prototypes/Main.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ module Main (main) where
22

33
import Test.Tasty
44

5-
import qualified FormatPage
5+
import qualified Test.FormatPage
66
import qualified Test.ScheduledMerges
77
import qualified Test.ScheduledMergesQLS
88

99
main :: IO ()
1010
main = defaultMain $ testGroup "prototypes" [
11-
FormatPage.tests
11+
Test.FormatPage.tests
1212
, Test.ScheduledMerges.tests
1313
, Test.ScheduledMergesQLS.tests
1414
]

0 commit comments

Comments
 (0)