Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate DBLayer tests with quickcheck-state-machine #259

Merged
merged 14 commits into from
May 31, 2019
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion .weeder.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,12 @@
- message:
- name: Module not compiled
- module: Cardano.Launcher.Windows

- package:
- name: cardano-wallet-core
- section:
- name: test:unit
- message:
- name: Weeds exported
- module:
- name: Cardano.Wallet.DB.StateMachine
- identifier: showLabelledExamples
5 changes: 5 additions & 0 deletions lib/core/cardano-wallet-core.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ test-suite unit
, deepseq
, file-embed
, fmt
, foldl
, generic-arbitrary
, hspec
, hspec-golden-aeson
Expand All @@ -128,13 +129,16 @@ test-suite unit
, memory
, QuickCheck
, quickcheck-instances
, quickcheck-state-machine >= 0.6.0
, random
, servant-server
, servant-swagger
, swagger2
, text
, text-class
, time
, transformers
, tree-diff
, yaml
type:
exitcode-stdio-1.0
Expand All @@ -146,6 +150,7 @@ test-suite unit
Cardano.Wallet.Api.TypesSpec
Cardano.Wallet.ApiSpec
Cardano.Wallet.DB.MVarSpec
Cardano.Wallet.DB.StateMachine
Cardano.Wallet.DB.SqliteSpec
Cardano.Wallet.DBSpec
Cardano.Wallet.EnvironmentSpec
Expand Down
7 changes: 6 additions & 1 deletion lib/core/src/Cardano/Wallet/DB.hs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ module Cardano.Wallet.DB
( -- * Interface
DBLayer(..)
, PrimaryKey(..)
, cleanDB

-- * Errors
, ErrNoSuchWallet(..)
Expand All @@ -27,7 +28,7 @@ import Cardano.Wallet.Primitive.Model
import Cardano.Wallet.Primitive.Types
( Hash, Tx, TxMeta, WalletId, WalletMetadata )
import Control.Monad.Trans.Except
( ExceptT )
( ExceptT, runExceptT )
import Data.Map.Strict
( Map )

Expand Down Expand Up @@ -146,3 +147,7 @@ newtype ErrWalletAlreadyExists
-- (like for instance, the last known network tip).
newtype PrimaryKey key = PrimaryKey key
deriving (Eq, Ord)

-- | Clean a database by removing all wallets.
cleanDB :: Monad m => DBLayer m s t -> m ()
cleanDB db = listWallets db >>= mapM_ (runExceptT . removeWallet db)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure we do want to keep this in the source code. This is purely for testing.

99 changes: 53 additions & 46 deletions lib/core/src/Cardano/Wallet/DB/Sqlite.hs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedLabels #-}
Expand All @@ -14,6 +15,7 @@

module Cardano.Wallet.DB.Sqlite
( newDBLayer
, DummyState(..)
) where

import Prelude
Expand Down Expand Up @@ -71,8 +73,6 @@ import Control.Monad.Trans.Maybe
( MaybeT (..) )
import Control.Monad.Trans.Resource
( runResourceT )
import Data.Bifunctor
( bimap )
import Data.Coerce
( coerce )
import Data.Either
Expand All @@ -97,7 +97,7 @@ import Database.Persist.Sql
, insert_
, putMany
, rawExecute
, runMigration
, runMigrationSilent
, runSqlConn
, selectFirst
, selectKeysList
Expand All @@ -112,6 +112,8 @@ import Database.Persist.Sqlite
( SqlBackend, SqlPersistM, SqlPersistT, wrapConnection )
import Database.Sqlite
( Error (ErrorConstraint), SqliteException (SqliteException) )
import GHC.Generics
( Generic )
import System.IO
( stderr )
import System.Log.FastLogger
Expand Down Expand Up @@ -184,7 +186,7 @@ newDBLayer fp = do
conn <- createSqliteBackend fp (dbLogs [LevelError])
let runQuery' = withMVar bigLock . const . runQuery conn

runQuery' $ void $ runMigration migrateAll
runQuery' $ void $ runMigrationSilent migrateAll
runQuery' addIndexes

return $ DBLayer
Expand Down Expand Up @@ -213,7 +215,8 @@ newDBLayer fp = do
Nothing -> pure $ Left $ ErrNoSuchWallet wid

, listWallets = runQuery' $
map (PrimaryKey . unWalletKey) <$> selectKeysList [] []
map (PrimaryKey . unWalletKey) <$>
selectKeysList [] [Asc WalTableId]
Copy link
Contributor

@jonathanknowles jonathanknowles May 31, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just wondering: is there any particular reason for this ordering?


{-----------------------------------------------------------------------
Checkpoints
Expand Down Expand Up @@ -267,9 +270,7 @@ newDBLayer fp = do
Just _ -> do
let (metas, txins, txouts) = mkTxHistory wid txs
putTxMetas wid metas
putMany txins
putMany txouts
deleteLooseTransactions
putTxs (TxId <$> Map.keys txs) txins txouts
pure $ Right ()
Nothing -> pure $ Left $ ErrNoSuchWallet wid

Expand Down Expand Up @@ -381,11 +382,10 @@ mkCheckpointEntity
-> W.Wallet s t
-> (Checkpoint, [UTxO], [PendingTx], [TxIn], [TxOut])
mkCheckpointEntity wid wal =
( cp, utxo, map (pendingTx . fst) pending
, concatMap (dist pendingTxIn . fmap W.inputs) pending
, concatMap (dist pendingTxOut . fmap (zip [0..] . W.outputs)) pending )
(cp, utxo, map (pendingTx . TxId . fst) pending, ins, outs)
where
pending = [(TxId (W.txId @t tx), tx) | tx <- Set.toList (W.getPending wal)]
pending = [(W.txId @t tx, tx) | tx <- Set.toList (W.getPending wal)]
(ins, outs) = mkTxInputsOutputs pending
sl = W.currentTip wal
cp = Checkpoint
{ checkpointTableWalletId = wid
Expand All @@ -396,22 +396,12 @@ mkCheckpointEntity wid wal =
, pendingTxTableCheckpointSlot = sl
, pendingTxTableId2 = tid
}
pendingTxIn tid txIn = TxIn
{ txInputTableTxId = tid
, txInputTableSourceTxId = TxId (W.inputId txIn)
, txInputTableSourceIndex = W.inputIx txIn
}
pendingTxOut tid (ix, txOut) = TxOut
{ txOutputTableTxId = tid
, txOutputTableIndex = ix
, txOutputTableAddress = W.address txOut
, txOutputTableAmount = W.coin txOut
}
utxo = [ UTxO wid sl (TxId input) ix addr coin
| (W.TxIn input ix, W.TxOut addr coin) <- utxoMap ]
utxoMap = Map.assocs (W.getUTxO (W.totalUTxO wal))

-- inputs and outputs must be sorted by txid, then ix
-- note: TxIn records must already be sorted by order
-- and TxOut records must already by sorted by index.
checkpointFromEntity
:: forall s t. (W.IsOurs s, NFData s, Show s, W.TxId t)
=> Checkpoint
Expand All @@ -426,7 +416,7 @@ checkpointFromEntity (Checkpoint _ tip) utxo ins outs =
utxo' = W.UTxO . Map.fromList $
[ (W.TxIn input ix, W.TxOut addr coin)
| UTxO _ _ (TxId input) ix addr coin <- utxo ]
ins' = [(txid, W.TxIn src ix) | TxIn txid (TxId src) ix <- ins]
ins' = [(txid, W.TxIn src ix) | TxIn txid _ (TxId src) ix <- ins]
outs' = [ (txid, W.TxOut addr amt)
| TxOut txid _ix addr amt <- outs ]
txids = Set.fromList $ map fst ins' ++ map fst outs'
Expand All @@ -440,25 +430,36 @@ mkTxHistory
:: W.WalletId
-> Map.Map (W.Hash "Tx") (W.Tx, W.TxMeta)
-> ([TxMeta], [TxIn], [TxOut])
mkTxHistory wid txs =
( map (uncurry (mkTxMetaEntity wid)) metas
, concatMap (dist mkTxIn . fmap W.inputs) hist
, concatMap (dist mkTxOut . fmap (zip [0..] . W.outputs)) hist )
mkTxHistory wid txs = (map (uncurry (mkTxMetaEntity wid)) metas, ins, outs)
where
pairs = Map.toList txs
metas = fmap snd <$> pairs
hist = bimap TxId fst <$> pairs
mkTxIn tid txIn = TxIn
{ txInputTableTxId = tid
hist = fmap fst <$> pairs
(ins, outs) = mkTxInputsOutputs hist

mkTxInputsOutputs :: [(W.Hash "Tx", W.Tx)] -> ([TxIn], [TxOut])
mkTxInputsOutputs txs =
( concatMap (dist mkTxIn . ordered W.inputs) txs
, concatMap (dist mkTxOut . ordered W.outputs) txs )
where
mkTxIn tid (ix, txIn) = TxIn
{ txInputTableTxId = TxId tid
, txInputTableOrder = ix
, txInputTableSourceTxId = TxId (W.inputId txIn)
, txInputTableSourceIndex = W.inputIx txIn
}
mkTxOut tid (ix, txOut) = TxOut
{ txOutputTableTxId = tid
{ txOutputTableTxId = TxId tid
, txOutputTableIndex = ix
, txOutputTableAddress = W.address txOut
, txOutputTableAmount = W.coin txOut
}
ordered f = fmap (zip [0..] . f)
-- | Distribute `a` accross many `b`s using the given function.
-- >>> dist TxOut (addr, [Coin 1, Coin 42, Coin 14])
-- [TxOut addr (Coin 1), TxOut addr (Coin 42), TxOut addr (Coin 14)]
dist :: (a -> b -> c) -> (a, [b]) -> [c]
dist f (a, bs) = [f a b | b <- bs]

mkTxMetaEntity :: W.WalletId -> W.Hash "Tx" -> W.TxMeta -> TxMeta
mkTxMetaEntity wid txid meta = TxMeta
Expand All @@ -471,7 +472,8 @@ mkTxMetaEntity wid txid meta = TxMeta
}
where getAmount (Quantity n) = n

-- note: TxOut records must already be sorted by index
-- note: TxIn records must already be sorted by order
-- and TxOut records must already be sorted by index
txHistoryFromEntity
:: [TxMeta]
-> [TxIn]
Expand Down Expand Up @@ -548,6 +550,14 @@ putTxMetas wid metas = do
, TxMetaTableTxId <-. map txMetaTableTxId metas ]
insertMany_ metas

-- | Insert multiple transactions, removing old instances first.
putTxs :: [TxId] -> [TxIn] -> [TxOut] -> SqlPersistM ()
putTxs txIds txins txouts = do
deleteWhere [TxInputTableTxId <-. txIds]
putMany txins
deleteWhere [TxOutputTableTxId <-. txIds]
putMany txouts

-- | Delete transactions that aren't referred to by either Pending or TxMeta of
-- any wallet.
deleteLooseTransactions :: SqlPersistM ()
Expand Down Expand Up @@ -584,7 +594,7 @@ selectTxs
-> SqlPersistM ([TxIn], [TxOut])
selectTxs txids = do
ins <- fmap entityVal <$> selectList [TxInputTableTxId <-. txids]
[Asc TxInputTableTxId, Asc TxInputTableSourceIndex]
[Asc TxInputTableTxId, Asc TxInputTableOrder]
outs <- fmap entityVal <$> selectList [TxOutputTableTxId <-. txids]
[Asc TxOutputTableTxId, Asc TxOutputTableIndex]
pure (ins, outs)
Expand All @@ -595,10 +605,7 @@ selectTxHistory
selectTxHistory wid = do
metas <- fmap entityVal <$> selectList [TxMetaTableWalletId ==. wid] []
let txids = map txMetaTableTxId metas
ins <- fmap entityVal <$> selectList [TxInputTableTxId <-. txids]
[Asc TxInputTableTxId, Asc TxInputTableSourceIndex]
outs <- fmap entityVal <$> selectList [TxOutputTableTxId <-. txids]
[Asc TxOutputTableTxId, Asc TxOutputTableIndex]
(ins, outs) <- selectTxs txids
pure $ txHistoryFromEntity metas ins outs

---------------------------------------------------------------------------
Expand Down Expand Up @@ -692,11 +699,11 @@ selectSeqStatePendingIxs ssid =
where
fromRes = fmap (W.Index . seqStatePendingIxIndex . entityVal)

----------------------------------------------------------------------------
-- Utilities
data DummyState = DummyState
deriving (Show, Eq, Generic)

-- | Distribute `a` accross many `b`s using the given function.
-- >>> dist TxOut (addr, [Coin 1, Coin 42, Coin 14])
-- [TxOut addr (Coin 1), TxOut addr (Coin 42), TxOut addr (Coin 14)]
dist :: (a -> b -> c) -> (a, [b]) -> [c]
dist f (a, bs) = [f a b | b <- bs]
instance PersistState DummyState where
insertState (wid, sl) _ = insert_ (SeqState wid sl)
selectState (wid, sl) = fmap (const DummyState) <$>
selectFirst [SeqStateTableWalletId ==. wid, SeqStateTableCheckpointSlot ==. sl] []
deleteState wid = deleteWhere [SeqStateTableWalletId ==. wid]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't be in the source code IMO :/

1 change: 1 addition & 0 deletions lib/core/src/Cardano/Wallet/DB/Sqlite/TH.hs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ TxMeta
-- not the wallet. txInputTableTxId is referred to by TxMeta and PendingTx
TxIn
txInputTableTxId TxId sql=tx_id
txInputTableOrder Int sql=order
txInputTableSourceTxId TxId sql=source_id
txInputTableSourceIndex Word32 sql=source_index

Expand Down
4 changes: 2 additions & 2 deletions lib/core/test/unit/Cardano/Wallet/DB/MVarSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import Cardano.Wallet.DB
import Cardano.Wallet.DB.MVar
( newDBLayer )
import Cardano.Wallet.DBSpec
( DummyTarget, dbPropertyTests, withDB )
( DummyTarget, SkipTests (..), dbPropertyTests, withDB )
import Cardano.Wallet.Primitive.AddressDiscovery
( IsOurs (..), SeqState (..) )
import Cardano.Wallet.Primitive.Model
Expand All @@ -31,7 +31,7 @@ import Test.QuickCheck

spec :: Spec
spec = withDB (newDBLayer :: IO (DBLayer IO (SeqState DummyTarget) DummyTarget)) $
describe "MVar" dbPropertyTests
describe "MVar" (dbPropertyTests RunAllTests)

newtype DummyStateMVar = DummyStateMVar Int
deriving (Show, Eq)
Expand Down
21 changes: 13 additions & 8 deletions lib/core/test/unit/Cardano/Wallet/DB/SqliteSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ import Cardano.Wallet.DB
( DBLayer (..), ErrWalletAlreadyExists (..), PrimaryKey (..) )
import Cardano.Wallet.DB.Sqlite
( newDBLayer )
import Cardano.Wallet.DB.StateMachine
( prop_parallel, prop_sequential )
import Cardano.Wallet.DBSpec
( DummyTarget, dbPropertyTests, withDB )
( DummyTarget, SkipTests (..), dbPropertyTests, withDB )
import Cardano.Wallet.Primitive.AddressDerivation
( Passphrase (..)
, encryptPassphrase
Expand Down Expand Up @@ -66,17 +68,20 @@ import Data.Time.Clock
import System.IO.Unsafe
( unsafePerformIO )
import Test.Hspec
( Spec, describe, it, shouldReturn )
( Spec, SpecWith, describe, it, shouldReturn, xit )

import qualified Data.Map as Map

spec :: Spec
spec = do
describe "Simple tests" simpleSpec
describe "Sqlite Property tests" $ withDB newMemoryDBLayer dbPropertyTests

simpleSpec :: Spec
simpleSpec = withDB newMemoryDBLayer $ do
spec = withDB newMemoryDBLayer $ do
describe "Sqlite Simple tests" simpleSpec
describe "Sqlite" (dbPropertyTests SkipTxHistoryReplaceTest)
describe "Sqlite State machine tests" $ do
it "Sequential" prop_sequential
xit "Parallel" prop_parallel

simpleSpec :: SpecWith (DBLayer IO (SeqState DummyTarget) DummyTarget)
simpleSpec = do
describe "Wallet table" $ do
it "create and list works" $ \db -> do
unsafeRunExceptT $ createWallet db testPk testCp testMetadata
Expand Down
Loading