/*
* Copyright 2015-present Facebook, Inc.
*
* Licensed 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 com.facebook.buck.bser;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.sameInstance;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.io.BaseEncoding;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.CharacterCodingException;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
public class BserSerializerTest {
@Rule public ExpectedException thrown = ExpectedException.none();
private ByteBuffer buffer;
private static final String EXPECTED_EMPTY_ARRAY;
private static final String EXPECTED_INT8_ARRAY;
private static final String EXPECTED_STRING;
private static final String EXPECTED_EMPTY_MAP;
private static final String EXPECTED_INT8_MAP;
private static final String EXPECTED_INT8;
private static final String EXPECTED_INT16;
private static final String EXPECTED_INT32;
private static final String EXPECTED_INT64;
private static final String EXPECTED_MIN_INT8;
private static final String EXPECTED_MIN_INT16;
private static final String EXPECTED_MIN_INT32;
private static final String EXPECTED_MIN_INT64;
private static final String EXPECTED_REAL;
private static final String EXPECTED_TRUE;
private static final String EXPECTED_FALSE;
private static final String EXPECTED_NULL;
// BSER encoding depends on the host byte order, and to keep the
// encoder simpler, we always encode the buffer length as a
// (endian-specific) 32-bit value at the start of the encoded byte
// sequence.
//
// So, we initialize these expected byte sequences statically depending
// on the host byte order.
static {
if (ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN) {
EXPECTED_EMPTY_ARRAY = "00010500000003000300";
EXPECTED_INT8_ARRAY = "000105000000090003030323034203F0";
EXPECTED_STRING = "0001050000000E02030B68656C6C6F20776F726C64";
EXPECTED_EMPTY_MAP = "00010500000003010300";
EXPECTED_INT8_MAP = "0001050000001B010303020303666F6F0323020303626172034202030362617A03F0";
EXPECTED_INT8 = "000105000000020342";
EXPECTED_INT16 = "000105000000030411FF";
EXPECTED_INT32 = "0001050000005051122EEFF";
EXPECTED_INT64 = "000105000000090611223344CCDDEEFF";
EXPECTED_MIN_INT8 = "000105000000020380";
EXPECTED_MIN_INT16 = "00010500000003048000";
EXPECTED_MIN_INT32 = "00010500000050580000000";
EXPECTED_MIN_INT64 = "00010500000009068000000000000000";
EXPECTED_REAL = "00010500000009073FBF9ADD3739635F";
EXPECTED_TRUE = "0001050000000108";
EXPECTED_FALSE = "0001050000000109";
EXPECTED_NULL = "000105000000010A";
} else {
EXPECTED_EMPTY_ARRAY = "00010503000000000300";
EXPECTED_INT8_ARRAY = "000105090000000003030323034203F0";
EXPECTED_STRING = "0001050E00000002030B68656C6C6F20776F726C64";
EXPECTED_EMPTY_MAP = "00010503000000010300";
EXPECTED_INT8_MAP = "0001051B000000010303020303666F6F0323020303626172034202030362617A03F0";
EXPECTED_INT8 = "000105020000000342";
EXPECTED_INT16 = "0001050300000004FF11";
EXPECTED_INT32 = "0001050500000005FFEE2211";
EXPECTED_INT64 = "0001050900000006FFEEDDCC44332211";
EXPECTED_MIN_INT8 = "000105020000000380";
EXPECTED_MIN_INT16 = "00010503000000040080";
EXPECTED_MIN_INT32 = "000105050000000500000080";
EXPECTED_MIN_INT64 = "00010509000000060000000000000080";
EXPECTED_REAL = "00010509000000075F633937DD9ABF3F";
EXPECTED_TRUE = "0001050100000008";
EXPECTED_FALSE = "0001050100000009";
EXPECTED_NULL = "000105010000000A";
}
}
@Before
public void setUp() {
buffer = ByteBuffer.allocate(512).order(ByteOrder.nativeOrder());
}
private ByteBuffer assertEncodingMatches(Object object, String base16) throws IOException {
BserSerializer serializer = new BserSerializer();
ByteBuffer result = serializer.serializeToBuffer(object, buffer);
result.flip();
byte[] base16Array = BaseEncoding.base16().decode(base16);
assertThat(
String.format(
"Encoded buffer mismatch (%s != %s)",
base16,
BaseEncoding.base16().encode(result.array(), result.position(), result.limit())),
result,
equalTo(ByteBuffer.wrap(base16Array)));
return result;
}
@Test
public void serializeEmptyArray() throws IOException {
assertEncodingMatches(ImmutableList.of(), EXPECTED_EMPTY_ARRAY);
}
@Test
public void serializeArrayOfInt8() throws IOException {
assertEncodingMatches(
ImmutableList.of((byte) 0x23, (byte) 0x42, (byte) 0xF0), EXPECTED_INT8_ARRAY);
}
@Test
public void serializeEmptySet() throws IOException {
assertEncodingMatches(ImmutableSet.of(), EXPECTED_EMPTY_ARRAY);
}
@Test
public void serializeSetOfInt8() throws IOException {
assertEncodingMatches(
ImmutableSet.of((byte) 0x23, (byte) 0x42, (byte) 0xF0), EXPECTED_INT8_ARRAY);
}
@Test
public void serializeString() throws IOException {
assertEncodingMatches("hello world", EXPECTED_STRING);
}
@Test
public void serializeEmptyMap() throws IOException {
assertEncodingMatches(ImmutableMap.of(), EXPECTED_EMPTY_MAP);
}
@Test
public void serializeInt8Map() throws IOException {
assertEncodingMatches(
ImmutableMap.of(
"foo", (byte) 0x23,
"bar", (byte) 0x42,
"baz", (byte) 0xF0),
EXPECTED_INT8_MAP);
}
@Test
public void serializeInt8() throws IOException {
assertEncodingMatches((byte) 0x42, EXPECTED_INT8);
}
@Test
public void serializeInt16() throws IOException {
assertEncodingMatches(0x11FF, EXPECTED_INT16);
}
@Test
public void serializeInt32() throws IOException {
assertEncodingMatches(0x1122EEFF, EXPECTED_INT32);
}
@Test
public void serializeInt64() throws IOException {
assertEncodingMatches(0x11223344CCDDEEFFL, EXPECTED_INT64);
}
@Test
public void serializeMinInt8() throws IOException {
assertEncodingMatches((byte) -0x80, EXPECTED_MIN_INT8);
}
@Test
public void serializeMinInt16() throws IOException {
assertEncodingMatches(-0x8000, EXPECTED_MIN_INT16);
}
@Test
public void serializeMinInt32() throws IOException {
assertEncodingMatches(-0x80000000, EXPECTED_MIN_INT32);
}
@Test
public void serializeMinInt64() throws IOException {
assertEncodingMatches(-0x8000000000000000L, EXPECTED_MIN_INT64);
}
@Test
public void serializeReal() throws IOException {
assertEncodingMatches(0.123456789, EXPECTED_REAL);
}
@Test
public void serializeTrue() throws IOException {
assertEncodingMatches(true, EXPECTED_TRUE);
}
@Test
public void serializeFalse() throws IOException {
assertEncodingMatches(false, EXPECTED_FALSE);
}
@Test
public void serializeNull() throws IOException {
assertEncodingMatches(null, EXPECTED_NULL);
}
@Test
public void throwIfStringNotUTF8() throws IOException {
thrown.expect(CharacterCodingException.class);
BserSerializer serializer = new BserSerializer();
// UTF-8 cannot legally represent half of a surrogate pair, so this should throw.
serializer.serializeToBuffer("\uDC00", buffer);
}
@Test
public void throwIfMapKeyNotString() throws IOException {
thrown.expect(IOException.class);
thrown.expectMessage("Unrecognized map key type class java.lang.Integer, expected string");
BserSerializer serializer = new BserSerializer();
serializer.serializeToBuffer(ImmutableMap.of(0, 1), buffer);
}
@Test
public void smallObjectReusesInputBuffer() throws IOException {
BserSerializer serializer = new BserSerializer();
ByteBuffer result = serializer.serializeToBuffer(true, buffer);
assertThat(result, is(sameInstance(buffer)));
}
@Test
public void largeObjectAllocatesNewBuffer() throws IOException {
BserSerializer serializer = new BserSerializer();
ByteBuffer result = serializer.serializeToBuffer(Strings.repeat("X", 10000), buffer);
assertThat(result, is(not(sameInstance(buffer))));
}
@Test
public void emptyBufferAllocatesNewBuffer() throws IOException {
BserSerializer serializer = new BserSerializer();
buffer = ByteBuffer.allocate(1).order(ByteOrder.nativeOrder());
ByteBuffer result = serializer.serializeToBuffer(true, buffer);
assertThat(result, is(not(sameInstance(buffer))));
}
class CloseableByteArrayOutputStream extends ByteArrayOutputStream {
private boolean closed = false;
@Override
public void close() throws IOException {
super.close();
this.closed = true;
}
public boolean isClosed() {
return closed;
}
}
@Test
public void serializeTrueToStream() throws IOException {
try (CloseableByteArrayOutputStream os = new CloseableByteArrayOutputStream()) {
BserSerializer serializer = new BserSerializer();
serializer.serializeToStream(true, os);
assertFalse(os.isClosed());
assertThat(os.toByteArray(), equalTo(BaseEncoding.base16().decode(EXPECTED_TRUE)));
}
}
}