-
Notifications
You must be signed in to change notification settings - Fork 30
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
Refactor testings code #191
Conversation
a4bc91b
to
3bbce60
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.
I like where this PR is going. My two main comments are (1) many new helper functions need additional documentation/clearer naming to help a reader understand their use in tests, and (2) There's currently still a lot of repeated test infrastructure used across so many of our tests, so I think it would be worth creating a general testutil
module in our protocol
library which contains these commonly used testing patterns.
merkletree/pad_test.go
Outdated
@@ -407,22 +366,31 @@ func benchPADLookup(b *testing.B, entries uint64) { | |||
// The STR will get updated every epoch defined by every multiple of | |||
// `updateEvery`. If `updateEvery > N` createPAD won't update the STR. | |||
func createPad(N uint64, keyPrefix string, valuePrefix []byte, snapLen uint64, | |||
updateEvery uint64) (*PAD, error) { | |||
pad, err := NewPAD(TestAd{""}, signKey, vrfPrivKey1, snapLen) | |||
afterCreateCB func(pad *PAD), |
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.
Can you please add an explanation for the use of callbacks in the createPad
documentation?
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.
done.
merkletree/proof_test.go
Outdated
if proof.Verify([]byte(key3), val3, m.hash) != nil { | ||
t.Error("Proof of inclusion verification failed.") | ||
} | ||
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" |
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.
Please add some documentation for the use of these constants.
merkletree/proof_test.go
Outdated
var src = rand.NewSource(time.Now().UnixNano()) | ||
|
||
// Credit: https://stackoverflow.com/a/31832326 | ||
func RandStringBytesMaskImprSrc(n int) string { |
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.
Please add documentation for RandStringBytesMaskImprSrc
.
merkletree/proof_test.go
Outdated
} | ||
var N uint64 = 3 // number of inclusions. | ||
|
||
func setup(t *testing.T) (*MerkleTree, []*mockTest) { |
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.
setup
seems to fulfill a similar role to other setup-functions like createPad
or newTestTree
. Can you find a more meaningful name for this function? Maybe something like setupTestKeys
? It might be good, at this point, to make sure we use more consistent naming for these different setup functions (e.g. can we just change createPad
to newTestPad
?)
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.
Renamed to setupTestProofs
.
merkletree/proof_test.go
Outdated
m := newTestTree(t) | ||
|
||
tuple := []*mockTest{} | ||
for i := uint64(0); i < uint64(N); i++ { |
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.
It looks like we have several such loops for doing inserts and lookups in our data structures in various tests. I think it might be worth refactoring them into their own helper functions.
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.
Insertion and lookups depends on test cases, so IMO we don't really need to refactor them into smaller functions.
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.
That makes sense. But there still is a lot of repeated test structure, and that's what I'm suggesting we refactor. It will make the tests cleaner and a lot easier to write new ones. I think it would be more valuable and helpful to us and other developers in the future if we could abstract away most of the internals of testing and just had to pass in the test cases we want to test. If it doesn't take too much effort, we should take care of that in this PR.
protocol/directory/directory_test.go
Outdated
EndEpoch: uint64(2)}) | ||
func TestDirectoryKeyLookupInEpochBadEpoch(t *testing.T) { | ||
d := NewTestDirectory(t) | ||
tests := []struct { |
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.
See my comment above about the mockTest
struct.
protocol/directory/directory_test.go
Outdated
{"bad end epoch", 4, 2, protocol.ErrMalformedMessage}, | ||
{"out-of-bounds", 6, d.LatestSTR().Epoch, protocol.ErrMalformedMessage}, | ||
} | ||
for _, tt := range 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.
I like this structure for our error condition testing. Since so many of our tests follow this structure, is it possible to further refactor this into a generic function that we can use everywhere instead of having to copy/paste this code everywhere (this will make writing future tests easier, too). Here's what I have in mind: We have a generic TestError
function:
func TestError(t *testing.T, tests, testFunction, testRequestType) {
for _, tt := range tests {
res := testFunction(newTestRequest(testRequestType, tt))
if res.Error != tt.want {
t.Errorf("Expect %s for %s", tt.want, tt.name)
}
}
}
Which then gets called as TestError(t, tests, d.GetSTRHistory, protocol.STRType)
in TestBadRequestGetSTRHistory
. Writing new tests is then only a matter of filling the appropriate test parameter struct and calling TestError
with the right parameters. What do you think?
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'm not sure about this since I think it doesn't really save us much. Besides we can sometimes run sub-tests in parallel (see https://golang.org/pkg/testing/#hdr-Subtests_and_Sub_benchmarks) using t.Run
.
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.
See #191 (comment). If we have cases in which we run parallel sub-tests, I think we could still write two separate functions, one for parallel testing and one for sequential testing, but still abstract away all the common testing internals.
crypto/testutil.go
Outdated
} | ||
|
||
// StaticSigning returns a static private signing key for _tests_. | ||
func StaticSigning(t *testing.T) sign.PrivateKey { |
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 we should use consistent naming and include the word Test
in these function names just to make it absolutely clear to the reader that these are used for testing.
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 still prefer to keep this name since it reflects exactly what the function does. Besides, the docstring states it clearly and the parameter requires testing.T
so I don't think this will cause any trouble.
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.
My point was a bit more about consistency in naming throughout all of our tests, we should agree on a convention. You're definitely right, if the docstring states the test clearly, the name doesn't need to include Test
.
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.
Test
is used as prefix only for testcases in _test.go
files, so I think this doesn't violate our convention ;)
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 true for the test cases. At the same time, for this function I would have expected a name more like NewTestSigningKey
since most other similar testutil
helpers follow the NewTest*
convention.
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.
What do you think about e06784b?
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 definitely works. Thanks!
merkletree/testutil.go
Outdated
var valuePrefix = []byte("value") | ||
|
||
// StaticPAD returns a pad with a static initial STR for _tests_. | ||
func StaticPAD(t *testing.T, ad AssocData) *PAD { |
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.
See my comment above regarding crypto.StaticVRF
and such.
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.
done.
@@ -0,0 +1 @@ | |||
package 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.
I'm assuming you plan to move tests from auditlog_test
etc into this module later on, and replace tests like consistencychecks_test
with this module?
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.
Yes, for instance, please see https://github.com/coniks-sys/coniks-go/pull/192/files#diff-4b27042e76fbd702dc90612e2d9e9372.
Thanks for your first round of reviews! I will push documentation/naming changes tomorrow. |
298c703
to
f222500
Compare
1644b01
to
64bf084
Compare
This is ready for another review. @masomel |
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! I'll leave a note about further refactoring of testing functionality and test case definition so I don't forget to get back to that.
Merged in c9491a4. |
Part of #18:
Remove some redundant tests in /protocol level.
Use deterministic tests.
Refactor tests in /merkletree based on work of @liamsi
Note:
auditlog_test.go
andconsistencychecks_test.go
should be rewritten and integrated with the directory tests, after our APIs are complete.