[TEST] share code between streamable/writeable/xcontent base test classes (#28785)
Today we have two test base classes that have a lot in common when it comes to testing wire and xcontent serialization: `AbstractSerializingTestCase` and `AbstractXContentStreamableTestCase`. There are subtle differences though between the two, in the way they work, what can be overridden and features that they support (e.g. insertion of random fields). This commit introduces a new base class called `AbstractWireTestCase` which holds all of the serialization test code in common between `Streamable` and `Writeable`. It has two minimal subclasses called `AbstractWireSerializingTestCase` and `AbstractStreamableTestCase` which are specialized for `Writeable` and `Streamable`. This commit also introduces a new test class called `AbstractXContentTestCase` for all of the xContent testing, which holds a testFromXContent method for parsing and rendering to xContent. This one can be delegated to from the existing `AbstractStreamableXContentTestCase` and `AbstractSerializingTestCase` so that we avoid code duplicate as much as possible and all these base classes offer the same functionalities in the same way. Having this last base class decoupled from the serialization testing may also help with the REST high-level client testing, as there are some classes where it's hard to implement equals/hashcode and this makes it possible to override `assertEqualInstances` for custom equality comparisons (also this base class doesn't require implementing equals/hashcode as it doesn't test such methods.
This commit is contained in:
parent
3728c50d85
commit
cd3d9c9f80
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.elasticsearch.common;
|
||||
|
||||
/**
|
||||
* A {@link java.util.function.BiFunction}-like interface which allows throwing checked exceptions.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface CheckedBiFunction<T, U, R, E extends Exception> {
|
||||
R apply(T t, U u) throws E;
|
||||
}
|
|
@ -205,8 +205,8 @@ public final class PersistentTasksCustomMetaData extends AbstractNamedDiffable<M
|
|||
return ALL_CONTEXTS;
|
||||
}
|
||||
|
||||
public static PersistentTasksCustomMetaData fromXContent(XContentParser parser) throws IOException {
|
||||
return PERSISTENT_TASKS_PARSER.parse(parser, null).build();
|
||||
public static PersistentTasksCustomMetaData fromXContent(XContentParser parser) {
|
||||
return PERSISTENT_TASKS_PARSER.apply(parser, null).build();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
|
|
@ -318,7 +318,7 @@ public class StoredScriptSource extends AbstractDiffable<StoredScriptSource> imp
|
|||
* Note that the "source" parameter can also handle template parsing including from
|
||||
* a complex JSON object.
|
||||
*/
|
||||
public static StoredScriptSource fromXContent(XContentParser parser) throws IOException {
|
||||
public static StoredScriptSource fromXContent(XContentParser parser) {
|
||||
return PARSER.apply(parser, null).build();
|
||||
}
|
||||
|
||||
|
|
|
@ -121,9 +121,8 @@ public class CollapseBuilder implements Writeable, ToXContentObject {
|
|||
}
|
||||
}
|
||||
|
||||
public static CollapseBuilder fromXContent(XContentParser parser) throws IOException {
|
||||
CollapseBuilder builder = PARSER.parse(parser, new CollapseBuilder(), null);
|
||||
return builder;
|
||||
public static CollapseBuilder fromXContent(XContentParser parser) {
|
||||
return PARSER.apply(parser, null);
|
||||
}
|
||||
|
||||
// for object parser only
|
||||
|
|
|
@ -25,7 +25,6 @@ import org.elasticsearch.common.settings.Settings;
|
|||
import org.elasticsearch.common.settings.Settings.Builder;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.test.AbstractStreamableXContentTestCase;
|
||||
import org.elasticsearch.test.EqualsHashCodeTestUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
@ -39,8 +38,7 @@ public class ClusterUpdateSettingsResponseTests extends AbstractStreamableXConte
|
|||
}
|
||||
|
||||
@Override
|
||||
protected EqualsHashCodeTestUtils.MutateFunction<ClusterUpdateSettingsResponse> getMutateFunction() {
|
||||
return response -> {
|
||||
protected ClusterUpdateSettingsResponse mutateInstance(ClusterUpdateSettingsResponse response) {
|
||||
int i = randomIntBetween(0, 2);
|
||||
switch(i) {
|
||||
case 0:
|
||||
|
@ -55,7 +53,6 @@ public class ClusterUpdateSettingsResponseTests extends AbstractStreamableXConte
|
|||
default:
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static Settings mutateSettings(Settings settings) {
|
||||
|
|
|
@ -21,7 +21,6 @@ package org.elasticsearch.action.admin.indices.alias;
|
|||
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.test.AbstractStreamableXContentTestCase;
|
||||
import org.elasticsearch.test.EqualsHashCodeTestUtils;
|
||||
|
||||
public class IndicesAliasesResponseTests extends AbstractStreamableXContentTestCase<IndicesAliasesResponse> {
|
||||
|
||||
|
@ -41,7 +40,7 @@ public class IndicesAliasesResponseTests extends AbstractStreamableXContentTestC
|
|||
}
|
||||
|
||||
@Override
|
||||
protected EqualsHashCodeTestUtils.MutateFunction<IndicesAliasesResponse> getMutateFunction() {
|
||||
return response -> new IndicesAliasesResponse(response.isAcknowledged() == false);
|
||||
protected IndicesAliasesResponse mutateInstance(IndicesAliasesResponse response) {
|
||||
return new IndicesAliasesResponse(response.isAcknowledged() == false);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@ package org.elasticsearch.action.admin.indices.close;
|
|||
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.test.AbstractStreamableXContentTestCase;
|
||||
import org.elasticsearch.test.EqualsHashCodeTestUtils;
|
||||
|
||||
public class CloseIndexResponseTests extends AbstractStreamableXContentTestCase<CloseIndexResponse> {
|
||||
|
||||
|
@ -41,7 +40,7 @@ public class CloseIndexResponseTests extends AbstractStreamableXContentTestCase<
|
|||
}
|
||||
|
||||
@Override
|
||||
protected EqualsHashCodeTestUtils.MutateFunction<CloseIndexResponse> getMutateFunction() {
|
||||
return response -> new CloseIndexResponse(response.isAcknowledged() == false);
|
||||
protected CloseIndexResponse mutateInstance(CloseIndexResponse response) {
|
||||
return new CloseIndexResponse(response.isAcknowledged() == false);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,6 @@ import org.elasticsearch.common.io.stream.StreamInput;
|
|||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.common.xcontent.json.JsonXContent;
|
||||
import org.elasticsearch.test.AbstractStreamableXContentTestCase;
|
||||
import org.elasticsearch.test.EqualsHashCodeTestUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
@ -46,8 +45,7 @@ public class CreateIndexResponseTests extends AbstractStreamableXContentTestCase
|
|||
}
|
||||
|
||||
@Override
|
||||
protected EqualsHashCodeTestUtils.MutateFunction<CreateIndexResponse> getMutateFunction() {
|
||||
return response -> {
|
||||
protected CreateIndexResponse mutateInstance(CreateIndexResponse response) {
|
||||
if (randomBoolean()) {
|
||||
if (randomBoolean()) {
|
||||
boolean acknowledged = response.isAcknowledged() == false;
|
||||
|
@ -62,7 +60,6 @@ public class CreateIndexResponseTests extends AbstractStreamableXContentTestCase
|
|||
return new CreateIndexResponse(response.isAcknowledged(), response.isShardsAcknowledged(),
|
||||
response.index() + randomAlphaOfLengthBetween(2, 5));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -22,7 +22,6 @@ package org.elasticsearch.action.admin.indices.delete;
|
|||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.test.AbstractStreamableXContentTestCase;
|
||||
import org.elasticsearch.test.EqualsHashCodeTestUtils;
|
||||
|
||||
public class DeleteIndexResponseTests extends AbstractStreamableXContentTestCase<DeleteIndexResponse> {
|
||||
|
||||
|
@ -48,7 +47,7 @@ public class DeleteIndexResponseTests extends AbstractStreamableXContentTestCase
|
|||
}
|
||||
|
||||
@Override
|
||||
protected EqualsHashCodeTestUtils.MutateFunction<DeleteIndexResponse> getMutateFunction() {
|
||||
return response -> new DeleteIndexResponse(response.isAcknowledged() == false);
|
||||
protected DeleteIndexResponse mutateInstance(DeleteIndexResponse response) {
|
||||
return new DeleteIndexResponse(response.isAcknowledged() == false);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,6 @@ package org.elasticsearch.action.admin.indices.mapping.put;
|
|||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.test.AbstractStreamableXContentTestCase;
|
||||
import org.elasticsearch.test.EqualsHashCodeTestUtils;
|
||||
|
||||
public class PutMappingResponseTests extends AbstractStreamableXContentTestCase<PutMappingResponse> {
|
||||
|
||||
|
@ -48,7 +47,7 @@ public class PutMappingResponseTests extends AbstractStreamableXContentTestCase<
|
|||
}
|
||||
|
||||
@Override
|
||||
protected EqualsHashCodeTestUtils.MutateFunction<PutMappingResponse> getMutateFunction() {
|
||||
return response -> new PutMappingResponse(response.isAcknowledged() == false);
|
||||
protected PutMappingResponse mutateInstance(PutMappingResponse response) {
|
||||
return new PutMappingResponse(response.isAcknowledged() == false);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@ package org.elasticsearch.action.admin.indices.open;
|
|||
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.test.AbstractStreamableXContentTestCase;
|
||||
import org.elasticsearch.test.EqualsHashCodeTestUtils;
|
||||
|
||||
public class OpenIndexResponseTests extends AbstractStreamableXContentTestCase<OpenIndexResponse> {
|
||||
|
||||
|
@ -43,8 +42,7 @@ public class OpenIndexResponseTests extends AbstractStreamableXContentTestCase<O
|
|||
}
|
||||
|
||||
@Override
|
||||
protected EqualsHashCodeTestUtils.MutateFunction<OpenIndexResponse> getMutateFunction() {
|
||||
return response -> {
|
||||
protected OpenIndexResponse mutateInstance(OpenIndexResponse response) {
|
||||
if (randomBoolean()) {
|
||||
boolean acknowledged = response.isAcknowledged() == false;
|
||||
boolean shardsAcknowledged = acknowledged && response.isShardsAcknowledged();
|
||||
|
@ -54,6 +52,5 @@ public class OpenIndexResponseTests extends AbstractStreamableXContentTestCase<O
|
|||
boolean acknowledged = shardsAcknowledged || response.isAcknowledged();
|
||||
return new OpenIndexResponse(acknowledged, shardsAcknowledged);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,6 @@ package org.elasticsearch.action.admin.indices.rollover;
|
|||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.test.AbstractStreamableXContentTestCase;
|
||||
import org.elasticsearch.test.EqualsHashCodeTestUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
@ -75,8 +74,7 @@ public class RolloverResponseTests extends AbstractStreamableXContentTestCase<Ro
|
|||
}
|
||||
|
||||
@Override
|
||||
protected EqualsHashCodeTestUtils.MutateFunction<RolloverResponse> getMutateFunction() {
|
||||
return response -> {
|
||||
protected RolloverResponse mutateInstance(RolloverResponse response) {
|
||||
int i = randomIntBetween(0, 6);
|
||||
switch(i) {
|
||||
case 0:
|
||||
|
@ -127,6 +125,5 @@ public class RolloverResponseTests extends AbstractStreamableXContentTestCase<Ro
|
|||
default:
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,6 @@ package org.elasticsearch.action.admin.indices.shrink;
|
|||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.test.AbstractStreamableXContentTestCase;
|
||||
import org.elasticsearch.test.EqualsHashCodeTestUtils;
|
||||
|
||||
public class ResizeResponseTests extends AbstractStreamableXContentTestCase<ResizeResponse> {
|
||||
|
||||
|
@ -51,8 +50,7 @@ public class ResizeResponseTests extends AbstractStreamableXContentTestCase<Resi
|
|||
}
|
||||
|
||||
@Override
|
||||
protected EqualsHashCodeTestUtils.MutateFunction<ResizeResponse> getMutateFunction() {
|
||||
return response -> {
|
||||
protected ResizeResponse mutateInstance(ResizeResponse response) {
|
||||
if (randomBoolean()) {
|
||||
if (randomBoolean()) {
|
||||
boolean acknowledged = response.isAcknowledged() == false;
|
||||
|
@ -67,6 +65,5 @@ public class ResizeResponseTests extends AbstractStreamableXContentTestCase<Resi
|
|||
return new ResizeResponse(response.isAcknowledged(), response.isShardsAcknowledged(),
|
||||
response.index() + randomAlphaOfLengthBetween(2, 5));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,6 @@ import org.elasticsearch.common.xcontent.XContentBuilder;
|
|||
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.test.AbstractStreamableXContentTestCase;
|
||||
import org.elasticsearch.test.EqualsHashCodeTestUtils;
|
||||
import org.elasticsearch.test.VersionUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -35,13 +34,6 @@ import java.util.Date;
|
|||
|
||||
public class MainResponseTests extends AbstractStreamableXContentTestCase<MainResponse> {
|
||||
|
||||
@Override
|
||||
protected MainResponse getExpectedFromXContent(MainResponse testInstance) {
|
||||
// we cannot recreate the "available" flag from xContent, but should be "true" if request came through
|
||||
testInstance.available = true;
|
||||
return testInstance;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MainResponse createTestInstance() {
|
||||
String clusterUuid = randomAlphaOfLength(10);
|
||||
|
@ -49,8 +41,7 @@ public class MainResponseTests extends AbstractStreamableXContentTestCase<MainRe
|
|||
String nodeName = randomAlphaOfLength(10);
|
||||
Build build = new Build(randomAlphaOfLength(8), new Date(randomNonNegativeLong()).toString(), randomBoolean());
|
||||
Version version = VersionUtils.randomVersion(random());
|
||||
boolean available = randomBoolean();
|
||||
return new MainResponse(nodeName, version, clusterName, clusterUuid , build, available);
|
||||
return new MainResponse(nodeName, version, clusterName, clusterUuid , build, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -87,14 +78,13 @@ public class MainResponseTests extends AbstractStreamableXContentTestCase<MainRe
|
|||
}
|
||||
|
||||
@Override
|
||||
protected EqualsHashCodeTestUtils.MutateFunction<MainResponse> getMutateFunction() {
|
||||
return o -> {
|
||||
String clusterUuid = o.getClusterUuid();
|
||||
boolean available = o.isAvailable();
|
||||
Build build = o.getBuild();
|
||||
Version version = o.getVersion();
|
||||
String nodeName = o.getNodeName();
|
||||
ClusterName clusterName = o.getClusterName();
|
||||
protected MainResponse mutateInstance(MainResponse mutateInstance) {
|
||||
String clusterUuid = mutateInstance.getClusterUuid();
|
||||
boolean available = mutateInstance.isAvailable();
|
||||
Build build = mutateInstance.getBuild();
|
||||
Version version = mutateInstance.getVersion();
|
||||
String nodeName = mutateInstance.getNodeName();
|
||||
ClusterName clusterName = mutateInstance.getClusterName();
|
||||
switch (randomIntBetween(0, 5)) {
|
||||
case 0:
|
||||
clusterUuid = clusterUuid + randomAlphaOfLength(5);
|
||||
|
@ -117,6 +107,5 @@ public class MainResponseTests extends AbstractStreamableXContentTestCase<MainRe
|
|||
break;
|
||||
}
|
||||
return new MainResponse(nodeName, version, clusterName, clusterUuid, build, available);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
package org.elasticsearch.index.refresh;
|
||||
|
||||
import org.elasticsearch.test.AbstractStreamableTestCase;
|
||||
import org.elasticsearch.test.EqualsHashCodeTestUtils.MutateFunction;
|
||||
|
||||
public class RefreshStatsTests extends AbstractStreamableTestCase<RefreshStats> {
|
||||
@Override
|
||||
|
@ -34,8 +33,7 @@ public class RefreshStatsTests extends AbstractStreamableTestCase<RefreshStats>
|
|||
}
|
||||
|
||||
@Override
|
||||
protected MutateFunction<RefreshStats> getMutateFunction() {
|
||||
return instance -> {
|
||||
protected RefreshStats mutateInstance(RefreshStats instance) {
|
||||
long total = instance.getTotal();
|
||||
long totalInMillis = instance.getTotalTimeInMillis();
|
||||
int listeners = instance.getListeners();
|
||||
|
@ -52,6 +50,5 @@ public class RefreshStatsTests extends AbstractStreamableTestCase<RefreshStats>
|
|||
break;
|
||||
}
|
||||
return new RefreshStats(total, totalInMillis, listeners);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,16 +34,15 @@ import org.elasticsearch.common.xcontent.ToXContent;
|
|||
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.common.xcontent.XContentType;
|
||||
import org.elasticsearch.tasks.Task;
|
||||
import org.elasticsearch.test.AbstractDiffableSerializationTestCase;
|
||||
import org.elasticsearch.persistent.PersistentTasksCustomMetaData.Assignment;
|
||||
import org.elasticsearch.persistent.PersistentTasksCustomMetaData.Builder;
|
||||
import org.elasticsearch.persistent.PersistentTasksCustomMetaData.PersistentTask;
|
||||
import org.elasticsearch.persistent.TestPersistentTasksPlugin.Status;
|
||||
import org.elasticsearch.persistent.TestPersistentTasksPlugin.TestParams;
|
||||
import org.elasticsearch.persistent.TestPersistentTasksPlugin.TestPersistentTasksExecutor;
|
||||
import org.elasticsearch.tasks.Task;
|
||||
import org.elasticsearch.test.AbstractDiffableSerializationTestCase;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
@ -123,18 +122,10 @@ public class PersistentTasksCustomMetaDataTests extends AbstractDiffableSerializ
|
|||
}
|
||||
|
||||
@Override
|
||||
protected PersistentTasksCustomMetaData doParseInstance(XContentParser parser) throws IOException {
|
||||
protected PersistentTasksCustomMetaData doParseInstance(XContentParser parser) {
|
||||
return PersistentTasksCustomMetaData.fromXContent(parser);
|
||||
}
|
||||
|
||||
/*
|
||||
@Override
|
||||
protected XContentBuilder toXContent(Custom instance, XContentType contentType) throws IOException {
|
||||
return toXContent(instance, contentType, new ToXContent.MapParams(
|
||||
Collections.singletonMap(MetaData.CONTEXT_MODE_PARAM, MetaData.XContentContext.API.toString())));
|
||||
}
|
||||
*/
|
||||
|
||||
private String addRandomTask(Builder builder) {
|
||||
String taskId = UUIDs.base64UUID();
|
||||
builder.addTask(taskId, TestPersistentTasksExecutor.NAME, new TestParams(randomAlphaOfLength(10)), randomAssignment());
|
||||
|
|
|
@ -55,7 +55,7 @@ public class StoredScriptSourceTests extends AbstractSerializingTestCase<StoredS
|
|||
}
|
||||
|
||||
@Override
|
||||
protected StoredScriptSource doParseInstance(XContentParser parser) throws IOException {
|
||||
protected StoredScriptSource doParseInstance(XContentParser parser) {
|
||||
return StoredScriptSource.fromXContent(parser);
|
||||
}
|
||||
|
||||
|
|
|
@ -27,8 +27,6 @@ import org.elasticsearch.common.xcontent.XContentParser;
|
|||
import org.elasticsearch.common.xcontent.XContentType;
|
||||
import org.elasticsearch.test.AbstractSerializingTestCase;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
@ -218,15 +216,11 @@ public class StoredScriptTests extends AbstractSerializingTestCase<StoredScriptS
|
|||
|
||||
@Override
|
||||
protected StoredScriptSource doParseInstance(XContentParser parser) {
|
||||
try {
|
||||
return StoredScriptSource.fromXContent(parser);
|
||||
} catch (IOException ioe) {
|
||||
throw new UncheckedIOException(ioe);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected StoredScriptSource mutateInstance(StoredScriptSource instance) throws IOException {
|
||||
protected StoredScriptSource mutateInstance(StoredScriptSource instance) {
|
||||
String source = instance.getSource();
|
||||
String lang = instance.getLang();
|
||||
Map<String, String> options = instance.getOptions();
|
||||
|
|
|
@ -19,14 +19,11 @@
|
|||
package org.elasticsearch.search.aggregations;
|
||||
|
||||
import org.elasticsearch.Version;
|
||||
import org.elasticsearch.common.bytes.BytesReference;
|
||||
import org.elasticsearch.common.io.stream.BytesStreamOutput;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.Writeable.Reader;
|
||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.common.xcontent.XContentParser.Token;
|
||||
import org.elasticsearch.common.xcontent.XContentType;
|
||||
import org.elasticsearch.search.aggregations.InternalOrder.CompoundOrder;
|
||||
import org.elasticsearch.test.AbstractSerializingTestCase;
|
||||
import org.elasticsearch.test.VersionUtils;
|
||||
|
@ -80,25 +77,9 @@ public class InternalOrderTests extends AbstractSerializingTestCase<BucketOrder>
|
|||
}
|
||||
|
||||
@Override
|
||||
protected BucketOrder assertSerialization(BucketOrder testInstance) throws IOException {
|
||||
// identical behavior to AbstractWireSerializingTestCase, except assertNotSame is only called for
|
||||
// compound and aggregation order because _key and _count orders are static instances.
|
||||
BucketOrder deserializedInstance = copyInstance(testInstance);
|
||||
assertEquals(testInstance, deserializedInstance);
|
||||
assertEquals(testInstance.hashCode(), deserializedInstance.hashCode());
|
||||
if(testInstance instanceof CompoundOrder || testInstance instanceof InternalOrder.Aggregation) {
|
||||
assertNotSame(testInstance, deserializedInstance);
|
||||
}
|
||||
return deserializedInstance;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void assertParsedInstance(XContentType xContentType, BytesReference instanceAsBytes, BucketOrder expectedInstance)
|
||||
throws IOException {
|
||||
protected void assertEqualInstances(BucketOrder expectedInstance, BucketOrder newInstance) {
|
||||
// identical behavior to AbstractSerializingTestCase, except assertNotSame is only called for
|
||||
// compound and aggregation order because _key and _count orders are static instances.
|
||||
XContentParser parser = createParser(XContentFactory.xContent(xContentType), instanceAsBytes);
|
||||
BucketOrder newInstance = parseInstance(parser);
|
||||
assertEquals(expectedInstance, newInstance);
|
||||
assertEquals(expectedInstance.hashCode(), newInstance.hashCode());
|
||||
if(expectedInstance instanceof CompoundOrder || expectedInstance instanceof InternalOrder.Aggregation) {
|
||||
|
|
|
@ -64,7 +64,7 @@ public class CollapseBuilderTests extends AbstractSerializingTestCase<CollapseBu
|
|||
}
|
||||
|
||||
@AfterClass
|
||||
public static void afterClass() throws Exception {
|
||||
public static void afterClass() {
|
||||
namedWriteableRegistry = null;
|
||||
xContentRegistry = null;
|
||||
}
|
||||
|
@ -186,7 +186,7 @@ public class CollapseBuilderTests extends AbstractSerializingTestCase<CollapseBu
|
|||
}
|
||||
}
|
||||
|
||||
public void testBuildWithSearchContextExceptions() throws IOException {
|
||||
public void testBuildWithSearchContextExceptions() {
|
||||
SearchContext context = mockSearchContext();
|
||||
{
|
||||
CollapseBuilder builder = new CollapseBuilder("unknown_field");
|
||||
|
@ -225,7 +225,7 @@ public class CollapseBuilderTests extends AbstractSerializingTestCase<CollapseBu
|
|||
}
|
||||
|
||||
@Override
|
||||
protected CollapseBuilder doParseInstance(XContentParser parser) throws IOException {
|
||||
protected CollapseBuilder doParseInstance(XContentParser parser) {
|
||||
return CollapseBuilder.fromXContent(parser);
|
||||
}
|
||||
|
||||
|
|
|
@ -19,14 +19,12 @@
|
|||
package org.elasticsearch.test;
|
||||
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.bytes.BytesReference;
|
||||
import org.elasticsearch.common.io.stream.Writeable;
|
||||
import org.elasticsearch.common.xcontent.ToXContent;
|
||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.common.xcontent.XContentType;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public abstract class AbstractSerializingTestCase<T extends ToXContent & Writeable> extends AbstractWireSerializingTestCase<T> {
|
||||
|
||||
|
@ -35,29 +33,9 @@ public abstract class AbstractSerializingTestCase<T extends ToXContent & Writeab
|
|||
* both for equality and asserts equality on the two instances.
|
||||
*/
|
||||
public final void testFromXContent() throws IOException {
|
||||
for (int runs = 0; runs < NUMBER_OF_TEST_RUNS; runs++) {
|
||||
T testInstance = createTestInstance();
|
||||
XContentType xContentType = randomFrom(XContentType.values());
|
||||
BytesReference shuffled = toShuffledXContent(testInstance, xContentType, ToXContent.EMPTY_PARAMS,
|
||||
false, getShuffleFieldsExceptions());
|
||||
assertParsedInstance(xContentType, shuffled, testInstance);
|
||||
}
|
||||
}
|
||||
|
||||
protected void assertParsedInstance(XContentType xContentType, BytesReference instanceAsBytes, T expectedInstance)
|
||||
throws IOException {
|
||||
|
||||
XContentParser parser = createParser(XContentFactory.xContent(xContentType), instanceAsBytes);
|
||||
T newInstance = parseInstance(parser);
|
||||
assertNotSame(newInstance, expectedInstance);
|
||||
assertEquals(expectedInstance, newInstance);
|
||||
assertEquals(expectedInstance.hashCode(), newInstance.hashCode());
|
||||
}
|
||||
|
||||
protected T parseInstance(XContentParser parser) throws IOException {
|
||||
T parsedInstance = doParseInstance(parser);
|
||||
assertNull(parser.nextToken());
|
||||
return parsedInstance;
|
||||
AbstractXContentTestCase.testFromXContent(NUMBER_OF_TEST_RUNS, this::createTestInstance, supportsUnknownFields(),
|
||||
getShuffleFieldsExceptions(), getRandomFieldsExcludeFilter(), this::createParser, this::doParseInstance,
|
||||
this::assertEqualInstances);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -65,6 +43,21 @@ public abstract class AbstractSerializingTestCase<T extends ToXContent & Writeab
|
|||
*/
|
||||
protected abstract T doParseInstance(XContentParser parser) throws IOException;
|
||||
|
||||
/**
|
||||
* 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 boolean supportsUnknownFields() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a predicate that given the field name indicates whether the field has to be excluded from random fields insertion or not
|
||||
*/
|
||||
protected Predicate<String> getRandomFieldsExcludeFilter() {
|
||||
return field -> false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fields that have to be ignored when shuffling as part of testFromXContent
|
||||
*/
|
||||
|
|
|
@ -19,27 +19,22 @@
|
|||
package org.elasticsearch.test;
|
||||
|
||||
import org.elasticsearch.Version;
|
||||
import org.elasticsearch.common.io.stream.BytesStreamOutput;
|
||||
import org.elasticsearch.common.io.stream.NamedWriteable;
|
||||
import org.elasticsearch.common.io.stream.NamedWriteableAwareStreamInput;
|
||||
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.Streamable;
|
||||
import org.elasticsearch.test.EqualsHashCodeTestUtils.CopyFunction;
|
||||
import org.elasticsearch.test.EqualsHashCodeTestUtils.MutateFunction;
|
||||
import org.elasticsearch.common.io.stream.Writeable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
|
||||
public abstract class AbstractStreamableTestCase<T extends Streamable> extends ESTestCase {
|
||||
protected static final int NUMBER_OF_TEST_RUNS = 20;
|
||||
public abstract class AbstractStreamableTestCase<T extends Streamable> extends AbstractWireTestCase<T> {
|
||||
|
||||
/**
|
||||
* 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();
|
||||
@Override
|
||||
protected final T copyInstance(T instance, Version version) throws IOException {
|
||||
return copyStreamable(instance, getNamedWriteableRegistry(), this::createBlankInstance, version);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final Writeable.Reader<T> instanceReader() {
|
||||
return Streamable.newWriteableReader(this::createBlankInstance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an empty instance to use when deserialising the
|
||||
|
@ -47,83 +42,4 @@ public abstract class AbstractStreamableTestCase<T extends Streamable> extends E
|
|||
* zer-arg constructor
|
||||
*/
|
||||
protected abstract T createBlankInstance();
|
||||
|
||||
/**
|
||||
* Returns a {@link CopyFunction} that can be used to make an exact copy of
|
||||
* the given instance. This defaults to a function that uses
|
||||
* {@link #copyInstance(Streamable, Version)} to create the copy.
|
||||
*/
|
||||
protected CopyFunction<T> getCopyFunction() {
|
||||
return (original) -> copyInstance(original, Version.CURRENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link MutateFunction} that can be used to create a copy
|
||||
* of the given instance that is different to this instance. This defaults
|
||||
* to null.
|
||||
*/
|
||||
// TODO: Make this abstract when all sub-classes implement this (https://github.com/elastic/elasticsearch/issues/25929)
|
||||
protected MutateFunction<T> getMutateFunction() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the equals and hashcode methods are consistent and copied
|
||||
* versions of the instance have are equal.
|
||||
*/
|
||||
public final void testEqualsAndHashcode() {
|
||||
for (int runs = 0; runs < NUMBER_OF_TEST_RUNS; runs++) {
|
||||
EqualsHashCodeTestUtils.checkEqualsAndHashCode(createTestInstance(), getCopyFunction(), getMutateFunction());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test serialization and deserialization of the test instance.
|
||||
*/
|
||||
public final void testSerialization() throws IOException {
|
||||
for (int runs = 0; runs < NUMBER_OF_TEST_RUNS; runs++) {
|
||||
T testInstance = createTestInstance();
|
||||
assertSerialization(testInstance);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize the given instance and asserts that both are equal
|
||||
*/
|
||||
protected T assertSerialization(T testInstance) throws IOException {
|
||||
T deserializedInstance = copyInstance(testInstance, Version.CURRENT);
|
||||
assertEquals(testInstance, deserializedInstance);
|
||||
assertEquals(testInstance.hashCode(), deserializedInstance.hashCode());
|
||||
assertNotSame(testInstance, deserializedInstance);
|
||||
return deserializedInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Round trip {@code instance} through binary serialization, setting the wire compatibility version to {@code version}.
|
||||
*/
|
||||
private T copyInstance(T instance, Version version) throws IOException {
|
||||
try (BytesStreamOutput output = new BytesStreamOutput()) {
|
||||
output.setVersion(version);
|
||||
instance.writeTo(output);
|
||||
try (StreamInput in = new NamedWriteableAwareStreamInput(output.bytes().streamInput(),
|
||||
getNamedWriteableRegistry())) {
|
||||
in.setVersion(version);
|
||||
T newInstance = createBlankInstance();
|
||||
newInstance.readFrom(in);
|
||||
return newInstance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link NamedWriteableRegistry} to use when de-serializing the object.
|
||||
*
|
||||
* Override this method if you need to register {@link NamedWriteable}s for the test object to de-serialize.
|
||||
*
|
||||
* By default this will return a {@link NamedWriteableRegistry} with no registered {@link NamedWriteable}s
|
||||
*/
|
||||
protected NamedWriteableRegistry getNamedWriteableRegistry() {
|
||||
return new NamedWriteableRegistry(Collections.emptyList());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -18,19 +18,14 @@
|
|||
*/
|
||||
package org.elasticsearch.test;
|
||||
|
||||
import org.elasticsearch.common.bytes.BytesReference;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.io.stream.Streamable;
|
||||
import org.elasticsearch.common.xcontent.ToXContent;
|
||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||
import org.elasticsearch.common.xcontent.XContentHelper;
|
||||
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.hamcrest.ElasticsearchAssertions.assertToXContentEquivalent;
|
||||
|
||||
public abstract class AbstractStreamableXContentTestCase<T extends ToXContent & Streamable> extends AbstractStreamableTestCase<T> {
|
||||
|
||||
/**
|
||||
|
@ -38,34 +33,15 @@ public abstract class AbstractStreamableXContentTestCase<T extends ToXContent &
|
|||
* both for equality and asserts equality on the two queries.
|
||||
*/
|
||||
public final void testFromXContent() throws IOException {
|
||||
for (int runs = 0; runs < NUMBER_OF_TEST_RUNS; runs++) {
|
||||
T testInstance = createTestInstance();
|
||||
XContentType xContentType = randomFrom(XContentType.values());
|
||||
BytesReference shuffled = toShuffledXContent(testInstance, xContentType, ToXContent.EMPTY_PARAMS, false);
|
||||
BytesReference withRandomFields;
|
||||
if (supportsUnknownFields()) {
|
||||
// we add a few random fields to check that parser is lenient on new fields
|
||||
withRandomFields = XContentTestUtils.insertRandomFields(xContentType, shuffled, getRandomFieldsExcludeFilter(), random());
|
||||
} else {
|
||||
withRandomFields = shuffled;
|
||||
}
|
||||
XContentParser parser = createParser(XContentFactory.xContent(xContentType), withRandomFields);
|
||||
T parsed = parseInstance(parser);
|
||||
T expected = getExpectedFromXContent(testInstance);
|
||||
assertNotSame(expected, parsed);
|
||||
assertEquals(expected, parsed);
|
||||
assertEquals(expected.hashCode(), parsed.hashCode());
|
||||
assertToXContentEquivalent(shuffled, XContentHelper.toXContent(parsed, xContentType, false), xContentType);
|
||||
}
|
||||
AbstractXContentTestCase.testFromXContent(NUMBER_OF_TEST_RUNS, this::createTestInstance, supportsUnknownFields(),
|
||||
getShuffleFieldsExceptions(), getRandomFieldsExcludeFilter(), this::createParser, this::doParseInstance,
|
||||
this::assertEqualInstances);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the expected parsed object given the test object that the parser will be fed with.
|
||||
* Useful in cases some fields are not written as part of toXContent, hence not parsed back.
|
||||
* Parses to a new instance using the provided {@link XContentParser}
|
||||
*/
|
||||
protected T getExpectedFromXContent(T testInstance) {
|
||||
return testInstance;
|
||||
}
|
||||
protected abstract T doParseInstance(XContentParser parser) throws IOException;;
|
||||
|
||||
/**
|
||||
* Indicates whether the parser supports unknown fields or not. In case it does, such behaviour will be tested by
|
||||
|
@ -75,18 +51,17 @@ public abstract class AbstractStreamableXContentTestCase<T extends ToXContent &
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a predicate that given the field name indicates whether the field has to be excluded from random fields insertion or not
|
||||
*/
|
||||
protected Predicate<String> getRandomFieldsExcludeFilter() {
|
||||
return field -> false;
|
||||
}
|
||||
|
||||
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}
|
||||
* Fields that have to be ignored when shuffling as part of testFromXContent
|
||||
*/
|
||||
protected abstract T doParseInstance(XContentParser parser);
|
||||
protected String[] getShuffleFieldsExceptions() {
|
||||
return Strings.EMPTY_ARRAY;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,100 +19,14 @@
|
|||
package org.elasticsearch.test;
|
||||
|
||||
import org.elasticsearch.Version;
|
||||
import org.elasticsearch.common.io.stream.BytesStreamOutput;
|
||||
import org.elasticsearch.common.io.stream.NamedWriteable;
|
||||
import org.elasticsearch.common.io.stream.NamedWriteableAwareStreamInput;
|
||||
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.Writeable;
|
||||
import org.elasticsearch.common.io.stream.Writeable.Reader;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
|
||||
public abstract class AbstractWireSerializingTestCase<T extends Writeable> extends ESTestCase {
|
||||
protected static final int NUMBER_OF_TEST_RUNS = 20;
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
/**
|
||||
* Returns a {@link Reader} that can be used to de-serialize the instance
|
||||
*/
|
||||
protected abstract Reader<T> instanceReader();
|
||||
|
||||
/**
|
||||
* Returns an instance which is mutated slightly so it should not be equal
|
||||
* to the given instance.
|
||||
*/
|
||||
// TODO: Make this abstract when all sub-classes implement this (https://github.com/elastic/elasticsearch/issues/25929)
|
||||
protected T mutateInstance(T instance) throws IOException {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the equals and hashcode methods are consistent and copied
|
||||
* versions of the instance have are equal.
|
||||
*/
|
||||
public final void testEqualsAndHashcode() {
|
||||
for (int runs = 0; runs < NUMBER_OF_TEST_RUNS; runs++) {
|
||||
EqualsHashCodeTestUtils.checkEqualsAndHashCode(createTestInstance(), this::copyInstance, this::mutateInstance);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test serialization and deserialization of the test instance.
|
||||
*/
|
||||
public final void testSerialization() throws IOException {
|
||||
for (int runs = 0; runs < NUMBER_OF_TEST_RUNS; runs++) {
|
||||
T testInstance = createTestInstance();
|
||||
assertSerialization(testInstance);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize the given instance and asserts that both are equal
|
||||
*/
|
||||
protected T assertSerialization(T testInstance) throws IOException {
|
||||
return assertSerialization(testInstance, Version.CURRENT);
|
||||
}
|
||||
|
||||
protected T assertSerialization(T testInstance, Version version) throws IOException {
|
||||
T deserializedInstance = copyInstance(testInstance, version);
|
||||
assertEquals(testInstance, deserializedInstance);
|
||||
assertEquals(testInstance.hashCode(), deserializedInstance.hashCode());
|
||||
assertNotSame(testInstance, deserializedInstance);
|
||||
return deserializedInstance;
|
||||
}
|
||||
|
||||
protected T copyInstance(T instance) throws IOException {
|
||||
return copyInstance(instance, Version.CURRENT);
|
||||
}
|
||||
public abstract class AbstractWireSerializingTestCase<T extends Writeable> extends AbstractWireTestCase<T> {
|
||||
|
||||
@Override
|
||||
protected T copyInstance(T instance, Version version) throws IOException {
|
||||
try (BytesStreamOutput output = new BytesStreamOutput()) {
|
||||
output.setVersion(version);
|
||||
instance.writeTo(output);
|
||||
try (StreamInput in = new NamedWriteableAwareStreamInput(output.bytes().streamInput(),
|
||||
getNamedWriteableRegistry())) {
|
||||
in.setVersion(version);
|
||||
return instanceReader().read(in);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link NamedWriteableRegistry} to use when de-serializing the object.
|
||||
*
|
||||
* Override this method if you need to register {@link NamedWriteable}s for the test object to de-serialize.
|
||||
*
|
||||
* By default this will return a {@link NamedWriteableRegistry} with no registered {@link NamedWriteable}s
|
||||
*/
|
||||
protected NamedWriteableRegistry getNamedWriteableRegistry() {
|
||||
return new NamedWriteableRegistry(Collections.emptyList());
|
||||
return copyWriteable(instance, getNamedWriteableRegistry(), instanceReader());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.elasticsearch.test;
|
||||
|
||||
import org.elasticsearch.Version;
|
||||
import org.elasticsearch.common.io.stream.NamedWriteable;
|
||||
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
|
||||
import org.elasticsearch.common.io.stream.Writeable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
|
||||
public abstract class AbstractWireTestCase<T> extends ESTestCase {
|
||||
|
||||
protected static final int NUMBER_OF_TEST_RUNS = 20;
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
/**
|
||||
* Returns a {@link Writeable.Reader} that can be used to de-serialize the instance
|
||||
*/
|
||||
protected abstract Writeable.Reader<T> instanceReader();
|
||||
|
||||
/**
|
||||
* Returns an instance which is mutated slightly so it should not be equal
|
||||
* to the given instance.
|
||||
*/
|
||||
// TODO: Make this abstract when all sub-classes implement this (https://github.com/elastic/elasticsearch/issues/25929)
|
||||
protected T mutateInstance(T instance) throws IOException {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the equals and hashcode methods are consistent and copied
|
||||
* versions of the instance have are equal.
|
||||
*/
|
||||
public final void testEqualsAndHashcode() {
|
||||
for (int runs = 0; runs < NUMBER_OF_TEST_RUNS; runs++) {
|
||||
EqualsHashCodeTestUtils.checkEqualsAndHashCode(createTestInstance(), this::copyInstance, this::mutateInstance);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test serialization and deserialization of the test instance.
|
||||
*/
|
||||
public final void testSerialization() throws IOException {
|
||||
for (int runs = 0; runs < NUMBER_OF_TEST_RUNS; runs++) {
|
||||
T testInstance = createTestInstance();
|
||||
assertSerialization(testInstance);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize the given instance and asserts that both are equal
|
||||
*/
|
||||
protected final T assertSerialization(T testInstance) throws IOException {
|
||||
return assertSerialization(testInstance, Version.CURRENT);
|
||||
}
|
||||
|
||||
protected final T assertSerialization(T testInstance, Version version) throws IOException {
|
||||
T deserializedInstance = copyInstance(testInstance, version);
|
||||
assertEqualInstances(testInstance, deserializedInstance);
|
||||
return deserializedInstance;
|
||||
}
|
||||
|
||||
protected void assertEqualInstances(T expectedInstance, T newInstance) {
|
||||
assertNotSame(newInstance, expectedInstance);
|
||||
assertEquals(expectedInstance, newInstance);
|
||||
assertEquals(expectedInstance.hashCode(), newInstance.hashCode());
|
||||
}
|
||||
|
||||
protected final T copyInstance(T instance) throws IOException {
|
||||
return copyInstance(instance, Version.CURRENT);
|
||||
}
|
||||
|
||||
protected abstract T copyInstance(T instance, Version version) throws IOException;
|
||||
|
||||
/**
|
||||
* Get the {@link NamedWriteableRegistry} to use when de-serializing the object.
|
||||
*
|
||||
* Override this method if you need to register {@link NamedWriteable}s for the test object to de-serialize.
|
||||
*
|
||||
* By default this will return a {@link NamedWriteableRegistry} with no registered {@link NamedWriteable}s
|
||||
*/
|
||||
protected NamedWriteableRegistry getNamedWriteableRegistry() {
|
||||
return new NamedWriteableRegistry(Collections.emptyList());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.elasticsearch.test;
|
||||
|
||||
import org.elasticsearch.common.CheckedBiFunction;
|
||||
import org.elasticsearch.common.CheckedFunction;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.bytes.BytesReference;
|
||||
import org.elasticsearch.common.xcontent.ToXContent;
|
||||
import org.elasticsearch.common.xcontent.XContent;
|
||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||
import org.elasticsearch.common.xcontent.XContentHelper;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.common.xcontent.XContentType;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertToXContentEquivalent;
|
||||
|
||||
public abstract class AbstractXContentTestCase<T extends ToXContent> extends ESTestCase {
|
||||
|
||||
public static <T extends ToXContent> void testFromXContent(int numberOfTestRuns, Supplier<T> instanceSupplier,
|
||||
boolean supportsUnknownFields, String[] shuffleFieldsExceptions,
|
||||
Predicate<String> randomFieldsExcludeFilter,
|
||||
CheckedBiFunction<XContent, BytesReference, XContentParser, IOException>
|
||||
createParserFunction,
|
||||
CheckedFunction<XContentParser, T, IOException> parseFunction,
|
||||
BiConsumer<T, T> assertEqualsConsumer) throws IOException {
|
||||
for (int runs = 0; runs < numberOfTestRuns; runs++) {
|
||||
T testInstance = instanceSupplier.get();
|
||||
XContentType xContentType = randomFrom(XContentType.values());
|
||||
BytesReference shuffled = toShuffledXContent(testInstance, xContentType, ToXContent.EMPTY_PARAMS, false, createParserFunction,
|
||||
shuffleFieldsExceptions);
|
||||
BytesReference withRandomFields;
|
||||
if (supportsUnknownFields) {
|
||||
// we add a few random fields to check that parser is lenient on new fields
|
||||
withRandomFields = XContentTestUtils.insertRandomFields(xContentType, shuffled, randomFieldsExcludeFilter, random());
|
||||
} else {
|
||||
withRandomFields = shuffled;
|
||||
}
|
||||
XContentParser parser = createParserFunction.apply(XContentFactory.xContent(xContentType), withRandomFields);
|
||||
T parsed = parseFunction.apply(parser);
|
||||
assertEqualsConsumer.accept(testInstance, parsed);
|
||||
assertToXContentEquivalent(shuffled, XContentHelper.toXContent(parsed, xContentType, false), xContentType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(numberOfTestRuns(), this::createTestInstance, supportsUnknownFields(), getShuffleFieldsExceptions(),
|
||||
getRandomFieldsExcludeFilter(), this::createParser, this::parseInstance, this::assertEqualInstances);
|
||||
}
|
||||
|
||||
protected abstract int numberOfTestRuns();
|
||||
|
||||
/**
|
||||
* 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());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<String> getRandomFieldsExcludeFilter() {
|
||||
return field -> false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fields that have to be ignored when shuffling as part of testFromXContent
|
||||
*/
|
||||
protected String[] getShuffleFieldsExceptions() {
|
||||
return Strings.EMPTY_ARRAY;
|
||||
}
|
||||
}
|
|
@ -29,7 +29,6 @@ import com.carrotsearch.randomizedtesting.generators.RandomNumbers;
|
|||
import com.carrotsearch.randomizedtesting.generators.RandomPicks;
|
||||
import com.carrotsearch.randomizedtesting.generators.RandomStrings;
|
||||
import com.carrotsearch.randomizedtesting.rules.TestRuleAdapter;
|
||||
|
||||
import org.apache.logging.log4j.Level;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
@ -48,6 +47,7 @@ import org.elasticsearch.bootstrap.BootstrapForTesting;
|
|||
import org.elasticsearch.client.Requests;
|
||||
import org.elasticsearch.cluster.ClusterModule;
|
||||
import org.elasticsearch.cluster.metadata.IndexMetaData;
|
||||
import org.elasticsearch.common.CheckedBiFunction;
|
||||
import org.elasticsearch.common.CheckedRunnable;
|
||||
import org.elasticsearch.common.bytes.BytesReference;
|
||||
import org.elasticsearch.common.io.PathUtils;
|
||||
|
@ -57,6 +57,7 @@ import org.elasticsearch.common.io.stream.NamedWriteable;
|
|||
import org.elasticsearch.common.io.stream.NamedWriteableAwareStreamInput;
|
||||
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.Streamable;
|
||||
import org.elasticsearch.common.io.stream.Writeable;
|
||||
import org.elasticsearch.common.logging.DeprecationLogger;
|
||||
import org.elasticsearch.common.logging.Loggers;
|
||||
|
@ -926,8 +927,21 @@ public abstract class ESTestCase extends LuceneTestCase {
|
|||
*/
|
||||
protected final BytesReference toShuffledXContent(ToXContent toXContent, XContentType xContentType, ToXContent.Params params,
|
||||
boolean humanReadable, String... exceptFieldNames) throws IOException{
|
||||
return toShuffledXContent(toXContent, xContentType, params, humanReadable, this::createParser, exceptFieldNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bytes that represent the XContent output of the provided {@link ToXContent} object, using the provided
|
||||
* {@link XContentType}. Wraps the output into a new anonymous object according to the value returned
|
||||
* by the {@link ToXContent#isFragment()} method returns. Shuffles the keys to make sure that parsing never relies on keys ordering.
|
||||
*/
|
||||
protected static BytesReference toShuffledXContent(ToXContent toXContent, XContentType xContentType, ToXContent.Params params,
|
||||
boolean humanReadable,
|
||||
CheckedBiFunction<XContent, BytesReference, XContentParser, IOException>
|
||||
parserFunction,
|
||||
String... exceptFieldNames) throws IOException{
|
||||
BytesReference bytes = XContentHelper.toXContent(toXContent, xContentType, params, humanReadable);
|
||||
try (XContentParser parser = createParser(xContentType.xContent(), bytes)) {
|
||||
try (XContentParser parser = parserFunction.apply(xContentType.xContent(), bytes)) {
|
||||
try (XContentBuilder builder = shuffleXContent(parser, rarely(), exceptFieldNames)) {
|
||||
return builder.bytes();
|
||||
}
|
||||
|
@ -952,7 +966,8 @@ public abstract class ESTestCase extends LuceneTestCase {
|
|||
* recursive shuffling behavior can be made by passing in the names of fields which
|
||||
* internally should stay untouched.
|
||||
*/
|
||||
public XContentBuilder shuffleXContent(XContentParser parser, boolean prettyPrint, String... exceptFieldNames) throws IOException {
|
||||
public static XContentBuilder shuffleXContent(XContentParser parser, boolean prettyPrint, String... exceptFieldNames)
|
||||
throws IOException {
|
||||
XContentBuilder xContentBuilder = XContentFactory.contentBuilder(parser.contentType());
|
||||
if (prettyPrint) {
|
||||
xContentBuilder.prettyPrint();
|
||||
|
@ -969,6 +984,7 @@ public abstract class ESTestCase extends LuceneTestCase {
|
|||
}
|
||||
|
||||
// shuffle fields of objects in the list, but not the list itself
|
||||
@SuppressWarnings("unchecked")
|
||||
private static List<Object> shuffleList(List<Object> list, Set<String> exceptFields) {
|
||||
List<Object> targetList = new ArrayList<>();
|
||||
for(Object value : list) {
|
||||
|
@ -985,6 +1001,7 @@ public abstract class ESTestCase extends LuceneTestCase {
|
|||
return targetList;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static LinkedHashMap<String, Object> shuffleMap(LinkedHashMap<String, Object> map, Set<String> exceptFields) {
|
||||
List<String> keys = new ArrayList<>(map.keySet());
|
||||
LinkedHashMap<String, Object> targetMap = new LinkedHashMap<>();
|
||||
|
@ -1010,11 +1027,39 @@ public abstract class ESTestCase extends LuceneTestCase {
|
|||
* potentially need to use a {@link NamedWriteableRegistry}, so this needs to be provided too (although it can be
|
||||
* empty if the object that is streamed doesn't contain any {@link NamedWriteable} objects itself.
|
||||
*/
|
||||
public static <T extends Writeable> T copyWriteable(T original, NamedWriteableRegistry namedWritabelRegistry,
|
||||
public static <T extends Writeable> T copyWriteable(T original, NamedWriteableRegistry namedWriteableRegistry,
|
||||
Writeable.Reader<T> reader) throws IOException {
|
||||
return copyWriteable(original, namedWriteableRegistry, reader, Version.CURRENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as {@link #copyWriteable(Writeable, NamedWriteableRegistry, Writeable.Reader)} but also allows to provide
|
||||
* a {@link Version} argument which will be used to write and read back the object.
|
||||
*/
|
||||
public static <T extends Writeable> T copyWriteable(T original, NamedWriteableRegistry namedWriteableRegistry,
|
||||
Writeable.Reader<T> reader, Version version) throws IOException {
|
||||
return copyInstance(original, namedWriteableRegistry, (out, value) -> value.writeTo(out), reader, version);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a copy of an original {@link Streamable} object by running it through a {@link BytesStreamOutput} and
|
||||
* reading it in again using a provided {@link Writeable.Reader}. The stream that is wrapped around the {@link StreamInput}
|
||||
* potentially need to use a {@link NamedWriteableRegistry}, so this needs to be provided too (although it can be
|
||||
* empty if the object that is streamed doesn't contain any {@link NamedWriteable} objects itself.
|
||||
*/
|
||||
public static <T extends Streamable> T copyStreamable(T original, NamedWriteableRegistry namedWriteableRegistry,
|
||||
Supplier<T> supplier, Version version) throws IOException {
|
||||
return copyInstance(original, namedWriteableRegistry, (out, value) -> value.writeTo(out),
|
||||
Streamable.newWriteableReader(supplier), version);
|
||||
}
|
||||
|
||||
private static <T> T copyInstance(T original, NamedWriteableRegistry namedWriteableRegistry, Writeable.Writer<T> writer,
|
||||
Writeable.Reader<T> reader, Version version) throws IOException {
|
||||
try (BytesStreamOutput output = new BytesStreamOutput()) {
|
||||
original.writeTo(output);
|
||||
try (StreamInput in = new NamedWriteableAwareStreamInput(output.bytes().streamInput(), namedWritabelRegistry)) {
|
||||
output.setVersion(version);
|
||||
writer.write(output, original);
|
||||
try (StreamInput in = new NamedWriteableAwareStreamInput(output.bytes().streamInput(), namedWriteableRegistry)) {
|
||||
in.setVersion(version);
|
||||
return reader.read(in);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue