-
Notifications
You must be signed in to change notification settings - Fork 217
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
Add createWallet and getWallet tests #82
Conversation
fc23a5f
to
39a094d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tests looks good 👍
Some remarks on the overall structure though and writing spec file in general.
test/unit/Cardano/WalletLayerSpec.hs
Outdated
resFromDb `shouldSatisfy` isJust | ||
--the same wallet creation second time | ||
secondTrial <- runExceptT $ createWallet wl newWallet | ||
secondTrial `shouldSatisfy` isLeft |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd advise to make two separate tests for this. One test that focuses on creating wallets with various parameters from NewWallet
, and one test that specifically test that a wallet can't be inserted twice. It's good to keep tests focused to one and one thing such that, when a test fail, you know exactly why it fails.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
now, setupFixture
prepares db, walletLayer and creates wallet giving its walletId
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
seperated this test into two as suggested
test/unit/Cardano/WalletLayerSpec.hs
Outdated
res <- runExceptT $ createWallet wl newWallet | ||
case res of | ||
Left e -> | ||
fail $ show e |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd say that the fixture should take care of creating the wallet here. If the test is about getting a wallet, we should prepare the state to make the test able to perform what it needs. Ideally, tests are always divided in three parts (or four) parts:
- The Setup / Fixture (preparing whatever is needed for the next step)
- The Action (actual tests)
- The Assertion (verifying the outcome of the previous step)
(- optionally, The Teardown / Cleanup (when it makes sense, releasing resources or wiping out some data created by the fixture and the action).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Each test now composes of three separated parts : setup, actions, verification of expectations
@@ -1,5 +1,7 @@ | |||
module Cardano.NetworkLayer.HttpBridgeSpec | |||
( spec | |||
, mockNetworkLayer | |||
, noLog |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This feels wrong. Tests shouldn't depend on each others. I'd rather have a bit of duplication than shared code like this between tests. The rational being that the mockNetworkLayer
was designed for the HttpBridgeSpec
(and it goes the same for Orphan instances). Having each ***Spec
file independent allows for test to be very easy to follow and maintain in the long-run. Making changes to one won't impact the others.
Especially because here, you don't need the "power" granted by the mockNetworkLayer
from the HttpBridge. You could have a dummy network layer that would yield empty list of blocks working just fine for your use-case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
corrected
test/unit/Cardano/WalletLayerSpec.hs
Outdated
import Cardano.Wallet.AddressDiscoverySpec | ||
() | ||
import Cardano.Wallet.MnemonicSpec | ||
() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here. In practice, Arbitrary
instances are local to a test because generators are tailored to the properties they test. I'd advise to re-define the instances here in this spec file, and goes for generic arbitrary as a default if it's enough to cover the need for this tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
now, no other module arbitrary imports
app/server/Main.hs
Outdated
, gap = minBound | ||
} | ||
watchWallet wallet wid | ||
widE <- runExceptT $ createWallet wallet NewWallet |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why have we decided to start a wallet server with a default wallet compared to starting a wallet server without one where wallet is created on demand by calling wallet server endpoint ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cannot answer here -> just refactor this snippet just in order to compile after changing createWallet
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@akegalj there's no default wallet. Instead, there's just a very basic CLI that will "monitor" a wallet associated to some mnemonic that are given. This is merely for testing and to be able to play with our code instead of waiting 4 months to actually integrate with anything.
There's no web server at the moment, and, although we'll probably implement the beginning of this in the upcoming days, at this stage, it's still quite useful to be able to connect all the pieces together and already perform some tests to make sure that we are doing something sensible with the primitives.
The server/Main.hs
is really not in its final stage.
test/unit/Cardano/WalletLayerSpec.hs
Outdated
, _fixtureWalletLayer :: WalletLayer IO SeqState | ||
} | ||
|
||
prepareWalletLayerFixture :: IO WalletLayerFixture |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now that we are designing tests from start - do we really want to do the same test design as we did before? Defining fixtures like this, which promote generating data within function body (instead of generating fixture and other arguments as function arguments) breaks quickchecks shrink functionality.
Maybe we should reconsider this approach and generate everything within function arguments so that property body is as deterministic as possible. In this specific example everything works correctly as mockNetworkLayer
is pure deterministic function. But I am afraid that this design encourages of someone in future to add
somethingArbitrary <- pick arbitrary
and to add somethingArbitrary
to WalletLayerFixture
to use later which would break quickchecks shrink functionality.
Maybe this is a good oportunity to have a chat with Edsko about https://iohk.io/blog/an-in-depth-look-at-quickcheck-state-machine/
Note that state machine approach won't eliminate fixture part - but instead it would become just one state transition (first one) in a chain of state transitions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel that the current fixture and test setup is very simple. setupFixture
prepares db, network, walletLayer and creates wallet returning its db, walletLayer and corresponding walletId. For every test, the same fixture is instantiated. No complex transitions are occurring here. Maybe address state machine
concept in separate PR and refactor then? Nevertheless,I think you raised very important issue here @akegalj !!!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@akegalj Your remark is sound and, note that we aren't going for the same approach as before. Here, the arbitrary generation happens via QuickCheck in the functions arguments. The fixture doesn't generate anything (and I agree, it OUGHT NOT), but rather, setup a state given some already generated data.
It will be quite tricky to do differently as, some I/O needs to happen for the property to happen, and the property is intrinsically monadic. It would be wrong to shove in any pick
or generate
call in the property. Yet, as they stand, the properties are totally deterministic (modulo the DB failure).
Beside, state machine testing is another stage in the testing process. They won't replace those basic unit-tests, but rather come as a complement. The nice thing with the DBLayer as it stands right now makes it possible to test the "MVar DBLayer" well, and then, use it as a model for state machine testing, comparing a more complex implementation (like an SQLite DBLayer) to the simple MVar one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
950888b
to
a943936
Compare
@paweljakubas , looking at the code coverage on those tests, we can see one interesting thing: most of the code is never evaluated. Which is rather problematic. An easy solution to this (which may also prevent some memory leaks in the meantime) would be to deepseq whatever is stored in the MVar DB layer. import Control.DeepSeq ( deepseq )
newDBLayer :: forall s. IO (DBLayer IO s)
newDBLayer = do
wallets <- newMVar mempty
return $ DBLayer
{ putCheckpoints = \key cps ->
cps `deepseq` (modifyMVar_ wallets (return . Map.insert key cps)) otherwise, unfortunately, tests are passing without executing most of the underlying code :/ ... |
indeed, before :
after the improvement @KtorZ suggested :
|
a943936
to
1469c47
Compare
adopted the improvement ^^^ , rebased and squashed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
[23] Add mockup unit test [23] Add create/get wallet tests [23] fix last test [23] enhance test suite [23] Turn on FlexibleContexts [23] make sure putCheckpoints is evaluated [23] remove unneeded import from cabal
1469c47
to
3908982
Compare
spec = do | ||
describe "WalletLayer works as expected" $ do | ||
it "Wallet upon creation is written down in db" | ||
(checkCoverage walletCreationProp) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it makes sense to use checkCoverage
when there is no cover
or coverTable
used in the property?
(Just asking, as I simply don't know :)
cc @paweljakubas , @KtorZ
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@piotr-iohk Nope it doesn't :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Without any cover
or coverTable
, it's equivalent to doing just property
.
Issue Number
#23
Overview
CreateWalletError
Comments
I have added
CreateWalletError
as a way to forbid and inform about it when the same newWallet is passed through more than once in order to create wallet. As a consequence, I changed createWallet type to :NewWallet -> ExceptT CreateWalletError m WalletId