44
55module Test.Database.LSMTree (tests ) where
66
7+ import Control.Exception
78import Control.Tracer
89import Data.Function (on )
910import Data.IORef
1011import Data.Monoid
12+ import Data.Typeable (Typeable )
1113import qualified Data.Vector as V
1214import qualified Data.Vector.Algorithms as VA
1315import Data.Void
@@ -16,6 +18,7 @@ import Database.LSMTree
1618import Database.LSMTree.Extras (showRangesOf )
1719import Database.LSMTree.Extras.Generators ()
1820import qualified System.FS.API as FS
21+ import qualified System.FS.BlockIO.API as FS
1922import Test.QuickCheck
2023import Test.Tasty
2124import Test.Tasty.QuickCheck
@@ -27,6 +30,14 @@ tests = testGroup "Test.Database.LSMTree" [
2730 -- openSession
2831 testProperty " prop_openSession_newSession" prop_openSession_newSession
2932 , testProperty " prop_openSession_restoreSession" prop_openSession_restoreSession
33+ -- happy path
34+ , testProperty " prop_newSession_restoreSession_happyPath" prop_newSession_restoreSession_happyPath
35+ -- missing session directory
36+ , testProperty " prop_sessionDirDoesNotExist" prop_sessionDirDoesNotExist
37+ -- session directory already locked
38+ , testProperty " prop_sessionDirLocked" prop_sessionDirLocked
39+ -- malformed session directory
40+ , testProperty " prop_sessionDirCorrupted" prop_sessionDirCorrupted
3041 -- salt
3142 , testProperty " prop_goodAndBadSessionSalt" prop_goodAndBadSessionSalt
3243 ]
@@ -56,6 +67,30 @@ instance Arbitrary NewOrRestore where
5667 arbitrary = arbitraryBoundedEnum
5768 shrink = shrinkBoundedEnum
5869
70+ -- | If 'New', use 'newSession', otherwise if 'Restore', use 'restoreSession'.
71+ --
72+ -- This allows us to run properties on both 'newSession' and 'restoreSession',
73+ -- without having to write almost identical code twice.
74+ --
75+ -- In a sense, this is somewhat similar to 'openSession', but whereas
76+ -- 'openSession' would defer to 'newSession' or 'restoreSession' based on the
77+ -- directory contents, here the user gets to pick whether to use 'newSession' or
78+ -- 'restoreSession'.
79+ withNewSessionOrRestoreSession ::
80+ (IOLike m , Typeable h )
81+ => NewOrRestore
82+ -> Tracer m LSMTreeTrace
83+ -> FS. HasFS m h
84+ -> FS. HasBlockIO m h
85+ -> Salt
86+ -> FS. FsPath
87+ -> (Session m -> m a )
88+ -> m a
89+ withNewSessionOrRestoreSession newOrRestore tr hfs hbio salt path =
90+ case newOrRestore of
91+ New -> withNewSession tr hfs hbio salt path
92+ Restore -> withRestoreSession tr hfs hbio path
93+
5994{- ------------------------------------------------------------------------------
6095 Session: openSession
6196-------------------------------------------------------------------------------}
@@ -105,6 +140,99 @@ mkSessionOpenModeTracer var = Tracer $ emit $ \case
105140 TraceRestoreSession {} -> modifyIORef var (" Restore" : )
106141 _ -> pure ()
107142
143+ {- ------------------------------------------------------------------------------
144+ Session: happy path
145+ -------------------------------------------------------------------------------}
146+
147+ prop_newSession_restoreSession_happyPath ::
148+ Positive (Small Int )
149+ -> V. Vector (Key , Value )
150+ -> Property
151+ prop_newSession_restoreSession_happyPath (Positive (Small bufferSize)) ins =
152+ ioProperty $
153+ withTempIOHasBlockIO " prop_newSession_restoreSession_happyPath" $ \ hfs hbio -> do
154+ withNewSession nullTracer hfs hbio testSalt (FS. mkFsPath [] ) $ \ session1 ->
155+ withTableWith conf session1 $ \ (table :: Table IO Key Value Blob ) -> do
156+ inserts table $ V. map (\ (k, v) -> (k, v, Nothing )) ins
157+ saveSnapshot " snap" " KeyValueBlob" table
158+ withRestoreSession nullTracer hfs hbio (FS. mkFsPath [] ) $ \ session2 ->
159+ withTableFromSnapshot session2 " snap" " KeyValueBlob"
160+ $ \ (_ :: Table IO Key Value Blob ) -> pure ()
161+ where
162+ testSalt = 6
163+ conf = defaultTableConfig {
164+ confWriteBufferAlloc = AllocNumEntries bufferSize
165+ }
166+
167+ {- ------------------------------------------------------------------------------
168+ Session: missing session directory
169+ -------------------------------------------------------------------------------}
170+
171+ prop_sessionDirDoesNotExist :: NewOrRestore -> Property
172+ prop_sessionDirDoesNotExist newOrRestore =
173+ ioProperty $
174+ withTempIOHasBlockIO " prop_sessionDirDoesNotExist" $ \ hfs hbio -> do
175+ result <- try @ SessionDirDoesNotExistError $
176+ withNewSessionOrRestoreSession
177+ newOrRestore
178+ nullTracer hfs hbio testSalt (FS. mkFsPath [" missing-dir" ])
179+ $ \ _session -> pure ()
180+ pure
181+ $ counterexample
182+ (" Expecting an ErrSessionDirDoesNotExist error, but got: " ++ show result)
183+ $ case result of
184+ Left ErrSessionDirDoesNotExist {} -> True
185+ _ -> False
186+ where
187+ testSalt = 6
188+
189+ {- ------------------------------------------------------------------------------
190+ Session: session directory already locked
191+ -------------------------------------------------------------------------------}
192+
193+ prop_sessionDirLocked :: NewOrRestore -> Property
194+ prop_sessionDirLocked newOrRestore =
195+ ioProperty $
196+ withTempIOHasBlockIO " prop_sessionDirLocked" $ \ hfs hbio -> do
197+ result <-
198+ withNewSession nullTracer hfs hbio testSalt (FS. mkFsPath [] ) $ \ _session1 -> do
199+ try @ SessionDirLockedError $
200+ withNewSessionOrRestoreSession
201+ newOrRestore
202+ nullTracer hfs hbio testSalt (FS. mkFsPath [] )
203+ $ \ _session2 -> pure ()
204+ pure
205+ $ counterexample
206+ (" Expecting an ErrSessionDirLocked error, but got: " ++ show result)
207+ $ case result of
208+ Left ErrSessionDirLocked {} -> True
209+ _ -> False
210+ where
211+ testSalt = 6
212+
213+ {- ------------------------------------------------------------------------------
214+ Session: malformed session directory
215+ -------------------------------------------------------------------------------}
216+
217+ prop_sessionDirCorrupted :: NewOrRestore -> Property
218+ prop_sessionDirCorrupted newOrRestore =
219+ ioProperty $
220+ withTempIOHasBlockIO " sessionDirCorrupted" $ \ hfs hbio -> do
221+ FS. createDirectory hfs (FS. mkFsPath [" unexpected-directory" ])
222+ result <- try @ SessionDirCorruptedError $
223+ withNewSessionOrRestoreSession
224+ newOrRestore
225+ nullTracer hfs hbio testSalt (FS. mkFsPath [] )
226+ $ \ _session -> pure ()
227+ pure
228+ $ counterexample
229+ (" Expecting an ErrSessionDirCorrupted error, but got: " ++ show result)
230+ $ case result of
231+ Left ErrSessionDirCorrupted {} -> True
232+ _ -> False
233+ where
234+ testSalt = 6
235+
108236{- ------------------------------------------------------------------------------
109237 Session: salt
110238-------------------------------------------------------------------------------}
0 commit comments