Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
import com.marklogic.client.io.Format;
import com.marklogic.client.io.JacksonHandle;
import com.marklogic.client.io.marker.JSONWriteHandle;
import com.marklogic.mgmt.resource.appservers.ServerManager;
import okhttp3.Credentials;
import okhttp3.OkHttpClient;
import okhttp3.Request;
Expand All @@ -35,8 +34,6 @@

public class ClientApiFunctionalTest extends AbstractFunctionalTest {

private final static String serverName = "java-functest";

private static DatabaseClient dbclient = null;

// Create an identifier for modules document - Client API call will be to endpoint
Expand Down Expand Up @@ -302,63 +299,6 @@ public void TestE2EModuleNotFound() {
}
}

// This test sets the App Server concurrent users to be 1 instead of 0
// (default). Expect to see ForbiddenUser when same user logins multiple times.
@Test
@Disabled("Disabling on 2023-01-03; this succeeds consistently locally, but fairly consistently fails on Jenkins. " +
"Not sure yet if it's due to the sleep time in the DS module or not. This is also not testing the Java Client " +
"but rather it's testing the 'concurrent request limit' feature of a MarkLogic app server.")
public void TestE2ENumberOfConcurrentUsers() throws InterruptedException {
StringBuilder msgEx = new StringBuilder();
Thread w1, w2, w3;

System.out.println("Running TestE2ENumberOfConcurrentUsers");
modifyConcurrentUsersOnHttpServer(serverName, 1);

class MultipleApiUsers implements Runnable {

final String msg;
Float f;

public void run() {
try {
f = TestE2EIntegerParaReturnDouble.on(dbclient).TestE2EItemPriceErrorCond(1000, 1000);
} catch (FailedRequestException ex) {
System.out.println("Exception is from Thread " + msg);
ex.printStackTrace();
msgEx.append(msg);
msgEx.append("******");
msgEx.append(ex.getMessage());
}
}

MultipleApiUsers(String in) {
msg = in;
}
}

// Setup 3 API calls
try {
w1 = new Thread(new MultipleApiUsers("m1"));
w2 = new Thread(new MultipleApiUsers("m2"));
w3 = new Thread(new MultipleApiUsers("m3"));

w1.start();
w2.start();
w3.start();
w1.join();
w2.join();
w3.join();
} finally {
modifyConcurrentUsersOnHttpServer(serverName, 0);
System.out.println("Exception from API responses of call are " + msgEx);
assertTrue(
msgEx.toString().contains("Local message: failed to POST at") &&
msgEx.toString().contains("/ext/TestE2EIntegerParamReturnDouble/TestE2EIntegerParamReturnDoubleErrorCond.sjs"),
"Unexpected error message: " + msgEx);
}
}

