[TEST] Convert SearchHitsTests to AbstractStreamableXContentTestCase (#36313)

This change adds a way to provide the content type of the rest serialization
tests when creating random instances. This is used by SearchHitsTests to ensure
that the internal members of the class are created with the same xContentType
and that equals can be used to compare an instances created from an XContent
view.
This commit is contained in:
Jim Ferenczi 2018-12-10 20:41:20 +01:00 committed by GitHub
parent ed7afd1a9e
commit 75392adf60
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 228 additions and 92 deletions

View File

@ -234,7 +234,7 @@ public final class SearchHits implements Streamable, ToXContentFragment, Iterabl
@Override
public int hashCode() {
return Objects.hash(totalHits, totalHits, maxScore, Arrays.hashCode(hits));
return Objects.hash(totalHits, maxScore, Arrays.hashCode(hits));
}
public static TotalHits parseTotalHitsFragment(XContentParser parser) throws IOException {

View File

@ -114,7 +114,7 @@ public class SearchResponseTests extends ESTestCase {
int skippedShards = randomIntBetween(0, totalShards);
InternalSearchResponse internalSearchResponse;
if (minimal == false) {
SearchHits hits = SearchHitsTests.createTestItem();
SearchHits hits = SearchHitsTests.createTestItem(true, true);
InternalAggregations aggregations = aggregationsTests.createTestInstance();
Suggest suggest = SuggestTests.createTestItem();
SearchProfileShardResults profileShardResults = SearchProfileShardResultsTests.createTestItem();

View File

@ -61,7 +61,11 @@ import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
public class SearchHitTests extends AbstractStreamableTestCase<SearchHit> {
public static SearchHit createTestItem(boolean withOptionalInnerHits) {
public static SearchHit createTestItem(boolean withOptionalInnerHits, boolean withShardTarget) {
return createTestItem(randomFrom(XContentType.values()), withOptionalInnerHits, withShardTarget);
}
public static SearchHit createTestItem(XContentType xContentType, boolean withOptionalInnerHits, boolean withShardTarget) {
int internalId = randomInt();
String uid = randomAlphaOfLength(10);
Text type = new Text(randomAlphaOfLengthBetween(5, 10));
@ -71,7 +75,7 @@ public class SearchHitTests extends AbstractStreamableTestCase<SearchHit> {
}
Map<String, DocumentField> fields = new HashMap<>();
if (randomBoolean()) {
fields = GetResultTests.randomDocumentFields(XContentType.JSON).v1();
fields = GetResultTests.randomDocumentFields(xContentType).v2();
}
SearchHit hit = new SearchHit(internalId, uid, type, nestedIdentity, fields);
if (frequently()) {
@ -82,7 +86,7 @@ public class SearchHitTests extends AbstractStreamableTestCase<SearchHit> {
}
}
if (frequently()) {
hit.sourceRef(RandomObjects.randomSource(random()));
hit.sourceRef(RandomObjects.randomSource(random(), xContentType));
}
if (randomBoolean()) {
hit.version(randomLong());
@ -115,12 +119,13 @@ public class SearchHitTests extends AbstractStreamableTestCase<SearchHit> {
if (innerHitsSize > 0) {
Map<String, SearchHits> innerHits = new HashMap<>(innerHitsSize);
for (int i = 0; i < innerHitsSize; i++) {
innerHits.put(randomAlphaOfLength(5), SearchHitsTests.createTestItem());
innerHits.put(randomAlphaOfLength(5),
SearchHitsTests.createTestItem(xContentType, false, withShardTarget));
}
hit.setInnerHits(innerHits);
}
}
if (randomBoolean()) {
if (withShardTarget && randomBoolean()) {
String index = randomAlphaOfLengthBetween(5, 10);
String clusterAlias = randomBoolean() ? null : randomAlphaOfLengthBetween(5, 10);
hit.shard(new SearchShardTarget(randomAlphaOfLengthBetween(5, 10),
@ -136,13 +141,13 @@ public class SearchHitTests extends AbstractStreamableTestCase<SearchHit> {
@Override
protected SearchHit createTestInstance() {
return createTestItem(randomBoolean());
return createTestItem(randomFrom(XContentType.values()), randomBoolean(), randomBoolean());
}
public void testFromXContent() throws IOException {
SearchHit searchHit = createTestItem(true);
boolean humanReadable = randomBoolean();
XContentType xContentType = randomFrom(XContentType.values());
SearchHit searchHit = createTestItem(xContentType, true, false);
boolean humanReadable = randomBoolean();
BytesReference originalBytes = toShuffledXContent(searchHit, xContentType, ToXContent.EMPTY_PARAMS, humanReadable);
SearchHit parsed;
try (XContentParser parser = createParser(xContentType.xContent(), originalBytes)) {
@ -164,8 +169,8 @@ public class SearchHitTests extends AbstractStreamableTestCase<SearchHit> {
* which are already tested elsewhere.
*/
public void testFromXContentLenientParsing() throws IOException {
SearchHit searchHit = createTestItem(true);
XContentType xContentType = randomFrom(XContentType.values());
SearchHit searchHit = createTestItem(xContentType, true, true);
BytesReference originalBytes = toXContent(searchHit, xContentType, true);
Predicate<String> pathsToExclude = path -> (path.endsWith("highlight") || path.endsWith("fields") || path.contains("_source")
|| path.contains("inner_hits"));
@ -345,7 +350,7 @@ public class SearchHitTests extends AbstractStreamableTestCase<SearchHit> {
}
}
private static Explanation createExplanation(int depth) {
static Explanation createExplanation(int depth) {
String description = randomAlphaOfLengthBetween(5, 20);
float value = randomFloat();
List<Explanation> details = new ArrayList<>();

View File

@ -21,88 +21,188 @@ package org.elasticsearch.search;
import org.apache.lucene.search.TotalHits;
import org.apache.lucene.util.TestUtil;
import org.elasticsearch.action.OriginalIndices;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.xcontent.LoggingDeprecationHandler;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.test.AbstractStreamableTestCase;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.index.Index;
import org.elasticsearch.index.shard.ShardId;
import org.elasticsearch.test.AbstractStreamableXContentTestCase;
import java.io.IOException;
import java.util.Collections;
import java.util.function.Predicate;
import static org.elasticsearch.common.xcontent.XContentHelper.toXContent;
import static org.elasticsearch.test.XContentTestUtils.insertRandomFields;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertToXContentEquivalent;
public class SearchHitsTests extends AbstractStreamableXContentTestCase<SearchHits> {
public static SearchHits createTestItem(boolean withOptionalInnerHits, boolean withShardTarget) {
return createTestItem(randomFrom(XContentType.values()), withOptionalInnerHits, withShardTarget);
}
public class SearchHitsTests extends AbstractStreamableTestCase<SearchHits> {
public static SearchHits createTestItem() {
int searchHits = randomIntBetween(0, 5);
SearchHit[] hits = new SearchHit[searchHits];
for (int i = 0; i < searchHits; i++) {
hits[i] = SearchHitTests.createTestItem(false); // creating random innerHits could create loops
private static SearchHit[] createSearchHitArray(int size, XContentType xContentType, boolean withOptionalInnerHits,
boolean withShardTarget) {
SearchHit[] hits = new SearchHit[size];
for (int i = 0; i < hits.length; i++) {
hits[i] = SearchHitTests.createTestItem(xContentType, withOptionalInnerHits, withShardTarget);
}
return hits;
}
private static TotalHits randomTotalHits() {
long totalHits = TestUtil.nextLong(random(), 0, Long.MAX_VALUE);
TotalHits.Relation relation = randomFrom(TotalHits.Relation.values());
float maxScore = frequently() ? randomFloat() : Float.NaN;
return new TotalHits(totalHits, relation);
}
return new SearchHits(hits, frequently() ? new TotalHits(totalHits, relation) : null, maxScore);
public static SearchHits createTestItem(XContentType xContentType, boolean withOptionalInnerHits, boolean withShardTarget) {
int searchHits = randomIntBetween(0, 5);
SearchHit[] hits = createSearchHitArray(searchHits, xContentType, withOptionalInnerHits, withShardTarget);
float maxScore = frequently() ? randomFloat() : Float.NaN;
return new SearchHits(hits, frequently() ? randomTotalHits() : null, maxScore);
}
@Override
protected SearchHits mutateInstance(SearchHits instance) {
switch (randomIntBetween(0, 2)) {
case 0:
return new SearchHits(createSearchHitArray(instance.getHits().length + 1,
randomFrom(XContentType.values()), false, randomBoolean()),
instance.getTotalHits(), instance.getMaxScore());
case 1:
final TotalHits totalHits;
if (instance.getTotalHits() == null) {
totalHits = randomTotalHits();
} else {
totalHits = null;
}
return new SearchHits(instance.getHits(), totalHits, instance.getMaxScore());
case 2:
final float maxScore;
if (Float.isNaN(instance.getMaxScore())) {
maxScore = randomFloat();
} else {
maxScore = Float.NaN;
}
return new SearchHits(instance.getHits(), instance.getTotalHits(), maxScore);
default:
throw new UnsupportedOperationException();
}
}
@Override
protected Predicate<String> getRandomFieldsExcludeFilter() {
return path -> (path.isEmpty() ||
path.contains("inner_hits") || path.contains("highlight") || path.contains("fields") || path.contains("_source"));
}
@Override
protected String[] getShuffleFieldsExceptions() {
return new String[] {"_source"};
}
@Override
protected SearchHits createBlankInstance() {
return new SearchHits(new SearchHit[0], new TotalHits(0, TotalHits.Relation.EQUAL_TO), Float.NaN);
return new SearchHits();
}
@Override
protected SearchHits createTestInstance() {
return createTestItem();
// This instance is used to test the transport serialization so it's fine
// to produce shard targets (withShardTarget is true) since they are serialized
// in this layer.
return createTestItem(randomFrom(XContentType.values()), true, true);
}
public void testFromXContent() throws IOException {
SearchHits searchHits = createTestItem();
XContentType xcontentType = randomFrom(XContentType.values());
boolean humanReadable = randomBoolean();
BytesReference originalBytes = toShuffledXContent(searchHits, xcontentType, ToXContent.EMPTY_PARAMS, humanReadable);
SearchHits parsed;
try (XContentParser parser = createParser(xcontentType.xContent(), originalBytes)) {
assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken());
assertEquals(XContentParser.Token.FIELD_NAME, parser.nextToken());
assertEquals(SearchHits.Fields.HITS, parser.currentName());
assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken());
parsed = SearchHits.fromXContent(parser);
assertEquals(XContentParser.Token.END_OBJECT, parser.currentToken());
assertEquals(XContentParser.Token.END_OBJECT, parser.nextToken());
assertNull(parser.nextToken());
}
assertToXContentEquivalent(originalBytes, toXContent(parsed, xcontentType, humanReadable), xcontentType);
@Override
protected SearchHits createXContextTestInstance(XContentType xContentType) {
// We don't set SearchHit#shard (withShardTarget is false) in this test
// because the rest serialization does not render this information so the
// deserialized hit cannot be equal to the original instance.
// There is another test (#testFromXContentWithShards) that checks the
// rest serialization with shard targets.
return createTestItem(xContentType,true, false);
}
/**
* This test adds randomized fields on all json objects and checks that we
* can parse it to ensure the parsing is lenient for forward compatibility.
* We need to exclude json objects with the "highlight" and "fields" field
* name since these objects allow arbitrary keys (the field names that are
* queries). Also we want to exclude to add anything under "_source" since
* it is not parsed.
*/
public void testFromXContentLenientParsing() throws IOException {
SearchHits searchHits = createTestItem();
XContentType xcontentType = randomFrom(XContentType.values());
BytesReference originalBytes = toXContent(searchHits, xcontentType, ToXContent.EMPTY_PARAMS, true);
Predicate<String> pathsToExclude = path -> (path.isEmpty() || path.endsWith("highlight") || path.endsWith("fields")
|| path.contains("_source"));
BytesReference withRandomFields = insertRandomFields(xcontentType, originalBytes, pathsToExclude, random());
SearchHits parsed = null;
try (XContentParser parser = createParser(xcontentType.xContent(), withRandomFields)) {
assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken());
assertEquals(XContentParser.Token.FIELD_NAME, parser.nextToken());
assertEquals(SearchHits.Fields.HITS, parser.currentName());
assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken());
parsed = SearchHits.fromXContent(parser);
assertEquals(XContentParser.Token.END_OBJECT, parser.currentToken());
assertEquals(XContentParser.Token.END_OBJECT, parser.nextToken());
assertNull(parser.nextToken());
@Override
protected SearchHits doParseInstance(XContentParser parser) throws IOException {
assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken());
assertEquals(XContentParser.Token.FIELD_NAME, parser.nextToken());
assertEquals(SearchHits.Fields.HITS, parser.currentName());
assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken());
SearchHits searchHits = SearchHits.fromXContent(parser);
assertEquals(XContentParser.Token.END_OBJECT, parser.currentToken());
assertEquals(XContentParser.Token.END_OBJECT, parser.nextToken());
return searchHits;
}
public void testToXContent() throws IOException {
SearchHit[] hits = new SearchHit[] {
new SearchHit(1, "id1", new Text("type"), Collections.emptyMap()),
new SearchHit(2, "id2", new Text("type"), Collections.emptyMap()) };
long totalHits = 1000;
float maxScore = 1.5f;
SearchHits searchHits = new SearchHits(hits, new TotalHits(totalHits, TotalHits.Relation.EQUAL_TO), maxScore);
XContentBuilder builder = JsonXContent.contentBuilder();
builder.startObject();
searchHits.toXContent(builder, ToXContent.EMPTY_PARAMS);
builder.endObject();
assertEquals("{\"hits\":{\"total\":{\"value\":1000,\"relation\":\"eq\"},\"max_score\":1.5," +
"\"hits\":[{\"_type\":\"type\",\"_id\":\"id1\",\"_score\":null},"+
"{\"_type\":\"type\",\"_id\":\"id2\",\"_score\":null}]}}", Strings.toString(builder));
}
public void testFromXContentWithShards() throws IOException {
for (boolean withExplanation : new boolean[] {true, false}) {
final SearchHit[] hits = new SearchHit[]{
new SearchHit(1, "id1", new Text("type"), Collections.emptyMap()),
new SearchHit(2, "id2", new Text("type"), Collections.emptyMap()),
new SearchHit(10, "id10", new Text("type"), Collections.emptyMap())
};
for (SearchHit hit : hits) {
String index = randomAlphaOfLengthBetween(5, 10);
String clusterAlias = randomBoolean() ? null : randomAlphaOfLengthBetween(5, 10);
final SearchShardTarget shardTarget = new SearchShardTarget(randomAlphaOfLengthBetween(5, 10),
new ShardId(new Index(index, randomAlphaOfLengthBetween(5, 10)), randomInt()), clusterAlias, OriginalIndices.NONE);
if (withExplanation) {
hit.explanation(SearchHitTests.createExplanation(randomIntBetween(0, 5)));
}
hit.shard(shardTarget);
}
long totalHits = 1000;
float maxScore = 1.5f;
SearchHits searchHits = new SearchHits(hits, new TotalHits(totalHits, TotalHits.Relation.EQUAL_TO), maxScore);
XContentType xContentType = randomFrom(XContentType.values());
BytesReference bytes = toShuffledXContent(searchHits, xContentType, ToXContent.EMPTY_PARAMS, false);
try (XContentParser parser = xContentType.xContent()
.createParser(xContentRegistry(), LoggingDeprecationHandler.INSTANCE, bytes.streamInput())) {
SearchHits newSearchHits = doParseInstance(parser);
assertEquals(3, newSearchHits.getHits().length);
assertEquals("id1", newSearchHits.getAt(0).getId());
for (int i = 0; i < hits.length; i++) {
assertEquals(hits[i].getExplanation(), newSearchHits.getAt(i).getExplanation());
if (withExplanation) {
assertEquals(hits[i].getShard().getIndex(), newSearchHits.getAt(i).getShard().getIndex());
assertEquals(hits[i].getShard().getShardId().getId(), newSearchHits.getAt(i).getShard().getShardId().getId());
assertEquals(hits[i].getShard().getShardId().getIndexName(),
newSearchHits.getAt(i).getShard().getShardId().getIndexName());
assertEquals(hits[i].getShard().getNodeId(), newSearchHits.getAt(i).getShard().getNodeId());
// The index uuid is not serialized in the rest layer
assertNotEquals(hits[i].getShard().getShardId().getIndex().getUUID(),
newSearchHits.getAt(i).getShard().getShardId().getIndex().getUUID());
} else {
assertNull(newSearchHits.getAt(i).getShard());
}
}
}
}
assertToXContentEquivalent(originalBytes, toXContent(parsed, xcontentType, true), xcontentType);
}
}

View File

@ -60,7 +60,7 @@ public class CompletionSuggestionOptionTests extends ESTestCase {
SearchHit hit = null;
float score = randomFloat();
if (randomBoolean()) {
hit = SearchHitTests.createTestItem(false);
hit = SearchHitTests.createTestItem(false, true);
score = hit.getScore();
}
Option option = new CompletionSuggestion.Entry.Option(docId, text, score, contexts);

View File

@ -23,10 +23,13 @@ import org.elasticsearch.common.io.stream.Streamable;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import java.io.IOException;
import java.util.function.Predicate;
import static org.elasticsearch.test.AbstractXContentTestCase.xContentTester;
public abstract class AbstractStreamableXContentTestCase<T extends ToXContent & Streamable> extends AbstractStreamableTestCase<T> {
/**
@ -34,9 +37,23 @@ public abstract class AbstractStreamableXContentTestCase<T extends ToXContent &
* both for equality and asserts equality on the two queries.
*/
public final void testFromXContent() throws IOException {
AbstractXContentTestCase.testFromXContent(NUMBER_OF_TEST_RUNS, this::createTestInstance, supportsUnknownFields(),
getShuffleFieldsExceptions(), getRandomFieldsExcludeFilter(), this::createParser, this::doParseInstance,
this::assertEqualInstances, true, getToXContentParams());
xContentTester(this::createParser, this::createXContextTestInstance, getToXContentParams(), this::doParseInstance)
.numberOfTestRuns(NUMBER_OF_TEST_RUNS)
.supportsUnknownFields(supportsUnknownFields())
.shuffleFieldsExceptions(getShuffleFieldsExceptions())
.randomFieldsExcludeFilter(getRandomFieldsExcludeFilter())
.assertEqualsConsumer(this::assertEqualInstances)
.assertToXContentEquivalence(true)
.test();
}
/**
* Creates a random instance to use in the xcontent tests.
* Override this method if the random instance that you build
* should be aware of the {@link XContentType} used in the test.
*/
protected T createXContextTestInstance(XContentType xContentType) {
return createTestInstance();
}
/**

View File

@ -34,6 +34,7 @@ import org.elasticsearch.common.xcontent.XContentType;
import java.io.IOException;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
@ -48,16 +49,16 @@ public abstract class AbstractXContentTestCase<T extends ToXContent> extends EST
Supplier<T> instanceSupplier,
CheckedBiConsumer<T, XContentBuilder, IOException> toXContent,
CheckedFunction<XContentParser, T, IOException> fromXContent) {
return new XContentTester<T>(
createParser,
instanceSupplier,
(testInstance, xContentType) -> {
try (XContentBuilder builder = XContentBuilder.builder(xContentType.xContent())) {
toXContent.accept(testInstance, builder);
return BytesReference.bytes(builder);
}
},
fromXContent);
return new XContentTester<>(
createParser,
x -> instanceSupplier.get(),
(testInstance, xContentType) -> {
try (XContentBuilder builder = XContentBuilder.builder(xContentType.xContent())) {
toXContent.accept(testInstance, builder);
return BytesReference.bytes(builder);
}
},
fromXContent);
}
public static <T extends ToXContent> XContentTester<T> xContentTester(
@ -72,12 +73,25 @@ public abstract class AbstractXContentTestCase<T extends ToXContent> extends EST
Supplier<T> instanceSupplier,
ToXContent.Params toXContentParams,
CheckedFunction<XContentParser, T, IOException> fromXContent) {
return new XContentTester<T>(
createParser,
instanceSupplier,
(testInstance, xContentType) ->
XContentHelper.toXContent(testInstance, xContentType, toXContentParams, false),
fromXContent);
return new XContentTester<>(
createParser,
x -> instanceSupplier.get(),
(testInstance, xContentType) ->
XContentHelper.toXContent(testInstance, xContentType, toXContentParams, false),
fromXContent);
}
public static <T extends ToXContent> XContentTester<T> xContentTester(
CheckedBiFunction<XContent, BytesReference, XContentParser, IOException> createParser,
Function<XContentType, T> instanceSupplier,
ToXContent.Params toXContentParams,
CheckedFunction<XContentParser, T, IOException> fromXContent) {
return new XContentTester<>(
createParser,
instanceSupplier,
(testInstance, xContentType) ->
XContentHelper.toXContent(testInstance, xContentType, toXContentParams, false),
fromXContent);
}
/**
@ -85,7 +99,7 @@ public abstract class AbstractXContentTestCase<T extends ToXContent> extends EST
*/
public static class XContentTester<T> {
private final CheckedBiFunction<XContent, BytesReference, XContentParser, IOException> createParser;
private final Supplier<T> instanceSupplier;
private final Function<XContentType, T> instanceSupplier;
private final CheckedBiFunction<T, XContentType, BytesReference, IOException> toXContent;
private final CheckedFunction<XContentParser, T, IOException> fromXContent;
@ -102,7 +116,7 @@ public abstract class AbstractXContentTestCase<T extends ToXContent> extends EST
private XContentTester(
CheckedBiFunction<XContent, BytesReference, XContentParser, IOException> createParser,
Supplier<T> instanceSupplier,
Function<XContentType, T> instanceSupplier,
CheckedBiFunction<T, XContentType, BytesReference, IOException> toXContent,
CheckedFunction<XContentParser, T, IOException> fromXContent) {
this.createParser = createParser;
@ -113,8 +127,8 @@ public abstract class AbstractXContentTestCase<T extends ToXContent> extends EST
public void test() throws IOException {
for (int runs = 0; runs < numberOfTestRuns; runs++) {
T testInstance = instanceSupplier.get();
XContentType xContentType = randomFrom(XContentType.values());
T testInstance = instanceSupplier.apply(xContentType);
BytesReference originalXContent = toXContent.apply(testInstance, xContentType);
BytesReference shuffledContent = insertRandomFieldsAndShuffle(originalXContent, xContentType, supportsUnknownFields,
shuffleFieldsExceptions, randomFieldsExcludeFilter, createParser);