From 820849b629b96350725696cf739388f482888c83 Mon Sep 17 00:00:00 2001 From: Junwei Dai Date: Wed, 18 Dec 2024 11:31:30 -0800 Subject: [PATCH] Add more unit test Signed-off-by: Junwei Dai --- .../pipeline/ProcessorExecutionDetail.java | 7 +- .../action/search/SearchResponseTests.java | 23 +++- .../ProcessorExecutionDetailTests.java | 122 ++++++++++++++++++ .../pipeline/SearchPipelineServiceTests.java | 110 ++++++++++++++++ 4 files changed, 260 insertions(+), 2 deletions(-) create mode 100644 server/src/test/java/org/opensearch/search/pipeline/ProcessorExecutionDetailTests.java diff --git a/server/src/main/java/org/opensearch/search/pipeline/ProcessorExecutionDetail.java b/server/src/main/java/org/opensearch/search/pipeline/ProcessorExecutionDetail.java index 81d906b075097..2f4a044c513e0 100644 --- a/server/src/main/java/org/opensearch/search/pipeline/ProcessorExecutionDetail.java +++ b/server/src/main/java/org/opensearch/search/pipeline/ProcessorExecutionDetail.java @@ -22,6 +22,8 @@ import java.util.List; import java.util.Objects; +import static org.opensearch.core.xcontent.XContentParserUtils.ensureExpectedToken; + /** * Detailed information about a processor execution in a search pipeline. * @@ -167,7 +169,10 @@ public static ProcessorExecutionDetail fromXContent(XContentParser parser) throw long durationMillis = 0; Object inputData = null; Object outputData = null; - + if (parser.currentToken() != XContentParser.Token.START_OBJECT) { + parser.nextToken(); + ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.currentToken(), parser); + } while (parser.nextToken() != XContentParser.Token.END_OBJECT) { String fieldName = parser.currentName(); parser.nextToken(); diff --git a/server/src/test/java/org/opensearch/action/search/SearchResponseTests.java b/server/src/test/java/org/opensearch/action/search/SearchResponseTests.java index 228773d04ab2c..48c206719cc28 100644 --- a/server/src/test/java/org/opensearch/action/search/SearchResponseTests.java +++ b/server/src/test/java/org/opensearch/action/search/SearchResponseTests.java @@ -62,6 +62,7 @@ import org.opensearch.search.aggregations.AggregationsTests; import org.opensearch.search.aggregations.InternalAggregations; import org.opensearch.search.internal.InternalSearchResponse; +import org.opensearch.search.pipeline.ProcessorExecutionDetail; import org.opensearch.search.profile.SearchProfileShardResults; import org.opensearch.search.profile.SearchProfileShardResultsTests; import org.opensearch.search.suggest.Suggest; @@ -312,6 +313,10 @@ public void testToXContent() { hit.score(2.0f); SearchHit[] hits = new SearchHit[] { hit }; String dummyId = UUID.randomUUID().toString(); + List processorResults = List.of( + new ProcessorExecutionDetail("processor1", 50, List.of(1), List.of(1)), + new ProcessorExecutionDetail("processor2", 30, List.of(3), List.of(3)) + ); { SearchResponse response = new SearchResponse( new InternalSearchResponse( @@ -323,7 +328,7 @@ public void testToXContent() { null, 1, List.of(new DummySearchExtBuilder(dummyId)), - Collections.emptyList() + processorResults ), null, 0, @@ -356,8 +361,24 @@ public void testToXContent() { { expectedString.append("{\"dummy\":\"" + dummyId + "\"}"); } + expectedString.append(",\"processor_results\":"); + expectedString.append("["); + for (int i = 0; i < processorResults.size(); i++) { + ProcessorExecutionDetail detail = processorResults.get(i); + expectedString.append("{"); + expectedString.append("\"processor_name\":\"").append(detail.getProcessorName()).append("\","); + expectedString.append("\"duration_millis\":").append(detail.getDurationMillis()).append(","); + expectedString.append("\"input_data\":").append(detail.getInputData()).append(","); + expectedString.append("\"output_data\":").append(detail.getOutputData()); + expectedString.append("}"); + if (i < processorResults.size() - 1) { + expectedString.append(","); + } + } + expectedString.append("]"); } expectedString.append("}"); + assertEquals(expectedString.toString(), Strings.toString(MediaTypeRegistry.JSON, response)); List searchExtBuilders = response.getInternalResponse().getSearchExtBuilders(); assertEquals(1, searchExtBuilders.size()); diff --git a/server/src/test/java/org/opensearch/search/pipeline/ProcessorExecutionDetailTests.java b/server/src/test/java/org/opensearch/search/pipeline/ProcessorExecutionDetailTests.java new file mode 100644 index 0000000000000..76ee99fc2f201 --- /dev/null +++ b/server/src/test/java/org/opensearch/search/pipeline/ProcessorExecutionDetailTests.java @@ -0,0 +1,122 @@ +/* + * 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. + */ + +package org.opensearch.search.pipeline; + +import org.opensearch.common.io.stream.BytesStreamOutput; +import org.opensearch.common.xcontent.XContentHelper; +import org.opensearch.common.xcontent.json.JsonXContent; +import org.opensearch.core.common.bytes.BytesReference; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.xcontent.DeprecationHandler; +import org.opensearch.core.xcontent.MediaType; +import org.opensearch.core.xcontent.MediaTypeRegistry; +import org.opensearch.core.xcontent.ToXContent; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentParser; +import org.opensearch.test.OpenSearchTestCase; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +public class ProcessorExecutionDetailTests extends OpenSearchTestCase { + + public void testSerializationRoundtrip() throws IOException { + ProcessorExecutionDetail detail = new ProcessorExecutionDetail("testProcessor", 123L, Map.of("key", "value"), List.of(1, 2, 3)); + ProcessorExecutionDetail deserialized; + try (BytesStreamOutput output = new BytesStreamOutput()) { + detail.writeTo(output); + try (StreamInput input = output.bytes().streamInput()) { + deserialized = new ProcessorExecutionDetail(input); + } + } + assertEquals("testProcessor", deserialized.getProcessorName()); + assertEquals(123L, deserialized.getDurationMillis()); + assertEquals(Map.of("key", "value"), deserialized.getInputData()); + assertEquals(List.of(1, 2, 3), deserialized.getOutputData()); + } + + public void testAddMethods() { + ProcessorExecutionDetail detail = new ProcessorExecutionDetail("testProcessor"); + detail.addTook(456L); + detail.addInput(Map.of("newKey", "newValue")); + detail.addOutput(List.of(4, 5, 6)); + assertEquals(456L, detail.getDurationMillis()); + assertEquals(Map.of("newKey", "newValue"), detail.getInputData()); + assertEquals(List.of(4, 5, 6), detail.getOutputData()); + } + + public void testEqualsAndHashCode() { + ProcessorExecutionDetail detail1 = new ProcessorExecutionDetail("processor1", 100L, "input1", "output1"); + ProcessorExecutionDetail detail2 = new ProcessorExecutionDetail("processor1", 100L, "input1", "output1"); + ProcessorExecutionDetail detail3 = new ProcessorExecutionDetail("processor2", 200L, "input2", "output2"); + + assertEquals(detail1, detail2); + assertNotEquals(detail1, detail3); + assertEquals(detail1.hashCode(), detail2.hashCode()); + assertNotEquals(detail1.hashCode(), detail3.hashCode()); + } + + public void testToString() { + ProcessorExecutionDetail detail = new ProcessorExecutionDetail("processorZ", 500L, "inputData", "outputData"); + String expected = + "ProcessorExecutionDetail{processorName='processorZ', durationMillis=500, inputData=inputData, outputData=outputData}"; + assertEquals(expected, detail.toString()); + } + + public void testToXContent() throws IOException { + ProcessorExecutionDetail detail = new ProcessorExecutionDetail("testProcessor", 123L, Map.of("key1", "value1"), List.of(1, 2, 3)); + + XContentBuilder actualBuilder = XContentBuilder.builder(JsonXContent.jsonXContent); + detail.toXContent(actualBuilder, ToXContent.EMPTY_PARAMS); + + String expected = "{" + + " \"processor_name\": \"testProcessor\"," + + " \"duration_millis\": 123," + + " \"input_data\": {\"key1\": \"value1\"}," + + " \"output_data\": [1, 2, 3]" + + "}"; + + XContentParser expectedParser = JsonXContent.jsonXContent.createParser( + this.xContentRegistry(), + DeprecationHandler.THROW_UNSUPPORTED_OPERATION, + expected + ); + XContentBuilder expectedBuilder = XContentBuilder.builder(JsonXContent.jsonXContent); + expectedBuilder.generator().copyCurrentStructure(expectedParser); + + assertEquals( + XContentHelper.convertToMap(BytesReference.bytes(expectedBuilder), false, (MediaType) MediaTypeRegistry.JSON), + XContentHelper.convertToMap(BytesReference.bytes(actualBuilder), false, (MediaType) MediaTypeRegistry.JSON) + ); + } + + public void testFromXContent() throws IOException { + String json = "{" + + " \"processor_name\": \"testProcessor\"," + + " \"duration_millis\": 123," + + " \"input_data\": {\"key1\": \"value1\"}," + + " \"output_data\": [1, 2, 3]" + + "}"; + + try ( + XContentParser parser = JsonXContent.jsonXContent.createParser( + this.xContentRegistry(), + DeprecationHandler.THROW_UNSUPPORTED_OPERATION, + json + ) + ) { + ProcessorExecutionDetail detail = ProcessorExecutionDetail.fromXContent(parser); + + assertEquals("testProcessor", detail.getProcessorName()); + assertEquals(123L, detail.getDurationMillis()); + assertEquals(Map.of("key1", "value1"), detail.getInputData()); + } + } +} diff --git a/server/src/test/java/org/opensearch/search/pipeline/SearchPipelineServiceTests.java b/server/src/test/java/org/opensearch/search/pipeline/SearchPipelineServiceTests.java index 99461bdf5c3be..88567e9cbb20c 100644 --- a/server/src/test/java/org/opensearch/search/pipeline/SearchPipelineServiceTests.java +++ b/server/src/test/java/org/opensearch/search/pipeline/SearchPipelineServiceTests.java @@ -1816,6 +1816,116 @@ public void testVerbosePipelineExecution() throws Exception { assertEquals("scale_request_size", executionDetails.get(0).getProcessorName()); assertEquals("fixed_score", executionDetails.get(1).getProcessorName()); } + public void testVerbosePipelineWithRequestProcessorOnly() throws Exception { + SearchPipelineService searchPipelineService = createWithProcessors(); + + // Only request processors + SearchPipelineMetadata metadata = new SearchPipelineMetadata( + Map.of( + "request_only_pipeline", + new PipelineConfiguration( + "request_only_pipeline", + new BytesArray( + "{" + + "\"request_processors\" : [ { \"scale_request_size\": { \"scale\" : 2 } } ]" + + "}" + ), + MediaTypeRegistry.JSON + ) + ) + ); + + ClusterState initialState = ClusterState.builder(new ClusterName("_name")).build(); + ClusterState updatedState = ClusterState.builder(initialState) + .metadata(Metadata.builder().putCustom(SearchPipelineMetadata.TYPE, metadata)) + .build(); + + searchPipelineService.applyClusterState(new ClusterChangedEvent("clusterStateUpdated", updatedState, initialState)); + + SearchRequest searchRequest = new SearchRequest().source(SearchSourceBuilder.searchSource().size(10)).pipeline("request_only_pipeline"); + searchRequest.source().verbosePipeline(true); + + PipelinedRequest pipelinedRequest = syncTransformRequest( + searchPipelineService.resolvePipeline(searchRequest, indexNameExpressionResolver) + ); + + // Mock response + SearchResponseSections sections = new SearchResponseSections( + new SearchHits(new SearchHit[0], new TotalHits(0, TotalHits.Relation.EQUAL_TO), 0.0f), + null, + null, + false, + null, + null, + 1, + List.of(), + List.of() + ); + + SearchResponse searchResponse = new SearchResponse(sections, null, 0, 0, 0, 0, null, null); + SearchResponse transformedResponse = syncTransformResponse(pipelinedRequest, searchResponse); + List executionDetails = transformedResponse.getInternalResponse().getProcessorResult(); + + assertNotNull(executionDetails); + assertEquals(1, executionDetails.size()); + assertEquals("scale_request_size", executionDetails.get(0).getProcessorName()); + } + + public void testVerbosePipelineWithResponseProcessorOnly() throws Exception { + SearchPipelineService searchPipelineService = createWithProcessors(); + + // Only response processors + SearchPipelineMetadata metadata = new SearchPipelineMetadata( + Map.of( + "response_only_pipeline", + new PipelineConfiguration( + "response_only_pipeline", + new BytesArray( + "{" + + "\"response_processors\": [ { \"fixed_score\": { \"score\": 5.0 } } ]" + + "}" + ), + MediaTypeRegistry.JSON + ) + ) + ); + + ClusterState initialState = ClusterState.builder(new ClusterName("_name")).build(); + ClusterState updatedState = ClusterState.builder(initialState) + .metadata(Metadata.builder().putCustom(SearchPipelineMetadata.TYPE, metadata)) + .build(); + + searchPipelineService.applyClusterState(new ClusterChangedEvent("clusterStateUpdated", updatedState, initialState)); + + SearchRequest searchRequest = new SearchRequest().source(SearchSourceBuilder.searchSource().size(10)).pipeline("response_only_pipeline"); + searchRequest.source().verbosePipeline(true); + + PipelinedRequest pipelinedRequest = syncTransformRequest( + searchPipelineService.resolvePipeline(searchRequest, indexNameExpressionResolver) + ); + + // Mock response + SearchResponseSections sections = new SearchResponseSections( + new SearchHits(new SearchHit[0], new TotalHits(0, TotalHits.Relation.EQUAL_TO), 0.0f), + null, + null, + false, + null, + null, + 1, + List.of(), + List.of() + ); + + SearchResponse searchResponse = new SearchResponse(sections, null, 0, 0, 0, 0, null, null); + SearchResponse transformedResponse = syncTransformResponse(pipelinedRequest, searchResponse); + List executionDetails = transformedResponse.getInternalResponse().getProcessorResult(); + + assertNotNull(executionDetails); + assertEquals(1, executionDetails.size()); + assertEquals("fixed_score", executionDetails.get(0).getProcessorName()); + } + private SearchSourceBuilder createDefaultSearchSourceBuilder() { return SearchSourceBuilder.searchSource().size(10);