-
Notifications
You must be signed in to change notification settings - Fork 31
STITCH-1956 Fill in missing unit tests for every relevant task and clean exisiting tests. #69
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
Conversation
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 tests so far. Have some various suggestions to improve the strength, readability, and correctness of the tests
null, | ||
BsonDocumentCodec()) | ||
|
||
dataSynchronizer.start() |
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.
[style] This takes up a lot of space. I would just group the operation you do on the element with the assertion right after. e.g.
dataSynchronizer.stop()
dataSynchronizer.disableSyncThread()
dataSynchronizer.start()
assertFalse(dataSynchronizer.isRunning)
|
||
@Test | ||
fun testNew() { | ||
assertFalse(dataSynchronizer.isRunning) |
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.
[minor] For unit tests, it reads easier if they don't use a test setup method and instead call a method to get what they need. For example in this test, it's odd that all we check is isRunning but the user reading doesn't know where it comes from.
} | ||
|
||
@Test | ||
@Suppress("UNCHECKED_CAST") |
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.
Are these suppression necessary?
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.
Removes unnecessary warnings due to us having to cast certain mocks.
|
||
dataSynchronizer.configure( | ||
namespace, | ||
mock(ConflictHandler::class.java) as ConflictHandler<BsonDocument>, |
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] Instead of mocking these, just provide real ones. There's no value gained in mocking them as they are never manipulated.
authMonitor | ||
)) | ||
|
||
private val dataSynchronizer = spy(DataSynchronizer( |
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] Move this into a method where new versions of these can be fetched. That means so should the service, local client, and so on. It makes the tests less brittle.
|
||
dataSynchronizer.deleteOneById(namespace, document1["_id"]) | ||
|
||
`when`(coreRemoteMongoCollectionMock.deleteOne(any())).thenAnswer { |
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] For all tests that end up emitting errors, we should verify what the error is after each acquire.
} | ||
|
||
@Test | ||
fun testFailedDelete() { |
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] This test should verify we have frozen sync for this. It would also be good to test after doing insertOneAndSync again that it's not longer frozen and we insert.
dataSynchronizer.insertOneAndSync(namespace, document1) | ||
|
||
// verify triggerListeningToNamespace has been called | ||
verify(dataSynchronizer, times(1)).triggerListeningToNamespace(namespace) |
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 this is looking too deep into the class. I would remove this and rely on stream function which is the true signal
dataSynchronizer.insertOneAndSync(namespace, document2) | ||
|
||
// verify trigger is called again | ||
verify(dataSynchronizer, times(3)).triggerListeningToNamespace(namespace) |
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, can delete.
eventSemaphore.acquire() | ||
|
||
// verify trigger is called, once for the insert, once for the configure | ||
verify(dataSynchronizer, times(2)).triggerListeningToNamespace(namespace) |
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 delete
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 looking pretty good and I definitely think it's going in the right direction. Had a few questions/minor comments, and will probably take a closer look once it's actually ready for review.
*/ | ||
public boolean doSyncPass() { | ||
if (!syncLock.tryLock()) { | ||
if (!this.isConfigured || !syncLock.tryLock()) { |
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.
[q] is this what you think might solve the flakiness we're seeing in the configure 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.
No, this is actually how the method should function. I don't believe this will solve any races.
verify(conflictHandler, times(1)).resolveConflict(eq(document1["_id"]), any(), any()) | ||
verify(errorListener, times(0)).onError(eq(document1["_id"]), any()) | ||
|
||
// verify we are inserting! |
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] verify we are not inserting? or that we desync-ed/froze?
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.
or is this verifying that the conflict resolver deleted the document?
verify(conflictHandler, times(0)).resolveConflict(eq(document2["_id"]), any(), any()) | ||
verify(errorListener, times(1)).onError(eq(document2["_id"]), any()) | ||
|
||
assertEquals( |
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.
[minor] I think in general, for all these tests, we should add a comment when checking the document in the local collection to explain why we're checking for the specific expected state. I was confused at first why we were expecting the document to exist when the insert failed, but I realized it's because we expect the document to be there locally, just not remotely.
verify(conflictHandler, times(1)).resolveConflict(eq(document1["_id"]), any(), any()) | ||
verify(errorListener, times(0)).onError(eq(document1["_id"]), any()) | ||
|
||
// verify we are inserting! |
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.
inserting the resolution to the conflict?
return BsonDocument("_id", BsonObjectId()).append(key, value) | ||
} | ||
|
||
private fun withoutVersionId(document: BsonDocument?): BsonDocument? { |
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.
[heads up] this should be renamed to withoutSyncVersion
to be consistent with the change I made in my PR
…into STITCH-1956
…platform independent
…into STITCH-1956
This is a large PR.
Note: Seems like GitHub is having some issues. This is the patch for the latest commit on evg: https://evergreen.mongodb.com/version/5bcdf2dd2a60ed479842434d |
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. Got a question or comment or two. Nice job.
|
||
@Test | ||
fun testStaleFetchMultiple() | ||
} No newline at end of file |
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.
EOF
// insert a document remotely, and wait for the event to store | ||
val sem = watchForEvents(syncTestRunner.namespace) | ||
remoteColl.insertOne(Document("_id", insertedId).append("fly", "away")) | ||
sem.tryAcquire(3, TimeUnit.SECONDS) |
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.
[mahor] none of these tests that previously had just a acquire should have try acquire now. That encourages races to occur. If 3 seconds pass and nothing happens, this proceeds.
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.
Fair point. It seemed to me that tryAcquire
would at least let the test fail, as opposed to hang. I had only noticed it was an option as it was added to some of the more recent tests. However, I'll remove it from the tests I added it to. In this test, we actually do not need to acquire that semaphore at all– it can be refactored to only play with a changeEvent semaphore, an errorListener semaphore, and a conflict handling semaphore.
} | ||
|
||
override fun waitForEvent() { | ||
eventSemaphore?.tryAcquire(2, TimeUnit.SECONDS) |
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.
These should all change too. We shouldn't base any of these on time
@@ -0,0 +1,567 @@ | |||
package com.mongodb.stitch.core.services.mongodb.remote.sync.internal |
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.
Should this have the word Unit in it to denote that it's only for unit 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 was under the impression that "harness" was a term generally for unit tests. Though Int tests have setup methods, you wouldn't want a harness per se.
On the contrary, adding the word Unit does make it undoubtably clear. So I will add it.
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.
Excellent work! This should make future testing a lot less painful. I have mostly nits and q's, some of which may be out of scope for this ticket anyway and are more just general questions I have about the overall implementation.
assertTrue(namespaceChangeStreamListener.openStream()) | ||
ctx.nextStreamEvent = Event.Builder().withEventName("message").build() | ||
namespaceChangeStreamListener.storeNextEvent() | ||
assertTrue(namespaceChangeStreamListener.isOpen) |
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] add a check for assertEquals(0, namespaceChangeStreamListener.events.size)
?
coreSync.insertOneAndSync(ctx.testDocument) | ||
fail("should have received duplicate key error index") | ||
} catch (e: MongoWriteException) { | ||
assertNotNull(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.
[nit] you can verify that this is duplicate key error by checking assertTrue(e.message.contains("E11000"))
(or something similar)
BsonObjectId().value.toHexString()) | ||
} | ||
|
||
fun withoutVersionId(document: BsonDocument?): BsonDocument? { |
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] I renamed this to withoutSyncVersion
in the integration tests since it's more than just an ID now, so you could change it here as well.
emitErrorSemaphore: Semaphore? = null, | ||
expectedDocumentId: BsonValue? = null | ||
): ErrorListener { | ||
open class TestErrorListener : ErrorListener { |
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 q] why is this class embedded in the function whereas the errors are at the level of the SyncTestHarness
class?
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.
At the moment, we don't need to interact with it. Might as well not muddle up what developers can access.
} | ||
|
||
override fun waitForEvent() { | ||
eventSemaphore?.tryAcquire(2, TimeUnit.SECONDS) |
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.
[minor] assertTrue(eventSemaphore?.tryAcquire(10, TimeUnit.SECONDS))
(or however you need to do it so the case where the semaphore is null works properly too).
// should be frozen since the operation type was unknown | ||
ctx.queueConsumableRemoteUpdateEvent() | ||
ctx.doSyncPass() | ||
assertEquals(ctx.testDocument, ctx.findTestDocumentFromLocalCollection()) |
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 q] same q as before, how do we know this is frozen? don't we need to try deleting first?
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.
We don't need to try deleting– making sure the document doesn't update works as well.
it.isFrozen = true | ||
} | ||
dataSynchronizer.reloadConfig() | ||
// 2: Local wins |
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] where is 1.
?
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.
Adding now.
|
||
ctx.doSyncPass() | ||
ctx.waitForError() | ||
// verify we are inserting! |
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] copy-pasted comment?
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
// assert this doc does not exist | ||
assertNull(ctx.findTestDocumentFromLocalCollection()) | ||
|
||
// update the non-existent document... |
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] update
-> delete
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.
👍
// change event listener have been called | ||
streamAndSync() | ||
|
||
changeEventListenerSemaphore.tryAcquire(3, TimeUnit.SECONDS) |
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.
[major] another tryAcquire to assertTrue
and change to 10 seconds
@adamchel PTAL |
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!
Do not review.
This is for agreement purposes only.