// =================================================================================================
// Copyright 2011 Twitter, Inc.
// -------------------------------------------------------------------------------------------------
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this work except in compliance with the License.
// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.thrift.text;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import com.google.common.base.Charsets;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.io.Resources;
import org.apache.commons.codec.binary.Base64;
import org.apache.thrift.TException;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TIOStreamTransport;
import org.apache.thrift.transport.TTransport;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
/**
* Test the TTextProtocol
*
* TODO(Alex Roetter): add more tests, especially ones that verify
* that we generate ParseErrors for invalid input
*
* @author Alex Roetter
*/
public class TTextProtocolTest {
// TODO(Alex Roetter): move this static variable over to ThriftCodec,
// alongside the others
private static final Function<TTransport, TProtocol> TEXT_PROTOCOL =
new Function<TTransport, TProtocol>() {
@Override
public TProtocol apply(TTransport transport) {
return new TTextProtocol(transport);
}
};
private String fileContents;
private Base64 base64Encoder;
/**
* Load a file containing a serialized thrift message in from disk
* @throws IOException
*/
@Before
public void setUp() throws IOException {
fileContents = Resources.toString(Resources.getResource(
getClass(),
"/com/twitter/common/thrift/text/TTextProtocol_TestData.txt"),
Charsets.UTF_8);
base64Encoder = new Base64();
}
/**
* Read in (deserialize) a thrift message in TTextProtocol format
* from a file on disk, then serialize it back out to a string.
* Finally, deserialize that string and compare to the original
* message.
* @throws IOException
*/
@Test
public void tTextProtocolReadWriteTest() throws IOException, TException {
// Deserialize the file contents into a thrift message.
ByteArrayInputStream bais1 = new ByteArrayInputStream(
fileContents.getBytes());
TTextProtocolTestMsg msg1 = new TTextProtocolTestMsg();
msg1.read(new TTextProtocol(new TIOStreamTransport(bais1)));
assertEquals(testMsg(), msg1);
// Serialize that thrift message out to a byte array
ByteArrayOutputStream baos = new ByteArrayOutputStream();
msg1.write(new TTextProtocol(new TIOStreamTransport(baos)));
byte[] bytes = baos.toByteArray();
// Deserialize that string back to a thrift message.
ByteArrayInputStream bais2 = new ByteArrayInputStream(bytes);
TTextProtocolTestMsg msg2 = new TTextProtocolTestMsg();
msg2.read(new TTextProtocol(new TIOStreamTransport(bais2)));
assertEquals(msg1, msg2);
}
private TTextProtocolTestMsg testMsg() {
return new TTextProtocolTestMsg()
.setA(12345L)
.setB(5)
.setC(sub(1, 10))
.setD(ImmutableList.of(7, 8, 9, 10, 11))
.setE(ImmutableList.of(
sub(2, 100),
sub(3, 200),
sub(4, 300)
))
.setF(true)
.setG((byte) 12)
.setH(ImmutableMap.<Integer, Long>of(
1, 2L,
3, 4L,
5, 6L
))
.setJ(ImmutableMap.<Short,List<Boolean>>of(
(short) 1, ImmutableList.<Boolean>of(true, true, false, true),
(short) 5, ImmutableList.<Boolean>of(false)
))
.setK(ImmutableSet.of(true, false, false, false, true))
.setL(base64Encoder.decode("SGVsbG8gV29ybGQ="))
.setM("hello \"spherical\" world!")
.setN((short) 678)
.setP(Letter.CHARLIE)
.setQ(EnumSet.allOf(Letter.class))
.setR(ImmutableMap.<Sub, Long>of(sub(1, 2), 100L))
.setS(ImmutableMap.<Map<Map<Long, Long>, Long> ,Long>of(
ImmutableMap.<Map<Long, Long>, Long>of(
ImmutableMap.<Long, Long>of(200L, 400L), 300L
), 100L
))
;
}
private Sub sub(int s, int x) {
return new Sub(s, new SubSub(x));
}
// For TUnion structure, TTextProtocol can only handle serialization, but not deserialization.
// Because when deserialization, we loose the context of which thrift class we are currently at.
// Specifically, because we rely on the callstack to determine which structure is currently being
// parsed, but TUnion actually implements of read/write. So when the parser comes to any TUnion,
// it only knows TUnion from the stack, but not the specific thrift struct.
// So here we only test serialization, not the deserialization part.
@Test
public void tTextProtocolWriteUnionTest() throws IOException, TException {
TTextProtocolTestMsgUnion msg = new TTextProtocolTestMsgUnion();
msg.setU(TestUnion.f2(2));
// Serialize that thrift message with union out to a byte array
ByteArrayOutputStream baos = new ByteArrayOutputStream();
msg.write(new TTextProtocol(new TIOStreamTransport(baos)));
String expectedMsg =
"{\n" +
" \"u\": {\n" +
" \"f2\": 2\n" +
" }\n" +
"}";
assertEquals(expectedMsg, baos.toString());
}
}