/* * 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.Constants; 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.junit.Test; import java.io.IOException; import java.util.Objects; import static org.hamcrest.Matchers.closeTo; import static org.hamcrest.Matchers.equalTo; /** * Tests for {@link BytesStreamOutput} paging behaviour. */ public class BytesStreamsTests extends ESTestCase { @Test public void testEmpty() throws Exception { BytesStreamOutput out = new BytesStreamOutput(); // test empty stream to array assertEquals(0, out.size()); assertEquals(0, out.bytes().toBytes().length); out.close(); } @Test 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, out.bytes().toBytes()); out.close(); } @Test 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, out.bytes().toBytes()); out.close(); } @Test public void testIllegalBulkWrite() throws Exception { BytesStreamOutput out = new BytesStreamOutput(); // bulk-write with wrong args try { out.writeBytes(new byte[]{}, 0, 1); fail("expected IllegalArgumentException: length > (size-offset)"); } catch (IllegalArgumentException iax1) { // expected } out.close(); } @Test 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, out.bytes().toBytes()); // bulk-write again with actual bytes expectedSize = 10; expectedData = randomizedByteArrayWithSize(expectedSize); out.writeBytes(expectedData); assertEquals(expectedSize, out.size()); assertArrayEquals(expectedData, out.bytes().toBytes()); out.close(); } @Test 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, out.bytes().toBytes()); out.close(); } @Test 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, out.bytes().toBytes()); out.close(); } @Test 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, out.bytes().toBytes()); out.close(); } @Test 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, out.bytes().toBytes()); out.close(); } @Test 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, out.bytes().toBytes()); out.close(); } @Test 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, out.bytes().toBytes()); out.close(); } @Test 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, out.bytes().toBytes().length); out.close(); } @Test 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()); out.close(); } @Test 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.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")); final byte[] bytes = out.bytes().toBytes(); StreamInput in = StreamInput.wrap(out.bytes().toBytes()); 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((long)-3)); assertThat(in.readVLong(), equalTo((long)4)); 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"))); in.close(); out.close(); } @Test public void testNamedWriteable() throws IOException { BytesStreamOutput out = new BytesStreamOutput(); NamedWriteableRegistry namedWriteableRegistry = new NamedWriteableRegistry(); namedWriteableRegistry.registerPrototype(BaseNamedWriteable.class, new TestNamedWriteable(null, null)); TestNamedWriteable namedWriteableIn = new TestNamedWriteable(randomAsciiOfLengthBetween(1, 10), randomAsciiOfLengthBetween(1, 10)); out.writeNamedWriteable(namedWriteableIn); byte[] bytes = out.bytes().toBytes(); StreamInput in = new NamedWriteableAwareStreamInput(StreamInput.wrap(bytes), namedWriteableRegistry); assertEquals(in.available(), bytes.length); BaseNamedWriteable namedWriteableOut = in.readNamedWriteable(BaseNamedWriteable.class); assertEquals(namedWriteableOut, namedWriteableIn); assertEquals(in.available(), 0); } @Test public void testNamedWriteableDuplicates() throws IOException { NamedWriteableRegistry namedWriteableRegistry = new NamedWriteableRegistry(); namedWriteableRegistry.registerPrototype(BaseNamedWriteable.class, new TestNamedWriteable(null, null)); try { namedWriteableRegistry.registerPrototype(BaseNamedWriteable.class, new TestNamedWriteable(null, null)); fail("registerPrototype should have failed"); } catch(IllegalArgumentException e) { assertThat(e.getMessage(), equalTo("named writeable of type [" + TestNamedWriteable.class.getName() + "] with name [" + TestNamedWriteable.NAME + "] is already registered by type [" + TestNamedWriteable.class.getName() + "] within category [" + BaseNamedWriteable.class.getName() + "]")); } } @Test public void testNamedWriteableUnknownCategory() throws IOException { BytesStreamOutput out = new BytesStreamOutput(); out.writeNamedWriteable(new TestNamedWriteable("test1", "test2")); StreamInput in = new NamedWriteableAwareStreamInput(StreamInput.wrap(out.bytes().toBytes()), new NamedWriteableRegistry()); try { //no named writeable registered with given name, can write but cannot read it back in.readNamedWriteable(BaseNamedWriteable.class); fail("read should have failed"); } catch(IllegalArgumentException e) { assertThat(e.getMessage(), equalTo("unknown named writeable category [" + BaseNamedWriteable.class.getName() + "]")); } } @Test public void testNamedWriteableUnknownNamedWriteable() throws IOException { NamedWriteableRegistry namedWriteableRegistry = new NamedWriteableRegistry(); namedWriteableRegistry.registerPrototype(BaseNamedWriteable.class, new TestNamedWriteable(null, null)); BytesStreamOutput out = new BytesStreamOutput(); out.writeNamedWriteable(new NamedWriteable() { @Override public String getWriteableName() { return "unknown"; } @Override public void writeTo(StreamOutput out) throws IOException { } @Override public Object readFrom(StreamInput in) throws IOException { return null; } }); StreamInput in = new NamedWriteableAwareStreamInput(StreamInput.wrap(out.bytes().toBytes()), namedWriteableRegistry); try { //no named writeable registered with given name under test category, can write but cannot read it back in.readNamedWriteable(BaseNamedWriteable.class); fail("read should have failed"); } catch(IllegalArgumentException e) { assertThat(e.getMessage(), equalTo("unknown named writeable with name [unknown] within category [" + BaseNamedWriteable.class.getName() + "]")); } } @Test(expected = UnsupportedOperationException.class) public void testNamedWriteableNotSupportedWithoutWrapping() throws IOException { BytesStreamOutput out = new BytesStreamOutput(); TestNamedWriteable testNamedWriteable = new TestNamedWriteable("test1", "test2"); out.writeNamedWriteable(testNamedWriteable); StreamInput in = StreamInput.wrap(out.bytes().toBytes()); in.readNamedWriteable(BaseNamedWriteable.class); } private static abstract class BaseNamedWriteable<T> implements NamedWriteable<T> { } private static class TestNamedWriteable extends BaseNamedWriteable<TestNamedWriteable> { 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; } @Override public String getWriteableName() { return NAME; } @Override public void writeTo(StreamOutput out) throws IOException { out.writeString(field1); out.writeString(field2); } @Override public TestNamedWriteable readFrom(StreamInput in) throws IOException { return new TestNamedWriteable(in.readString(), in.readString()); } @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") @Test 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 try { out.writeByte((byte)0); fail("expected IllegalStateException: stream closed"); } catch (IllegalStateException iex1) { // expected } // writing in bulk must fail try { out.writeBytes(new byte[0], 0, 0); fail("expected IllegalStateException: stream closed"); } catch (IllegalStateException iex1) { // expected } // toByteArray() must fail try { out.bytes().toBytes(); fail("expected IllegalStateException: stream closed"); } catch (IllegalStateException iex1) { // expected } } // create & fill byte[] with randomized data protected byte[] randomizedByteArrayWithSize(int size) { byte[] data = new byte[size]; getRandom().nextBytes(data); return data; } public void testReadWriteGeoPoint() throws IOException { { BytesStreamOutput out = new BytesStreamOutput(); GeoPoint geoPoint = new GeoPoint(randomDouble(), randomDouble()); out.writeGenericValue(geoPoint); StreamInput wrap = StreamInput.wrap(out.bytes()); GeoPoint point = (GeoPoint) wrap.readGenericValue(); assertEquals(point, geoPoint); } { BytesStreamOutput out = new BytesStreamOutput(); GeoPoint geoPoint = new GeoPoint(randomDouble(), randomDouble()); out.writeGeoPoint(geoPoint); StreamInput wrap = StreamInput.wrap(out.bytes()); GeoPoint point = wrap.readGeoPoint(); assertEquals(point, geoPoint); } } }