Skip to content

Conversation

@thebrianchen
Copy link

Porting from node (multiple commits).

TODO:

  • Add POJO support.

@thebrianchen thebrianchen self-assigned this Jun 3, 2020
@googlebot googlebot added the cla: yes This human has signed the Contributor License Agreement. label Jun 3, 2020
@codecov
Copy link

codecov bot commented Jun 3, 2020

Codecov Report

Merging #233 into bc/bulk-master will increase coverage by 0.10%.
The diff coverage is 81.13%.

Impacted file tree graph

@@                 Coverage Diff                  @@
##             bc/bulk-master     #233      +/-   ##
====================================================
+ Coverage             73.62%   73.72%   +0.10%     
- Complexity             1048     1087      +39     
====================================================
  Files                    65       67       +2     
  Lines                  5650     5866     +216     
  Branches                622      642      +20     
====================================================
+ Hits                   4160     4325     +165     
- Misses                 1296     1341      +45     
- Partials                194      200       +6     
Impacted Files Coverage Δ Complexity Δ
.../com/google/cloud/firestore/BulkWriterOptions.java 0.00% <0.00%> (ø) 0.00 <0.00> (?)
...oogle/cloud/firestore/spi/v1/GrpcFirestoreRpc.java 0.00% <0.00%> (ø) 0.00 <0.00> (ø)
...gle/cloud/firestore/v1/FirestoreAdminSettings.java 13.20% <0.00%> (ø) 2.00 <0.00> (ø)
...m/google/cloud/firestore/v1/FirestoreSettings.java 16.36% <0.00%> (ø) 4.00 <0.00> (ø)
...le/cloud/firestore/v1/stub/FirestoreAdminStub.java 5.88% <0.00%> (ø) 1.00 <0.00> (ø)
.../google/cloud/firestore/v1/stub/FirestoreStub.java 5.88% <0.00%> (ø) 1.00 <0.00> (ø)
...gle/cloud/firestore/v1beta1/FirestoreSettings.java 13.20% <0.00%> (ø) 2.00 <0.00> (ø)
...le/cloud/firestore/v1beta1/stub/FirestoreStub.java 6.25% <0.00%> (ø) 1.00 <0.00> (ø)
...java/com/google/cloud/firestore/FirestoreImpl.java 78.19% <50.00%> (-0.44%) 25.00 <1.00> (-1.00)
...oogle/cloud/firestore/v1/FirestoreAdminClient.java 57.46% <50.00%> (ø) 38.00 <23.00> (ø)
... and 36 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update b60ce7f...05ab240. Read the comment docs.

public class BulkWriterOptions {
private final boolean disableThrottling;

BulkWriterOptions(boolean disableThrottling) {
Copy link
Author

Choose a reason for hiding this comment

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

I noticed that all the other options objects have private constructors and expose create() instead. Should I do this here as well?

Copy link
Contributor

Choose a reason for hiding this comment

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

The idea behind these classes is to offer "named arguments". In this case, we would want to have a static factory method that is called "disableThrottling" or "withThrottlingDisabled". I would suggest to look at SetOptions rather than at TransactionOptions for inspiration :)

Copy link
Author

Choose a reason for hiding this comment

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

Thanks, added withThrottlingDisabled(). Given that the BulkWriter will default to throttling when no options object is passed in, do you see value in adding a withThrottlingEnabled() option? I can't think of a use case for it, but want to make sure.

Copy link
Contributor

Choose a reason for hiding this comment

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

Not yet.

Copy link
Contributor

@schmidt-sebastian schmidt-sebastian left a comment

Choose a reason for hiding this comment

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

I didn't have time to look at tests yet, will do before lunch.

*/
@Nonnull
public ApiFuture<WriteResult> delete(
@Nonnull DocumentReference documentReference, @Nonnull Precondition options) {
Copy link
Contributor

Choose a reason for hiding this comment

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

s/options/precondition

Also: Precondition should be singular. Only a single option can be set.

If it is not too much work, maybe we can fix this in UpdateBuilder too.

Copy link
Author

Choose a reason for hiding this comment

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

I originally had precondition but changed it to options to match UpdateBuilder.

Changed to precondition here and in UpdateBuilder.

* Delete a document from the database.
*
* @param documentReference The DocumentReference to delete.
* @return An ApiFuture containing the result of the delete. Throws an error if the delete fails.
Copy link
Contributor

Choose a reason for hiding this comment

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

We don't throw an error, we return an error via the ApiFuture.

Copy link
Author

Choose a reason for hiding this comment

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

changed to contains.

flushComplete.set(null);
}
},
MoreExecutors.directExecutor());
Copy link
Contributor

