org.elasticsearch.test.AbstractXContentTestCase Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of framework Show documentation
Show all versions of framework Show documentation
Elasticsearch subproject :test:framework
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
package org.elasticsearch.test;
import org.elasticsearch.common.CheckedBiConsumer;
import org.elasticsearch.common.CheckedBiFunction;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.xcontent.ChunkedToXContent;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.core.CheckedFunction;
import org.elasticsearch.core.Predicates;
import org.elasticsearch.xcontent.ToXContent;
import org.elasticsearch.xcontent.XContent;
import org.elasticsearch.xcontent.XContentBuilder;
import org.elasticsearch.xcontent.XContentFactory;
import org.elasticsearch.xcontent.XContentParser;
import org.elasticsearch.xcontent.XContentType;
import java.io.IOException;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertToXContentEquivalent;
public abstract class AbstractXContentTestCase extends ESTestCase {
public static final int NUMBER_OF_TEST_RUNS = 20;
public static XContentTester xContentTester(
CheckedBiFunction createParser,
Supplier instanceSupplier,
CheckedBiConsumer toXContent,
CheckedFunction 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 XContentTester xContentTester(
CheckedBiFunction createParser,
Supplier instanceSupplier,
CheckedFunction fromXContent
) {
return xContentTester(createParser, instanceSupplier, ToXContent.EMPTY_PARAMS, fromXContent);
}
public static XContentTester xContentTester(
CheckedBiFunction createParser,
Supplier instanceSupplier,
ToXContent.Params toXContentParams,
CheckedFunction fromXContent
) {
return new XContentTester<>(
createParser,
x -> instanceSupplier.get(),
(testInstance, xContentType) -> XContentHelper.toXContent(testInstance, xContentType, toXContentParams, false),
fromXContent
);
}
public static XContentTester xContentTester(
CheckedBiFunction createParser,
Function instanceSupplier,
ToXContent.Params toXContentParams,
CheckedFunction fromXContent
) {
return new XContentTester<>(
createParser,
instanceSupplier,
(testInstance, xContentType) -> XContentHelper.toXContent(testInstance, xContentType, toXContentParams, false),
fromXContent
);
}
public static XContentTester chunkedXContentTester(
CheckedBiFunction createParser,
Function instanceSupplier,
ToXContent.Params toXContentParams,
CheckedFunction fromXContent
) {
return new XContentTester<>(createParser, instanceSupplier, (testInstance, xContentType) -> {
try (XContentBuilder builder = XContentBuilder.builder(xContentType.xContent())) {
var serialization = testInstance.toXContentChunked(toXContentParams);
if (testInstance.isFragment()) {
builder.startObject();
}
while (serialization.hasNext()) {
serialization.next().toXContent(builder, toXContentParams);
}
if (testInstance.isFragment()) {
builder.endObject();
}
return BytesReference.bytes(builder);
}
}, fromXContent);
}
/**
* Tests converting to and from xcontent.
*/
public static class XContentTester {
private final CheckedBiFunction createParser;
private final Function instanceSupplier;
private final CheckedBiFunction toXContent;
private final CheckedFunction fromXContent;
private int numberOfTestRuns = NUMBER_OF_TEST_RUNS;
private boolean supportsUnknownFields = false;
private String[] shuffleFieldsExceptions = Strings.EMPTY_ARRAY;
private Predicate randomFieldsExcludeFilter = field -> false;
private BiConsumer assertEqualsConsumer = (expectedInstance, newInstance) -> {
assertNotSame(newInstance, expectedInstance);
assertEquals(expectedInstance, newInstance);
assertEquals(expectedInstance.hashCode(), newInstance.hashCode());
};
private boolean assertToXContentEquivalence = true;
private Consumer dispose = t -> {};
private XContentTester(
CheckedBiFunction createParser,
Function instanceSupplier,
CheckedBiFunction toXContent,
CheckedFunction fromXContent
) {
this.createParser = createParser;
this.instanceSupplier = instanceSupplier;
this.toXContent = toXContent;
this.fromXContent = fromXContent;
}
public void test() throws IOException {
for (int runs = 0; runs < numberOfTestRuns; runs++) {
XContentType xContentType = randomFrom(XContentType.values()).canonical();
T testInstance = instanceSupplier.apply(xContentType);
try {
BytesReference originalXContent = toXContent.apply(testInstance, xContentType);
BytesReference shuffledContent = insertRandomFieldsAndShuffle(
originalXContent,
xContentType,
supportsUnknownFields,
shuffleFieldsExceptions,
randomFieldsExcludeFilter,
createParser
);
final T parsed;
try (XContentParser parser = createParser.apply(XContentFactory.xContent(xContentType), shuffledContent)) {
parsed = fromXContent.apply(parser);
}
try {
assertEqualsConsumer.accept(testInstance, parsed);
if (assertToXContentEquivalence) {
assertToXContentEquivalent(
toXContent.apply(testInstance, xContentType),
toXContent.apply(parsed, xContentType),
xContentType
);
}
} finally {
dispose.accept(parsed);
}
} finally {
dispose.accept(testInstance);
}
}
}
public XContentTester numberOfTestRuns(int numberOfTestRuns) {
this.numberOfTestRuns = numberOfTestRuns;
return this;
}
public XContentTester supportsUnknownFields(boolean supportsUnknownFields) {
this.supportsUnknownFields = supportsUnknownFields;
return this;
}
public XContentTester shuffleFieldsExceptions(String[] shuffleFieldsExceptions) {
this.shuffleFieldsExceptions = shuffleFieldsExceptions;
return this;
}
public XContentTester randomFieldsExcludeFilter(Predicate randomFieldsExcludeFilter) {
this.randomFieldsExcludeFilter = randomFieldsExcludeFilter;
return this;
}
public XContentTester assertEqualsConsumer(BiConsumer assertEqualsConsumer) {
this.assertEqualsConsumer = assertEqualsConsumer;
return this;
}
public XContentTester assertToXContentEquivalence(boolean assertToXContentEquivalence) {
this.assertToXContentEquivalence = assertToXContentEquivalence;
return this;
}
public XContentTester dispose(Consumer dispose) {
this.dispose = dispose;
return this;
}
}
public static void testFromXContent(
int numberOfTestRuns,
Supplier instanceSupplier,
boolean supportsUnknownFields,
String[] shuffleFieldsExceptions,
Predicate randomFieldsExcludeFilter,
CheckedBiFunction createParserFunction,
CheckedFunction fromXContent,
BiConsumer assertEqualsConsumer,
boolean assertToXContentEquivalence,
ToXContent.Params toXContentParams
) throws IOException {
testFromXContent(
numberOfTestRuns,
instanceSupplier,
supportsUnknownFields,
shuffleFieldsExceptions,
randomFieldsExcludeFilter,
createParserFunction,
fromXContent,
assertEqualsConsumer,
assertToXContentEquivalence,
toXContentParams,
t -> {}
);
}
public static void testFromXContent(
int numberOfTestRuns,
Supplier instanceSupplier,
boolean supportsUnknownFields,
String[] shuffleFieldsExceptions,
Predicate randomFieldsExcludeFilter,
CheckedBiFunction createParserFunction,
CheckedFunction fromXContent,
BiConsumer assertEqualsConsumer,
boolean assertToXContentEquivalence,
ToXContent.Params toXContentParams,
Consumer dispose
) throws IOException {
xContentTester(createParserFunction, instanceSupplier, toXContentParams, fromXContent).numberOfTestRuns(numberOfTestRuns)
.supportsUnknownFields(supportsUnknownFields)
.shuffleFieldsExceptions(shuffleFieldsExceptions)
.randomFieldsExcludeFilter(randomFieldsExcludeFilter)
.assertEqualsConsumer(assertEqualsConsumer)
.assertToXContentEquivalence(assertToXContentEquivalence)
.dispose(dispose)
.test();
}
/**
* Generic test that creates new instance from the test instance and checks
* both for equality and asserts equality on the two queries.
*/
public final void testFromXContent() throws IOException {
testFromXContent(
NUMBER_OF_TEST_RUNS,
this::createTestInstance,
supportsUnknownFields(),
getShuffleFieldsExceptions(),
getRandomFieldsExcludeFilter(),
this::createParser,
this::parseInstance,
this::assertEqualInstances,
assertToXContentEquivalence(),
getToXContentParams(),
this::dispose
);
}
/**
* Callback invoked after a test instance is no longer needed that can be overridden to release resources associated with the instance.
* @param instance test instance that is no longer used
*/
protected void dispose(T instance) {}
/**
* Creates a random test instance to use in the tests. This method will be
* called multiple times during test execution and should return a different
* random instance each time it is called.
*/
protected abstract T createTestInstance();
private T parseInstance(XContentParser parser) throws IOException {
T parsedInstance = doParseInstance(parser);
assertNull(parser.nextToken());
return parsedInstance;
}
/**
* Parses to a new instance using the provided {@link XContentParser}
*/
protected abstract T doParseInstance(XContentParser parser) throws IOException;
protected void assertEqualInstances(T expectedInstance, T newInstance) {
assertNotSame(newInstance, expectedInstance);
assertEquals(expectedInstance, newInstance);
assertEquals(expectedInstance.hashCode(), newInstance.hashCode());
}
protected boolean assertToXContentEquivalence() {
return true;
}
/**
* Indicates whether the parser supports unknown fields or not. In case it does, such behaviour will be tested by
* inserting random fields before parsing and checking that they don't make parsing fail.
*/
protected abstract boolean supportsUnknownFields();
/**
* Returns a predicate that given the field name indicates whether the field has to be excluded from random fields insertion or not
*/
protected Predicate getRandomFieldsExcludeFilter() {
return Predicates.never();
}
/**
* Fields that have to be ignored when shuffling as part of testFromXContent
*/
protected String[] getShuffleFieldsExceptions() {
return Strings.EMPTY_ARRAY;
}
/**
* Params that have to be provided when calling {@link ToXContent#toXContent(XContentBuilder, ToXContent.Params)}
*/
protected ToXContent.Params getToXContentParams() {
return ToXContent.EMPTY_PARAMS;
}
static BytesReference insertRandomFieldsAndShuffle(
BytesReference xContent,
XContentType xContentType,
boolean supportsUnknownFields,
String[] shuffleFieldsExceptions,
Predicate randomFieldsExcludeFilter,
CheckedBiFunction createParserFunction
) throws IOException {
BytesReference withRandomFields;
if (supportsUnknownFields) {
// add a few random fields to check that the parser is lenient on new fields
withRandomFields = XContentTestUtils.insertRandomFields(xContentType, xContent, randomFieldsExcludeFilter, random());
} else {
withRandomFields = xContent;
}
try (XContentParser parserWithRandomFields = createParserFunction.apply(XContentFactory.xContent(xContentType), withRandomFields)) {
return BytesReference.bytes(ESTestCase.shuffleXContent(parserWithRandomFields, false, shuffleFieldsExceptions));
}
}
}