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

Refactor testings code #191

Merged
merged 6 commits into from
Dec 20, 2017
Merged

Refactor testings code #191

merged 6 commits into from
Dec 20, 2017

Conversation

vqhuy
Copy link
Member

@vqhuy vqhuy commented Oct 25, 2017

Part of #18:

  • Remove some redundant tests in /protocol level.

  • Use deterministic tests.

  • Refactor tests in /merkletree based on work of @liamsi

Note:

  • Tests in auditlog_test.go and consistencychecks_test.go should be rewritten and integrated with the directory tests, after our APIs are complete.

@vqhuy vqhuy requested review from arlolra, masomel and liamsi October 25, 2017 21:16
@vqhuy vqhuy self-assigned this Oct 25, 2017
@vqhuy vqhuy added this to the 0.2.0 milestone Oct 25, 2017
@vqhuy vqhuy force-pushed the refactor-tests branch 2 times, most recently from a4bc91b to 3bbce60 Compare October 28, 2017 18:48
Copy link
Member

@masomel masomel left a 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.

@@ -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),
Copy link
Member

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?

Copy link
Member Author

Choose a reason for hiding this comment

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

done.

if proof.Verify([]byte(key3), val3, m.hash) != nil {
t.Error("Proof of inclusion verification failed.")
}
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
Copy link
Member

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.

var src = rand.NewSource(time.Now().UnixNano())

// Credit: https://stackoverflow.com/a/31832326
func RandStringBytesMaskImprSrc(n int) string {
Copy link
Member

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.

}
var N uint64 = 3 // number of inclusions.

func setup(t *testing.T) (*MerkleTree, []*mockTest) {
Copy link
Member

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?)

Copy link
Member Author

@vqhuy vqhuy Dec 15, 2017

Choose a reason for hiding this comment

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

Renamed to setupTestProofs.

m := newTestTree(t)

tuple := []*mockTest{}
for i := uint64(0); i < uint64(N); i++ {
Copy link
Member

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.

Copy link
Member Author

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.

Copy link
Member

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.

EndEpoch: uint64(2)})
func TestDirectoryKeyLookupInEpochBadEpoch(t *testing.T) {
d := NewTestDirectory(t)
tests := []struct {
Copy link
Member

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.

{"bad end epoch", 4, 2, protocol.ErrMalformedMessage},
{"out-of-bounds", 6, d.LatestSTR().Epoch, protocol.ErrMalformedMessage},
}
for _, tt := range tests {
Copy link
Member

@masomel masomel Oct 30, 2017

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?

Copy link
Member Author

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.

Copy link
Member

@masomel masomel Oct 31, 2017

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.

}

// StaticSigning returns a static private signing key for _tests_.
func StaticSigning(t *testing.T) sign.PrivateKey {
Copy link
Member

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.

Copy link
Member Author

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.

Copy link
Member

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.

Copy link
Member Author

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 ;)

Copy link
Member

@masomel masomel Dec 15, 2017

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.

Copy link
Member Author

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?

Copy link
Member

Choose a reason for hiding this comment

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

This definitely works. Thanks!

var valuePrefix = []byte("value")

// StaticPAD returns a pad with a static initial STR for _tests_.
func StaticPAD(t *testing.T, ad AssocData) *PAD {
Copy link
Member

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.

Copy link
Member Author

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
Copy link
Member

@masomel masomel Oct 30, 2017

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?

Copy link
Member Author

Choose a reason for hiding this comment

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

@vqhuy
Copy link
Member Author

vqhuy commented Oct 31, 2017

Thanks for your first round of reviews! I will push documentation/naming changes tomorrow.

@masomel masomel modified the milestones: 0.2.0, 0.2.1 Dec 1, 2017
@vqhuy vqhuy modified the milestones: 0.2.1, 0.2.0 Dec 15, 2017
@vqhuy
Copy link
Member Author

vqhuy commented Dec 16, 2017

This is ready for another review. @masomel

Copy link
Member

@masomel masomel left a 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.

@masomel masomel mentioned this pull request Dec 19, 2017
@vqhuy vqhuy merged commit 5348e1e into master Dec 20, 2017
@vqhuy vqhuy deleted the refactor-tests branch December 20, 2017 01:02
@vqhuy
Copy link
Member Author

vqhuy commented Dec 20, 2017

Merged in c9491a4.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants