From cd3d9c9f8062e86f1c26233196a3888b620023a2 Mon Sep 17 00:00:00 2001 From: Luca Cavanna Date: Fri, 23 Feb 2018 10:48:48 +0100 Subject: [PATCH] [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. --- .../common/CheckedBiFunction.java | 28 ++++ .../PersistentTasksCustomMetaData.java | 4 +- .../script/StoredScriptSource.java | 2 +- .../search/collapse/CollapseBuilder.java | 5 +- .../ClusterUpdateSettingsResponseTests.java | 33 +++-- .../alias/IndicesAliasesResponseTests.java | 5 +- .../close/CloseIndexResponseTests.java | 5 +- .../create/CreateIndexResponseTests.java | 27 ++-- .../delete/DeleteIndexResponseTests.java | 5 +- .../mapping/put/PutMappingResponseTests.java | 5 +- .../indices/open/OpenIndexResponseTests.java | 23 ++-- .../rollover/RolloverResponseTests.java | 99 +++++++------- .../indices/shrink/ResizeResponseTests.java | 27 ++-- .../action/main/MainResponseTests.java | 71 +++++----- .../index/refresh/RefreshStatsTests.java | 37 +++--- .../PersistentTasksCustomMetaDataTests.java | 15 +-- .../script/StoredScriptSourceTests.java | 2 +- .../script/StoredScriptTests.java | 10 +- .../aggregations/InternalOrderTests.java | 21 +-- .../search/collapse/CollapseBuilderTests.java | 6 +- .../test/AbstractSerializingTestCase.java | 43 +++--- .../test/AbstractStreamableTestCase.java | 106 ++------------- .../AbstractStreamableXContentTestCase.java | 51 ++------ .../test/AbstractWireSerializingTestCase.java | 92 +------------ .../test/AbstractWireTestCase.java | 110 ++++++++++++++++ .../test/AbstractXContentTestCase.java | 122 ++++++++++++++++++ .../org/elasticsearch/test/ESTestCase.java | 57 +++++++- 27 files changed, 523 insertions(+), 488 deletions(-) create mode 100644 server/src/main/java/org/elasticsearch/common/CheckedBiFunction.java create mode 100644 test/framework/src/main/java/org/elasticsearch/test/AbstractWireTestCase.java create mode 100644 test/framework/src/main/java/org/elasticsearch/test/AbstractXContentTestCase.java diff --git a/server/src/main/java/org/elasticsearch/common/CheckedBiFunction.java b/server/src/main/java/org/elasticsearch/common/CheckedBiFunction.java new file mode 100644 index 0000000000000..2e37bc814a286 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/common/CheckedBiFunction.java @@ -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 { + R apply(T t, U u) throws E; +} diff --git a/server/src/main/java/org/elasticsearch/persistent/PersistentTasksCustomMetaData.java b/server/src/main/java/org/elasticsearch/persistent/PersistentTasksCustomMetaData.java index 237157e44c43c..25b3567ac395d 100644 --- a/server/src/main/java/org/elasticsearch/persistent/PersistentTasksCustomMetaData.java +++ b/server/src/main/java/org/elasticsearch/persistent/PersistentTasksCustomMetaData.java @@ -205,8 +205,8 @@ public EnumSet context() { 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") diff --git a/server/src/main/java/org/elasticsearch/script/StoredScriptSource.java b/server/src/main/java/org/elasticsearch/script/StoredScriptSource.java index a74108c55defa..81b1b5575feba 100644 --- a/server/src/main/java/org/elasticsearch/script/StoredScriptSource.java +++ b/server/src/main/java/org/elasticsearch/script/StoredScriptSource.java @@ -318,7 +318,7 @@ public static StoredScriptSource parse(BytesReference content, XContentType xCon * 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(); } diff --git a/server/src/main/java/org/elasticsearch/search/collapse/CollapseBuilder.java b/server/src/main/java/org/elasticsearch/search/collapse/CollapseBuilder.java index 696e7f2c4e2ef..90e35c34e28f8 100644 --- a/server/src/main/java/org/elasticsearch/search/collapse/CollapseBuilder.java +++ b/server/src/main/java/org/elasticsearch/search/collapse/CollapseBuilder.java @@ -121,9 +121,8 @@ public void writeTo(StreamOutput out) throws IOException { } } - 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 diff --git a/server/src/test/java/org/elasticsearch/action/admin/cluster/settings/ClusterUpdateSettingsResponseTests.java b/server/src/test/java/org/elasticsearch/action/admin/cluster/settings/ClusterUpdateSettingsResponseTests.java index efbf33c9683b0..5ea5fd5ac0226 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/cluster/settings/ClusterUpdateSettingsResponseTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/cluster/settings/ClusterUpdateSettingsResponseTests.java @@ -25,7 +25,6 @@ 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,23 +38,21 @@ protected ClusterUpdateSettingsResponse doParseInstance(XContentParser parser) { } @Override - protected EqualsHashCodeTestUtils.MutateFunction getMutateFunction() { - return response -> { - int i = randomIntBetween(0, 2); - switch(i) { - case 0: - return new ClusterUpdateSettingsResponse(response.isAcknowledged() == false, - response.transientSettings, response.persistentSettings); - case 1: - return new ClusterUpdateSettingsResponse(response.isAcknowledged(), mutateSettings(response.transientSettings), - response.persistentSettings); - case 2: - return new ClusterUpdateSettingsResponse(response.isAcknowledged(), response.transientSettings, - mutateSettings(response.persistentSettings)); - default: - throw new UnsupportedOperationException(); - } - }; + protected ClusterUpdateSettingsResponse mutateInstance(ClusterUpdateSettingsResponse response) { + int i = randomIntBetween(0, 2); + switch(i) { + case 0: + return new ClusterUpdateSettingsResponse(response.isAcknowledged() == false, + response.transientSettings, response.persistentSettings); + case 1: + return new ClusterUpdateSettingsResponse(response.isAcknowledged(), mutateSettings(response.transientSettings), + response.persistentSettings); + case 2: + return new ClusterUpdateSettingsResponse(response.isAcknowledged(), response.transientSettings, + mutateSettings(response.persistentSettings)); + default: + throw new UnsupportedOperationException(); + } } private static Settings mutateSettings(Settings settings) { diff --git a/server/src/test/java/org/elasticsearch/action/admin/indices/alias/IndicesAliasesResponseTests.java b/server/src/test/java/org/elasticsearch/action/admin/indices/alias/IndicesAliasesResponseTests.java index b4f48805154c6..b3eb9c81e8f96 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/indices/alias/IndicesAliasesResponseTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/indices/alias/IndicesAliasesResponseTests.java @@ -21,7 +21,6 @@ import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.test.AbstractStreamableXContentTestCase; -import org.elasticsearch.test.EqualsHashCodeTestUtils; public class IndicesAliasesResponseTests extends AbstractStreamableXContentTestCase { @@ -41,7 +40,7 @@ protected IndicesAliasesResponse createBlankInstance() { } @Override - protected EqualsHashCodeTestUtils.MutateFunction getMutateFunction() { - return response -> new IndicesAliasesResponse(response.isAcknowledged() == false); + protected IndicesAliasesResponse mutateInstance(IndicesAliasesResponse response) { + return new IndicesAliasesResponse(response.isAcknowledged() == false); } } diff --git a/server/src/test/java/org/elasticsearch/action/admin/indices/close/CloseIndexResponseTests.java b/server/src/test/java/org/elasticsearch/action/admin/indices/close/CloseIndexResponseTests.java index 6701270d11a99..3dd4f1e68ce51 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/indices/close/CloseIndexResponseTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/indices/close/CloseIndexResponseTests.java @@ -21,7 +21,6 @@ import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.test.AbstractStreamableXContentTestCase; -import org.elasticsearch.test.EqualsHashCodeTestUtils; public class CloseIndexResponseTests extends AbstractStreamableXContentTestCase { @@ -41,7 +40,7 @@ protected CloseIndexResponse createBlankInstance() { } @Override - protected EqualsHashCodeTestUtils.MutateFunction getMutateFunction() { - return response -> new CloseIndexResponse(response.isAcknowledged() == false); + protected CloseIndexResponse mutateInstance(CloseIndexResponse response) { + return new CloseIndexResponse(response.isAcknowledged() == false); } } diff --git a/server/src/test/java/org/elasticsearch/action/admin/indices/create/CreateIndexResponseTests.java b/server/src/test/java/org/elasticsearch/action/admin/indices/create/CreateIndexResponseTests.java index 21bf3a13a9624..0cb0063727fe7 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/indices/create/CreateIndexResponseTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/indices/create/CreateIndexResponseTests.java @@ -26,7 +26,6 @@ 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,23 +45,21 @@ protected CreateIndexResponse createBlankInstance() { } @Override - protected EqualsHashCodeTestUtils.MutateFunction getMutateFunction() { - return response -> { + protected CreateIndexResponse mutateInstance(CreateIndexResponse response) { + if (randomBoolean()) { if (randomBoolean()) { - if (randomBoolean()) { - boolean acknowledged = response.isAcknowledged() == false; - boolean shardsAcknowledged = acknowledged && response.isShardsAcknowledged(); - return new CreateIndexResponse(acknowledged, shardsAcknowledged, response.index()); - } else { - boolean shardsAcknowledged = response.isShardsAcknowledged() == false; - boolean acknowledged = shardsAcknowledged || response.isAcknowledged(); - return new CreateIndexResponse(acknowledged, shardsAcknowledged, response.index()); - } + boolean acknowledged = response.isAcknowledged() == false; + boolean shardsAcknowledged = acknowledged && response.isShardsAcknowledged(); + return new CreateIndexResponse(acknowledged, shardsAcknowledged, response.index()); } else { - return new CreateIndexResponse(response.isAcknowledged(), response.isShardsAcknowledged(), - response.index() + randomAlphaOfLengthBetween(2, 5)); + boolean shardsAcknowledged = response.isShardsAcknowledged() == false; + boolean acknowledged = shardsAcknowledged || response.isAcknowledged(); + return new CreateIndexResponse(acknowledged, shardsAcknowledged, response.index()); } - }; + } else { + return new CreateIndexResponse(response.isAcknowledged(), response.isShardsAcknowledged(), + response.index() + randomAlphaOfLengthBetween(2, 5)); + } } @Override diff --git a/server/src/test/java/org/elasticsearch/action/admin/indices/delete/DeleteIndexResponseTests.java b/server/src/test/java/org/elasticsearch/action/admin/indices/delete/DeleteIndexResponseTests.java index 9325dbdfa2bd8..4f3313e3d6b74 100755 --- a/server/src/test/java/org/elasticsearch/action/admin/indices/delete/DeleteIndexResponseTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/indices/delete/DeleteIndexResponseTests.java @@ -22,7 +22,6 @@ 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 { @@ -48,7 +47,7 @@ protected DeleteIndexResponse createBlankInstance() { } @Override - protected EqualsHashCodeTestUtils.MutateFunction getMutateFunction() { - return response -> new DeleteIndexResponse(response.isAcknowledged() == false); + protected DeleteIndexResponse mutateInstance(DeleteIndexResponse response) { + return new DeleteIndexResponse(response.isAcknowledged() == false); } } diff --git a/server/src/test/java/org/elasticsearch/action/admin/indices/mapping/put/PutMappingResponseTests.java b/server/src/test/java/org/elasticsearch/action/admin/indices/mapping/put/PutMappingResponseTests.java index 7d42d707605ac..d687b251e3432 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/indices/mapping/put/PutMappingResponseTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/indices/mapping/put/PutMappingResponseTests.java @@ -22,7 +22,6 @@ 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 { @@ -48,7 +47,7 @@ protected PutMappingResponse createBlankInstance() { } @Override - protected EqualsHashCodeTestUtils.MutateFunction getMutateFunction() { - return response -> new PutMappingResponse(response.isAcknowledged() == false); + protected PutMappingResponse mutateInstance(PutMappingResponse response) { + return new PutMappingResponse(response.isAcknowledged() == false); } } diff --git a/server/src/test/java/org/elasticsearch/action/admin/indices/open/OpenIndexResponseTests.java b/server/src/test/java/org/elasticsearch/action/admin/indices/open/OpenIndexResponseTests.java index 5eca6254edf4f..2ed86ca5d3be6 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/indices/open/OpenIndexResponseTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/indices/open/OpenIndexResponseTests.java @@ -21,7 +21,6 @@ import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.test.AbstractStreamableXContentTestCase; -import org.elasticsearch.test.EqualsHashCodeTestUtils; public class OpenIndexResponseTests extends AbstractStreamableXContentTestCase { @@ -43,17 +42,15 @@ protected OpenIndexResponse createBlankInstance() { } @Override - protected EqualsHashCodeTestUtils.MutateFunction getMutateFunction() { - return response -> { - if (randomBoolean()) { - boolean acknowledged = response.isAcknowledged() == false; - boolean shardsAcknowledged = acknowledged && response.isShardsAcknowledged(); - return new OpenIndexResponse(acknowledged, shardsAcknowledged); - } else { - boolean shardsAcknowledged = response.isShardsAcknowledged() == false; - boolean acknowledged = shardsAcknowledged || response.isAcknowledged(); - return new OpenIndexResponse(acknowledged, shardsAcknowledged); - } - }; + protected OpenIndexResponse mutateInstance(OpenIndexResponse response) { + if (randomBoolean()) { + boolean acknowledged = response.isAcknowledged() == false; + boolean shardsAcknowledged = acknowledged && response.isShardsAcknowledged(); + return new OpenIndexResponse(acknowledged, shardsAcknowledged); + } else { + boolean shardsAcknowledged = response.isShardsAcknowledged() == false; + boolean acknowledged = shardsAcknowledged || response.isAcknowledged(); + return new OpenIndexResponse(acknowledged, shardsAcknowledged); + } } } diff --git a/server/src/test/java/org/elasticsearch/action/admin/indices/rollover/RolloverResponseTests.java b/server/src/test/java/org/elasticsearch/action/admin/indices/rollover/RolloverResponseTests.java index 2f76f0f7ea725..a50f06cc54038 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/indices/rollover/RolloverResponseTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/indices/rollover/RolloverResponseTests.java @@ -22,7 +22,6 @@ 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,58 +74,56 @@ protected Predicate getRandomFieldsExcludeFilter() { } @Override - protected EqualsHashCodeTestUtils.MutateFunction getMutateFunction() { - return response -> { - int i = randomIntBetween(0, 6); - switch(i) { - case 0: - return new RolloverResponse(response.getOldIndex() + randomAlphaOfLengthBetween(2, 5), - response.getNewIndex(), response.getConditionStatus(), response.isDryRun(), response.isRolledOver(), - response.isAcknowledged(), response.isShardsAcknowledged()); - case 1: - return new RolloverResponse(response.getOldIndex(), response.getNewIndex() + randomAlphaOfLengthBetween(2, 5), - response.getConditionStatus(), response.isDryRun(), response.isRolledOver(), - response.isAcknowledged(), response.isShardsAcknowledged()); - case 2: - Map results; - if (response.getConditionStatus().isEmpty()) { - results = randomResults(false); - } else { - results = new HashMap<>(response.getConditionStatus().size()); - List keys = randomSubsetOf(randomIntBetween(1, response.getConditionStatus().size()), - response.getConditionStatus().keySet()); - for (Map.Entry entry : response.getConditionStatus().entrySet()) { - boolean value = keys.contains(entry.getKey()) ? entry.getValue() == false : entry.getValue(); - results.put(entry.getKey(), value); - } + protected RolloverResponse mutateInstance(RolloverResponse response) { + int i = randomIntBetween(0, 6); + switch(i) { + case 0: + return new RolloverResponse(response.getOldIndex() + randomAlphaOfLengthBetween(2, 5), + response.getNewIndex(), response.getConditionStatus(), response.isDryRun(), response.isRolledOver(), + response.isAcknowledged(), response.isShardsAcknowledged()); + case 1: + return new RolloverResponse(response.getOldIndex(), response.getNewIndex() + randomAlphaOfLengthBetween(2, 5), + response.getConditionStatus(), response.isDryRun(), response.isRolledOver(), + response.isAcknowledged(), response.isShardsAcknowledged()); + case 2: + Map results; + if (response.getConditionStatus().isEmpty()) { + results = randomResults(false); + } else { + results = new HashMap<>(response.getConditionStatus().size()); + List keys = randomSubsetOf(randomIntBetween(1, response.getConditionStatus().size()), + response.getConditionStatus().keySet()); + for (Map.Entry entry : response.getConditionStatus().entrySet()) { + boolean value = keys.contains(entry.getKey()) ? entry.getValue() == false : entry.getValue(); + results.put(entry.getKey(), value); } - return new RolloverResponse(response.getOldIndex(), response.getNewIndex(), results, response.isDryRun(), - response.isRolledOver(), response.isAcknowledged(), response.isShardsAcknowledged()); - case 3: - return new RolloverResponse(response.getOldIndex(), response.getNewIndex(), - response.getConditionStatus(), response.isDryRun() == false, response.isRolledOver(), - response.isAcknowledged(), response.isShardsAcknowledged()); - case 4: - return new RolloverResponse(response.getOldIndex(), response.getNewIndex(), - response.getConditionStatus(), response.isDryRun(), response.isRolledOver() == false, - response.isAcknowledged(), response.isShardsAcknowledged()); - case 5: { - boolean acknowledged = response.isAcknowledged() == false; - boolean shardsAcknowledged = acknowledged && response.isShardsAcknowledged(); - return new RolloverResponse(response.getOldIndex(), response.getNewIndex(), - response.getConditionStatus(), response.isDryRun(), response.isRolledOver(), - acknowledged, shardsAcknowledged); } - case 6: { - boolean shardsAcknowledged = response.isShardsAcknowledged() == false; - boolean acknowledged = shardsAcknowledged || response.isAcknowledged(); - return new RolloverResponse(response.getOldIndex(), response.getNewIndex(), - response.getConditionStatus(), response.isDryRun(), response.isRolledOver(), - acknowledged, shardsAcknowledged); - } - default: - throw new UnsupportedOperationException(); + return new RolloverResponse(response.getOldIndex(), response.getNewIndex(), results, response.isDryRun(), + response.isRolledOver(), response.isAcknowledged(), response.isShardsAcknowledged()); + case 3: + return new RolloverResponse(response.getOldIndex(), response.getNewIndex(), + response.getConditionStatus(), response.isDryRun() == false, response.isRolledOver(), + response.isAcknowledged(), response.isShardsAcknowledged()); + case 4: + return new RolloverResponse(response.getOldIndex(), response.getNewIndex(), + response.getConditionStatus(), response.isDryRun(), response.isRolledOver() == false, + response.isAcknowledged(), response.isShardsAcknowledged()); + case 5: { + boolean acknowledged = response.isAcknowledged() == false; + boolean shardsAcknowledged = acknowledged && response.isShardsAcknowledged(); + return new RolloverResponse(response.getOldIndex(), response.getNewIndex(), + response.getConditionStatus(), response.isDryRun(), response.isRolledOver(), + acknowledged, shardsAcknowledged); + } + case 6: { + boolean shardsAcknowledged = response.isShardsAcknowledged() == false; + boolean acknowledged = shardsAcknowledged || response.isAcknowledged(); + return new RolloverResponse(response.getOldIndex(), response.getNewIndex(), + response.getConditionStatus(), response.isDryRun(), response.isRolledOver(), + acknowledged, shardsAcknowledged); } - }; + default: + throw new UnsupportedOperationException(); + } } } diff --git a/server/src/test/java/org/elasticsearch/action/admin/indices/shrink/ResizeResponseTests.java b/server/src/test/java/org/elasticsearch/action/admin/indices/shrink/ResizeResponseTests.java index 7e03e980304fd..3e522e451011b 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/indices/shrink/ResizeResponseTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/indices/shrink/ResizeResponseTests.java @@ -22,7 +22,6 @@ 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 { @@ -51,22 +50,20 @@ protected ResizeResponse createBlankInstance() { } @Override - protected EqualsHashCodeTestUtils.MutateFunction getMutateFunction() { - return response -> { + protected ResizeResponse mutateInstance(ResizeResponse response) { + if (randomBoolean()) { if (randomBoolean()) { - if (randomBoolean()) { - boolean acknowledged = response.isAcknowledged() == false; - boolean shardsAcknowledged = acknowledged && response.isShardsAcknowledged(); - return new ResizeResponse(acknowledged, shardsAcknowledged, response.index()); - } else { - boolean shardsAcknowledged = response.isShardsAcknowledged() == false; - boolean acknowledged = shardsAcknowledged || response.isAcknowledged(); - return new ResizeResponse(acknowledged, shardsAcknowledged, response.index()); - } + boolean acknowledged = response.isAcknowledged() == false; + boolean shardsAcknowledged = acknowledged && response.isShardsAcknowledged(); + return new ResizeResponse(acknowledged, shardsAcknowledged, response.index()); } else { - return new ResizeResponse(response.isAcknowledged(), response.isShardsAcknowledged(), - response.index() + randomAlphaOfLengthBetween(2, 5)); + boolean shardsAcknowledged = response.isShardsAcknowledged() == false; + boolean acknowledged = shardsAcknowledged || response.isAcknowledged(); + return new ResizeResponse(acknowledged, shardsAcknowledged, response.index()); } - }; + } else { + return new ResizeResponse(response.isAcknowledged(), response.isShardsAcknowledged(), + response.index() + randomAlphaOfLengthBetween(2, 5)); + } } } diff --git a/server/src/test/java/org/elasticsearch/action/main/MainResponseTests.java b/server/src/test/java/org/elasticsearch/action/main/MainResponseTests.java index a7e18c29ce743..03e75f90ff9e3 100644 --- a/server/src/test/java/org/elasticsearch/action/main/MainResponseTests.java +++ b/server/src/test/java/org/elasticsearch/action/main/MainResponseTests.java @@ -27,7 +27,6 @@ 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 @@ public class MainResponseTests extends AbstractStreamableXContentTestCase { - @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 @@ protected MainResponse createTestInstance() { 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,36 +78,34 @@ public void testToXContent() throws IOException { } @Override - protected EqualsHashCodeTestUtils.MutateFunction 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(); - switch (randomIntBetween(0, 5)) { - case 0: - clusterUuid = clusterUuid + randomAlphaOfLength(5); - break; - case 1: - nodeName = nodeName + randomAlphaOfLength(5); - break; - case 2: - available = !available; - break; - case 3: - // toggle the snapshot flag of the original Build parameter - build = new Build(build.shortHash(), build.date(), !build.isSnapshot()); - break; - case 4: - version = randomValueOtherThan(version, () -> VersionUtils.randomVersion(random())); - break; - case 5: - clusterName = new ClusterName(clusterName + randomAlphaOfLength(5)); - break; - } - return new MainResponse(nodeName, version, clusterName, clusterUuid, build, available); - }; + 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); + break; + case 1: + nodeName = nodeName + randomAlphaOfLength(5); + break; + case 2: + available = !available; + break; + case 3: + // toggle the snapshot flag of the original Build parameter + build = new Build(build.shortHash(), build.date(), !build.isSnapshot()); + break; + case 4: + version = randomValueOtherThan(version, () -> VersionUtils.randomVersion(random())); + break; + case 5: + clusterName = new ClusterName(clusterName + randomAlphaOfLength(5)); + break; + } + return new MainResponse(nodeName, version, clusterName, clusterUuid, build, available); } } diff --git a/server/src/test/java/org/elasticsearch/index/refresh/RefreshStatsTests.java b/server/src/test/java/org/elasticsearch/index/refresh/RefreshStatsTests.java index 6dfcc53d43646..e0713fecff296 100644 --- a/server/src/test/java/org/elasticsearch/index/refresh/RefreshStatsTests.java +++ b/server/src/test/java/org/elasticsearch/index/refresh/RefreshStatsTests.java @@ -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 { @Override @@ -34,24 +33,22 @@ protected RefreshStats createBlankInstance() { } @Override - protected MutateFunction getMutateFunction() { - return instance -> { - long total = instance.getTotal(); - long totalInMillis = instance.getTotalTimeInMillis(); - int listeners = instance.getListeners(); - switch (randomInt(2)) { - case 0: - total += between(1, 2000); - break; - case 1: - totalInMillis += between(1, 2000); - break; - case 2: - default: - listeners += between(1, 2000); - break; - } - return new RefreshStats(total, totalInMillis, listeners); - }; + protected RefreshStats mutateInstance(RefreshStats instance) { + long total = instance.getTotal(); + long totalInMillis = instance.getTotalTimeInMillis(); + int listeners = instance.getListeners(); + switch (randomInt(2)) { + case 0: + total += between(1, 2000); + break; + case 1: + totalInMillis += between(1, 2000); + break; + case 2: + default: + listeners += between(1, 2000); + break; + } + return new RefreshStats(total, totalInMillis, listeners); } } diff --git a/server/src/test/java/org/elasticsearch/persistent/PersistentTasksCustomMetaDataTests.java b/server/src/test/java/org/elasticsearch/persistent/PersistentTasksCustomMetaDataTests.java index 7e731884dda41..537fc21ed433f 100644 --- a/server/src/test/java/org/elasticsearch/persistent/PersistentTasksCustomMetaDataTests.java +++ b/server/src/test/java/org/elasticsearch/persistent/PersistentTasksCustomMetaDataTests.java @@ -34,16 +34,15 @@ 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 @@ protected Writeable.Reader> diffReader() { } @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()); diff --git a/server/src/test/java/org/elasticsearch/script/StoredScriptSourceTests.java b/server/src/test/java/org/elasticsearch/script/StoredScriptSourceTests.java index 1a3df61f599a5..9174943e48b06 100644 --- a/server/src/test/java/org/elasticsearch/script/StoredScriptSourceTests.java +++ b/server/src/test/java/org/elasticsearch/script/StoredScriptSourceTests.java @@ -55,7 +55,7 @@ protected StoredScriptSource createTestInstance() { } @Override - protected StoredScriptSource doParseInstance(XContentParser parser) throws IOException { + protected StoredScriptSource doParseInstance(XContentParser parser) { return StoredScriptSource.fromXContent(parser); } diff --git a/server/src/test/java/org/elasticsearch/script/StoredScriptTests.java b/server/src/test/java/org/elasticsearch/script/StoredScriptTests.java index b7908383554d4..f66f4b68b55b1 100644 --- a/server/src/test/java/org/elasticsearch/script/StoredScriptTests.java +++ b/server/src/test/java/org/elasticsearch/script/StoredScriptTests.java @@ -27,8 +27,6 @@ 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 @@ protected Writeable.Reader instanceReader() { @Override protected StoredScriptSource doParseInstance(XContentParser parser) { - try { - return StoredScriptSource.fromXContent(parser); - } catch (IOException ioe) { - throw new UncheckedIOException(ioe); - } + return StoredScriptSource.fromXContent(parser); } @Override - protected StoredScriptSource mutateInstance(StoredScriptSource instance) throws IOException { + protected StoredScriptSource mutateInstance(StoredScriptSource instance) { String source = instance.getSource(); String lang = instance.getLang(); Map options = instance.getOptions(); diff --git a/server/src/test/java/org/elasticsearch/search/aggregations/InternalOrderTests.java b/server/src/test/java/org/elasticsearch/search/aggregations/InternalOrderTests.java index 130e54284e1da..a79934ea035de 100644 --- a/server/src/test/java/org/elasticsearch/search/aggregations/InternalOrderTests.java +++ b/server/src/test/java/org/elasticsearch/search/aggregations/InternalOrderTests.java @@ -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 @@ protected BucketOrder doParseInstance(XContentParser parser) throws IOException } @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) { diff --git a/server/src/test/java/org/elasticsearch/search/collapse/CollapseBuilderTests.java b/server/src/test/java/org/elasticsearch/search/collapse/CollapseBuilderTests.java index b4a840426687f..566df804f2bc9 100644 --- a/server/src/test/java/org/elasticsearch/search/collapse/CollapseBuilderTests.java +++ b/server/src/test/java/org/elasticsearch/search/collapse/CollapseBuilderTests.java @@ -64,7 +64,7 @@ public static void init() { } @AfterClass - public static void afterClass() throws Exception { + public static void afterClass() { namedWriteableRegistry = null; xContentRegistry = null; } @@ -186,7 +186,7 @@ public void testBuild() throws IOException { } } - public void testBuildWithSearchContextExceptions() throws IOException { + public void testBuildWithSearchContextExceptions() { SearchContext context = mockSearchContext(); { CollapseBuilder builder = new CollapseBuilder("unknown_field"); @@ -225,7 +225,7 @@ public Query existsQuery(QueryShardContext context) { } @Override - protected CollapseBuilder doParseInstance(XContentParser parser) throws IOException { + protected CollapseBuilder doParseInstance(XContentParser parser) { return CollapseBuilder.fromXContent(parser); } diff --git a/test/framework/src/main/java/org/elasticsearch/test/AbstractSerializingTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/AbstractSerializingTestCase.java index cca2f5c6702dd..f6423a6a1f014 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/AbstractSerializingTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/test/AbstractSerializingTestCase.java @@ -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 extends AbstractWireSerializingTestCase { @@ -35,35 +33,30 @@ public abstract class AbstractSerializingTestCase getRandomFieldsExcludeFilter() { + return field -> false; + } /** * Fields that have to be ignored when shuffling as part of testFromXContent diff --git a/test/framework/src/main/java/org/elasticsearch/test/AbstractStreamableTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/AbstractStreamableTestCase.java index 8690e894cd2b0..15ce07e977514 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/AbstractStreamableTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/test/AbstractStreamableTestCase.java @@ -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 extends ESTestCase { - protected static final int NUMBER_OF_TEST_RUNS = 20; +public abstract class AbstractStreamableTestCase extends AbstractWireTestCase { - /** - * 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 instanceReader() { + return Streamable.newWriteableReader(this::createBlankInstance); + } /** * Creates an empty instance to use when deserialising the @@ -47,83 +42,4 @@ public abstract class AbstractStreamableTestCase 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 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 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()); - } - } diff --git a/test/framework/src/main/java/org/elasticsearch/test/AbstractStreamableXContentTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/AbstractStreamableXContentTestCase.java index 657bc6d0e25d2..858ee7e46b2ca 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/AbstractStreamableXContentTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/test/AbstractStreamableXContentTestCase.java @@ -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 extends AbstractStreamableTestCase { /** @@ -38,34 +33,15 @@ public abstract class AbstractStreamableXContentTestCase 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; + } } diff --git a/test/framework/src/main/java/org/elasticsearch/test/AbstractWireSerializingTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/AbstractWireSerializingTestCase.java index 49e12f82e79ab..3478c14cfda53 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/AbstractWireSerializingTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/test/AbstractWireSerializingTestCase.java @@ -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 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 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 extends AbstractWireTestCase { + @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()); } } diff --git a/test/framework/src/main/java/org/elasticsearch/test/AbstractWireTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/AbstractWireTestCase.java new file mode 100644 index 0000000000000..bcd0c7cd3a7e2 --- /dev/null +++ b/test/framework/src/main/java/org/elasticsearch/test/AbstractWireTestCase.java @@ -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 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 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()); + } +} diff --git a/test/framework/src/main/java/org/elasticsearch/test/AbstractXContentTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/AbstractXContentTestCase.java new file mode 100644 index 0000000000000..a17744b2dd6cb --- /dev/null +++ b/test/framework/src/main/java/org/elasticsearch/test/AbstractXContentTestCase.java @@ -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 extends ESTestCase { + + public static void testFromXContent(int numberOfTestRuns, Supplier instanceSupplier, + boolean supportsUnknownFields, String[] shuffleFieldsExceptions, + Predicate randomFieldsExcludeFilter, + CheckedBiFunction + createParserFunction, + CheckedFunction parseFunction, + BiConsumer 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 getRandomFieldsExcludeFilter() { + return field -> false; + } + + /** + * Fields that have to be ignored when shuffling as part of testFromXContent + */ + protected String[] getShuffleFieldsExceptions() { + return Strings.EMPTY_ARRAY; + } +} diff --git a/test/framework/src/main/java/org/elasticsearch/test/ESTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/ESTestCase.java index 26e9c5e2327e1..e5efa0da8c08d 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/ESTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/test/ESTestCase.java @@ -29,7 +29,6 @@ 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.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.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 GeohashGenerator() { */ 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 + 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 @@ protected final XContentBuilder shuffleXContent(XContentBuilder builder, String. * 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 XContentBuilder shuffleXContent(XContentParser parser, boolean prettyPrin } // shuffle fields of objects in the list, but not the list itself + @SuppressWarnings("unchecked") private static List shuffleList(List list, Set exceptFields) { List targetList = new ArrayList<>(); for(Object value : list) { @@ -985,6 +1001,7 @@ private static List shuffleList(List list, Set exceptFie return targetList; } + @SuppressWarnings("unchecked") public static LinkedHashMap shuffleMap(LinkedHashMap map, Set exceptFields) { List keys = new ArrayList<>(map.keySet()); LinkedHashMap targetMap = new LinkedHashMap<>(); @@ -1010,11 +1027,39 @@ public static LinkedHashMap shuffleMap(LinkedHashMap T copyWriteable(T original, NamedWriteableRegistry namedWritabelRegistry, + public static T copyWriteable(T original, NamedWriteableRegistry namedWriteableRegistry, Writeable.Reader 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 copyWriteable(T original, NamedWriteableRegistry namedWriteableRegistry, + Writeable.Reader 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 copyStreamable(T original, NamedWriteableRegistry namedWriteableRegistry, + Supplier supplier, Version version) throws IOException { + return copyInstance(original, namedWriteableRegistry, (out, value) -> value.writeTo(out), + Streamable.newWriteableReader(supplier), version); + } + + private static T copyInstance(T original, NamedWriteableRegistry namedWriteableRegistry, Writeable.Writer writer, + Writeable.Reader 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); } }