POC for polysemy - #771
Conversation
| expectedTx <- noteInputFile "test/cardano-cli-golden/files/input/byron/tx/normal.tx" | ||
| createdTx <- noteTempFile tempDir "tx" | ||
| void $ execCardanoCLI | ||
| hprop_byronTx = propertyOnce $ localWorkspace $ do |
There was a problem hiding this comment.
The tempDir argument is no longer needed because that information now propagates into our tests via Reader ProjectRoot.
| hprop_byronTx = propertyOnce $ localWorkspace $ do | ||
| signingKey <- jotPkgInputFile "test/cardano-cli-golden/files/input/byron/keys/byron.skey" | ||
| expectedTx <- jotPkgInputFile "test/cardano-cli-golden/files/input/byron/tx/normal.tx" | ||
| createdTx <- jotTempFile "tx" |
There was a problem hiding this comment.
We can ask for a temp file without supplying the tempDir because it can get it from Reader ProjectRoot.
2ae181c to
92ff831
Compare
| compareByronTxs :: () | ||
| => HasCallStack | ||
| => Member (Embed IO) r | ||
| => Member Hedgehog r |
There was a problem hiding this comment.
Test functions have a Member Hedgehog r constraint.
| => Member Hedgehog r | ||
| => FilePath | ||
| -> FilePath | ||
| -> Sem r () |
There was a problem hiding this comment.
Even though getTxByteString is not a test function we can use it seamlessly below.
| f & moduleWorkspace "cardano-cli" | ||
| & runReader (ProjectRoot cabalProjectDir) | ||
| & runReader (PackagePath "cardano-cli") | ||
| & runResource |
There was a problem hiding this comment.
The localWorkspace function sets up all the effects for the tests. For example readers for ProjectRoot and PackagePath are supplied. Additional moduleWorkspace provides a reader for Workspace which is used to create temporary files.
There was a problem hiding this comment.
You can have multiple readers - very nice!
| => HasCallStack | ||
| => Member (Embed IO) r | ||
| => Member Hedgehog r | ||
| => Member Log r |
There was a problem hiding this comment.
We can log in test and non-test functions and those will get annotated in the test report.
There was a problem hiding this comment.
where does this Log effect come from?
There was a problem hiding this comment.
The Log effect comes from polysemy-log.
There was a problem hiding this comment.
The interpreters for the Log effect are registered by propertyOnce here:
See the lines:
& interpretLogDataLog
& setLogLevel (Just Info)
& interpretDataLogHedgehog formatLogEntry getLogEntryCallStack
& interpretDataLogHedgehog id (const GHC.callStack)
& interpretTimeGhc
7203b38 to
7805ddc
Compare
| -> Sem r () | ||
| checkVrfFilePermissions vrfSignKey = | ||
| embed (runExceptT (Api.checkVrfFilePermissions vrfSignKey)) | ||
| & onLeftM throw |
There was a problem hiding this comment.
It's very easy to create polysemy wrappers for functions.
|
|
||
| checkVrfFilePermissions (File vrfSignKey) | ||
| -- key-gen-VRF cli command created a VRF signing key file with the wrong permissions | ||
| & trapFail @VRFPrivateKeyFilePermissionError |
There was a problem hiding this comment.
Error handling in tests is much more succinct. If an error is thrown, we just trap it here and fail with minimal ceremony. Error handling supports multiple errors and it is always clear which error it is that occured.
7805ddc to
1107902
Compare
| @@ -0,0 +1,36 @@ | |||
| {-# LANGUAGE DataKinds #-} | |||
| {-# LANGUAGE FlexibleContexts #-} | |||
| {-# LANGUAGE OverloadedStrings #-} | |||
Check warning
Code scanning / HLint
Unused LANGUAGE pragma
| {-# LANGUAGE DataKinds #-} | ||
| {-# LANGUAGE FlexibleContexts #-} | ||
| {-# LANGUAGE OverloadedStrings #-} | ||
| {-# LANGUAGE TypeApplications #-} |
Check warning
Code scanning / HLint
Unused LANGUAGE pragma
| {-# LANGUAGE FlexibleContexts #-} | ||
| {-# LANGUAGE OverloadedStrings #-} | ||
| {-# LANGUAGE TypeApplications #-} | ||
| {-# LANGUAGE TypeOperators #-} |
Check warning
Code scanning / HLint
Unused LANGUAGE pragma
| (embed $ runExceptT $ Cli.readByronSigningKey bKeyFormat fp) | ||
| & onLeftM throw |
Check notice
Code scanning / HLint
Move brackets to avoid $
| => Member Log r | ||
| => Member (Embed IO) r | ||
| => Sem | ||
| ( Reader Workspace |
There was a problem hiding this comment.
Where does Workspace type come from?
There was a problem hiding this comment.
There was a problem hiding this comment.
Very nice! I love the idea of using of inversion of control for our tests! We should do it for cardano-cli at some point too.
I haven't used polysemy much, so I'm wondering why polysemy and not some other effect system?
Sould we be worried about its peformance? I guess not much, becase it's just for tests, but it's something to be mindful of.
I'm not approving yet, I'm wondering what rest of the team thinks about it. Definitely there's some learning curve here.
|
|
||
| source-repository-package | ||
| type: git | ||
| location: https://github.com/newhoggy/polysemy-conc |
There was a problem hiding this comment.
Do we need this SRP? I don't see a difference between your fork and @tek's version.
And also I don't see a dependency to polysemy-conc in the modified cabal files!?
There was a problem hiding this comment.
We don't use it directly. The SRP is because polysemy-conc is a transitive dependency and it has a dependency that doesn't compile on Windows.
See: tek/polysemy-conc#4
| createdATxAuxBS <- getTxByteString createdTx & trapFail @ByronTxError | ||
| expectedATxAuxBS <- getTxByteString expectedTx & trapFail @ByronTxError |
There was a problem hiding this comment.
What happens if we don't write those trapFail (where is this function defined btw? I could not find it)?
I'm worried we're not saving on boilerplate if for every IO call, we have to annotate it with trapFail theExceptionThatItCanThrow.
There was a problem hiding this comment.
What trapFail gives to is a quick way to catch the error and annotate it at the point of failure. The failure annotation will attach to the specific trapFail that failed so you can tell which one it was that failed very easily.
It is possible to trapFail once for the entire test, however what you get if you do that is the at the failure annotation will be at the trapFail at the catch-all level not at the operation that failed.
If you don't care for the usefulness of getting the annotation at the point of failure you can reduce boilerplate further.
There was a problem hiding this comment.
The trapFail function is defined here:
The library hw-polysemy is like hedgehog-extras by written using polysemy.
There was a problem hiding this comment.
What happens if we don't write those trapFail?
If you don't call trapFail you get a compile error. For example:
test/cardano-cli-golden/Test/Golden/Byron/Tx.hs:74:23: error: [GHC-39999]
• Could not deduce ‘Member (Error ByronTxError) r’
arising from a use of ‘getTxByteString’
from the context: (HasCallStack, Member (Embed IO) r,
Member Hedgehog r)
bound by the type signature for:
compareByronTxs :: forall (r :: Polysemy.Internal.Kind.EffectRow).
(HasCallStack, Member (Embed IO) r, Member Hedgehog r) =>
FilePath -> FilePath -> Sem r ()
at test/cardano-cli-golden/Test/Golden/Byron/Tx.hs:(65,1)-(71,13)
• In a stmt of a 'do' block:
expectedATxAuxBS <- getTxByteString expectedTx
In the expression:
do createdATxAuxBS <- getTxByteString createdTx
& trapFail @ByronTxError
expectedATxAuxBS <- getTxByteString expectedTx
normalByronTxToGenTx expectedATxAuxBS
=== normalByronTxToGenTx createdATxAuxBS
In an equation for ‘compareByronTxs’:
compareByronTxs createdTx expectedTx
= do createdATxAuxBS <- getTxByteString createdTx
& trapFail @ByronTxError
expectedATxAuxBS <- getTxByteString expectedTx
normalByronTxToGenTx expectedATxAuxBS
=== normalByronTxToGenTx createdATxAuxBS
|
74 | expectedATxAuxBS <- getTxByteString expectedTx
| ^^^^^^^^^^^^^^^
There was a problem hiding this comment.
If you get an error that says Could not deduce ‘Member (Error MyError) r’, you have two choices. You can handle it right there use trap, which is like catch, or trapFail which is like trap by fails immediately with a hedgehog annotation or you can propagate the error by adding Member (Error MyError) r to the function signature in which case you punt the responsibility of handling the error to the caller.
Note that a function can declare that it throws multiple errors so you don't have to create a new sum type with many constructors to propagate multiple errors.
|
In the current state, I am neutral:
|
|
One advantage is that you could log in the functions being tested and then capture those logs in a test annotation like this: The line: Is logging from the |
Mainly due to familiarity and ecosystem - in particular the logging library |
5ad1e8c to
8f77407
Compare
|
All in all, I'm not convinced:
If we are convinced this is important, I would wait a few months to see if this part of the ecosystem gains traction. |
b11d193 to
e06c483
Compare
e06c483 to
9a46ab0
Compare
9a46ab0 to
63e629b
Compare
|
This PR is stale because it has been open 45 days with no activity. |
|
This issue was closed because it has been stalled for 60 days with no activity. |
Changelog
Context
Using
polysemyto simplify tests.How to trust this PR
Highlight important bits of the PR that will make the review faster. If there are commands the reviewer can run to observe the new behavior, describe them.
Checklist