Skip to content

Creating Stream Tests for the Web Client #260

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

Merged
merged 6 commits into from
Oct 25, 2017

Conversation

schmidt-sebastian
Copy link
Contributor

This is a port of firebase/firebase-ios-sdk#391

* Fails the test if the expected callback does not match the name of the
* actual callback function.
*/
awaitCallback(expectedCallback: StreamStatusCallback): Promise<void> {
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd normally put the public surface area of the class (awaitCallback / verifyNoPendingCallbacks) at the top, but feel free to leave as-is for symmetry with iOS.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moved to top.

import { getDefaultDatabaseInfo } from '../util/helpers';
import FirestoreError = firestore.FirestoreError;

type StreamStatusCallback =
Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps StreamCallbackType or StreameEvent? (not all of these are "status" events, and I'd expect an actual "callback" type to be a function, not a string)

I think the slightly strange thing here is that we're including 'on' in the names, which makes them refer to the method you call to register an event callback rather than the event itself. So it's also tempting to drop the 'on's ?

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, is it worth adding a comment calling out that this is a merging of the watch stream and write stream callback types?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I renamed it to StreameEventType, which indeed suggests to remove the 'on' from the type definition.

| 'onClose';

class StreamStatusListener implements WatchStreamListener, WriteStreamListener {
private pendingStates: StreamStatusCallback[] = [];
Copy link
Contributor

Choose a reason for hiding this comment

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

'state' seems out-of-place. pendingCallbacks?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

private pendingStates: StreamStatusCallback[] = [];
private pendingPromises: Deferred<StreamStatusCallback>[] = [];

private resolvePending(actualCallback: StreamStatusCallback) {
Copy link
Contributor

Choose a reason for hiding this comment

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

can you declare the return type?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

private pendingPromises: Deferred<StreamStatusCallback>[] = [];

private resolvePending(actualCallback: StreamStatusCallback) {
let pendingPromise = this.pendingPromises.shift();
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: Here you're calling shift() and checking for undefined. Below (for pendingStates) you check the length > 0 before calling shift. I prefer the latter, but regardless I'd try to be consistent.

Copy link
Contributor Author

@schmidt-sebastian schmidt-sebastian Oct 25, 2017

Choose a reason for hiding this comment

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

I went with the length check, but left the intermediate variable since it makes the control flow easier to follow.

return deferred.promise.then(actualCallback => {
expect(actualCallback).to.equal(expectedCallback);
});
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This code is a bit wonky in that on failure it might throw an exception or might return a failed promise, depending on if we have pendingStates (and that the expectedCallback expectation is repeated). Perhaps do something like:

let promise: Promise<StreamStatusCallback>;
if (this.pendingStates.length > 0) {
  promise = Promise.resolve(this.pendingStates.shift());
} else {
  const deferred = new Deferred<StreamStatusCallback>();
  this.pendingPromises.push(deferred);
  promise = deferred.promise;
}
return promise.then(actualCallback => {
  expect(actualCallback).to.equal(expectedCallback);
});

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I changed the code to always return a Promise, and to reject it if the callback type doesn't match.

new EmptyCredentialsProvider(),
serializer
);
})
Copy link
Contributor

Choose a reason for hiding this comment

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

Does it make sense to factor out an initializeDatastore() method to avoid 90% of this repeated code?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

})
.then(() => {
writeStream.stop();
});
Copy link
Contributor

Choose a reason for hiding this comment

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

Why does the web test not verify that attempting to write before handshake throws but works after? If we're going to the effort of porting the tests, we should make sure they're at parity (or at least add a comment for why they're not). :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I totally forgot to port this part :(

});
}

asyncIt('can be stopped before handshake', () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

same comment as above about not testing we don't get extra onClose event after stop().

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As above.

});
}

asyncIt('can be stopped before handshake', () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

The iOS test has a comment:

/** Verifies that the watch stream does not issue an onClose callback after a call to stop(). */

and does indeed trigger a GRPC close event (to make sure that our stream doesn't trigger a close event in response). Why aren't we testing that on web?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I went back and forth on this just now, but decided to ultimately leave it as is. The handleStreamClose callback on the Web only ever gets called when the stream is still open and asserts that this is the case. To bring it closer to the other platforms, I would have to break this invariant.

If you prefer, I can also assert here that 'handleStreamClose' throws an error at this point. Just making it public for that doesn't seem worth it - especially since it invites other consumers to trip over this assertion.

Copy link
Contributor

Choose a reason for hiding this comment

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

I see. To make web a more direct port, I think we'd refactor

this.stream.onClose((error: FirestoreError) => {
so that it delegates to a function that could then be called from this test. But going to that much trouble probably isn't warranted.

Copy link
Contributor

@mikelehen mikelehen left a comment

Choose a reason for hiding this comment

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

Thanks! Nice withTestDatastore() cleanup.

return Promise.reject(
`Unexpected callback encountered. Expected '${expectedCallback}', but got '${actualCallback}'.`
);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't have any strong preference, but FYI I think using expect(...).to.equal(...) here was probably fine.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

watchStream.start();

return streamListener.awaitCallback('open').then(() => {
// Stop must not call onClose because the full implementation of the delegate could
Copy link
Contributor

Choose a reason for hiding this comment

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

delegate => callback ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

// Don't start the handshake.

// Stop must not call onClose because the full implementation of the delegate could
// attempt to restart the stream in the event it had pending watches.
Copy link
Contributor

Choose a reason for hiding this comment

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

delagate => callback and while you're here maybe fix "pending watches" to "pending writes" (even though Obj-C is wrong too :-))

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

@schmidt-sebastian schmidt-sebastian merged commit e1dceac into master Oct 25, 2017
@firebase firebase locked and limited conversation to collaborators Oct 25, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants