/*
* 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.io.stream;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.Constants;
import org.elasticsearch.Version;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.lucene.BytesRefs;
import org.elasticsearch.common.util.BigArrays;
import org.elasticsearch.test.ESTestCase;
import org.joda.time.DateTimeZone;
import java.io.EOFException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import static org.hamcrest.Matchers.closeTo;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
/**
* Tests for {@link BytesStreamOutput} paging behaviour.
*/
public class BytesStreamsTests extends ESTestCase {
public void testEmpty() throws Exception {
BytesStreamOutput out = new BytesStreamOutput();
// test empty stream to array
assertEquals(0, out.size());
assertEquals(0, out.bytes().length());
out.close();
}
public void testSingleByte() throws Exception {
BytesStreamOutput out = new BytesStreamOutput();
assertEquals(0, out.size());
int expectedSize = 1;
byte[] expectedData = randomizedByteArrayWithSize(expectedSize);
// write single byte
out.writeByte(expectedData[0]);
assertEquals(expectedSize, out.size());
assertArrayEquals(expectedData, BytesReference.toBytes(out.bytes()));
out.close();
}
public void testSingleShortPage() throws Exception {
BytesStreamOutput out = new BytesStreamOutput();
int expectedSize = 10;
byte[] expectedData = randomizedByteArrayWithSize(expectedSize);
// write byte-by-byte
for (int i = 0; i < expectedSize; i++) {
out.writeByte(expectedData[i]);
}
assertEquals(expectedSize, out.size());
assertArrayEquals(expectedData, BytesReference.toBytes(out.bytes()));
out.close();
}
public void testIllegalBulkWrite() throws Exception {
BytesStreamOutput out = new BytesStreamOutput();
// bulk-write with wrong args
expectThrows(IllegalArgumentException.class, () -> out.writeBytes(new byte[]{}, 0, 1));
out.close();
}
public void testSingleShortPageBulkWrite() throws Exception {
BytesStreamOutput out = new BytesStreamOutput();
// first bulk-write empty array: should not change anything
int expectedSize = 0;
byte[] expectedData = randomizedByteArrayWithSize(expectedSize);
out.writeBytes(expectedData);
assertEquals(expectedSize, out.size());
assertArrayEquals(expectedData, BytesReference.toBytes(out.bytes()));
// bulk-write again with actual bytes
expectedSize = 10;
expectedData = randomizedByteArrayWithSize(expectedSize);
out.writeBytes(expectedData);
assertEquals(expectedSize, out.size());
assertArrayEquals(expectedData, BytesReference.toBytes(out.bytes()));
out.close();
}
public void testSingleFullPageBulkWrite() throws Exception {
BytesStreamOutput out = new BytesStreamOutput();
int expectedSize = BigArrays.BYTE_PAGE_SIZE;
byte[] expectedData = randomizedByteArrayWithSize(expectedSize);
// write in bulk
out.writeBytes(expectedData);
assertEquals(expectedSize, out.size());
assertArrayEquals(expectedData, BytesReference.toBytes(out.bytes()));
out.close();
}
public void testSingleFullPageBulkWriteWithOffset() throws Exception {
BytesStreamOutput out = new BytesStreamOutput();
int initialOffset = 10;
int additionalLength = BigArrays.BYTE_PAGE_SIZE;
byte[] expectedData = randomizedByteArrayWithSize(initialOffset + additionalLength);
// first create initial offset
out.writeBytes(expectedData, 0, initialOffset);
assertEquals(initialOffset, out.size());
// now write the rest - more than fits into the remaining first page
out.writeBytes(expectedData, initialOffset, additionalLength);
assertEquals(expectedData.length, out.size());
assertArrayEquals(expectedData, BytesReference.toBytes(out.bytes()));
out.close();
}
public void testSingleFullPageBulkWriteWithOffsetCrossover() throws Exception {
BytesStreamOutput out = new BytesStreamOutput();
int initialOffset = 10;
int additionalLength = BigArrays.BYTE_PAGE_SIZE * 2;
byte[] expectedData = randomizedByteArrayWithSize(initialOffset + additionalLength);
out.writeBytes(expectedData, 0, initialOffset);
assertEquals(initialOffset, out.size());
// now write the rest - more than fits into the remaining page + a full page after
// that,
// ie. we cross over into a third
out.writeBytes(expectedData, initialOffset, additionalLength);
assertEquals(expectedData.length, out.size());
assertArrayEquals(expectedData, BytesReference.toBytes(out.bytes()));
out.close();
}
public void testSingleFullPage() throws Exception {
BytesStreamOutput out = new BytesStreamOutput();
int expectedSize = BigArrays.BYTE_PAGE_SIZE;
byte[] expectedData = randomizedByteArrayWithSize(expectedSize);
// write byte-by-byte
for (int i = 0; i < expectedSize; i++) {
out.writeByte(expectedData[i]);
}
assertEquals(expectedSize, out.size());
assertArrayEquals(expectedData, BytesReference.toBytes(out.bytes()));
out.close();
}
public void testOneFullOneShortPage() throws Exception {
BytesStreamOutput out = new BytesStreamOutput();
int expectedSize = BigArrays.BYTE_PAGE_SIZE + 10;
byte[] expectedData = randomizedByteArrayWithSize(expectedSize);
// write byte-by-byte
for (int i = 0; i < expectedSize; i++) {
out.writeByte(expectedData[i]);
}
assertEquals(expectedSize, out.size());
assertArrayEquals(expectedData, BytesReference.toBytes(out.bytes()));
out.close();
}
public void testTwoFullOneShortPage() throws Exception {
BytesStreamOutput out = new BytesStreamOutput();
int expectedSize = (BigArrays.BYTE_PAGE_SIZE * 2) + 1;
byte[] expectedData = randomizedByteArrayWithSize(expectedSize);
// write byte-by-byte
for (int i = 0; i < expectedSize; i++) {
out.writeByte(expectedData[i]);
}
assertEquals(expectedSize, out.size());
assertArrayEquals(expectedData, BytesReference.toBytes(out.bytes()));
out.close();
}
public void testSeek() throws Exception {
BytesStreamOutput out = new BytesStreamOutput();
int position = 0;
assertEquals(position, out.position());
out.seek(position += 10);
out.seek(position += BigArrays.BYTE_PAGE_SIZE);
out.seek(position += BigArrays.BYTE_PAGE_SIZE + 10);
out.seek(position += BigArrays.BYTE_PAGE_SIZE * 2);
assertEquals(position, out.position());
assertEquals(position, BytesReference.toBytes(out.bytes()).length);
IllegalArgumentException iae = expectThrows(IllegalArgumentException.class, () -> out.seek(Integer.MAX_VALUE + 1L));
assertEquals("BytesStreamOutput cannot hold more than 2GB of data", iae.getMessage());
out.close();
}
public void testSkip() throws Exception {
BytesStreamOutput out = new BytesStreamOutput();
int position = 0;
assertEquals(position, out.position());
int forward = 100;
out.skip(forward);
assertEquals(position + forward, out.position());
IllegalArgumentException iae = expectThrows(IllegalArgumentException.class, () -> out.skip(Integer.MAX_VALUE - 50));
assertEquals("BytesStreamOutput cannot hold more than 2GB of data", iae.getMessage());
out.close();
}
public void testSimpleStreams() throws Exception {
assumeTrue("requires a 64-bit JRE ... ?!", Constants.JRE_IS_64BIT);
BytesStreamOutput out = new BytesStreamOutput();
out.writeBoolean(false);
out.writeByte((byte) 1);
out.writeShort((short) -1);
out.writeInt(-1);
out.writeVInt(2);
out.writeLong(-3);
out.writeVLong(4);
out.writeOptionalLong(11234234L);
out.writeFloat(1.1f);
out.writeDouble(2.2);
int[] intArray = {1, 2, 3};
out.writeGenericValue(intArray);
int[] vIntArray = {4, 5, 6};
out.writeVIntArray(vIntArray);
long[] longArray = {1, 2, 3};
out.writeGenericValue(longArray);
long[] vLongArray = {4, 5, 6};
out.writeVLongArray(vLongArray);
float[] floatArray = {1.1f, 2.2f, 3.3f};
out.writeGenericValue(floatArray);
double[] doubleArray = {1.1, 2.2, 3.3};
out.writeGenericValue(doubleArray);
out.writeString("hello");
out.writeString("goodbye");
out.writeGenericValue(BytesRefs.toBytesRef("bytesref"));
out.writeStringArray(new String[] {"a", "b", "cat"});
out.writeBytesReference(new BytesArray("test"));
out.writeOptionalBytesReference(new BytesArray("test"));
out.writeOptionalDouble(null);
out.writeOptionalDouble(1.2);
out.writeTimeZone(DateTimeZone.forID("CET"));
out.writeOptionalTimeZone(DateTimeZone.getDefault());
out.writeOptionalTimeZone(null);
final byte[] bytes = BytesReference.toBytes(out.bytes());
StreamInput in = StreamInput.wrap(BytesReference.toBytes(out.bytes()));
assertEquals(in.available(), bytes.length);
assertThat(in.readBoolean(), equalTo(false));
assertThat(in.readByte(), equalTo((byte)1));
assertThat(in.readShort(), equalTo((short)-1));
assertThat(in.readInt(), equalTo(-1));
assertThat(in.readVInt(), equalTo(2));
assertThat(in.readLong(), equalTo(-3L));
assertThat(in.readVLong(), equalTo(4L));
assertThat(in.readOptionalLong(), equalTo(11234234L));
assertThat((double)in.readFloat(), closeTo(1.1, 0.0001));
assertThat(in.readDouble(), closeTo(2.2, 0.0001));
assertThat(in.readGenericValue(), equalTo((Object) intArray));
assertThat(in.readVIntArray(), equalTo(vIntArray));
assertThat(in.readGenericValue(), equalTo((Object)longArray));
assertThat(in.readVLongArray(), equalTo(vLongArray));
assertThat(in.readGenericValue(), equalTo((Object)floatArray));
assertThat(in.readGenericValue(), equalTo((Object)doubleArray));
assertThat(in.readString(), equalTo("hello"));
assertThat(in.readString(), equalTo("goodbye"));
assertThat(in.readGenericValue(), equalTo((Object)BytesRefs.toBytesRef("bytesref")));
assertThat(in.readStringArray(), equalTo(new String[] {"a", "b", "cat"}));
assertThat(in.readBytesReference(), equalTo(new BytesArray("test")));
assertThat(in.readOptionalBytesReference(), equalTo(new BytesArray("test")));
assertNull(in.readOptionalDouble());
assertThat(in.readOptionalDouble(), closeTo(1.2, 0.0001));
assertEquals(DateTimeZone.forID("CET"), in.readTimeZone());
assertEquals(DateTimeZone.getDefault(), in.readOptionalTimeZone());
assertNull(in.readOptionalTimeZone());
assertEquals(0, in.available());
in.close();
out.close();
}
public void testNamedWriteable() throws IOException {
try (BytesStreamOutput out = new BytesStreamOutput()) {
NamedWriteableRegistry namedWriteableRegistry = new NamedWriteableRegistry(Collections.singletonList(
new NamedWriteableRegistry.Entry(BaseNamedWriteable.class, TestNamedWriteable.NAME, TestNamedWriteable::new)));
TestNamedWriteable namedWriteableIn = new TestNamedWriteable(randomAlphaOfLengthBetween(1, 10),
randomAlphaOfLengthBetween(1, 10));
out.writeNamedWriteable(namedWriteableIn);
byte[] bytes = BytesReference.toBytes(out.bytes());
try (StreamInput in = new NamedWriteableAwareStreamInput(StreamInput.wrap(bytes), namedWriteableRegistry)) {
assertEquals(in.available(), bytes.length);
BaseNamedWriteable namedWriteableOut = in.readNamedWriteable(BaseNamedWriteable.class);
assertEquals(namedWriteableIn, namedWriteableOut);
assertEquals(0, in.available());
}
}
}
public void testNamedWriteableList() throws IOException {
NamedWriteableRegistry namedWriteableRegistry = new NamedWriteableRegistry(Collections.singletonList(
new NamedWriteableRegistry.Entry(BaseNamedWriteable.class, TestNamedWriteable.NAME, TestNamedWriteable::new)
));
int size = between(0, 100);
List<BaseNamedWriteable> expected = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
expected.add(new TestNamedWriteable(randomAlphaOfLengthBetween(1, 10), randomAlphaOfLengthBetween(1, 10)));
}
try (BytesStreamOutput out = new BytesStreamOutput()) {
out.writeNamedWriteableList(expected);
try (StreamInput in = new NamedWriteableAwareStreamInput(out.bytes().streamInput(), namedWriteableRegistry)) {
assertEquals(expected, in.readNamedWriteableList(BaseNamedWriteable.class));
assertEquals(0, in.available());
}
}
}
public void testNamedWriteableNotSupportedWithoutWrapping() throws IOException {
try (BytesStreamOutput out = new BytesStreamOutput()) {
TestNamedWriteable testNamedWriteable = new TestNamedWriteable("test1", "test2");
out.writeNamedWriteable(testNamedWriteable);
StreamInput in = StreamInput.wrap(BytesReference.toBytes(out.bytes()));
Exception e = expectThrows(UnsupportedOperationException.class, () -> in.readNamedWriteable(BaseNamedWriteable.class));
assertThat(e.getMessage(), is("can't read named writeable from StreamInput"));
}
}
public void testNamedWriteableReaderReturnsNull() throws IOException {
try (BytesStreamOutput out = new BytesStreamOutput()) {
NamedWriteableRegistry namedWriteableRegistry = new NamedWriteableRegistry(Collections.singletonList(
new NamedWriteableRegistry.Entry(BaseNamedWriteable.class, TestNamedWriteable.NAME, (StreamInput in) -> null)));
TestNamedWriteable namedWriteableIn = new TestNamedWriteable(randomAlphaOfLengthBetween(1, 10),
randomAlphaOfLengthBetween(1, 10));
out.writeNamedWriteable(namedWriteableIn);
byte[] bytes = BytesReference.toBytes(out.bytes());
try (StreamInput in = new NamedWriteableAwareStreamInput(StreamInput.wrap(bytes), namedWriteableRegistry)) {
assertEquals(in.available(), bytes.length);
IOException e = expectThrows(IOException.class, () -> in.readNamedWriteable(BaseNamedWriteable.class));
assertThat(e.getMessage(), endsWith("] returned null which is not allowed and probably means it screwed up the stream."));
}
}
}
public void testOptionalWriteableReaderReturnsNull() throws IOException {
try (BytesStreamOutput out = new BytesStreamOutput()) {
out.writeOptionalWriteable(new TestNamedWriteable(randomAlphaOfLengthBetween(1, 10), randomAlphaOfLengthBetween(1, 10)));
StreamInput in = StreamInput.wrap(BytesReference.toBytes(out.bytes()));
IOException e = expectThrows(IOException.class, () -> in.readOptionalWriteable((StreamInput ignored) -> null));
assertThat(e.getMessage(), endsWith("] returned null which is not allowed and probably means it screwed up the stream."));
}
}
public void testWriteableReaderReturnsWrongName() throws IOException {
try (BytesStreamOutput out = new BytesStreamOutput()) {
NamedWriteableRegistry namedWriteableRegistry = new NamedWriteableRegistry(
Collections.singletonList(new NamedWriteableRegistry.Entry(BaseNamedWriteable.class, TestNamedWriteable.NAME,
(StreamInput in) -> new TestNamedWriteable(in) {
@Override
public String getWriteableName() {
return "intentionally-broken";
}
})));
TestNamedWriteable namedWriteableIn = new TestNamedWriteable(randomAlphaOfLengthBetween(1, 10),
randomAlphaOfLengthBetween(1, 10));
out.writeNamedWriteable(namedWriteableIn);
byte[] bytes = BytesReference.toBytes(out.bytes());
try (StreamInput in = new NamedWriteableAwareStreamInput(StreamInput.wrap(bytes), namedWriteableRegistry)) {
assertEquals(in.available(), bytes.length);
AssertionError e = expectThrows(AssertionError.class, () -> in.readNamedWriteable(BaseNamedWriteable.class));
assertThat(e.getMessage(),
endsWith(" claims to have a different name [intentionally-broken] than it was read from [test-named-writeable]."));
}
}
}
public void testWriteStreamableList() throws IOException {
final int size = randomIntBetween(0, 5);
final List<TestStreamable> expected = new ArrayList<>(size);
for (int i = 0; i < size; ++i) {
expected.add(new TestStreamable(randomBoolean()));
}
final BytesStreamOutput out = new BytesStreamOutput();
out.writeStreamableList(expected);
final StreamInput in = StreamInput.wrap(BytesReference.toBytes(out.bytes()));
final List<TestStreamable> loaded = in.readStreamableList(TestStreamable::new);
assertThat(loaded, hasSize(expected.size()));
for (int i = 0; i < expected.size(); ++i) {
assertEquals(expected.get(i).value, loaded.get(i).value);
}
assertEquals(0, in.available());
in.close();
out.close();
}
public void testWriteMap() throws IOException {
final int size = randomIntBetween(0, 100);
final Map<String, String> expected = new HashMap<>(randomIntBetween(0, 100));
for (int i = 0; i < size; ++i) {
expected.put(randomAlphaOfLength(2), randomAlphaOfLength(5));
}
final BytesStreamOutput out = new BytesStreamOutput();
out.writeMap(expected, StreamOutput::writeString, StreamOutput::writeString);
final StreamInput in = StreamInput.wrap(BytesReference.toBytes(out.bytes()));
final Map<String, String> loaded = in.readMap(StreamInput::readString, StreamInput::readString);
assertThat(loaded.size(), equalTo(expected.size()));
assertThat(expected, equalTo(loaded));
}
public void testWriteMapOfLists() throws IOException {
final int size = randomIntBetween(0, 5);
final Map<String, List<String>> expected = new HashMap<>(size);
for (int i = 0; i < size; ++i) {
int listSize = randomIntBetween(0, 5);
List<String> list = new ArrayList<>(listSize);
for (int j = 0; j < listSize; ++j) {
list.add(randomAlphaOfLength(5));
}
expected.put(randomAlphaOfLength(2), list);
}
final BytesStreamOutput out = new BytesStreamOutput();
out.writeMapOfLists(expected, StreamOutput::writeString, StreamOutput::writeString);
final StreamInput in = StreamInput.wrap(BytesReference.toBytes(out.bytes()));
final Map<String, List<String>> loaded = in.readMapOfLists(StreamInput::readString, StreamInput::readString);
assertThat(loaded.size(), equalTo(expected.size()));
for (Map.Entry<String, List<String>> entry : expected.entrySet()) {
assertThat(loaded.containsKey(entry.getKey()), equalTo(true));
List<String> loadedList = loaded.get(entry.getKey());
assertThat(loadedList, hasSize(entry.getValue().size()));
for (int i = 0; i < loadedList.size(); ++i) {
assertEquals(entry.getValue().get(i), loadedList.get(i));
}
}
assertEquals(0, in.available());
in.close();
out.close();
}
private abstract static class BaseNamedWriteable implements NamedWriteable {
}
private static class TestNamedWriteable extends BaseNamedWriteable {
private static final String NAME = "test-named-writeable";
private final String field1;
private final String field2;
TestNamedWriteable(String field1, String field2) {
this.field1 = field1;
this.field2 = field2;
}
TestNamedWriteable(StreamInput in) throws IOException {
field1 = in.readString();
field2 = in.readString();
}
@Override
public String getWriteableName() {
return NAME;
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeString(field1);
out.writeString(field2);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TestNamedWriteable that = (TestNamedWriteable) o;
return Objects.equals(field1, that.field1) &&
Objects.equals(field2, that.field2);
}
@Override
public int hashCode() {
return Objects.hash(field1, field2);
}
}
// we ignore this test for now since all existing callers of BytesStreamOutput happily
// call bytes() after close().
@AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch/issues/12620")
public void testAccessAfterClose() throws Exception {
BytesStreamOutput out = new BytesStreamOutput();
// immediately close
out.close();
assertEquals(-1, out.size());
assertEquals(-1, out.position());
// writing a single byte must fail
expectThrows(IllegalArgumentException.class, () -> out.writeByte((byte)0));
// writing in bulk must fail
expectThrows(IllegalArgumentException.class, () -> out.writeBytes(new byte[0], 0, 0));
// toByteArray() must fail
expectThrows(IllegalArgumentException.class, () -> BytesReference.toBytes(out.bytes()));
}
// create & fill byte[] with randomized data
protected byte[] randomizedByteArrayWithSize(int size) {
byte[] data = new byte[size];
random().nextBytes(data);
return data;
}
public void testReadWriteGeoPoint() throws IOException {
try (BytesStreamOutput out = new BytesStreamOutput()) {;
GeoPoint geoPoint = new GeoPoint(randomDouble(), randomDouble());
out.writeGenericValue(geoPoint);
StreamInput wrap = out.bytes().streamInput();
GeoPoint point = (GeoPoint) wrap.readGenericValue();
assertEquals(point, geoPoint);
}
try (BytesStreamOutput out = new BytesStreamOutput()) {
GeoPoint geoPoint = new GeoPoint(randomDouble(), randomDouble());
out.writeGeoPoint(geoPoint);
StreamInput wrap = out.bytes().streamInput();
GeoPoint point = wrap.readGeoPoint();
assertEquals(point, geoPoint);
}
}
private static class TestStreamable implements Streamable {
private boolean value;
TestStreamable() { }
TestStreamable(boolean value) {
this.value = value;
}
@Override
public void readFrom(StreamInput in) throws IOException {
value = in.readBoolean();
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeBoolean(value);
}
}
public void testWriteMapWithConsistentOrder() throws IOException {
Map<String, String> map =
randomMap(new TreeMap<>(), randomIntBetween(2, 20),
() -> randomAlphaOfLength(5),
() -> randomAlphaOfLength(5));
Map<String, Object> reverseMap = new TreeMap<>(Collections.reverseOrder());
reverseMap.putAll(map);
List<String> mapKeys = map.entrySet().stream().map(Map.Entry::getKey).collect(Collectors.toList());
List<String> reverseMapKeys = reverseMap.entrySet().stream().map(Map.Entry::getKey).collect(Collectors.toList());
assertNotEquals(mapKeys, reverseMapKeys);
try (BytesStreamOutput output = new BytesStreamOutput(); BytesStreamOutput reverseMapOutput = new BytesStreamOutput()) {
output.writeMapWithConsistentOrder(map);
reverseMapOutput.writeMapWithConsistentOrder(reverseMap);
assertEquals(output.bytes(), reverseMapOutput.bytes());
}
}
public void testReadMapByUsingWriteMapWithConsistentOrder() throws IOException {
Map<String, String> streamOutMap =
randomMap(new HashMap<>(), randomIntBetween(2, 20),
() -> randomAlphaOfLength(5),
() -> randomAlphaOfLength(5));
try (BytesStreamOutput streamOut = new BytesStreamOutput()) {
streamOut.writeMapWithConsistentOrder(streamOutMap);
StreamInput in = StreamInput.wrap(BytesReference.toBytes(streamOut.bytes()));
Map<String, Object> streamInMap = in.readMap();
assertEquals(streamOutMap, streamInMap);
}
}
public void testWriteMapWithConsistentOrderWithLinkedHashMapShouldThrowAssertError() throws IOException {
try (BytesStreamOutput output = new BytesStreamOutput()) {
Map<String, Object> map = new LinkedHashMap<>();
Throwable e = expectThrows(AssertionError.class, () -> output.writeMapWithConsistentOrder(map));
assertEquals(AssertionError.class, e.getClass());
}
}
private static <K, V> Map<K, V> randomMap(Map<K, V> map, int size, Supplier<K> keyGenerator, Supplier<V> valueGenerator) {
IntStream.range(0, size).forEach(i -> map.put(keyGenerator.get(), valueGenerator.get()));
return map;
}
public void testWriteRandomStrings() throws IOException {
final int iters = scaledRandomIntBetween(5, 20);
for (int iter = 0; iter < iters; iter++) {
List<String> strings = new ArrayList<>();
int numStrings = randomIntBetween(100, 1000);
BytesStreamOutput output = new BytesStreamOutput(0);
for (int i = 0; i < numStrings; i++) {
String s = randomRealisticUnicodeOfLengthBetween(0, 2048);
strings.add(s);
output.writeString(s);
}
try (StreamInput streamInput = output.bytes().streamInput()) {
for (int i = 0; i < numStrings; i++) {
String s = streamInput.readString();
assertEquals(strings.get(i), s);
}
}
}
}
/*
* tests the extreme case where characters use more than 2 bytes
*/
public void testWriteLargeSurrogateOnlyString() throws IOException {
String deseretLetter = "\uD801\uDC00";
assertEquals(2, deseretLetter.length());
String largeString = IntStream.range(0, 2048).mapToObj(s -> deseretLetter).collect(Collectors.joining("")).trim();
assertEquals("expands to 4 bytes", 4, new BytesRef(deseretLetter).length);
try (BytesStreamOutput output = new BytesStreamOutput(0)) {
output.writeString(largeString);
try (StreamInput streamInput = output.bytes().streamInput()) {
assertEquals(largeString, streamInput.readString());
}
}
}
public void testReadTooLargeArraySize() throws IOException {
try (BytesStreamOutput output = new BytesStreamOutput(0)) {
output.writeVInt(10);
for (int i = 0; i < 10; i ++) {
output.writeInt(i);
}
output.writeVInt(Integer.MAX_VALUE);
for (int i = 0; i < 10; i ++) {
output.writeInt(i);
}
try (StreamInput streamInput = output.bytes().streamInput()) {
int[] ints = streamInput.readIntArray();
for (int i = 0; i < 10; i ++) {
assertEquals(i, ints[i]);
}
expectThrows(IllegalStateException.class, () -> streamInput.readIntArray());
}
}
}
public void testReadCorruptedArraySize() throws IOException {
try (BytesStreamOutput output = new BytesStreamOutput(0)) {
output.writeVInt(10);
for (int i = 0; i < 10; i ++) {
output.writeInt(i);
}
output.writeVInt(100);
for (int i = 0; i < 10; i ++) {
output.writeInt(i);
}
try (StreamInput streamInput = output.bytes().streamInput()) {
int[] ints = streamInput.readIntArray();
for (int i = 0; i < 10; i ++) {
assertEquals(i, ints[i]);
}
EOFException eofException = expectThrows(EOFException.class, () -> streamInput.readIntArray());
assertEquals("tried to read: 100 bytes but only 40 remaining", eofException.getMessage());
}
}
}
public void testReadNegativeArraySize() throws IOException {
try (BytesStreamOutput output = new BytesStreamOutput(0)) {
output.writeVInt(10);
for (int i = 0; i < 10; i ++) {
output.writeInt(i);
}
output.writeVInt(Integer.MIN_VALUE);
for (int i = 0; i < 10; i ++) {
output.writeInt(i);
}
try (StreamInput streamInput = output.bytes().streamInput()) {
int[] ints = streamInput.readIntArray();
for (int i = 0; i < 10; i ++) {
assertEquals(i, ints[i]);
}
NegativeArraySizeException exception = expectThrows(NegativeArraySizeException.class, () -> streamInput.readIntArray());
assertEquals("array size must be positive but was: -2147483648", exception.getMessage());
}
}
}
public void testVInt() throws IOException {
final int value = randomInt();
BytesStreamOutput output = new BytesStreamOutput();
output.writeVInt(value);
StreamInput input = output.bytes().streamInput();
assertEquals(value, input.readVInt());
}
public void testVLong() throws IOException {
final long value = randomLong();
{
// Read works for positive and negative numbers
BytesStreamOutput output = new BytesStreamOutput();
output.writeVLongNoCheck(value); // Use NoCheck variant so we can write negative numbers
StreamInput input = output.bytes().streamInput();
assertEquals(value, input.readVLong());
}
if (value < 0) {
// Write doesn't work for negative numbers
BytesStreamOutput output = new BytesStreamOutput();
Exception e = expectThrows(IllegalStateException.class, () -> output.writeVLong(value));
assertEquals("Negative longs unsupported, use writeLong or writeZLong for negative numbers [" + value + "]", e.getMessage());
}
assertTrue("If we're not compatible with 5.1.1 we can drop the assertion below",
Version.CURRENT.minimumCompatibilityVersion().onOrBefore(Version.V_5_1_1_UNRELEASED));
/* Read -1 as serialized by a version of Elasticsearch that supported writing negative numbers with writeVLong. Note that this
* should be the same test as the first case (when value is negative) but we've kept some bytes so no matter what we do to
* writeVLong in the future we can be sure we can read bytes as written by Elasticsearch before 5.1.2 */
StreamInput in = new BytesArray(Base64.getDecoder().decode("////////////AQAAAAAAAA==")).streamInput();
assertEquals(-1, in.readVLong());
}
public enum TestEnum {
ONE,
TWO,
THREE
}
public void testEnum() throws IOException {
TestEnum value = randomFrom(TestEnum.values());
BytesStreamOutput output = new BytesStreamOutput();
output.writeEnum(value);
StreamInput input = output.bytes().streamInput();
assertEquals(value, input.readEnum(TestEnum.class));
assertEquals(0, input.available());
}
public void testInvalidEnum() throws IOException {
BytesStreamOutput output = new BytesStreamOutput();
int randomNumber = randomInt();
boolean validEnum = randomNumber >= 0 && randomNumber < TestEnum.values().length;
output.writeVInt(randomNumber);
StreamInput input = output.bytes().streamInput();
if (validEnum) {
assertEquals(TestEnum.values()[randomNumber], input.readEnum(TestEnum.class));
} else {
IOException ex = expectThrows(IOException.class, () -> input.readEnum(TestEnum.class));
assertEquals("Unknown TestEnum ordinal [" + randomNumber + "]", ex.getMessage());
}
assertEquals(0, input.available());
}
}