From d3a156089f3fc6f030f605046923f70b2c8b5225 Mon Sep 17 00:00:00 2001 From: Alex Sun <52179851+spbjss@users.noreply.github.com> Date: Tue, 13 Jul 2021 10:58:52 -0700 Subject: [PATCH] Add integration test for ml plugin. (#59) Signed-off-by: Alex Sun Co-authored-by: Alex Sun --- .../ml/action/prediction/PredictionIT.java | 104 ++++++++++ .../ml/action/stats/MLStatsNodeIT.java | 71 +++++++ .../ml/action/training/TrainingIT.java | 142 ++++++++++++++ .../opensearch/ml/utils/IntegTestUtils.java | 181 ++++++++++++++++++ 4 files changed, 498 insertions(+) create mode 100644 plugin/src/test/java/org/opensearch/ml/action/prediction/PredictionIT.java create mode 100644 plugin/src/test/java/org/opensearch/ml/action/stats/MLStatsNodeIT.java create mode 100644 plugin/src/test/java/org/opensearch/ml/action/training/TrainingIT.java create mode 100644 plugin/src/test/java/org/opensearch/ml/utils/IntegTestUtils.java diff --git a/plugin/src/test/java/org/opensearch/ml/action/prediction/PredictionIT.java b/plugin/src/test/java/org/opensearch/ml/action/prediction/PredictionIT.java new file mode 100644 index 0000000000..ff047a3ddf --- /dev/null +++ b/plugin/src/test/java/org/opensearch/ml/action/prediction/PredictionIT.java @@ -0,0 +1,104 @@ +package org.opensearch.ml.action.prediction; + +import static org.opensearch.ml.utils.IntegTestUtils.DATA_FRAME_INPUT_DATASET; +import static org.opensearch.ml.utils.IntegTestUtils.TESTING_DATA; +import static org.opensearch.ml.utils.IntegTestUtils.TESTING_INDEX_NAME; +import static org.opensearch.ml.utils.IntegTestUtils.generateEmptyDataset; +import static org.opensearch.ml.utils.IntegTestUtils.generateMLTestingData; +import static org.opensearch.ml.utils.IntegTestUtils.generateSearchSourceBuilder; +import static org.opensearch.ml.utils.IntegTestUtils.predictAndVerifyResult; +import static org.opensearch.ml.utils.IntegTestUtils.trainModel; +import static org.opensearch.ml.utils.IntegTestUtils.verifyGeneratedTestingData; +import static org.opensearch.ml.utils.IntegTestUtils.waitModelAvailable; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.concurrent.ExecutionException; + +import org.junit.Before; +import org.opensearch.ResourceNotFoundException; +import org.opensearch.action.ActionFuture; +import org.opensearch.action.ActionRequestValidationException; +import org.opensearch.common.io.stream.NotSerializableExceptionWrapper; +import org.opensearch.ml.common.dataset.MLInputDataset; +import org.opensearch.ml.common.dataset.SearchQueryInputDataset; +import org.opensearch.ml.common.transport.prediction.MLPredictionTaskAction; +import org.opensearch.ml.common.transport.prediction.MLPredictionTaskRequest; +import org.opensearch.ml.common.transport.prediction.MLPredictionTaskResponse; +import org.opensearch.ml.plugin.MachineLearningPlugin; +import org.opensearch.plugins.Plugin; +import org.opensearch.search.builder.SearchSourceBuilder; +import org.opensearch.test.OpenSearchIntegTestCase; + +@OpenSearchIntegTestCase.ClusterScope(transportClientRatio = 0.9) +public class PredictionIT extends OpenSearchIntegTestCase { + private String taskId; + + @Before + public void initTestingData() throws ExecutionException, InterruptedException { + generateMLTestingData(); + + SearchSourceBuilder searchSourceBuilder = generateSearchSourceBuilder(); + MLInputDataset inputDataset = new SearchQueryInputDataset(Collections.singletonList(TESTING_INDEX_NAME), searchSourceBuilder); + taskId = trainModel(inputDataset); + waitModelAvailable(taskId); + } + + @Override + protected Collection> nodePlugins() { + return Collections.singletonList(MachineLearningPlugin.class); + } + + @Override + protected Collection> transportClientPlugins() { + return Collections.singletonList(MachineLearningPlugin.class); + } + + public void testTestingData() throws ExecutionException, InterruptedException { + verifyGeneratedTestingData(TESTING_DATA); + waitModelAvailable(taskId); + } + + public void testPredictionWithSearchInput() throws IOException { + SearchSourceBuilder searchSourceBuilder = generateSearchSourceBuilder(); + MLInputDataset inputDataset = new SearchQueryInputDataset(Collections.singletonList(TESTING_INDEX_NAME), searchSourceBuilder); + + predictAndVerifyResult(taskId, inputDataset); + } + + public void testPredictionWithDataInput() throws IOException { + predictAndVerifyResult(taskId, DATA_FRAME_INPUT_DATASET); + } + + public void testPredictionWithoutAlgorithm() throws IOException { + MLPredictionTaskRequest predictionRequest = new MLPredictionTaskRequest("", new ArrayList<>(), taskId, DATA_FRAME_INPUT_DATASET); + ActionFuture predictionFuture = client().execute(MLPredictionTaskAction.INSTANCE, predictionRequest); + expectThrows(ActionRequestValidationException.class, () -> predictionFuture.actionGet()); + } + + public void testPredictionWithoutModelId() throws IOException { + MLPredictionTaskRequest predictionRequest = new MLPredictionTaskRequest("kmeans", new ArrayList<>(), "", DATA_FRAME_INPUT_DATASET); + ActionFuture predictionFuture = client().execute(MLPredictionTaskAction.INSTANCE, predictionRequest); + expectThrows(ResourceNotFoundException.class, () -> predictionFuture.actionGet()); + } + + public void testPredictionWithoutDataset() throws IOException { + MLPredictionTaskRequest predictionRequest = new MLPredictionTaskRequest("kmeans", new ArrayList<>(), taskId, null); + ActionFuture predictionFuture = client().execute(MLPredictionTaskAction.INSTANCE, predictionRequest); + expectThrows(ActionRequestValidationException.class, () -> predictionFuture.actionGet()); + } + + public void testPredictionWithEmptyDataset() throws IOException { + MLInputDataset emptySearchInputDataset = generateEmptyDataset(); + MLPredictionTaskRequest predictionRequest = new MLPredictionTaskRequest( + "kmeans", + new ArrayList<>(), + taskId, + emptySearchInputDataset + ); + ActionFuture predictionFuture = client().execute(MLPredictionTaskAction.INSTANCE, predictionRequest); + expectThrows(NotSerializableExceptionWrapper.class, () -> predictionFuture.actionGet()); + } +} diff --git a/plugin/src/test/java/org/opensearch/ml/action/stats/MLStatsNodeIT.java b/plugin/src/test/java/org/opensearch/ml/action/stats/MLStatsNodeIT.java new file mode 100644 index 0000000000..c3cfe96aa6 --- /dev/null +++ b/plugin/src/test/java/org/opensearch/ml/action/stats/MLStatsNodeIT.java @@ -0,0 +1,71 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + * + */ + +package org.opensearch.ml.action.stats; + +import static org.opensearch.ml.action.stats.MLStatsNodesRequest.ALL_STATS_KEY; +import static org.opensearch.ml.utils.IntegTestUtils.TESTING_DATA; +import static org.opensearch.ml.utils.IntegTestUtils.generateMLTestingData; +import static org.opensearch.ml.utils.IntegTestUtils.verifyGeneratedTestingData; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; + +import org.junit.Before; +import org.opensearch.action.ActionFuture; +import org.opensearch.ml.plugin.MachineLearningPlugin; +import org.opensearch.plugins.Plugin; +import org.opensearch.test.OpenSearchIntegTestCase; + +@OpenSearchIntegTestCase.ClusterScope(transportClientRatio = 0.9) +public class MLStatsNodeIT extends OpenSearchIntegTestCase { + @Before + public void initTestingData() throws ExecutionException, InterruptedException { + generateMLTestingData(); + } + + @Override + protected Collection> nodePlugins() { + return Collections.singletonList(MachineLearningPlugin.class); + } + + @Override + protected Collection> transportClientPlugins() { + return Collections.singletonList(MachineLearningPlugin.class); + } + + public void testGeneratedTestingData() throws ExecutionException, InterruptedException { + verifyGeneratedTestingData(TESTING_DATA); + } + + public void testNormalCase() throws ExecutionException, InterruptedException { + MLStatsNodesRequest request = new MLStatsNodesRequest(new String[0]); + request.addStat(ALL_STATS_KEY); + + ActionFuture future = client().execute(MLStatsNodesAction.INSTANCE, request); + MLStatsNodesResponse response = future.get(); + assertNotNull(response); + + List responseList = response.getNodes(); + assertNotNull(responseList); + assertEquals(1, responseList.size()); + + MLStatsNodeResponse nodeResponse = responseList.get(0); + Map statsMap = nodeResponse.getStatsMap(); + + assertNotNull(statsMap); + assertEquals(0, statsMap.size()); + } +} diff --git a/plugin/src/test/java/org/opensearch/ml/action/training/TrainingIT.java b/plugin/src/test/java/org/opensearch/ml/action/training/TrainingIT.java new file mode 100644 index 0000000000..027f71ec1a --- /dev/null +++ b/plugin/src/test/java/org/opensearch/ml/action/training/TrainingIT.java @@ -0,0 +1,142 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + * + */ + +package org.opensearch.ml.action.training; + +import static org.opensearch.ml.indices.MLIndicesHandler.OS_ML_MODEL_RESULT; +import static org.opensearch.ml.utils.IntegTestUtils.DATA_FRAME_INPUT_DATASET; +import static org.opensearch.ml.utils.IntegTestUtils.TESTING_DATA; +import static org.opensearch.ml.utils.IntegTestUtils.TESTING_INDEX_NAME; +import static org.opensearch.ml.utils.IntegTestUtils.generateMLTestingData; +import static org.opensearch.ml.utils.IntegTestUtils.generateSearchSourceBuilder; +import static org.opensearch.ml.utils.IntegTestUtils.trainModel; +import static org.opensearch.ml.utils.IntegTestUtils.verifyGeneratedTestingData; +import static org.opensearch.ml.utils.IntegTestUtils.waitModelAvailable; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.concurrent.ExecutionException; + +import org.junit.Before; +import org.opensearch.action.ActionFuture; +import org.opensearch.action.ActionRequestValidationException; +import org.opensearch.action.search.SearchAction; +import org.opensearch.action.search.SearchRequest; +import org.opensearch.action.search.SearchResponse; +import org.opensearch.index.query.QueryBuilder; +import org.opensearch.index.query.QueryBuilders; +import org.opensearch.ml.common.dataset.MLInputDataset; +import org.opensearch.ml.common.dataset.SearchQueryInputDataset; +import org.opensearch.ml.common.transport.training.MLTrainingTaskAction; +import org.opensearch.ml.common.transport.training.MLTrainingTaskRequest; +import org.opensearch.ml.common.transport.training.MLTrainingTaskResponse; +import org.opensearch.ml.plugin.MachineLearningPlugin; +import org.opensearch.plugins.Plugin; +import org.opensearch.search.builder.SearchSourceBuilder; +import org.opensearch.test.OpenSearchIntegTestCase; + +@OpenSearchIntegTestCase.ClusterScope(transportClientRatio = 0.9) +public class TrainingIT extends OpenSearchIntegTestCase { + @Before + public void initTestingData() throws ExecutionException, InterruptedException { + generateMLTestingData(); + } + + @Override + protected Collection> nodePlugins() { + return Collections.singletonList(MachineLearningPlugin.class); + } + + @Override + protected Collection> transportClientPlugins() { + return Collections.singletonList(MachineLearningPlugin.class); + } + + public void testGeneratedTestingData() throws ExecutionException, InterruptedException { + verifyGeneratedTestingData(TESTING_DATA); + } + + public void testTrainingWithSearchInput() throws ExecutionException, InterruptedException, IOException { + SearchSourceBuilder searchSourceBuilder = generateSearchSourceBuilder(); + MLInputDataset inputDataset = new SearchQueryInputDataset(Collections.singletonList(TESTING_INDEX_NAME), searchSourceBuilder); + + String taskId = trainModel(inputDataset); + + waitModelAvailable(taskId); + } + + public void testTrainingWithDataInput() throws ExecutionException, InterruptedException, IOException { + String taskId = trainModel(DATA_FRAME_INPUT_DATASET); + + waitModelAvailable(taskId); + } + + // Train a model without algorithm. + public void testTrainingWithoutAlgorithm() { + SearchSourceBuilder searchSourceBuilder = generateSearchSourceBuilder(); + MLInputDataset inputDataset = new SearchQueryInputDataset(Collections.singletonList(TESTING_INDEX_NAME), searchSourceBuilder); + MLTrainingTaskRequest trainingRequest = new MLTrainingTaskRequest("", new ArrayList<>(), inputDataset); + expectThrows(ActionRequestValidationException.class, () -> { + ActionFuture trainingFuture = client().execute(MLTrainingTaskAction.INSTANCE, trainingRequest); + trainingFuture.actionGet(); + }); + } + + // Train a model without dataset. + public void testTrainingWithoutDataset() { + MLTrainingTaskRequest trainingRequest = new MLTrainingTaskRequest("kmeans", new ArrayList<>(), null); + expectThrows(ActionRequestValidationException.class, () -> { + ActionFuture trainingFuture = client().execute(MLTrainingTaskAction.INSTANCE, trainingRequest); + trainingFuture.actionGet(); + }); + } + + // Train a model with empty dataset. + public void testTrainingWithEmptyDataset() throws InterruptedException { + SearchSourceBuilder searchSourceBuilder = generateSearchSourceBuilder(); + searchSourceBuilder.query(QueryBuilders.matchQuery("noSuchName", "")); + MLInputDataset inputDataset = new SearchQueryInputDataset(Collections.singletonList(TESTING_INDEX_NAME), searchSourceBuilder); + MLTrainingTaskRequest trainingRequest = new MLTrainingTaskRequest("kmeans", new ArrayList<>(), inputDataset); + + ActionFuture trainingFuture = client().execute(MLTrainingTaskAction.INSTANCE, trainingRequest); + MLTrainingTaskResponse trainingResponse = trainingFuture.actionGet(); + + // The training taskId and status will be response to the client. + assertNotNull(trainingResponse); + String taskId = trainingResponse.getTaskId(); + String status = trainingResponse.getStatus(); + assertNotNull(taskId); + assertFalse(taskId.isEmpty()); + assertEquals("CREATED", status); + + SearchSourceBuilder modelSearchSourceBuilder = new SearchSourceBuilder(); + QueryBuilder queryBuilder = QueryBuilders.termQuery("taskId", taskId); + modelSearchSourceBuilder.query(queryBuilder); + SearchRequest modelSearchRequest = new SearchRequest(new String[] { OS_ML_MODEL_RESULT }, modelSearchSourceBuilder); + SearchResponse modelSearchResponse = null; + int i = 0; + while ((modelSearchResponse == null || modelSearchResponse.getHits().getTotalHits().value == 0) && i < 100) { + try { + ActionFuture searchFuture = client().execute(SearchAction.INSTANCE, modelSearchRequest); + modelSearchResponse = searchFuture.actionGet(); + } catch (Exception e) {} finally { + // Wait 100 ms until get valid search response or timeout. + Thread.sleep(100); + } + i++; + } + // No model would be trained successfully with empty dataset. + assertNull(modelSearchResponse); + } +} diff --git a/plugin/src/test/java/org/opensearch/ml/utils/IntegTestUtils.java b/plugin/src/test/java/org/opensearch/ml/utils/IntegTestUtils.java new file mode 100644 index 0000000000..3a6ab12961 --- /dev/null +++ b/plugin/src/test/java/org/opensearch/ml/utils/IntegTestUtils.java @@ -0,0 +1,181 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + * + */ + +package org.opensearch.ml.utils; + +import static org.opensearch.ml.indices.MLIndicesHandler.OS_ML_MODEL_RESULT; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +import org.opensearch.action.ActionFuture; +import org.opensearch.action.DocWriteResponse; +import org.opensearch.action.index.IndexAction; +import org.opensearch.action.index.IndexRequest; +import org.opensearch.action.index.IndexResponse; +import org.opensearch.action.search.SearchAction; +import org.opensearch.action.search.SearchRequest; +import org.opensearch.action.search.SearchResponse; +import org.opensearch.action.support.WriteRequest; +import org.opensearch.common.Strings; +import org.opensearch.common.unit.TimeValue; +import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.common.xcontent.XContentType; +import org.opensearch.index.query.QueryBuilder; +import org.opensearch.index.query.QueryBuilders; +import org.opensearch.ml.common.dataframe.DataFrameBuilder; +import org.opensearch.ml.common.dataset.DataFrameInputDataset; +import org.opensearch.ml.common.dataset.MLInputDataset; +import org.opensearch.ml.common.dataset.SearchQueryInputDataset; +import org.opensearch.ml.common.transport.prediction.MLPredictionTaskAction; +import org.opensearch.ml.common.transport.prediction.MLPredictionTaskRequest; +import org.opensearch.ml.common.transport.prediction.MLPredictionTaskResponse; +import org.opensearch.ml.common.transport.training.MLTrainingTaskAction; +import org.opensearch.ml.common.transport.training.MLTrainingTaskRequest; +import org.opensearch.ml.common.transport.training.MLTrainingTaskResponse; +import org.opensearch.rest.RestStatus; +import org.opensearch.search.SearchHit; +import org.opensearch.search.SearchHits; +import org.opensearch.search.builder.SearchSourceBuilder; +import org.opensearch.test.OpenSearchIntegTestCase; + +public class IntegTestUtils extends OpenSearchIntegTestCase { + public static final String TESTING_DATA = "{\n" + + "\"k1\":1.1,\n" + + "\"k2\":1.2,\n" + + "\"k3\":1.3,\n" + + "\"k4\":1.4,\n" + + "\"k5\":1.5\n" + + "}"; + public static final String TESTING_INDEX_NAME = "test_data"; + public static final DataFrameInputDataset DATA_FRAME_INPUT_DATASET = DataFrameInputDataset + .builder() + .dataFrame(DataFrameBuilder.load(Collections.singletonList(new HashMap() { + { + put("k1", 1.1); + put("k2", 1.2); + put("k3", 1.3); + put("k4", 1.4); + put("k5", 1.5); + } + }))) + .build(); + + // Generate testing data in the testing cluster. + public static void generateMLTestingData() throws ExecutionException, InterruptedException { + IndexRequest indexRequest = new IndexRequest(TESTING_INDEX_NAME).id("1").setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE); + indexRequest.source(TESTING_DATA, XContentType.JSON); + + ActionFuture future = client().execute(IndexAction.INSTANCE, indexRequest); + IndexResponse response = future.actionGet(); + + assertNotNull(response); + assertEquals(RestStatus.CREATED.getStatus(), response.status().getStatus()); + assertEquals(DocWriteResponse.Result.CREATED.getLowercase(), response.getResult().getLowercase()); + + verifyGeneratedTestingData(TESTING_DATA); + } + + // Verify the testing data was generated in the testing cluster. + public static void verifyGeneratedTestingData(String testingData) throws ExecutionException, InterruptedException { + SearchSourceBuilder searchSourceBuilder = generateSearchSourceBuilder(); + + SearchRequest searchRequest = new SearchRequest().indices(TESTING_INDEX_NAME).source(searchSourceBuilder); + ActionFuture searchFuture = client().execute(SearchAction.INSTANCE, searchRequest); + SearchResponse searchResponse = searchFuture.actionGet(); + + assertNotNull(searchResponse); + SearchHits hits = searchResponse.getHits(); + assertNotNull(hits); + assertEquals(1, hits.getHits().length); + SearchHit hit = hits.getHits()[0]; + assertNotNull(hit); + assertEquals(testingData, hit.getSourceAsString()); + } + + public static SearchSourceBuilder generateSearchSourceBuilder() { + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.from(0); + searchSourceBuilder.size(100); + searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS)); + searchSourceBuilder.query(QueryBuilders.matchAllQuery()); + return searchSourceBuilder; + } + + // Train a model. + public static String trainModel(MLInputDataset inputDataset) throws ExecutionException, InterruptedException { + MLTrainingTaskRequest trainingRequest = new MLTrainingTaskRequest("kmeans", new ArrayList<>(), inputDataset); + ActionFuture trainingFuture = client().execute(MLTrainingTaskAction.INSTANCE, trainingRequest); + MLTrainingTaskResponse trainingResponse = trainingFuture.actionGet(); + assertNotNull(trainingResponse); + String taskId = trainingResponse.getTaskId(); + String status = trainingResponse.getStatus(); + assertNotNull(taskId); + assertFalse(taskId.isEmpty()); + assertEquals("CREATED", status); + + return taskId; + } + + // Wait a while (20 seconds at most) for the model to be available in the ml index. + public static SearchResponse waitModelAvailable(String taskId) throws InterruptedException { + SearchSourceBuilder modelSearchSourceBuilder = new SearchSourceBuilder(); + QueryBuilder queryBuilder = QueryBuilders.termQuery("taskId", taskId); + modelSearchSourceBuilder.query(queryBuilder); + SearchRequest modelSearchRequest = new SearchRequest(new String[] { OS_ML_MODEL_RESULT }, modelSearchSourceBuilder); + SearchResponse modelSearchResponse = null; + int i = 0; + while ((modelSearchResponse == null || modelSearchResponse.getHits().getTotalHits().value == 0) && i < 200) { + try { + ActionFuture searchFuture = client().execute(SearchAction.INSTANCE, modelSearchRequest); + modelSearchResponse = searchFuture.actionGet(); + } catch (Exception e) {} finally { + // Wait 100 ms until get valid search response or timeout. + Thread.sleep(100); + } + i++; + } + assertNotNull(modelSearchResponse); + assertTrue(modelSearchResponse.getHits().getTotalHits().value > 0); + return modelSearchResponse; + } + + // Predict with the model generated, and verify the prediction result. + public static void predictAndVerifyResult(String taskId, MLInputDataset inputDataset) throws IOException { + MLPredictionTaskRequest predictionRequest = new MLPredictionTaskRequest("kmeans", new ArrayList<>(), taskId, inputDataset); + ActionFuture predictionFuture = client().execute(MLPredictionTaskAction.INSTANCE, predictionRequest); + MLPredictionTaskResponse predictionResponse = predictionFuture.actionGet(); + XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON); + builder.startObject(); + predictionResponse.getPredictionResult().toXContent(builder); + builder.endObject(); + String jsonStr = Strings.toString(builder); + String expectedStr1 = "{\"column_metas\":[{\"name\":\"Cluster ID\",\"column_type\":\"INTEGER\"}]," + + "\"rows\":[{\"values\":[{\"column_type\":\"INTEGER\",\"value\":0}]}]}"; + String expectedStr2 = "{\"column_metas\":[{\"name\":\"Cluster ID\",\"column_type\":\"INTEGER\"}]," + + "\"rows\":[{\"values\":[{\"column_type\":\"INTEGER\",\"value\":1}]}]}"; + // The prediction result would not be a fixed value. + assertTrue(expectedStr1.equals(jsonStr) || expectedStr2.equals(jsonStr)); + } + + // Generate empty testing dataset. + public static MLInputDataset generateEmptyDataset() { + SearchSourceBuilder searchSourceBuilder = generateSearchSourceBuilder(); + searchSourceBuilder.query(QueryBuilders.matchQuery("noSuchName", "")); + return new SearchQueryInputDataset(Collections.singletonList(TESTING_INDEX_NAME), searchSourceBuilder); + } +}