// Test users with invalid roles
@Test
public void TestE2EuserWithInvalidRole() {
Expand Down Expand Up @@ -629,13 +569,6 @@ public void TestOpenApiDirectory() throws Exception {
assertTrue( jsNode.path("openapi").asText().contains("3.0.0"));
assertTrue( jsNode.path("info").path("title").asText().contains("/ext/TestOpenApi services"));
}

public static void modifyConcurrentUsersOnHttpServer(String restServerName, int numberOfUsers) {
String body = "{\"server-name\":\"" + restServerName + "\", " +
"\"group-name\": \"Default\", " +
"\"concurrent-request-limit\":\"" + numberOfUsers + "\"}";
new ServerManager(newManageClient()).save(body);
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2665,7 +2665,6 @@ public void testQueryDSLJavaScriptFn() {

// Similar to testgroupBy
@Test
@Disabled("Going to fix this in a separate PR, it has been failing intermittently for a while.")
public void testgroupByUnion() {
System.out.println("In testgroupByUnion method");
RowManager rowMgr = client.newRowManager();
Expand Down Expand Up @@ -2698,18 +2697,27 @@ public void testgroupByUnion() {
System.out.println("Results are : " + jsonBindingsNodes);
// Should have 10 array nodes returned.
assertEquals( 10, jsonBindingsNodes.size());
assertEquals( "blue", jsonBindingsNodes.get(0).path("color").path("value").asText());
assertEquals( 3, jsonBindingsNodes.get(0).path("ColorCount").path("value").asInt());
assertEquals( "20.02", jsonBindingsNodes.get(0).path("AverageAmout").path("value").asText());
// Rows with the same ColorCount have no guaranteed relative order, so locate rows by content instead of by index.
JsonNode blueRow = findRow(jsonBindingsNodes, "color", "value", "blue");
assertNotNull(blueRow, "Expected a row for the blue color group");
assertEquals( 3, blueRow.path("ColorCount").path("value").asInt());
assertEquals( "20.02", blueRow.path("AverageAmout").path("value").asText());

assertEquals( "green", jsonBindingsNodes.get(1).path("color").path("value").asText());
assertEquals( 3, jsonBindingsNodes.get(1).path("ColorCount").path("value").asInt());
assertEquals( "50.05", jsonBindingsNodes.get(1).path("AverageAmout").path("value").asText());
// Assert for null in both grouping columns. We will have one row with nulls
JsonNode greenRow = findRow(jsonBindingsNodes, "color", "value", "green");
assertNotNull(greenRow, "Expected a row for the green color group");
assertEquals( 3, greenRow.path("ColorCount").path("value").asInt());
assertEquals( "50.05", greenRow.path("AverageAmout").path("value").asText());

assertEquals( "null", jsonBindingsNodes.get(9).path("color").path("type").asText());
assertEquals( 0, jsonBindingsNodes.get(9).path("ColorCount").path("value").asInt());
assertEquals( "null", jsonBindingsNodes.get(9).path("AverageAmout").path("type").asText());
// Assert for null in both grouping columns. We will have one row with nulls
boolean nullRowFound = false;
for (JsonNode row : jsonBindingsNodes) {
if ("null".equals(row.path("color").path("type").asText())
&& "null".equals(row.path("AverageAmout").path("type").asText())) {
assertEquals( 0, row.path("ColorCount").path("value").asInt());
nullRowFound = true;
}
}
assertTrue(nullRowFound, "Expected a row with null color and null average amount");
// Use date in aggregate
ModifyPlan plan4 = plan1.union(plan2)
.select(p.as("MasterDate", p.schemaCol("opticFunctionalTest", "master", "date")),
Expand Down Expand Up @@ -2761,11 +2769,21 @@ public void testgroupByUnion() {
assertEquals( 9, jsonBindingsNodesColOrd.size());
assertEquals( "2015-12-02", jsonBindingsNodesColOrd.get(0).path("MasterDate").path("value").asText());
assertEquals( 1, jsonBindingsNodesColOrd.get(0).path("DateCount").path("value").asInt());
assertEquals( "null", jsonBindingsNodesColOrd.get(7).path("color").path("type").asText());
assertEquals( "blue", jsonBindingsNodesColOrd.get(6).path("color").path("value").asText());
// Rows with a null MasterDate have no guaranteed relative order, so locate rows by content instead of by index.
assertNotNull(findRow(jsonBindingsNodesColOrd, "color", "value", "blue"), "Expected a row for the blue color group");
assertNotNull(findRow(jsonBindingsNodesColOrd, "color", "type", "null"), "Expected a row with a null color");

}

private static JsonNode findRow(JsonNode rows, String column, String field, String expectedValue) {
for (JsonNode row : rows) {
if (expectedValue.equals(row.path(column).path(field).asText())) {
return row;
}
}
return null;
}

// Same as testgroupsByUnion with facade.
@Test
public void testgroupByArrays() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,40 +6,16 @@
import com.marklogic.client.datamovement.DataMovementManager;
import com.marklogic.client.datamovement.QueryBatcher;
import com.marklogic.client.fastfunctest.AbstractFunctionalTest;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class SetMaxBatchesTest extends AbstractFunctionalTest {

@Disabled("Getting this test in place to verify that setMaxBatches does not work when used with a query.")
@Test
void testWithQuery() {
writeJsonDocs(50, "max-batches-test");

DataMovementManager dmm = client.newDataMovementManager();
AtomicInteger uriCount = new AtomicInteger();
QueryBatcher queryBatcher = dmm
.newQueryBatcher(client.newQueryManager().newStructuredQueryBuilder().collection("max-batches-test"))
.withThreadCount(1)
.withBatchSize(10)
.onUrisReady(batch -> uriCount.addAndGet(batch.getItems().length));
queryBatcher.setMaxBatches(2);
dmm.startJob(queryBatcher);
queryBatcher.awaitCompletion();
dmm.stopJob(queryBatcher);

assertEquals(20, uriCount.get(), "Because the batch size is 10 and we asked for 2 batches back, we only " +
"expect 20 URIs back. But through 6.2.2 (and probably going back much further), all URIs are returned. " +
"Modifying the thread count and batch size do not appear to affect this at all.");
}

/**
* This verifies that setMaxBatches works with an iterator. The feature appears to have been introduced in 5.1.0,
* so since then, it's only ever worked for an iterator. It does not work when an actual query is involved.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,23 @@
import com.marklogic.client.io.InputStreamHandle;
import com.marklogic.client.io.JacksonHandle;
import com.marklogic.client.io.StringHandle;
import com.marklogic.client.test.AbstractClientTest;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Stream;

public class BulkIOInputCallerTest {
public class BulkIOInputCallerTest extends AbstractClientTest {

static ObjectNode[] apiObj = new ObjectNode[2];
static String[] apiNames = new String[]{"bulkIOInputCaller.api", "bulkIOAnyDocumentInputCaller.api"};
Expand Down Expand Up @@ -84,58 +88,52 @@ public void bulkInputEndpointTestWithMultipleCallContexts() {
map.get("bulkInputTest_2") >= 1);
}

@Disabled("Fails intermittently on Jenkins, never locally. Will revisit if we make enhancements to Bulk DS.")
@Test
public void bulkInputEndpointTestWithAnyDocument() {

String endpointState = "{\"next\":"+1+"}";
String endpointConstants = "{\"max\":6,\"collection\":\"bulkInputTest_1\"}";

String endpointState1 = "{\"next\":"+1+"}";
String endpointConstants1 = "{\"max\":6,\"collection\":\"bulkInputTest_2\"}";
final String collection1 = "bulkInputAnyDocTest_1";
final String collection2 = "bulkInputAnyDocTest_2";
final String endpointState = "{\"next\":1}";

InputCaller<StringHandle> loadEndpt = InputCaller.onHandles(IOTestUtil.db, new JacksonHandle(apiObj[1]), new StringHandle());
IOEndpoint.CallContext[] callContextArray = {loadEndpt.newCallContext()
IOEndpoint.CallContext[] callContextArray = {
loadEndpt.newCallContext()
.withEndpointStateAs(endpointState)
.withEndpointConstantsAs(endpointConstants), loadEndpt.newCallContext()
.withEndpointStateAs(endpointState1)
.withEndpointConstantsAs(endpointConstants1)};
.withEndpointConstantsAs("{\"max\":6,\"collection\":\"" + collection1 + "\"}"),
loadEndpt.newCallContext()
.withEndpointStateAs(endpointState)
.withEndpointConstantsAs("{\"max\":6,\"collection\":\"" + collection2 + "\"}")
};
InputCaller.BulkInputCaller<StringHandle> loader = loadEndpt.bulkCaller(callContextArray);

Set<String> input = IOTestUtil.setOf( // Set.of(
"{\"docNum\":1, \"docName\":\"doc1\"}",
"{\"docNum\":2, \"docName\":\"doc2\"}",
"{\"docNum\":3, \"docName\":\"doc3\"}",
"{\"docNum\":4, \"docName\":\"doc4\"}",
"{\"docNum\":5, \"docName\":\"doc5\"}",
"{\"docNum\":6, \"docName\":\"doc6\"}",
"{\"docNum\":7, \"docName\":\"doc7\"}",
"{\"docNum\":8, \"docName\":\"doc8\"}"
);

input.stream().forEach(value -> {
//System.out.println("adding "+value);
loader.accept(new StringHandle(value).withFormat(Format.JSON));
});
Set<Integer> expectedDocNums = new TreeSet<>();
for (int i = 1; i <= 8; i++) {
expectedDocNums.add(i);
loader.accept(new StringHandle("{\"docNum\":" + i + ", \"docName\":\"doc" + i + "\"}").withFormat(Format.JSON));
}
loader.awaitCompletion();
checkDocuments("bulkInputTest_1");
checkDocuments("bulkInputTest_2");
assertTrue( counter == 4);
assertTrue(
map.get("bulkInputTest_1") >= 1);
assertTrue(
map.get("bulkInputTest_2") >= 1);

// How the batches are spread across the two call contexts depends on thread timing, so only assert on the
// union of the inputs that were written, not on which collection or URI received them.
List<Integer> actualDocNums = new ArrayList<>();
for (String collection : new String[]{collection1, collection2}) {
for (int next = 2; next <= 6; next++) {
String uri = "/marklogic/ds/test/bulkInputCaller/" + collection + "/" + next + ".json";
if (docMgr.exists(uri) != null) {
JsonNode doc = docMgr.read(uri, new JacksonHandle()).get();
assertEquals(next, doc.get("state").get("next").asInt());
assertEquals(6, doc.get("work").get("max").asInt());
doc.get("inputs").forEach(input -> actualDocNums.add(input.get("docNum").asInt()));
}
}
}
Collections.sort(actualDocNums);
assertEquals(new ArrayList<>(expectedDocNums), actualDocNums,
"Every input should have been written exactly once, regardless of how batches were distributed");
}

@AfterAll
public static void cleanup(){

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I would rework this test class to extend AbstractClientTest which will clean the database before the test starts instead of after. That's a better practice because if the database is cleaned afterwards and the test fails, it's much harder to debug since the data is all gone. Extending that class also avoids duplication of deletion code like this.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

sure , BulkIOInputCallerTest now extends AbstractClientTest. The class's own deleteTestDocuments helper and its calls (in the test and in @afterall) are removed, so the database is cleaned before each test by the shared base class. @afterall now only removes the endpoint module we upload

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Sure, though that's actually not necessary either. I know now the dataservices tests do a lot of that - loading modules and then deleting them - but they could all be setup via the test-app as part of mlDeploy, and then the tests wouldn't need all the additional code. Not important to change, just an FYI.

IOTestUtil.modMgr.delete(scriptPath, apiPath);
for(int i=2; i<5; i++) {
String uri = "/marklogic/ds/test/bulkInputCaller/bulkInputTest_1/" +i+".json";
docMgr.delete(uri);
uri = "/marklogic/ds/test/bulkInputCaller/bulkInputTest_2/" +i+".json";
docMgr.delete(uri);
}
}

private void checkDocuments(String collection) {
Expand Down
Loading
Loading