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.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
@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
|
||||
*/
|
||||
|
|
|
@ -25,6 +25,7 @@ import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator;
|
|||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
public abstract class InternalNumericMetricsAggregation extends InternalMetricsAggregation {
|
||||
|
||||
|
@ -102,4 +103,16 @@ public abstract class InternalNumericMetricsAggregation extends InternalMetricsA
|
|||
protected InternalNumericMetricsAggregation(StreamInput in) throws IOException {
|
||||
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.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
public class InternalMin extends InternalNumericMetricsAggregation.SingleValue implements Min {
|
||||
private final double min;
|
||||
|
@ -89,4 +90,15 @@ public class InternalMin extends InternalNumericMetricsAggregation.SingleValue i
|
|||
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…
Reference in New Issue