blob: 522ff786cbadb25d157c96fbd497299c2ad6cde0 [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.io.gcp.spanner;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import com.google.auto.value.AutoValue;
import java.util.List;
import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.collect.ImmutableList;
import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.io.BaseEncoding;
import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.primitives.Bytes;
import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.primitives.UnsignedBytes;
import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.primitives.UnsignedInteger;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** A set of unit tests to verify {@link OrderedCode}. */
@RunWith(JUnit4.class)
public class OrderedCodeTest {
/** Data for a generic coding test case with known encoded outputs. */
abstract static class CodingTestCase<T> {
/** The test value. */
abstract T value();
/** Test value's encoding in increasing order (obtained from the C++ implementation). */
abstract String increasingBytes();
/** Test value's encoding in dencreasing order (obtained from the C++ implementation). */
abstract String decreasingBytes();
// Helper methods to implement in concrete classes.
abstract byte[] encodeIncreasing();
abstract byte[] encodeDecreasing();
T decodeIncreasing() {
return decodeIncreasing(new OrderedCode(bytesFromHexString(increasingBytes())));
}
T decodeDecreasing() {
return decodeDecreasing(new OrderedCode(bytesFromHexString(decreasingBytes())));
}
abstract T decodeIncreasing(OrderedCode orderedCode);
abstract T decodeDecreasing(OrderedCode orderedCode);
}
@AutoValue
abstract static class UnsignedNumber extends CodingTestCase<Long> {
@Override
byte[] encodeIncreasing() {
OrderedCode orderedCode = new OrderedCode();
orderedCode.writeNumIncreasing(value());
return orderedCode.getEncodedBytes();
}
@Override
byte[] encodeDecreasing() {
OrderedCode orderedCode = new OrderedCode();
orderedCode.writeNumDecreasing(value());
return orderedCode.getEncodedBytes();
}
@Override
Long decodeIncreasing(OrderedCode orderedCode) {
return orderedCode.readNumIncreasing();
}
@Override
Long decodeDecreasing(OrderedCode orderedCode) {
return orderedCode.readNumDecreasing();
}
private static UnsignedNumber testCase(
long value, String increasingBytes, String decreasingBytes) {
return new AutoValue_OrderedCodeTest_UnsignedNumber(value, increasingBytes, decreasingBytes);
}
/** Test cases for unsigned numbers, in increasing (unsigned) order by value. */
private static final ImmutableList<UnsignedNumber> TEST_CASES =
ImmutableList.of(
testCase(0, "00", "ff"),
testCase(1, "0101", "fefe"),
testCase(33, "0121", "fede"),
testCase(55000, "02d6d8", "fd2927"),
testCase(Integer.MAX_VALUE, "047fffffff", "fb80000000"),
testCase(Long.MAX_VALUE, "087fffffffffffffff", "f78000000000000000"),
testCase(Long.MIN_VALUE, "088000000000000000", "f77fffffffffffffff"),
testCase(-100, "08ffffffffffffff9c", "f70000000000000063"),
testCase(-1, "08ffffffffffffffff", "f70000000000000000"));
}
@AutoValue
abstract static class BytesTest extends CodingTestCase<String> {
@Override
byte[] encodeIncreasing() {
OrderedCode orderedCode = new OrderedCode();
orderedCode.writeBytes(bytesFromHexString(value()));
return orderedCode.getEncodedBytes();
}
@Override
byte[] encodeDecreasing() {
OrderedCode orderedCode = new OrderedCode();
orderedCode.writeBytesDecreasing(bytesFromHexString(value()));
return orderedCode.getEncodedBytes();
}
@Override
String decodeIncreasing(OrderedCode orderedCode) {
return bytesToHexString(orderedCode.readBytes());
}
@Override
String decodeDecreasing(OrderedCode orderedCode) {
return bytesToHexString(orderedCode.readBytesDecreasing());
}
private static BytesTest testCase(
String value, String increasingBytes, String decreasingBytes) {
return new AutoValue_OrderedCodeTest_BytesTest(value, increasingBytes, decreasingBytes);
}
/** Test cases for byte arrays, in increasing order by value. */
private static final ImmutableList<BytesTest> TEST_CASES =
ImmutableList.of(
testCase("", "0001", "fffe"),
testCase("00", "00ff0001", "ff00fffe"),
testCase("0000", "00ff00ff0001", "ff00ff00fffe"),
testCase("0001", "00ff010001", "ff00fefffe"),
testCase("0041", "00ff410001", "ff00befffe"),
testCase("00ff", "00ffff000001", "ff0000fffffe"),
testCase("01", "010001", "fefffe"),
testCase("0100", "0100ff0001", "feff00fffe"),
testCase("6f776c", "6f776c0001", "908893fffe"),
testCase("ff", "ff000001", "00fffffe"),
testCase("ff00", "ff0000ff0001", "00ffff00fffe"),
testCase("ff01", "ff00010001", "00fffefffe"),
testCase("ffff", "ff00ff000001", "00ff00fffffe"),
testCase("ffffff", "ff00ff00ff000001", "00ff00ff00fffffe"));
}
@Test
public void testUnsignedEncoding() {
testEncoding(UnsignedNumber.TEST_CASES);
}
@Test
public void testUnsignedDecoding() {
testDecoding(UnsignedNumber.TEST_CASES);
}
@Test
public void testUnsignedOrdering() {
testOrdering(UnsignedNumber.TEST_CASES);
}
@Test
public void testBytesEncoding() {
testEncoding(BytesTest.TEST_CASES);
}
@Test
public void testBytesDecoding() {
testDecoding(BytesTest.TEST_CASES);
}
@Test
public void testBytesOrdering() {
testOrdering(BytesTest.TEST_CASES);
}
private void testEncoding(List<? extends CodingTestCase<?>> testCases) {
for (CodingTestCase<?> testCase : testCases) {
byte[] actualIncreasing = testCase.encodeIncreasing();
byte[] expectedIncreasing = bytesFromHexString(testCase.increasingBytes());
assertEquals(0, compare(actualIncreasing, expectedIncreasing));
byte[] actualDecreasing = testCase.encodeDecreasing();
byte[] expectedDecreasing = bytesFromHexString(testCase.decreasingBytes());
assertEquals(0, compare(actualDecreasing, expectedDecreasing));
}
}
private void testDecoding(List<? extends CodingTestCase<?>> testCases) {
for (CodingTestCase<?> testCase : testCases) {
assertEquals(testCase.value(), testCase.decodeIncreasing());
assertEquals(testCase.value(), testCase.decodeDecreasing());
}
}
private void testOrdering(List<? extends CodingTestCase<?>> testCases) {
// This is verifiable by inspection of the C++ encodings, but it seems
// worth checking explicitly
for (int caseIndex = 0; caseIndex < testCases.size() - 1; caseIndex++) {
byte[] encodedValue = testCases.get(caseIndex).encodeIncreasing();
byte[] nextEncodedValue = testCases.get(caseIndex + 1).encodeIncreasing();
assertTrue(compare(encodedValue, nextEncodedValue) < 0);
encodedValue = testCases.get(caseIndex).encodeDecreasing();
nextEncodedValue = testCases.get(caseIndex + 1).encodeDecreasing();
assertTrue(compare(encodedValue, nextEncodedValue) > 0);
}
}
@Test
public void testWriteInfinity() {
OrderedCode orderedCode = new OrderedCode();
try {
orderedCode.readInfinity();
fail("Expected IllegalArgumentException.");
} catch (IllegalArgumentException e) {
// expected
}
orderedCode.writeInfinity();
assertTrue(orderedCode.readInfinity());
try {
orderedCode.readInfinity();
fail("Expected IllegalArgumentException.");
} catch (IllegalArgumentException e) {
// expected
}
}
@Test
public void testWriteInfinityDecreasing() {
OrderedCode orderedCode = new OrderedCode();
try {
orderedCode.readInfinityDecreasing();
fail("Expected IllegalArgumentException.");
} catch (IllegalArgumentException e) {
// expected
}
orderedCode.writeInfinityDecreasing();
assertTrue(orderedCode.readInfinityDecreasing());
try {
orderedCode.readInfinityDecreasing();
fail("Expected IllegalArgumentException.");
} catch (IllegalArgumentException e) {
// expected
}
}
@Test
public void testWriteBytes() {
byte[] first = {'a', 'b', 'c'};
byte[] second = {'d', 'e', 'f'};
byte[] last = {'x', 'y', 'z'};
OrderedCode orderedCode = new OrderedCode();
orderedCode.writeBytes(first);
byte[] firstEncoded = orderedCode.getEncodedBytes();
assertArrayEquals(orderedCode.readBytes(), first);
orderedCode.writeBytes(first);
orderedCode.writeBytes(second);
orderedCode.writeBytes(last);
byte[] allEncoded = orderedCode.getEncodedBytes();
assertArrayEquals(orderedCode.readBytes(), first);
assertArrayEquals(orderedCode.readBytes(), second);
assertArrayEquals(orderedCode.readBytes(), last);
orderedCode = new OrderedCode(firstEncoded);
orderedCode.writeBytes(second);
orderedCode.writeBytes(last);
assertArrayEquals(orderedCode.getEncodedBytes(), allEncoded);
assertArrayEquals(orderedCode.readBytes(), first);
assertArrayEquals(orderedCode.readBytes(), second);
assertArrayEquals(orderedCode.readBytes(), last);
orderedCode = new OrderedCode(allEncoded);
assertArrayEquals(orderedCode.readBytes(), first);
assertArrayEquals(orderedCode.readBytes(), second);
assertArrayEquals(orderedCode.readBytes(), last);
}
@Test
public void testWriteBytesDecreasing() {
byte[] first = {'a', 'b', 'c'};
byte[] second = {'d', 'e', 'f'};
byte[] last = {'x', 'y', 'z'};
OrderedCode orderedCode = new OrderedCode();
orderedCode.writeBytesDecreasing(first);
byte[] firstEncoded = orderedCode.getEncodedBytes();
assertArrayEquals(orderedCode.readBytesDecreasing(), first);
orderedCode.writeBytesDecreasing(first);
orderedCode.writeBytesDecreasing(second);
orderedCode.writeBytesDecreasing(last);
byte[] allEncoded = orderedCode.getEncodedBytes();
assertArrayEquals(orderedCode.readBytesDecreasing(), first);
assertArrayEquals(orderedCode.readBytesDecreasing(), second);
assertArrayEquals(orderedCode.readBytesDecreasing(), last);
orderedCode = new OrderedCode(firstEncoded);
orderedCode.writeBytesDecreasing(second);
orderedCode.writeBytesDecreasing(last);
assertArrayEquals(orderedCode.getEncodedBytes(), allEncoded);
assertArrayEquals(orderedCode.readBytesDecreasing(), first);
assertArrayEquals(orderedCode.readBytesDecreasing(), second);
assertArrayEquals(orderedCode.readBytesDecreasing(), last);
orderedCode = new OrderedCode(allEncoded);
assertArrayEquals(orderedCode.readBytesDecreasing(), first);
assertArrayEquals(orderedCode.readBytesDecreasing(), second);
assertArrayEquals(orderedCode.readBytesDecreasing(), last);
}
@Test
public void testWriteNumIncreasing() {
OrderedCode orderedCode = new OrderedCode();
orderedCode.writeNumIncreasing(0);
orderedCode.writeNumIncreasing(1);
orderedCode.writeNumIncreasing(Long.MIN_VALUE);
orderedCode.writeNumIncreasing(Long.MAX_VALUE);
assertEquals(0, orderedCode.readNumIncreasing());
assertEquals(1, orderedCode.readNumIncreasing());
assertEquals(Long.MIN_VALUE, orderedCode.readNumIncreasing());
assertEquals(Long.MAX_VALUE, orderedCode.readNumIncreasing());
}
@Test
public void testWriteNumIncreasing_unsignedInt() {
OrderedCode orderedCode = new OrderedCode();
orderedCode.writeNumIncreasing(UnsignedInteger.fromIntBits(0));
orderedCode.writeNumIncreasing(UnsignedInteger.fromIntBits(1));
orderedCode.writeNumIncreasing(UnsignedInteger.fromIntBits(Integer.MIN_VALUE));
orderedCode.writeNumIncreasing(UnsignedInteger.fromIntBits(Integer.MAX_VALUE));
assertEquals(0, orderedCode.readNumIncreasing());
assertEquals(1, orderedCode.readNumIncreasing());
assertEquals((long) Integer.MAX_VALUE + 1L, orderedCode.readNumIncreasing());
assertEquals(Integer.MAX_VALUE, orderedCode.readNumIncreasing());
}
@Test
public void testWriteNumDecreasing() {
OrderedCode orderedCode = new OrderedCode();
orderedCode.writeNumDecreasing(0);
orderedCode.writeNumDecreasing(1);
orderedCode.writeNumDecreasing(Long.MIN_VALUE);
orderedCode.writeNumDecreasing(Long.MAX_VALUE);
assertEquals(0, orderedCode.readNumDecreasing());
assertEquals(1, orderedCode.readNumDecreasing());
assertEquals(Long.MIN_VALUE, orderedCode.readNumDecreasing());
assertEquals(Long.MAX_VALUE, orderedCode.readNumDecreasing());
}
@Test
public void testWriteNumDecreasing_unsignedInt() {
OrderedCode orderedCode = new OrderedCode();
orderedCode.writeNumDecreasing(UnsignedInteger.fromIntBits(0));
orderedCode.writeNumDecreasing(UnsignedInteger.fromIntBits(1));
orderedCode.writeNumDecreasing(UnsignedInteger.fromIntBits(Integer.MIN_VALUE));
orderedCode.writeNumDecreasing(UnsignedInteger.fromIntBits(Integer.MAX_VALUE));
assertEquals(0, orderedCode.readNumDecreasing());
assertEquals(1, orderedCode.readNumDecreasing());
assertEquals((long) Integer.MAX_VALUE + 1L, orderedCode.readNumDecreasing());
assertEquals(Integer.MAX_VALUE, orderedCode.readNumDecreasing());
}
/**
* Assert that encoding the specified long via {@link OrderedCode#writeSignedNumIncreasing(long)}
* results in the bytes represented by the specified string of hex digits. E.g.
* assertSignedNumIncreasingEncodingEquals("3fbf", -65) asserts that -65 is encoded as { (byte)
* 0x3f, (byte) 0xbf }.
*/
private static void assertSignedNumIncreasingEncodingEquals(
String expectedHexEncoding, long num) {
OrderedCode orderedCode = new OrderedCode();
orderedCode.writeSignedNumIncreasing(num);
assertEquals(
"Unexpected encoding for " + num,
expectedHexEncoding,
bytesToHexString(orderedCode.getEncodedBytes()));
}
/**
* Assert that encoding various long values via {@link OrderedCode#writeSignedNumIncreasing(long)}
* produces the expected bytes. Expected byte sequences were generated via the c++ (authoritative)
* implementation of OrderedCode::WriteSignedNumIncreasing.
*/
@Test
public void testSignedNumIncreasing_write() {
assertSignedNumIncreasingEncodingEquals("003f8000000000000000", Long.MIN_VALUE);
assertSignedNumIncreasingEncodingEquals("003f8000000000000001", Long.MIN_VALUE + 1);
assertSignedNumIncreasingEncodingEquals("077fffffff", Integer.MIN_VALUE - 1L);
assertSignedNumIncreasingEncodingEquals("0780000000", Integer.MIN_VALUE);
assertSignedNumIncreasingEncodingEquals("0780000001", Integer.MIN_VALUE + 1);
assertSignedNumIncreasingEncodingEquals("3fbf", -65);
assertSignedNumIncreasingEncodingEquals("40", -64);
assertSignedNumIncreasingEncodingEquals("41", -63);
assertSignedNumIncreasingEncodingEquals("7d", -3);
assertSignedNumIncreasingEncodingEquals("7e", -2);
assertSignedNumIncreasingEncodingEquals("7f", -1);
assertSignedNumIncreasingEncodingEquals("80", 0);
assertSignedNumIncreasingEncodingEquals("81", 1);
assertSignedNumIncreasingEncodingEquals("82", 2);
assertSignedNumIncreasingEncodingEquals("83", 3);
assertSignedNumIncreasingEncodingEquals("bf", 63);
assertSignedNumIncreasingEncodingEquals("c040", 64);
assertSignedNumIncreasingEncodingEquals("c041", 65);
assertSignedNumIncreasingEncodingEquals("f87ffffffe", Integer.MAX_VALUE - 1);
assertSignedNumIncreasingEncodingEquals("f87fffffff", Integer.MAX_VALUE);
assertSignedNumIncreasingEncodingEquals("f880000000", Integer.MAX_VALUE + 1L);
assertSignedNumIncreasingEncodingEquals("ffc07ffffffffffffffe", Long.MAX_VALUE - 1);
assertSignedNumIncreasingEncodingEquals("ffc07fffffffffffffff", Long.MAX_VALUE);
}
/**
* Convert a string of hex digits (e.g. "3fbf") to a byte[] (e.g. { (byte) 0x3f, (byte) 0xbf }).
*/
private static byte[] bytesFromHexString(String hexDigits) {
return BaseEncoding.base16().lowerCase().decode(hexDigits);
}
/**
* Convert a byte[] (e.g. { (byte) 0x3f, (byte) 0xbf }) to a string of hex digits (e.g. "3fbf").
*/
private static String bytesToHexString(byte[] bytes) {
return BaseEncoding.base16().lowerCase().encode(bytes);
}
/**
* Assert that decoding (via {@link OrderedCode#readSignedNumIncreasing()}) the bytes represented
* by the specified string of hex digits results in the expected long value. E.g.
* assertDecodedSignedNumIncreasingEquals(-65, "3fbf") asserts that the byte array { (byte) 0x3f,
* (byte) 0xbf } is decoded as -65.
*/
private static void assertDecodedSignedNumIncreasingEquals(
long expectedNum, String encodedHexString) {
OrderedCode orderedCode = new OrderedCode(bytesFromHexString(encodedHexString));
assertEquals(
"Unexpected value when decoding 0x" + encodedHexString,
expectedNum,
orderedCode.readSignedNumIncreasing());
assertFalse(
"Unexpected encoded bytes remain after decoding 0x" + encodedHexString,
orderedCode.hasRemainingEncodedBytes());
}
/**
* Assert that decoding various sequences of bytes via {@link
* OrderedCode#readSignedNumIncreasing()} produces the expected long value. Input byte sequences
* were generated via the c++ (authoritative) implementation of
* OrderedCode::WriteSignedNumIncreasing.
*/
@Test
public void testSignedNumIncreasing_read() {
assertDecodedSignedNumIncreasingEquals(Long.MIN_VALUE, "003f8000000000000000");
assertDecodedSignedNumIncreasingEquals(Long.MIN_VALUE + 1, "003f8000000000000001");
assertDecodedSignedNumIncreasingEquals(Integer.MIN_VALUE - 1L, "077fffffff");
assertDecodedSignedNumIncreasingEquals(Integer.MIN_VALUE, "0780000000");
assertDecodedSignedNumIncreasingEquals(Integer.MIN_VALUE + 1, "0780000001");
assertDecodedSignedNumIncreasingEquals(-65, "3fbf");
assertDecodedSignedNumIncreasingEquals(-64, "40");
assertDecodedSignedNumIncreasingEquals(-63, "41");
assertDecodedSignedNumIncreasingEquals(-3, "7d");
assertDecodedSignedNumIncreasingEquals(-2, "7e");
assertDecodedSignedNumIncreasingEquals(-1, "7f");
assertDecodedSignedNumIncreasingEquals(0, "80");
assertDecodedSignedNumIncreasingEquals(1, "81");
assertDecodedSignedNumIncreasingEquals(2, "82");
assertDecodedSignedNumIncreasingEquals(3, "83");
assertDecodedSignedNumIncreasingEquals(63, "bf");
assertDecodedSignedNumIncreasingEquals(64, "c040");
assertDecodedSignedNumIncreasingEquals(65, "c041");
assertDecodedSignedNumIncreasingEquals(Integer.MAX_VALUE - 1, "f87ffffffe");
assertDecodedSignedNumIncreasingEquals(Integer.MAX_VALUE, "f87fffffff");
assertDecodedSignedNumIncreasingEquals(Integer.MAX_VALUE + 1L, "f880000000");
assertDecodedSignedNumIncreasingEquals(Long.MAX_VALUE - 1, "ffc07ffffffffffffffe");
assertDecodedSignedNumIncreasingEquals(Long.MAX_VALUE, "ffc07fffffffffffffff");
}
/**
* Assert that encoding (via {@link OrderedCode#writeSignedNumIncreasing(long)}) the specified
* long value and then decoding (via {@link OrderedCode#readSignedNumIncreasing()}) results in the
* original value.
*/
private static void assertSignedNumIncreasingWriteAndReadIsLossless(long num) {
OrderedCode orderedCode = new OrderedCode();
orderedCode.writeSignedNumIncreasing(num);
assertEquals(
"Unexpected result when decoding writeSignedNumIncreasing(" + num + ")",
num,
orderedCode.readSignedNumIncreasing());
assertFalse(
"Unexpected remaining encoded bytes after decoding " + num,
orderedCode.hasRemainingEncodedBytes());
}
/**
* Assert that for various long values, encoding (via {@link
* OrderedCode#writeSignedNumIncreasing(long)}) and then decoding (via {@link
* OrderedCode#readSignedNumIncreasing()}) results in the original value.
*/
@Test
public void testSignedNumIncreasing_writeAndRead() {
assertSignedNumIncreasingWriteAndReadIsLossless(Long.MIN_VALUE);
assertSignedNumIncreasingWriteAndReadIsLossless(Long.MIN_VALUE + 1);
assertSignedNumIncreasingWriteAndReadIsLossless(Integer.MIN_VALUE - 1L);
assertSignedNumIncreasingWriteAndReadIsLossless(Integer.MIN_VALUE);
assertSignedNumIncreasingWriteAndReadIsLossless(Integer.MIN_VALUE + 1);
assertSignedNumIncreasingWriteAndReadIsLossless(-65);
assertSignedNumIncreasingWriteAndReadIsLossless(-64);
assertSignedNumIncreasingWriteAndReadIsLossless(-63);
assertSignedNumIncreasingWriteAndReadIsLossless(-3);
assertSignedNumIncreasingWriteAndReadIsLossless(-2);
assertSignedNumIncreasingWriteAndReadIsLossless(-1);
assertSignedNumIncreasingWriteAndReadIsLossless(0);
assertSignedNumIncreasingWriteAndReadIsLossless(1);
assertSignedNumIncreasingWriteAndReadIsLossless(2);
assertSignedNumIncreasingWriteAndReadIsLossless(3);
assertSignedNumIncreasingWriteAndReadIsLossless(63);
assertSignedNumIncreasingWriteAndReadIsLossless(64);
assertSignedNumIncreasingWriteAndReadIsLossless(65);
assertSignedNumIncreasingWriteAndReadIsLossless(Integer.MAX_VALUE - 1);
assertSignedNumIncreasingWriteAndReadIsLossless(Integer.MAX_VALUE);
assertSignedNumIncreasingWriteAndReadIsLossless(Integer.MAX_VALUE + 1L);
assertSignedNumIncreasingWriteAndReadIsLossless(Long.MAX_VALUE - 1);
assertSignedNumIncreasingWriteAndReadIsLossless(Long.MAX_VALUE);
}
/**
* Assert that encoding (via {@link OrderedCode#writeSignedNumDecreasing(long)}) the specified
* long value and then decoding (via {@link OrderedCode#readSignedNumDecreasing()}) results in the
* original value.
*/
private static void assertSignedNumDecreasingWriteAndReadIsLossless(long num) {
OrderedCode orderedCode = new OrderedCode();
orderedCode.writeSignedNumDecreasing(num);
assertEquals(
"Unexpected result when decoding writeSignedNumDecreasing(" + num + ")",
num,
orderedCode.readSignedNumDecreasing());
assertFalse(
"Unexpected remaining encoded bytes after decoding " + num,
orderedCode.hasRemainingEncodedBytes());
}
/**
* Assert that for various long values, encoding (via {@link
* OrderedCode#writeSignedNumDecreasing(long)}) and then decoding (via {@link
* OrderedCode#readSignedNumDecreasing()}) results in the original value.
*/
@Test
public void testSignedNumDecreasing_writeAndRead() {
assertSignedNumDecreasingWriteAndReadIsLossless(Long.MIN_VALUE);
assertSignedNumDecreasingWriteAndReadIsLossless(Long.MIN_VALUE + 1);
assertSignedNumDecreasingWriteAndReadIsLossless(Integer.MIN_VALUE - 1L);
assertSignedNumDecreasingWriteAndReadIsLossless(Integer.MIN_VALUE);
assertSignedNumDecreasingWriteAndReadIsLossless(Integer.MIN_VALUE + 1);
assertSignedNumDecreasingWriteAndReadIsLossless(-65);
assertSignedNumDecreasingWriteAndReadIsLossless(-64);
assertSignedNumDecreasingWriteAndReadIsLossless(-63);
assertSignedNumDecreasingWriteAndReadIsLossless(-3);
assertSignedNumDecreasingWriteAndReadIsLossless(-2);
assertSignedNumDecreasingWriteAndReadIsLossless(-1);
assertSignedNumDecreasingWriteAndReadIsLossless(0);
assertSignedNumDecreasingWriteAndReadIsLossless(1);
assertSignedNumDecreasingWriteAndReadIsLossless(2);
assertSignedNumDecreasingWriteAndReadIsLossless(3);
assertSignedNumDecreasingWriteAndReadIsLossless(63);
assertSignedNumDecreasingWriteAndReadIsLossless(64);
assertSignedNumDecreasingWriteAndReadIsLossless(65);
assertSignedNumDecreasingWriteAndReadIsLossless(Integer.MAX_VALUE - 1);
assertSignedNumDecreasingWriteAndReadIsLossless(Integer.MAX_VALUE);
assertSignedNumDecreasingWriteAndReadIsLossless(Integer.MAX_VALUE + 1L);
assertSignedNumDecreasingWriteAndReadIsLossless(Long.MAX_VALUE - 1);
assertSignedNumDecreasingWriteAndReadIsLossless(Long.MAX_VALUE);
}
/** Ensures that numbers encoded as "decreasing" do indeed sort in reverse order. */
@Test
public void testDecreasing() {
OrderedCode orderedCode = new OrderedCode();
orderedCode.writeSignedNumDecreasing(10L);
byte[] ten = orderedCode.getEncodedBytes();
orderedCode = new OrderedCode();
orderedCode.writeSignedNumDecreasing(20L);
byte[] twenty = orderedCode.getEncodedBytes();
// In decreasing order, twenty preceeds ten.
assertTrue(compare(twenty, ten) < 0);
}
@Test
public void testLog2Floor_Positive() {
OrderedCode orderedCode = new OrderedCode();
assertEquals(0, orderedCode.log2Floor(1));
assertEquals(1, orderedCode.log2Floor(2));
assertEquals(1, orderedCode.log2Floor(3));
assertEquals(2, orderedCode.log2Floor(4));
assertEquals(5, orderedCode.log2Floor(63));
assertEquals(6, orderedCode.log2Floor(64));
assertEquals(62, orderedCode.log2Floor(Long.MAX_VALUE));
}
/**
* OrderedCode.log2Floor(long) is defined to return -1 given an input of zero (because that's what
* Bits::Log2Floor64(uint64) does).
*/
@Test
public void testLog2Floor_zero() {
OrderedCode orderedCode = new OrderedCode();
assertEquals(-1, orderedCode.log2Floor(0));
}
@Test
public void testLog2Floor_negative() {
OrderedCode orderedCode = new OrderedCode();
try {
orderedCode.log2Floor(-1);
fail("Expected an IllegalArgumentException.");
} catch (IllegalArgumentException expected) {
// Expected!
}
}
@Test
public void testGetSignedEncodingLength() {
OrderedCode orderedCode = new OrderedCode();
assertEquals(10, orderedCode.getSignedEncodingLength(Long.MIN_VALUE));
assertEquals(10, orderedCode.getSignedEncodingLength(~(1L << 62)));
assertEquals(9, orderedCode.getSignedEncodingLength(~(1L << 62) + 1));
assertEquals(3, orderedCode.getSignedEncodingLength(-8193));
assertEquals(2, orderedCode.getSignedEncodingLength(-8192));
assertEquals(2, orderedCode.getSignedEncodingLength(-65));
assertEquals(1, orderedCode.getSignedEncodingLength(-64));
assertEquals(1, orderedCode.getSignedEncodingLength(-2));
assertEquals(1, orderedCode.getSignedEncodingLength(-1));
assertEquals(1, orderedCode.getSignedEncodingLength(0));
assertEquals(1, orderedCode.getSignedEncodingLength(1));
assertEquals(1, orderedCode.getSignedEncodingLength(63));
assertEquals(2, orderedCode.getSignedEncodingLength(64));
assertEquals(2, orderedCode.getSignedEncodingLength(8191));
assertEquals(3, orderedCode.getSignedEncodingLength(8192));
assertEquals(9, orderedCode.getSignedEncodingLength(1L << 62) - 1);
assertEquals(10, orderedCode.getSignedEncodingLength(1L << 62));
assertEquals(10, orderedCode.getSignedEncodingLength(Long.MAX_VALUE));
}
@Test
public void testWriteTrailingBytes() {
byte[] escapeChars =
new byte[] {
OrderedCode.ESCAPE1,
OrderedCode.NULL_CHARACTER,
OrderedCode.SEPARATOR,
OrderedCode.ESCAPE2,
OrderedCode.INFINITY,
OrderedCode.FF_CHARACTER
};
byte[] anotherArray = new byte[] {'a', 'b', 'c', 'd', 'e'};
OrderedCode orderedCode = new OrderedCode();
orderedCode.writeTrailingBytes(escapeChars);
assertArrayEquals(orderedCode.getEncodedBytes(), escapeChars);
assertArrayEquals(orderedCode.readTrailingBytes(), escapeChars);
try {
orderedCode.readInfinity();
fail("Expected IllegalArgumentException.");
} catch (IllegalArgumentException e) {
// expected
}
orderedCode = new OrderedCode();
orderedCode.writeTrailingBytes(anotherArray);
assertArrayEquals(orderedCode.getEncodedBytes(), anotherArray);
assertArrayEquals(orderedCode.readTrailingBytes(), anotherArray);
}
@Test
public void testMixedWrite() {
byte[] first = {'a', 'b', 'c'};
byte[] second = {'d', 'e', 'f'};
byte[] last = {'x', 'y', 'z'};
byte[] escapeChars =
new byte[] {
OrderedCode.ESCAPE1,
OrderedCode.NULL_CHARACTER,
OrderedCode.SEPARATOR,
OrderedCode.ESCAPE2,
OrderedCode.INFINITY,
OrderedCode.FF_CHARACTER
};
OrderedCode orderedCode = new OrderedCode();
orderedCode.writeBytes(first);
orderedCode.writeBytes(second);
orderedCode.writeBytes(last);
orderedCode.writeInfinity();
orderedCode.writeNumIncreasing(0);
orderedCode.writeNumIncreasing(1);
orderedCode.writeNumIncreasing(Long.MIN_VALUE);
orderedCode.writeNumIncreasing(Long.MAX_VALUE);
orderedCode.writeSignedNumIncreasing(0);
orderedCode.writeSignedNumIncreasing(1);
orderedCode.writeSignedNumIncreasing(Long.MIN_VALUE);
orderedCode.writeSignedNumIncreasing(Long.MAX_VALUE);
orderedCode.writeTrailingBytes(escapeChars);
byte[] allEncoded = orderedCode.getEncodedBytes();
assertArrayEquals(orderedCode.readBytes(), first);
assertArrayEquals(orderedCode.readBytes(), second);
assertFalse(orderedCode.readInfinity());
assertArrayEquals(orderedCode.readBytes(), last);
assertTrue(orderedCode.readInfinity());
assertEquals(0, orderedCode.readNumIncreasing());
assertEquals(1, orderedCode.readNumIncreasing());
assertFalse(orderedCode.readInfinity());
assertEquals(Long.MIN_VALUE, orderedCode.readNumIncreasing());
assertEquals(Long.MAX_VALUE, orderedCode.readNumIncreasing());
assertEquals(0, orderedCode.readSignedNumIncreasing());
assertEquals(1, orderedCode.readSignedNumIncreasing());
assertFalse(orderedCode.readInfinity());
assertEquals(Long.MIN_VALUE, orderedCode.readSignedNumIncreasing());
assertEquals(Long.MAX_VALUE, orderedCode.readSignedNumIncreasing());
assertArrayEquals(orderedCode.getEncodedBytes(), escapeChars);
assertArrayEquals(orderedCode.readTrailingBytes(), escapeChars);
orderedCode = new OrderedCode(allEncoded);
assertArrayEquals(orderedCode.readBytes(), first);
assertArrayEquals(orderedCode.readBytes(), second);
assertFalse(orderedCode.readInfinity());
assertArrayEquals(orderedCode.readBytes(), last);
assertTrue(orderedCode.readInfinity());
assertEquals(0, orderedCode.readNumIncreasing());
assertEquals(1, orderedCode.readNumIncreasing());
assertFalse(orderedCode.readInfinity());
assertEquals(Long.MIN_VALUE, orderedCode.readNumIncreasing());
assertEquals(Long.MAX_VALUE, orderedCode.readNumIncreasing());
assertEquals(0, orderedCode.readSignedNumIncreasing());
assertEquals(1, orderedCode.readSignedNumIncreasing());
assertFalse(orderedCode.readInfinity());
assertEquals(Long.MIN_VALUE, orderedCode.readSignedNumIncreasing());
assertEquals(Long.MAX_VALUE, orderedCode.readSignedNumIncreasing());
assertArrayEquals(orderedCode.getEncodedBytes(), escapeChars);
assertArrayEquals(orderedCode.readTrailingBytes(), escapeChars);
}
@Test
public void testEdgeCases() {
byte[] ffChar = {OrderedCode.FF_CHARACTER};
byte[] nullChar = {OrderedCode.NULL_CHARACTER};
byte[] separatorEncoded = {OrderedCode.ESCAPE1, OrderedCode.SEPARATOR};
byte[] ffCharEncoded = {OrderedCode.ESCAPE1, OrderedCode.NULL_CHARACTER};
byte[] nullCharEncoded = {OrderedCode.ESCAPE2, OrderedCode.FF_CHARACTER};
byte[] infinityEncoded = {OrderedCode.ESCAPE2, OrderedCode.INFINITY};
OrderedCode orderedCode = new OrderedCode();
orderedCode.writeBytes(ffChar);
orderedCode.writeBytes(nullChar);
orderedCode.writeInfinity();
assertArrayEquals(
orderedCode.getEncodedBytes(),
Bytes.concat(
ffCharEncoded, separatorEncoded, nullCharEncoded, separatorEncoded, infinityEncoded));
assertArrayEquals(orderedCode.readBytes(), ffChar);
assertArrayEquals(orderedCode.readBytes(), nullChar);
assertTrue(orderedCode.readInfinity());
orderedCode = new OrderedCode(Bytes.concat(ffCharEncoded, separatorEncoded));
assertArrayEquals(orderedCode.readBytes(), ffChar);
orderedCode = new OrderedCode(Bytes.concat(nullCharEncoded, separatorEncoded));
assertArrayEquals(orderedCode.readBytes(), nullChar);
byte[] invalidEncodingForRead = {
OrderedCode.ESCAPE2, OrderedCode.ESCAPE2, OrderedCode.ESCAPE1, OrderedCode.SEPARATOR
};
orderedCode = new OrderedCode(invalidEncodingForRead);
try {
orderedCode.readBytes();
fail("Should have failed.");
} catch (Exception e) {
// Expected
}
assertTrue(orderedCode.hasRemainingEncodedBytes());
}
@Test
public void testHasRemainingEncodedBytes() {
byte[] bytes = {'a', 'b', 'c'};
long number = 12345;
// Empty
OrderedCode orderedCode = new OrderedCode();
assertFalse(orderedCode.hasRemainingEncodedBytes());
// First and only field of each type.
orderedCode.writeBytes(bytes);
assertTrue(orderedCode.hasRemainingEncodedBytes());
assertArrayEquals(orderedCode.readBytes(), bytes);
assertFalse(orderedCode.hasRemainingEncodedBytes());
orderedCode.writeNumIncreasing(number);
assertTrue(orderedCode.hasRemainingEncodedBytes());
assertEquals(orderedCode.readNumIncreasing(), number);
assertFalse(orderedCode.hasRemainingEncodedBytes());
orderedCode.writeSignedNumIncreasing(number);
assertTrue(orderedCode.hasRemainingEncodedBytes());
assertEquals(orderedCode.readSignedNumIncreasing(), number);
assertFalse(orderedCode.hasRemainingEncodedBytes());
orderedCode.writeInfinity();
assertTrue(orderedCode.hasRemainingEncodedBytes());
assertTrue(orderedCode.readInfinity());
assertFalse(orderedCode.hasRemainingEncodedBytes());
orderedCode.writeTrailingBytes(bytes);
assertTrue(orderedCode.hasRemainingEncodedBytes());
assertArrayEquals(orderedCode.readTrailingBytes(), bytes);
assertFalse(orderedCode.hasRemainingEncodedBytes());
// Two fields of same type.
orderedCode.writeBytes(bytes);
orderedCode.writeBytes(bytes);
assertTrue(orderedCode.hasRemainingEncodedBytes());
assertArrayEquals(orderedCode.readBytes(), bytes);
assertArrayEquals(orderedCode.readBytes(), bytes);
assertFalse(orderedCode.hasRemainingEncodedBytes());
}
@Test
public void testOrderingInfinity() {
OrderedCode inf = new OrderedCode();
inf.writeInfinity();
OrderedCode negInf = new OrderedCode();
negInf.writeInfinityDecreasing();
OrderedCode longValue = new OrderedCode();
longValue.writeSignedNumIncreasing(1);
assertTrue(compare(inf.getEncodedBytes(), negInf.getEncodedBytes()) > 0);
assertTrue(compare(longValue.getEncodedBytes(), negInf.getEncodedBytes()) > 0);
assertTrue(compare(inf.getEncodedBytes(), longValue.getEncodedBytes()) > 0);
}
private int compare(byte[] bytes1, byte[] bytes2) {
return UnsignedBytes.lexicographicalComparator().compare(bytes1, bytes2);
}
}