urbit/pkg/hs/urbit-king/test/LogTests.hs

200 lines
6.4 KiB
Haskell

module LogTests (tests) where
import Data.Acquire
import Data.Conduit
import Data.Conduit.List hiding (filter)
import Test.QuickCheck hiding ((.&.))
import Test.Tasty
import Test.Tasty.QuickCheck
import Test.Tasty.TH
import Urbit.EventLog.LMDB
import Urbit.Prelude
import Urbit.Vere.Pier.Types
import Control.Concurrent (runInBoundThread, threadDelay)
import Data.LargeWord (LargeKey(..))
import GHC.Natural (Natural)
import Urbit.King.App (KingEnv, runKingEnvNoLog)
import qualified Options
import qualified Urbit.EventLog.LMDB as Log
-- Utils -----------------------------------------------------------------------
withTestDir :: (FilePath -> RIO e a) -> RIO e a
withTestDir = withTempDirectory "./" ".testlog."
data NotEqual = NotEqual String String
deriving (Eq, Ord, Show)
instance Exception NotEqual where
assertEqual :: MonadIO m => (Show a, Eq a) => a -> a -> m ()
assertEqual x y = do
unless (x == y) $ io $ throwIO $ NotEqual (show x) (show y)
-- Database Operations ---------------------------------------------------------
data Db = Db LogIdentity [ByteString] (Map Word64 ByteString)
deriving (Eq, Ord, Show)
addEvents :: Db -> [ByteString] -> Db
addEvents (Db id evs efs) new = Db id (evs <> new) efs
readDb :: EventLog -> RIO KingEnv Db
readDb log = do
events <- runConduit (streamEvents log 1 .| consume)
effects <- runConduit (streamEffectsRows log 0 .| consume)
pure $ Db (Log.identity log) events (mapFromList effects)
withDb :: FilePath -> Db -> (EventLog -> RIO KingEnv a) -> RIO KingEnv a
withDb dir (Db dId dEvs dFx) act = do
rwith (Log.new dir dId) $ \log -> do
Log.appendEvents log (fromList dEvs)
for_ (mapToList dFx) $ \(k,v) ->
Log.writeEffectsRow log k v
act log
--------------------------------------------------------------------------------
runApp :: RIO KingEnv a -> IO a
runApp = runKingEnvNoLog
tryReadIdentity :: Property
tryReadIdentity = forAll arbitrary (ioProperty . runApp . runTest)
where
runTest :: LogIdentity -> RIO KingEnv Bool
runTest ident = do
env <- ask
io $ runInBoundThread $ runRIO env $
withTestDir $ \dir -> do
rwith (Log.new dir ident) $ \log ->
assertEqual ident (Log.identity log)
rwith (Log.existing dir) $ \log ->
assertEqual ident (Log.identity log)
rwith (Log.existing dir) $ \log ->
assertEqual ident (Log.identity log)
pure True
tryReadDatabase :: Property
tryReadDatabase = forAll arbitrary (ioProperty . runApp . runTest)
where
runTest :: Db -> RIO KingEnv Bool
runTest db = do
env <- ask
io $ runInBoundThread $ runRIO env $
withTestDir $ \dir -> do
withDb dir db $ \log -> do
readDb log >>= assertEqual db
pure True
tryReadDatabaseFuzz :: Property
tryReadDatabaseFuzz = forAll arbitrary (ioProperty . runApp . runTest)
where
runTest :: Db -> RIO KingEnv Bool
runTest db = do
env <- ask
io $ runInBoundThread $ runRIO env $
withTestDir $ \dir -> do
withDb dir db $ \log -> do
readDb log >>= assertEqual db
rwith (Log.existing dir) $ \log -> do
readDb log >>= assertEqual db
rwith (Log.existing dir) $ \log -> do
readDb log >>= assertEqual db
readDb log >>= assertEqual db
pure True
tryAppend :: Property
tryAppend = forAll arbitrary (ioProperty . runApp . runTest)
where
runTest :: ([ByteString], Db) -> RIO KingEnv Bool
runTest (extra, db) = do
env <- ask
io $ runInBoundThread $ runRIO env $
withTestDir $ \dir -> do
db' <- pure (addEvents db extra)
withDb dir db $ \log -> do
readDb log >>= assertEqual db
Log.appendEvents log (fromList extra)
readDb log >>= assertEqual db'
rwith (Log.existing dir) $ \log -> do
readDb log >>= assertEqual db'
pure True
tryAppendHuge :: Options.Brass -> Property
tryAppendHuge brass =
forAll arbitrary (ioProperty . runApp . runTest)
where
runTest :: ([ByteString], Db) -> RIO KingEnv Bool
runTest (extra, db) = do
env <- ask
io $ runInBoundThread $ runRIO env $ do
extra <- do
b <- readFile =<< Options.getPillPath brass
pure (extra <> [b] <> extra)
withTestDir $ \dir -> do
db' <- pure (addEvents db extra)
withDb dir db $ \log -> do
readDb log >>= assertEqual db
Log.appendEvents log (fromList extra)
readDb log >>= assertEqual db'
rwith (Log.existing dir) $ \log -> do
readDb log >>= assertEqual db'
pure True
tests :: TestTree
tests =
testGroup "Log"
[ localOption (QuickCheckTests 10) $
testProperty "Read/Write Log Identity" $
tryReadIdentity
, localOption (QuickCheckTests 15) $
testProperty "Read/Write Database" $
tryReadDatabase
, localOption (QuickCheckTests 5) $
testProperty "Read/Write Database Multiple Times" $
tryReadDatabaseFuzz
, localOption (QuickCheckTests 10) $
testProperty "Append Random Events" $
tryAppend
, localOption (QuickCheckTests 1) $
askOption $ \path ->
testProperty "Append Huge Events" $
tryAppendHuge path
]
-- Generate Arbitrary Values ---------------------------------------------------
arb :: Arbitrary a => Gen a
arb = arbitrary
instance Arbitrary ByteString where
arbitrary = pack <$> arbitrary
instance (Arbitrary a, Arbitrary b) => Arbitrary (LargeKey a b) where
arbitrary = LargeKey <$> arb <*> arb
instance Arbitrary Ship where
arbitrary = Ship <$> arb
arbEffects :: [ByteString] -> Gen (Map Word64 ByteString)
arbEffects evs = do
hax <- for (zip [1..] evs) $ \(i, bs) -> do keep :: Bool <- arbitrary
pure (keep, (i, bs))
pure $ mapFromList $ snd <$> filter fst hax
instance Arbitrary Db where
arbitrary = do
ident <- arbitrary
evs <- arbitrary
efs <- arbEffects evs
pure (Db ident evs efs)
instance Arbitrary LogIdentity where
arbitrary = LogIdentity <$> arb <*> arb <*> arb