Choose a reason for hiding this comment

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

I am actually not sure what directExecutor refers to here. Do you mind doing a quick verification for me?
If a consumer calls flush().get() does it resolve? If MoreExecutors.directExecutor() schedules the runnable on the user thread, then it will block. It likely gets executed on GRPC's thread (the thread that resolves the bulk commits), in which case we are ok.

Copy link
Author

Choose a reason for hiding this comment

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

If the executor scheduled the runnable on the user thread, wouldn't the unit test calls to flush.get() hang indefinitely?

From what I've researched, directExecutor() will run the task in the thread that invokes execute (link). I'm not sure how to determine which thread is responsible for that, but from what I've seen in the codebase, most if not all calls to ApiFutures.transform() are called with directExecutor.

Copy link
Contributor

Choose a reason for hiding this comment

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

I verified that this is running on the GAX executor in the transaction code, so this pattern is fine (and your research validated). Thanks for digging this up!

FWIW I don't think we can judge threading behavior with our unit tests. Our integration tests are great for that.

Copy link
Author

Choose a reason for hiding this comment

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

Thanks for checking :)

// Send the batch if it is under the rate limit, or schedule another attempt after the
// appropriate timeout.
long delayMs = rateLimiter.getNextRequestDelayMs(batch.getOpCount());
Preconditions.checkArgument(delayMs != -1, "Batch size should be under capacity");
Copy link
Contributor

Choose a reason for hiding this comment

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

s/checkArgument/checkState

Copy link
Author

Choose a reason for hiding this comment

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

done.

boolean success = rateLimiter.tryMakeRequest(batch.getOpCount());
Preconditions.checkState(success, "Batch should be under rate limit to be sent.");
try {
List<BatchWriteResult> results = batch.bulkCommit().get();
Copy link
Contributor

Choose a reason for hiding this comment

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

You are blocking on the bulkCommit() here. You need to add a listener to proceed with the batch result logic instead of calling get(). Look at ApiFutures for convenience methods that help with listener regristration.

Copy link
Author

Choose a reason for hiding this comment

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

Moved logic to a listener, but I can't find a way to implement this without 2 separate try/catch blocks. I need the outer one in case bulkCommit() throws an error, and the inner one is required to call .get().

* WriteBatch}.
*/
public abstract class UpdateBuilder<T extends UpdateBuilder> {
public abstract class UpdateBuilder<T> {
Copy link
Contributor

Choose a reason for hiding this comment

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

This implementation is now very different from Node. Do you think we can easily get Node to match? I am a little hesitant to provide two drastically different reference implementations.

Copy link
Author

Choose a reason for hiding this comment

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

I think we could probably get node to match, but my fear is that it would result in more work for the contractors on top of all the other changes that BulkWriter requires. I took a look at the python and php admin SDKs and they follow the current node pattern. Java seems unique in using the UpdateBuilder pattern. Thoughts?

Copy link
Contributor

@schmidt-sebastian schmidt-sebastian Jun 4, 2020

Choose a reason for hiding this comment

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

Let's leave as is. Java remains outlier. We need to point that out in our docs for the contractors.

Copy link
Author

Choose a reason for hiding this comment

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

will make a note.

return state;
}

HashSet<String> getDocPaths() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Please return Set as returning a HashSet ties us to this implementation.

Copy link
Author

Choose a reason for hiding this comment

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

done.

return docPaths;
}

int getOpCount() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Java doesn't use abbreviations.

Copy link
Author

Choose a reason for hiding this comment

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

renamed to getOperationCount

