Skip to content

Commit ee32c39

Browse files
authored
Merge pull request #1473 from marklogic/feature/querybatcher-failure-testing
Added test to capture how onQueryFailure works
2 parents 54f39d2 + abab0b4 commit ee32c39

File tree

1 file changed

+90
-0
lines changed

1 file changed

+90
-0
lines changed
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package com.marklogic.client.test.datamovement;
2+
3+
import com.marklogic.client.DatabaseClient;
4+
import com.marklogic.client.FailedRequestException;
5+
import com.marklogic.client.datamovement.DataMovementManager;
6+
import com.marklogic.client.datamovement.QueryBatcher;
7+
import com.marklogic.client.test.Common;
8+
import org.junit.Test;
9+
10+
import java.util.ArrayList;
11+
import java.util.Arrays;
12+
import java.util.List;
13+
14+
import static org.junit.Assert.*;
15+
16+
/**
17+
* Captures what may be surprising behavior of onQueryFailure on QueryBatcher - i.e. it does not capture exceptions
18+
* from the initial query failing, nor does it capture exceptions from an onUrisReady listener failing.
19+
* <p>
20+
* Based on code analysis of QueryBatcherImpl, it seems that the only time an onQueryFailure listener will fail is
21+
* due to some kind of programming error or a very unexpected error when assembling a batch of URIs based on the
22+
* already-queried-for list of URIs. For example, QueryBatcherTest has a "testBadIteratorAndThrowException" test that
23+
* uses a bad implementation of Iterator to throw an exception. That seems to adequately capture the very remote chance
24+
* that onQueryFailure would ever receive an exception - i.e. we rarely expect an Iterator to fail.
25+
* <p>
26+
* Thus, it's best to think of "onQueryFailure" as = a very unexpected failure that occurs while assembling
27+
* already-queried-for batches and handing them off to a QueryBatchListener.
28+
* <p>
29+
* Note that onQueryFailure is not invoked if the queryMgr.uris call in QueryBatcherImpl fails - i.e. when the call
30+
* is made to query for URIs for a batch before the batch is processed. That may be a bug - that seems like when it's
31+
* supposed to be invoked. You can verify this by forcing an exception to be thrown right after the URIs are retrieved.
32+
*/
33+
public class QueryBatcherFailureTest {
34+
35+
@Test
36+
public void invalidQuery() {
37+
final List<String> failureMessages = new ArrayList<>();
38+
39+
DatabaseClient client = Common.connect();
40+
41+
FailedRequestException ex = assertThrows(FailedRequestException.class, () ->
42+
client.newDataMovementManager().newQueryBatcher(
43+
client.newQueryManager().newStructuredQueryBuilder().directory(0, "/invalid/path")
44+
).onQueryFailure(failure -> failureMessages.add(failure.getMessage()))
45+
);
46+
47+
assertTrue(
48+
"Unexpected message: " + ex.getMessage(),
49+
ex.getMessage().contains("Directory URI path must end with '/'")
50+
);
51+
52+
assertEquals(
53+
"Despite the name, 'onQueryFailure' does not capture exceptions based on the query failing. If the original " +
54+
"query fails, then an exception will be immediately thrown by 'newQueryBatcher' - which is good! " +
55+
"A user can try/catch that and act accordingly. But an onQueryFailure listener is not invoked. ",
56+
0, failureMessages.size());
57+
}
58+
59+
@Test
60+
public void batchProcessingFails() {
61+
final List<String> failureMessages = new ArrayList<>();
62+
final List<String> processedItems = new ArrayList<>();
63+
64+
DatabaseClient client = Common.connect();
65+
DataMovementManager dmm = client.newDataMovementManager();
66+
QueryBatcher qb = dmm.newQueryBatcher(Arrays.asList("item1", "item2").iterator())
67+
.withThreadCount(1)
68+
.withBatchSize(1)
69+
.onUrisReady(batch -> {
70+
if ("item2".equals(batch.getItems()[0])) {
71+
throw new RuntimeException("item2 explodes!");
72+
}
73+
processedItems.add(batch.getItems()[0]);
74+
})
75+
.onQueryFailure(failure -> failureMessages.add(failure.getMessage()));
76+
77+
dmm.startJob(qb);
78+
qb.awaitCompletion();
79+
dmm.stopJob(qb);
80+
81+
assertEquals(1, processedItems.size());
82+
assertEquals("item1", processedItems.get(0));
83+
84+
assertEquals(
85+
"An onQueryFailure listener does not receive errors from an onUrisReady listener. Instead, " +
86+
"QueryBatcherImpl simply logs these, such that they are effectively swallowed. It's up to the user " +
87+
"implementing a QueryBatchListener to provide a better error-handling mechanism.",
88+
0, failureMessages.size());
89+
}
90+
}

0 commit comments

Comments
 (0)