-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
test: cli: chain category unit tests #8048
Conversation
CLI actions lack unit tests. I decided to use the approach similar to what I found in `send_test.go` using gomock, but I don't rely on custom "service" implementations but mock the whole FullNode API. This first commit validates the test setup by testing the simplest method of the chain category, e.g. `chain head`. This requires a minor refactor of the CLI action code: - The constructor (`GetFullNodeAPI`) checks if there's an injected mock API in the app Metadata and uses that in unit tests. - Actions shouldn't use raw `fmt.*` but instead write to the `app.Writer` so the CLI output is testable
…to cli-chain-tests
Codecov Report
@@ Coverage Diff @@
## master #8048 +/- ##
==========================================
+ Coverage 39.21% 39.54% +0.33%
==========================================
Files 660 660
Lines 71436 71474 +38
==========================================
+ Hits 28014 28266 +252
+ Misses 38602 38294 -308
- Partials 4820 4914 +94
Continue to review full report at Codecov.
|
Unit test for the cli `chain getblock` command. Tests if output is JSON in the expected format.
(converted to draft as the description notes that it's still WIP) |
Simple test that checks if this CLI method prints the IPLD node referenced by the given CID encoded in hexadecimal.
Contains two subtests, that check if the --really-do-it flag (force) is respected, since removing wrong objects may lead to sync issues.
Test expected output with respect to the --base flag
I also added some helper functions for mocking in the types/mock pkg
Also moved the mock definition to a separate file (mocks_test.go) because it's gonna be used in other test files, and it didn't make sense for it to stay inside chain_test.go.
Some "funky" string matching in this one, but I think that's ok. Chain is love. ❤️
Cover the essential function execution paths, no time for every -as-type combination.
Modified ChainExportCmd to use io.WriterCloser instead of os.File so it the file can be mocked in unit tests, without side effects to the FS.
Contains some funny mocking logic, because estimate gas price is called multiple times (for various nblocks) and I wanted to make it as flexible as possible.
@magik6k I changed the status back to "Ready to Review" because it's as ready as it's ever gonna be 🙂 |
chain/types/mock/chain.go
Outdated
@@ -96,3 +89,30 @@ func TipSet(blks ...*types.BlockHeader) *types.TipSet { | |||
} | |||
return ts | |||
} | |||
|
|||
func RandomActorAddress() (*address.Address, error) { |
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.
In general I'm opposed to random artifacts in tests as they make reproducibility hard. What happens if we instead take a seed and derive something randomly off of that? Then if we want a lot of coverage of different values we can iterate.
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.
If we want coverage over lots of addresses do the RandomActorAddress(seed)
approach with deterministic seed and add loops over seed value into tests calling RandomActorAddress.
If we just want an arbitrary address make a simple default choice and stick with it. I think this is probably what we want.
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.
Agreed. I've refactored the random generator to use a provided seed to generate the desired number of addresses.
We could make a default arbitrary address, but I think having a random address gen function can come in handy.
chain/types/mock/chain.go
Outdated
|
||
func RandomActorAddress() (*address.Address, error) { | ||
bytes := make([]byte, 32) | ||
_, err := rand.Read(bytes) |
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.
nit: If we do end up going with random artifacts we should probably use math since there is no need for crypto guarantees in tests. The interface doesn't even change so it's real easy to switch.
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.
Switched to math.rand
for the generator
@@ -199,7 +205,7 @@ var ChainReadObjCmd = &cli.Command{ | |||
return err | |||
} | |||
|
|||
fmt.Printf("%x\n", obj) | |||
afmt.Printf("%x\n", obj) |
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.
@magik6k any reason to not use the app writer for chain CLI?
ParentMessages []cid.Cid | ||
}{} | ||
|
||
err = json.Unmarshal(buf.Bytes(), &out) |
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.
Not sure if it's worth it but there's maybe some value in passing along specific values from the mocks rather than empty values and then checking that cli output data matches. This could catch a bug that makes a command only return empty valued data.
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 like this idea. Checking for command outputs is a good enough solution, though it might miss bugs similar to the ones you have mentioned.
However, for this PR, I would leave the current solution as it stands, and we can refactor it in the future.
cli/chain_test.go
Outdated
// output is plaintext, had to do string matching | ||
assert.Contains(t, out, from.String()) | ||
assert.Contains(t, out, to.String()) | ||
assert.Contains(t, out, "Send") |
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 is going to match the line By Sender
every time whereas I think you want to match the method type. "Send " (with a space) is an ok workaround. "By Method: \nSend" might be a bit better but you'll need to get the exact formatting down.
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.
Maybe another regex matching Send with no [er] after
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 think match both By Sender
and Send
is the best way to go in this test, so I added an extra regex match.
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 don't think this works. "By Sender" will cause both asserts to pass, right?
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.
You're right.
I changed the assertion check to verify the method separately, now.
cli/chain_test.go
Outdated
blk.Height = 1 | ||
head := mock.TipSet(blk) | ||
|
||
head.Height() |
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.
nit: unused?
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.
Nice catch! Removed.
Refactor random addr generation to use a rand seed. Remove unused lines in tests.
Since @TheDivic is on vacation at the moment, I have taken the liberty of reviewing and fixing issues in the PR. |
Proposed Changes
CLI actions lack unit tests. I decided to use the approach similar to what I found in
send_test.go
using gomock, but I don't rely on custom "service" implementations but mock the whole FullNode API.What's in this PR:
chain
category.GetFullNodeAPI
constructor function checks if there's an injected mock API in the app Metadata and uses that in unit tests.fmt.*
but instead write to theapp.Writer
so the CLI output is testableos.File
with aio.WriterCloser
and implemented a construction function that check app metadata for an instance ofWriterCloser
before callingos.Create
to prevent side-effects in unit tests.Additional Info
These unit tests heavily rely on mocking and there’s some funky string matching but I think they are valuable because:
Checklist
Before you mark the PR ready for review, please make sure that:
<PR type>: <area>: <change being made>
fix: mempool: Introduce a cache for valid signatures
PR type
: fix, feat, INTERFACE BREAKING CHANGE, CONSENSUS BREAKING, build, chore, ci, docs,perf, refactor, revert, style, testarea
: api, chain, state, vm, data transfer, market, mempool, message, block production, multisig, networking, paychan, proving, sealing, wallet, deps