/** Resolves the individual operations in the batch with the results. */
void processResults(List<BatchWriteResult> results, @Nullable Exception error) {
if (error == null) {
for (int i = 0; i < getOpCount(); ++i) {
Copy link
Contributor

Choose a reason for hiding this comment

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

You are looping over the resultsMap, but this is not apparent. This would be easier to read if you inlined getOpCount here and below (and maybe we can remove it altogether?).

Copy link
Author

Choose a reason for hiding this comment

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

Moved the if-else statements into a single for-loop and switched to using resultsMap.size().

BulkWriter uses getOperationCount() to calculate the RateLimiter delay, so we can't remove the method unless we expose the entire resultsMap variable.

private DocumentReference doc1;
private DocumentReference doc2;

private ApiFuture<BatchWriteResponse> successResponse(int seconds) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe s/seconds/updateTimeSeconds/

Copy link
Author

Choose a reason for hiding this comment

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

done.

private ApiFuture<BatchWriteResponse> failResponse() {
BatchWriteResponse.Builder response = BatchWriteResponse.newBuilder();
response.addWriteResultsBuilder().build();
response.addStatusBuilder().setCode(14).build();
Copy link
Contributor

Choose a reason for hiding this comment

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

Use the Code enum here?

Copy link
Author

Choose a reason for hiding this comment

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

done.

{
put(
batchWrite(set(LocalFirestoreHelper.SINGLE_FIELD_PROTO, "coll/doc1")),
failResponse());
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we pass the error code to failResponse?

Nit: I think the name should be failedResponse, since it returns a failed response.

Copy link
Author

Choose a reason for hiding this comment

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

done. I had considered providing the option for an error code, but I don't see a reason to pass in different error codes in the tests.

{
put(
batchWrite(create(LocalFirestoreHelper.SINGLE_FIELD_PROTO, "coll/doc1")),
successResponse(2));
Copy link
Contributor

Choose a reason for hiding this comment

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

Use 1 here?

Copy link
Author

Choose a reason for hiding this comment

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

done.

{
put(
batchWrite(set(LocalFirestoreHelper.SINGLE_FIELD_PROTO, "coll/doc1")),
successResponse(0));
Copy link
Contributor

Choose a reason for hiding this comment

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

IMHO 1 is a better starting point since it is different from the default timestamp.

Copy link
Author

Choose a reason for hiding this comment

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

done here and throughout.

successResponse(3));
}
};
requestResponseMap.initializeStub(batchWriteCapture, firestoreMock, true);
Copy link
Contributor

Choose a reason for hiding this comment

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

What is true?

Copy link
Author

Choose a reason for hiding this comment

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

separated out into separate class, N/A now.

// timeout.

List<Integer> arrayRange = new ArrayList<>();
for (int i = 0; i < 500; ++i) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we "compute" this value inline?

Copy link
Author

Choose a reason for hiding this comment

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

I tried researching this, but it seems that the clean implementations such as IntStream require Java 8 (https://stackoverflow.com/questions/16020741/shortest-way-of-filling-an-array-with-1-2-n). The other option involves creating a Set with a Range, which doesn't seem so straightforward.

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 see why we need it. There is only one place where we are looping over arrayRange and we could just use for (int i = 0; i < 500; ++i) instead.

Copy link
Author

Choose a reason for hiding this comment

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

I somehow had it in my head that I needed to have a range of numbers. Oops...

return fromJsonString(json.replace("'", "\""));
}

static class RequestResponseMap
Copy link
Contributor

Choose a reason for hiding this comment

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

I feel this should no longer be called RequestResponseMap, this does much more now. Can you come up with a more specific name?

I also think we could split this into two classes - the base RequestResponseMap and a subclass that enforces the one active request setting. With good naming (tm) it would now be super obvious what is going on.

Copy link
Author

Choose a reason for hiding this comment

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

split into two classes with an attempt at naming.


void awaitLatch() {
try {
latch.await();
Copy link
Contributor

Choose a reason for hiding this comment

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

You should try to generalized this code if you keep it in this generalized helper. A good way to do this would be a BlockingQueue of requests. This could be awaitRequest and use BlockingQueue.poll() to wait for the next request. You would supply the requests in the stub handler (and call BlockingQueue.offer()).

Copy link
Author

Choose a reason for hiding this comment

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

Does this modified implementation work? I'm still using a single latch and future, but it can process multiple serial requests.

Copy link
Contributor

Choose a reason for hiding this comment

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

If I follow this code correctly, you cannot wait for n requests or buffer n requests. You have to send, wait, send, wait, send and so on. If I send 2 requests, I should be able to invoke awaitRequest() twice without blocking.

Copy link
Author

Choose a reason for hiding this comment

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

I changed it from CountDownLatch to Semaphore, and I think you can buffer multiple requests now. Buffering n requests releases n permits that n awaitRequest() calls can instantly consume without blocking.

SettableApiFuture<Void> activeRequestComplete = SettableApiFuture.create();
CountDownLatch latch = new CountDownLatch(1);

void markComplete() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Please use more descriptive name.

Copy link
Author

Choose a reason for hiding this comment

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

changed to markInitialRequestComplete.

Copy link
Author

@thebrianchen thebrianchen left a comment

Choose a reason for hiding this comment

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

thanks for the review!

* Delete a document from the database.
*
* @param documentReference The DocumentReference to delete.
* @return An ApiFuture containing the result of the delete. Throws an error if the delete fails.
Copy link
Author

Choose a reason for hiding this comment

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

changed to contains.

*/
@Nonnull
public ApiFuture<WriteResult> delete(
@Nonnull DocumentReference documentReference, @Nonnull Precondition options) {
Copy link
Author

Choose a reason for hiding this comment

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

I originally had precondition but changed it to options to match UpdateBuilder.

Changed to precondition here and in UpdateBuilder.

// Send the batch if it is under the rate limit, or schedule another attempt after the
// appropriate timeout.
long delayMs = rateLimiter.getNextRequestDelayMs(batch.getOpCount());
Preconditions.checkArgument(delayMs != -1, "Batch size should be under capacity");
Copy link
Author

Choose a reason for hiding this comment

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

done.

// Remove the batch from BatchQueue after it has been processed.
int batchIndex = batchQueue.indexOf(batch);
Preconditions.checkState(batchIndex != -1, "The batch should be in the BatchQueue.");
batchQueue.remove(batchIndex);
Copy link
Author

Choose a reason for hiding this comment

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

done.

* READY_TO_SEND (2) not write to references that are currently in flight.
*/
private boolean isBatchSendable(BulkCommitBatch batch) {
if (batch.getState() != UpdateBuilder.BatchState.READY_TO_SEND) {
Copy link
Author

Choose a reason for hiding this comment

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

Changed.

bulkWriter.create(
firestoreMock.document("coll/doc3"), LocalFirestoreHelper.SINGLE_FIELD_MAP);

// The 4th write should not be sent because it should be in a new batch. However, this write
Copy link
Author

Choose a reason for hiding this comment

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

comment typo thanks.

successResponse(3));
}
};
requestResponseMap.initializeStub(batchWriteCapture, firestoreMock, true);
Copy link
Author

Choose a reason for hiding this comment

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

separated out into separate class, N/A now.


// Wait for flush() to perform logic and reach the stubbed response. This simulates a first
// batch that has been sent with its response still pending.
requestResponseMap.awaitLatch();
Copy link
Author

Choose a reason for hiding this comment

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

changed to awaitRequest.

// timeout.

List<Integer> arrayRange = new ArrayList<>();
for (int i = 0; i < 500; ++i) {
Copy link
Author

Choose a reason for hiding this comment

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

I tried researching this, but it seems that the clean implementations such as IntStream require Java 8 (https://stackoverflow.com/questions/16020741/shortest-way-of-filling-an-array-with-1-2-n). The other option involves creating a Set with a Range, which doesn't seem so straightforward.


void awaitLatch() {
try {
latch.await();
Copy link
Author

Choose a reason for hiding this comment

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

Does this modified implementation work? I'm still using a single latch and future, but it can process multiple serial requests.

Copy link
Contributor

@schmidt-sebastian schmidt-sebastian left a comment

Choose a reason for hiding this comment

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

Some drive-by comments. More thorough review later tonight.

private final boolean disableThrottling;

BulkWriterOptions(boolean disableThrottling) {
private BulkWriterOptions(boolean disableThrottling) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Denver would be very happy if the internal API used enableThrottling. I would prefer that too!

Copy link
Author

Choose a reason for hiding this comment

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

done.


/**
* Create a set of options to disable or enable throttling.
* An option object that will disable throttling in the created BulkWriter.
Copy link
Contributor

Choose a reason for hiding this comment

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

s/option/options/

Copy link
Author

Choose a reason for hiding this comment

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

done.

}

/**
* Wraps the result of the write operation before it is returned.
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe add: 'WrapResult is used to generate the return value for all public methods.'

Copy link
Author

Choose a reason for hiding this comment

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

added in 2nd paragraph.


static class RequestResponseMap
/** Map of request/response pairs to be returned when `sendRequest()` is called. */
static class RequestResponseStubber
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: This only stubs responses. Maybe ResponseStubber?

It also seems like the comment is no longer applicable.

Copy link
Author

Choose a reason for hiding this comment

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

renamed and updated comment.

*
* <p>Enforces that only one active request can be pending at a time.
*/
static class SerialRequestStubber extends RequestResponseStubber {
Copy link
Contributor

Choose a reason for hiding this comment

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

Similarly, this could be SerialResponseStubber.

Copy link
Author

Choose a reason for hiding this comment

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

done.

CountDownLatch latch = new CountDownLatch(1);

void markComplete() {
void markRequestComplete() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe markAllRequestsComplete()? Not sure.

Copy link
Author

Choose a reason for hiding this comment

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

Done. That name makes sense if we are allowing multiple requests.

}

void awaitLatch() {
void awaitRequest() {
Copy link
Contributor

Choose a reason for hiding this comment

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

awaitNextRequest?

Copy link
Author

Choose a reason for hiding this comment

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

Shouldn't it be await (this) request (the request that you just made)? You would call awaitRequest() after you've made a request first, and not the other way around.

} catch (Exception e) {
fail("latch.await() should not fail");
}
latch = new CountDownLatch(1);
Copy link
Contributor

Choose a reason for hiding this comment

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

I think you have to use semaphores here to allow for multiple requests to build up and be consumed. The state needs to span the lifetime of the Stubber.

Copy link
Author

Choose a reason for hiding this comment

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

Done.

Copy link
Contributor

@schmidt-sebastian schmidt-sebastian left a comment

Choose a reason for hiding this comment

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

Thanks for the updates! Especially the test helper is now much cleaner. Some final nit as always :)

flushComplete.set(null);
}
},
MoreExecutors.directExecutor());
Copy link
Contributor

Choose a reason for hiding this comment

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

I verified that this is running on the GAX executor in the transaction code, so this pattern is fine (and your research validated). Thanks for digging this up!

FWIW I don't think we can judge threading behavior with our unit tests. Our integration tests are great for that.

this.disableThrottling = disableThrottling;
}

boolean getDisableThrottling() {
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be isThrottlingEnabled

Copy link
Author

Choose a reason for hiding this comment

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

done.

// timeout.

List<Integer> arrayRange = new ArrayList<>();
for (int i = 0; i < 500; ++i) {
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 see why we need it. There is only one place where we are looping over arrayRange and we could just use for (int i = 0; i < 500; ++i) instead.


void awaitLatch() {
try {
latch.await();
Copy link
Contributor

Choose a reason for hiding this comment

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

If I follow this code correctly, you cannot wait for n requests or buffer n requests. You have to send, wait, send, wait, send and so on. If I send 2 requests, I should be able to invoke awaitRequest() twice without blocking.

@Override
public ApiFuture<? extends GeneratedMessageV3> answer(
InvocationOnMock invocationOnMock) throws Throwable {
return checkOneActiveRequestAndReturnResponse(response);
Copy link
Contributor

Choose a reason for hiding this comment

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

If you make checkOneActiveRequestAndReturnResponse() a validateRequest() method that in the base class doesn't do anything and in this class is checkOneActiveRequestAndReturnResponse(), then you can have a single shared implementation of initializeStub().

Copy link
Author

Choose a reason for hiding this comment

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

Great suggestion, thanks!

*/
static class ResponseStubber
extends LinkedHashMap<GeneratedMessageV3, ApiFuture<? extends GeneratedMessageV3>> {
ApiFuture<? extends GeneratedMessageV3> checkOneActiveRequestAndReturnResponse(
Copy link
Contributor

@schmidt-sebastian schmidt-sebastian Jun 7, 2020

Choose a reason for hiding this comment

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

This method should:

  • have a generic name
  • be documented

Copy link
Contributor

Choose a reason for hiding this comment

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

(ignore the previous version of this comment)

Copy link
Author

Choose a reason for hiding this comment

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

Renamed to verifyResponse and added comments.

};
stubber = (stubber != null) ? stubber.doAnswer(answer) : doAnswer(answer);
}
if (stubber != null) {
Copy link
Contributor

Choose a reason for hiding this comment

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

If stubber is null here, how would the tests work? Seems like we should assert that stubber is not null.

Copy link
Author

Choose a reason for hiding this comment

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

Good point, the if statement is a lazy solution. Added a checkNotNull

Copy link
Contributor

@schmidt-sebastian schmidt-sebastian left a comment

Choose a reason for hiding this comment

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

LGTM with final nits.

@thebrianchen thebrianchen merged commit 64007a2 into bc/bulk-master Jun 8, 2020
@thebrianchen thebrianchen deleted the bc/bulk-refactor branch June 8, 2020 13:22
BenWhitehead pushed a commit that referenced this pull request Aug 7, 2020
BenWhitehead pushed a commit that referenced this pull request Aug 7, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla: yes This human has signed the Contributor License Agreement.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants