mirror of
https://github.com/honeymoose/OpenSearch.git
synced 2025-02-17 10:25:15 +00:00
Adds abstract test classes for serialisation (#22281)
This adds test classes that can be used to test the wire serialisation and (optionally) the XContent serialisation of objects that implement Streamable/Writeable and ToXContent. These test classes will enable classes sich as InternalAggregation (or at least its implementations) to be tested in a consistent way when is comes to testing serialisation.
This commit is contained in:
parent
7946396fe6
commit
06576ed13b
@ -32,6 +32,7 @@ import org.elasticsearch.search.aggregations.support.AggregationPath;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An internal implementation of {@link Aggregation}. Serves as a base class for all aggregation implementations.
|
* An internal implementation of {@link Aggregation}. Serves as a base class for all aggregation implementations.
|
||||||
@ -189,6 +190,48 @@ public abstract class InternalAggregation implements Aggregation, ToXContent, Na
|
|||||||
|
|
||||||
public abstract XContentBuilder doXContentBody(XContentBuilder builder, Params params) throws IOException;
|
public abstract XContentBuilder doXContentBody(XContentBuilder builder, Params params) throws IOException;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(name, metaData, pipelineAggregators, doHashCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
// norelease: make this abstract when all InternalAggregations implement this method
|
||||||
|
/**
|
||||||
|
* Opportunity for subclasses to the {@link #hashCode(Object)} for this
|
||||||
|
* class.
|
||||||
|
**/
|
||||||
|
protected int doHashCode() {
|
||||||
|
return System.identityHashCode(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (obj.getClass() != getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
InternalAggregation other = (InternalAggregation) obj;
|
||||||
|
return Objects.equals(name, other.name) &&
|
||||||
|
Objects.equals(pipelineAggregators, other.pipelineAggregators) &&
|
||||||
|
Objects.equals(metaData, other.metaData) &&
|
||||||
|
doEquals(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
// norelease: make this abstract when all InternalAggregations implement this method
|
||||||
|
/**
|
||||||
|
* Opportunity for subclasses to add criteria to the {@link #equals(Object)}
|
||||||
|
* method for this class.
|
||||||
|
*
|
||||||
|
* This method can safely cast <code>obj</code> to the subclass since the
|
||||||
|
* {@link #equals(Object)} method checks that <code>obj</code> is the same
|
||||||
|
* class as <code>this</code>
|
||||||
|
*/
|
||||||
|
protected boolean doEquals(Object obj) {
|
||||||
|
return this == obj;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Common xcontent fields that are shared among addAggregation
|
* Common xcontent fields that are shared among addAggregation
|
||||||
*/
|
*/
|
||||||
|
@ -25,6 +25,7 @@ import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
public abstract class InternalNumericMetricsAggregation extends InternalMetricsAggregation {
|
public abstract class InternalNumericMetricsAggregation extends InternalMetricsAggregation {
|
||||||
|
|
||||||
@ -102,4 +103,16 @@ public abstract class InternalNumericMetricsAggregation extends InternalMetricsA
|
|||||||
protected InternalNumericMetricsAggregation(StreamInput in) throws IOException {
|
protected InternalNumericMetricsAggregation(StreamInput in) throws IOException {
|
||||||
super(in);
|
super(in);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int doHashCode() {
|
||||||
|
return Objects.hash(format, super.hashCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean doEquals(Object obj) {
|
||||||
|
InternalNumericMetricsAggregation other = (InternalNumericMetricsAggregation) obj;
|
||||||
|
return super.equals(obj) &&
|
||||||
|
Objects.equals(format, other.format);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
public class InternalMin extends InternalNumericMetricsAggregation.SingleValue implements Min {
|
public class InternalMin extends InternalNumericMetricsAggregation.SingleValue implements Min {
|
||||||
private final double min;
|
private final double min;
|
||||||
@ -89,4 +90,15 @@ public class InternalMin extends InternalNumericMetricsAggregation.SingleValue i
|
|||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int doHashCode() {
|
||||||
|
return Objects.hash(min);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean doEquals(Object obj) {
|
||||||
|
InternalMin other = (InternalMin) obj;
|
||||||
|
return Objects.equals(min, other.min);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* 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.search.aggregations.metrics.min;
|
||||||
|
|
||||||
|
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
|
||||||
|
import org.elasticsearch.common.io.stream.NamedWriteableRegistry.Entry;
|
||||||
|
import org.elasticsearch.common.io.stream.Writeable.Reader;
|
||||||
|
import org.elasticsearch.search.DocValueFormat;
|
||||||
|
import org.elasticsearch.test.AbstractWireSerializingTestCase;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class InternalMinTests extends AbstractWireSerializingTestCase<InternalMin> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected InternalMin createTestInstance() {
|
||||||
|
return new InternalMin(randomAsciiOfLengthBetween(1, 20), randomDouble(),
|
||||||
|
randomFrom(DocValueFormat.BOOLEAN, DocValueFormat.GEOHASH, DocValueFormat.IP, DocValueFormat.RAW), Collections.emptyList(),
|
||||||
|
new HashMap<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Reader<InternalMin> instanceReader() {
|
||||||
|
return InternalMin::new;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected NamedWriteableRegistry getNamedWriteableRegistry() {
|
||||||
|
List<Entry> entries = new ArrayList<>();
|
||||||
|
entries.add(new NamedWriteableRegistry.Entry(DocValueFormat.class, DocValueFormat.BOOLEAN.getWriteableName(),
|
||||||
|
in -> DocValueFormat.BOOLEAN));
|
||||||
|
entries.add(new NamedWriteableRegistry.Entry(DocValueFormat.class, DocValueFormat.DateTime.NAME, DocValueFormat.DateTime::new));
|
||||||
|
entries.add(new NamedWriteableRegistry.Entry(DocValueFormat.class, DocValueFormat.Decimal.NAME, DocValueFormat.Decimal::new));
|
||||||
|
entries.add(new NamedWriteableRegistry.Entry(DocValueFormat.class, DocValueFormat.GEOHASH.getWriteableName(),
|
||||||
|
in -> DocValueFormat.GEOHASH));
|
||||||
|
entries.add(new NamedWriteableRegistry.Entry(DocValueFormat.class, DocValueFormat.IP.getWriteableName(), in -> DocValueFormat.IP));
|
||||||
|
entries.add(
|
||||||
|
new NamedWriteableRegistry.Entry(DocValueFormat.class, DocValueFormat.RAW.getWriteableName(), in -> DocValueFormat.RAW));
|
||||||
|
return new NamedWriteableRegistry(entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,104 @@
|
|||||||
|
/*
|
||||||
|
* 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.bytes.BytesArray;
|
||||||
|
import org.elasticsearch.common.bytes.BytesReference;
|
||||||
|
import org.elasticsearch.common.io.stream.Writeable;
|
||||||
|
import org.elasticsearch.common.xcontent.ToXContent;
|
||||||
|
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||||
|
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.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public abstract class AbstractSerializingTestCase<T extends ToXContent & Writeable> extends AbstractWireSerializingTestCase<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic test that creates new instance from the test instance and checks
|
||||||
|
* both for equality and asserts equality on the two instances.
|
||||||
|
*/
|
||||||
|
public void testFromXContent() throws IOException {
|
||||||
|
for (int runs = 0; runs < NUMBER_OF_TEST_RUNS; runs++) {
|
||||||
|
T testInstance = createTestInstance();
|
||||||
|
XContentType xContentType = randomFrom(XContentType.values());
|
||||||
|
XContentBuilder builder = toXContent(testInstance, xContentType);
|
||||||
|
XContentBuilder shuffled = shuffleXContent(builder);
|
||||||
|
assertParsedInstance(xContentType, shuffled.bytes(), testInstance);
|
||||||
|
for (Map.Entry<String, T> alternateVersion : getAlternateVersions().entrySet()) {
|
||||||
|
String instanceAsString = alternateVersion.getKey();
|
||||||
|
assertParsedInstance(XContentType.JSON, new BytesArray(instanceAsString), alternateVersion.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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());
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the provided instance in XContent
|
||||||
|
*
|
||||||
|
* @param instance
|
||||||
|
* the instance to render
|
||||||
|
* @param contentType
|
||||||
|
* the content type to render to
|
||||||
|
*/
|
||||||
|
protected XContentBuilder toXContent(T instance, XContentType contentType)
|
||||||
|
throws IOException {
|
||||||
|
XContentBuilder builder = XContentFactory.contentBuilder(contentType);
|
||||||
|
if (randomBoolean()) {
|
||||||
|
builder.prettyPrint();
|
||||||
|
}
|
||||||
|
instance.toXContent(builder, ToXContent.EMPTY_PARAMS);
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns alternate string representation of the instance that need to be
|
||||||
|
* tested as they are never used as output of the test instance. By default
|
||||||
|
* there are no alternate versions.
|
||||||
|
*
|
||||||
|
* These alternatives must be JSON strings.
|
||||||
|
*/
|
||||||
|
protected Map<String, T> getAlternateVersions() {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,127 @@
|
|||||||
|
/*
|
||||||
|
* 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.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 java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
|
|
||||||
|
public abstract class AbstractStreamableTestCase<T extends Streamable> 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();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an empty instance to use when deserialising the
|
||||||
|
* {@link Streamable}. This usually returns an instance created using the
|
||||||
|
* zer-arg constructor
|
||||||
|
*/
|
||||||
|
protected abstract T createBlankInstance();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that the equals and hashcode methods are consistent and copied
|
||||||
|
* versions of the instance have are equal.
|
||||||
|
*/
|
||||||
|
public void testEqualsAndHashcode() throws IOException {
|
||||||
|
for (int runs = 0; runs < NUMBER_OF_TEST_RUNS; runs++) {
|
||||||
|
T firstInstance = createTestInstance();
|
||||||
|
assertFalse("instance is equal to null", firstInstance.equals(null));
|
||||||
|
assertFalse("instance is equal to incompatible type", firstInstance.equals(""));
|
||||||
|
assertEquals("instance is not equal to self", firstInstance, firstInstance);
|
||||||
|
assertThat("same instance's hashcode returns different values if called multiple times", firstInstance.hashCode(),
|
||||||
|
equalTo(firstInstance.hashCode()));
|
||||||
|
|
||||||
|
T secondInstance = copyInstance(firstInstance);
|
||||||
|
assertEquals("instance is not equal to self", secondInstance, secondInstance);
|
||||||
|
assertEquals("instance is not equal to its copy", firstInstance, secondInstance);
|
||||||
|
assertEquals("equals is not symmetric", secondInstance, firstInstance);
|
||||||
|
assertThat("instance copy's hashcode is different from original hashcode", secondInstance.hashCode(),
|
||||||
|
equalTo(firstInstance.hashCode()));
|
||||||
|
|
||||||
|
T thirdInstance = copyInstance(secondInstance);
|
||||||
|
assertEquals("instance is not equal to self", thirdInstance, thirdInstance);
|
||||||
|
assertEquals("instance is not equal to its copy", secondInstance, thirdInstance);
|
||||||
|
assertThat("instance copy's hashcode is different from original hashcode", secondInstance.hashCode(),
|
||||||
|
equalTo(thirdInstance.hashCode()));
|
||||||
|
assertEquals("equals is not transitive", firstInstance, thirdInstance);
|
||||||
|
assertThat("instance copy's hashcode is different from original hashcode", firstInstance.hashCode(),
|
||||||
|
equalTo(thirdInstance.hashCode()));
|
||||||
|
assertEquals("equals is not symmetric", thirdInstance, secondInstance);
|
||||||
|
assertEquals("equals is not symmetric", thirdInstance, firstInstance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test serialization and deserialization of the test instance.
|
||||||
|
*/
|
||||||
|
public 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);
|
||||||
|
assertEquals(testInstance, deserializedInstance);
|
||||||
|
assertEquals(testInstance.hashCode(), deserializedInstance.hashCode());
|
||||||
|
assertNotSame(testInstance, deserializedInstance);
|
||||||
|
return deserializedInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private T copyInstance(T instance) throws IOException {
|
||||||
|
try (BytesStreamOutput output = new BytesStreamOutput()) {
|
||||||
|
instance.writeTo(output);
|
||||||
|
try (StreamInput in = new NamedWriteableAwareStreamInput(output.bytes().streamInput(),
|
||||||
|
getNamedWriteableRegistry())) {
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* 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.bytes.BytesArray;
|
||||||
|
import org.elasticsearch.common.bytes.BytesReference;
|
||||||
|
import org.elasticsearch.common.io.stream.Streamable;
|
||||||
|
import org.elasticsearch.common.xcontent.ToXContent;
|
||||||
|
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||||
|
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.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public abstract class AbstractStreamableXContentTestCase<T extends ToXContent & Streamable> extends AbstractStreamableTestCase<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic test that creates new instance from the test instance and checks
|
||||||
|
* both for equality and asserts equality on the two queries.
|
||||||
|
*/
|
||||||
|
public void testFromXContent() throws IOException {
|
||||||
|
for (int runs = 0; runs < NUMBER_OF_TEST_RUNS; runs++) {
|
||||||
|
T testInstance = createTestInstance();
|
||||||
|
XContentType xContentType = randomFrom(XContentType.values());
|
||||||
|
XContentBuilder builder = toXContent(testInstance, xContentType);
|
||||||
|
XContentBuilder shuffled = shuffleXContent(builder);
|
||||||
|
assertParsedInstance(xContentType, shuffled.bytes(), testInstance);
|
||||||
|
for (Map.Entry<String, T> alternateVersion : getAlternateVersions().entrySet()) {
|
||||||
|
String instanceAsString = alternateVersion.getKey();
|
||||||
|
assertParsedInstance(XContentType.JSON, new BytesArray(instanceAsString), alternateVersion.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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());
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the provided instance in XContent
|
||||||
|
*
|
||||||
|
* @param instance
|
||||||
|
* the instance to render
|
||||||
|
* @param contentType
|
||||||
|
* the content type to render to
|
||||||
|
*/
|
||||||
|
protected static <T extends ToXContent> XContentBuilder toXContent(T instance, XContentType contentType)
|
||||||
|
throws IOException {
|
||||||
|
XContentBuilder builder = XContentFactory.contentBuilder(contentType);
|
||||||
|
if (randomBoolean()) {
|
||||||
|
builder.prettyPrint();
|
||||||
|
}
|
||||||
|
instance.toXContent(builder, ToXContent.EMPTY_PARAMS);
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns alternate string representation of the instance that need to be
|
||||||
|
* tested as they are never used as output of the test instance. By default
|
||||||
|
* there are no alternate versions.
|
||||||
|
*
|
||||||
|
* These alternatives must be JSON strings.
|
||||||
|
*/
|
||||||
|
protected Map<String, T> getAlternateVersions() {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,123 @@
|
|||||||
|
/*
|
||||||
|
* 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.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;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that the equals and hashcode methods are consistent and copied
|
||||||
|
* versions of the instance have are equal.
|
||||||
|
*/
|
||||||
|
public void testEqualsAndHashcode() throws IOException {
|
||||||
|
for (int runs = 0; runs < NUMBER_OF_TEST_RUNS; runs++) {
|
||||||
|
T firstInstance = createTestInstance();
|
||||||
|
assertFalse("instance is equal to null", firstInstance.equals(null));
|
||||||
|
assertFalse("instance is equal to incompatible type", firstInstance.equals(""));
|
||||||
|
assertEquals("instance is not equal to self", firstInstance, firstInstance);
|
||||||
|
assertThat("same instance's hashcode returns different values if called multiple times", firstInstance.hashCode(),
|
||||||
|
equalTo(firstInstance.hashCode()));
|
||||||
|
|
||||||
|
T secondInstance = copyInstance(firstInstance);
|
||||||
|
assertEquals("instance is not equal to self", secondInstance, secondInstance);
|
||||||
|
assertEquals("instance is not equal to its copy", firstInstance, secondInstance);
|
||||||
|
assertEquals("equals is not symmetric", secondInstance, firstInstance);
|
||||||
|
assertThat("instance copy's hashcode is different from original hashcode", secondInstance.hashCode(),
|
||||||
|
equalTo(firstInstance.hashCode()));
|
||||||
|
|
||||||
|
T thirdInstance = copyInstance(secondInstance);
|
||||||
|
assertEquals("instance is not equal to self", thirdInstance, thirdInstance);
|
||||||
|
assertEquals("instance is not equal to its copy", secondInstance, thirdInstance);
|
||||||
|
assertThat("instance copy's hashcode is different from original hashcode", secondInstance.hashCode(),
|
||||||
|
equalTo(thirdInstance.hashCode()));
|
||||||
|
assertEquals("equals is not transitive", firstInstance, thirdInstance);
|
||||||
|
assertThat("instance copy's hashcode is different from original hashcode", firstInstance.hashCode(),
|
||||||
|
equalTo(thirdInstance.hashCode()));
|
||||||
|
assertEquals("equals is not symmetric", thirdInstance, secondInstance);
|
||||||
|
assertEquals("equals is not symmetric", thirdInstance, firstInstance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test serialization and deserialization of the test instance.
|
||||||
|
*/
|
||||||
|
public 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);
|
||||||
|
assertEquals(testInstance, deserializedInstance);
|
||||||
|
assertEquals(testInstance.hashCode(), deserializedInstance.hashCode());
|
||||||
|
assertNotSame(testInstance, deserializedInstance);
|
||||||
|
return deserializedInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private T copyInstance(T instance) throws IOException {
|
||||||
|
try (BytesStreamOutput output = new BytesStreamOutput()) {
|
||||||
|
instance.writeTo(output);
|
||||||
|
try (StreamInput in = new NamedWriteableAwareStreamInput(output.bytes().streamInput(),
|
||||||
|
getNamedWriteableRegistry())) {
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user