blob: 144609da1ca28350ff840d40e34eaef4e63ff17c [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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.apache.beam.sdk.util;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.IOException;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Unit tests for {@link VarInt}. */
@RunWith(JUnit4.class)
public class VarIntTest {
@Rule public final ExpectedException thrown = ExpectedException.none();
// Long values to check for boundary cases.
private static final long[] LONG_VALUES = {
0,
1,
127,
128,
16383,
16384,
2097151,
2097152,
268435455,
268435456,
34359738367L,
34359738368L,
9223372036854775807L,
-9223372036854775808L,
-1,
};
// VarInt encoding of the above VALUES.
private static final byte[][] LONG_ENCODED = {
// 0
{0x00},
// 1
{0x01},
// 127
{0x7f},
// 128
{(byte) 0x80, 0x01},
// 16383
{(byte) 0xff, 0x7f},
// 16834
{(byte) 0x80, (byte) 0x80, 0x01},
// 2097151
{(byte) 0xff, (byte) 0xff, 0x7f},
// 2097152
{(byte) 0x80, (byte) 0x80, (byte) 0x80, 0x01},
// 268435455
{(byte) 0xff, (byte) 0xff, (byte) 0xff, 0x7f},
// 268435456
{(byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, 0x01},
// 34359738367
{(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 0x7f},
// 34359738368
{(byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, 0x01},
// 9223372036854775807
{
(byte) 0xff,
(byte) 0xff,
(byte) 0xff,
(byte) 0xff,
(byte) 0xff,
(byte) 0xff,
(byte) 0xff,
(byte) 0xff,
(byte) 0x7f
},
// -9223372036854775808L
{
(byte) 0x80,
(byte) 0x80,
(byte) 0x80,
(byte) 0x80,
(byte) 0x80,
(byte) 0x80,
(byte) 0x80,
(byte) 0x80,
(byte) 0x80,
0x01
},
// -1
{
(byte) 0xff,
(byte) 0xff,
(byte) 0xff,
(byte) 0xff,
(byte) 0xff,
(byte) 0xff,
(byte) 0xff,
(byte) 0xff,
(byte) 0xff,
0x01
}
};
// Integer values to check for boundary cases.
private static final int[] INT_VALUES = {
0,
1,
127,
128,
16383,
16384,
2097151,
2097152,
268435455,
268435456,
2147483647,
-2147483648,
-1,
};
// VarInt encoding of the above VALUES.
private static final byte[][] INT_ENCODED = {
// 0
{(byte) 0x00},
// 1
{(byte) 0x01},
// 127
{(byte) 0x7f},
// 128
{(byte) 0x80, (byte) 0x01},
// 16383
{(byte) 0xff, (byte) 0x7f},
// 16834
{(byte) 0x80, (byte) 0x80, (byte) 0x01},
// 2097151
{(byte) 0xff, (byte) 0xff, (byte) 0x7f},
// 2097152
{(byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x01},
// 268435455
{(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f},
// 268435456
{(byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x01},
// 2147483647
{(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07},
// -2147483648
{(byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x08},
// -1
{(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f}
};
private static byte[] encodeInt(int v) throws IOException {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
VarInt.encode(v, stream);
return stream.toByteArray();
}
private static byte[] encodeLong(long v) throws IOException {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
VarInt.encode(v, stream);
return stream.toByteArray();
}
private static int decodeInt(byte[] encoded) throws IOException {
ByteArrayInputStream stream = new ByteArrayInputStream(encoded);
return VarInt.decodeInt(stream);
}
private static long decodeLong(byte[] encoded) throws IOException {
ByteArrayInputStream stream = new ByteArrayInputStream(encoded);
return VarInt.decodeLong(stream);
}
@Test
public void decodeValues() throws IOException {
assertEquals(LONG_VALUES.length, LONG_ENCODED.length);
for (int i = 0; i < LONG_ENCODED.length; ++i) {
ByteArrayInputStream stream = new ByteArrayInputStream(LONG_ENCODED[i]);
long parsed = VarInt.decodeLong(stream);
assertEquals(LONG_VALUES[i], parsed);
assertEquals(-1, stream.read());
}
assertEquals(INT_VALUES.length, INT_ENCODED.length);
for (int i = 0; i < INT_ENCODED.length; ++i) {
ByteArrayInputStream stream = new ByteArrayInputStream(INT_ENCODED[i]);
int parsed = VarInt.decodeInt(stream);
assertEquals(INT_VALUES[i], parsed);
assertEquals(-1, stream.read());
}
}
@Test
public void encodeValuesAndGetLength() throws IOException {
assertEquals(LONG_VALUES.length, LONG_ENCODED.length);
for (int i = 0; i < LONG_VALUES.length; ++i) {
byte[] encoded = encodeLong(LONG_VALUES[i]);
assertThat(encoded, equalTo(LONG_ENCODED[i]));
assertEquals(LONG_ENCODED[i].length, VarInt.getLength(LONG_VALUES[i]));
}
assertEquals(INT_VALUES.length, INT_ENCODED.length);
for (int i = 0; i < INT_VALUES.length; ++i) {
byte[] encoded = encodeInt(INT_VALUES[i]);
assertThat(encoded, equalTo(INT_ENCODED[i]));
assertEquals(INT_ENCODED[i].length, VarInt.getLength(INT_VALUES[i]));
}
}
@Test
public void decodeThrowsExceptionForOverflow() throws IOException {
final byte[] tooLargeNumber = {
(byte) 0xff,
(byte) 0xff,
(byte) 0xff,
(byte) 0xff,
(byte) 0xff,
(byte) 0xff,
(byte) 0xff,
(byte) 0xff,
(byte) 0xff,
0x02
};
thrown.expect(IOException.class);
decodeLong(tooLargeNumber);
}
@Test
public void decodeThrowsExceptionForIntOverflow() throws IOException {
byte[] encoded = encodeLong(1L << 32);
thrown.expect(IOException.class);
decodeInt(encoded);
}
@Test
public void decodeThrowsExceptionForIntUnderflow() throws IOException {
byte[] encoded = encodeLong(-1);
thrown.expect(IOException.class);
decodeInt(encoded);
}
@Test
public void decodeThrowsExceptionForNonterminated() throws IOException {
final byte[] nonTerminatedNumber = {(byte) 0xff, (byte) 0xff};
thrown.expect(IOException.class);
decodeLong(nonTerminatedNumber);
}
@Test
public void decodeParsesEncodedValues() throws IOException {
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
for (int i = 10; i < Integer.MAX_VALUE; i = (int) (i * 1.1)) {
VarInt.encode(i, outStream);
VarInt.encode(-i, outStream);
}
for (long i = 10; i < Long.MAX_VALUE; i = (long) (i * 1.1)) {
VarInt.encode(i, outStream);
VarInt.encode(-i, outStream);
}
ByteArrayInputStream inStream = new ByteArrayInputStream(outStream.toByteArray());
for (int i = 10; i < Integer.MAX_VALUE; i = (int) (i * 1.1)) {
assertEquals(i, VarInt.decodeInt(inStream));
assertEquals(-i, VarInt.decodeInt(inStream));
}
for (long i = 10; i < Long.MAX_VALUE; i = (long) (i * 1.1)) {
assertEquals(i, VarInt.decodeLong(inStream));
assertEquals(-i, VarInt.decodeLong(inStream));
}
}
@Test
public void endOfFileThrowsException() throws Exception {
ByteArrayInputStream inStream = new ByteArrayInputStream(new byte[0]);
thrown.expect(EOFException.class);
VarInt.decodeInt(inStream);
}
@Test
public void unterminatedThrowsException() throws Exception {
byte[] e = encodeLong(Long.MAX_VALUE);
byte[] s = new byte[1];
s[0] = e[0];
ByteArrayInputStream inStream = new ByteArrayInputStream(s);
thrown.expect(IOException.class);
VarInt.decodeInt(inStream);
}
}