diff options
author | Nobuaki Sukegawa <nsuke@apache.org> | 2015-11-01 17:00:34 +0900 |
---|---|---|
committer | Nobuaki Sukegawa <nsuke@apache.org> | 2015-11-03 22:02:55 +0900 |
commit | b31f0900b57b7473a8f84c7b74f73381dcff2818 (patch) | |
tree | 7ef6c5c8d52ab9d8cf136c8bef3e0ae65b85c171 | |
parent | 36c357a90eeda250d1c7eb1d419dbe9010e0d65f (diff) | |
download | thrift-b31f0900b57b7473a8f84c7b74f73381dcff2818.tar.gz |
THRIFT-338 Compact Protocol in Erlang
Client: Erlang
Patch: Nobuaki Sukegawa
This closes #338
-rw-r--r-- | lib/erl/src/thrift_client_util.erl | 14 | ||||
-rw-r--r-- | lib/erl/src/thrift_compact_protocol.erl | 389 | ||||
-rw-r--r-- | lib/erl/src/thrift_protocol.erl | 11 | ||||
-rw-r--r-- | lib/erl/src/thrift_socket_server.erl | 21 | ||||
-rw-r--r-- | lib/erl/test/test_thrift_compact_protocol.erl | 219 | ||||
-rw-r--r-- | test/erl/src/test_client.erl | 22 | ||||
-rw-r--r-- | test/erl/src/test_thrift_server.erl | 3 | ||||
-rw-r--r-- | test/known_failures_Linux.json | 11 | ||||
-rw-r--r-- | test/tests.json | 3 |
9 files changed, 676 insertions, 17 deletions
diff --git a/lib/erl/src/thrift_client_util.erl b/lib/erl/src/thrift_client_util.erl index b51a0b425..265c308bc 100644 --- a/lib/erl/src/thrift_client_util.erl +++ b/lib/erl/src/thrift_client_util.erl @@ -34,7 +34,8 @@ split_options([], ProtoIn, TransIn) -> split_options([Opt = {OptKey, _} | Rest], ProtoIn, TransIn) when OptKey =:= strict_read; - OptKey =:= strict_write -> + OptKey =:= strict_write; + OptKey =:= protocol -> split_options(Rest, [Opt | ProtoIn], TransIn); split_options([Opt = {OptKey, _} | Rest], ProtoIn, TransIn) @@ -48,7 +49,6 @@ split_options([Opt = {OptKey, _} | Rest], ProtoIn, TransIn) %% Client constructor for the common-case of socket transports -%% with the binary protocol new(Host, Port, Service, Options) when is_integer(Port), is_atom(Service), is_list(Options) -> {ProtoOpts, TransOpts0} = split_options(Options), @@ -58,11 +58,17 @@ new(Host, Port, Service, Options) false -> {thrift_socket_transport, TransOpts0} end, + {ProtocolModule, ProtoOpts1} = case lists:keytake(protocol, 1, ProtoOpts) of + {value, {_, compact}, Opts} -> {thrift_compact_protocol, Opts}; + {value, {_, json}, Opts} -> {thrift_json_protocol, Opts}; + {value, {_, binary}, Opts} -> {thrift_binary_protocol, Opts}; + false -> {thrift_binary_protocol, ProtoOpts} + end, {ok, TransportFactory} = TransportModule:new_transport_factory(Host, Port, TransOpts2), - {ok, ProtocolFactory} = thrift_binary_protocol:new_protocol_factory( - TransportFactory, ProtoOpts), + {ok, ProtocolFactory} = ProtocolModule:new_protocol_factory( + TransportFactory, ProtoOpts1), case ProtocolFactory() of {ok, Protocol} -> diff --git a/lib/erl/src/thrift_compact_protocol.erl b/lib/erl/src/thrift_compact_protocol.erl new file mode 100644 index 000000000..359eaa986 --- /dev/null +++ b/lib/erl/src/thrift_compact_protocol.erl @@ -0,0 +1,389 @@ +%% +%% 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. +%% + +-module(thrift_compact_protocol). + +-behaviour(thrift_protocol). + +-include("thrift_constants.hrl"). +-include("thrift_protocol.hrl"). + +-export([new/1, new/2, + read/2, + write/2, + flush_transport/1, + close_transport/1, + new_protocol_factory/2 + ]). + +-define(ID_NONE, 16#10000). +-define(CBOOL_NONE, 0). +-define(CBOOL_FALSE, 1). +-define(CBOOL_TRUE, 2). + +-record(t_compact, {transport, + % state for pending boolean fields + read_stack=[], + read_value=?CBOOL_NONE, + write_stack=[], + write_id=?ID_NONE + }). +-type state() :: #t_compact{}. +-include("thrift_protocol_behaviour.hrl"). + +-define(PROTOCOL_ID, 16#82). +-define(VERSION_MASK, 16#1f). +-define(VERSION_1, 16#01). +-define(TYPE_MASK, 16#E0). +-define(TYPE_BITS, 16#07). +-define(TYPE_SHIFT_AMOUNT, 5). + +typeid_to_compact(?tType_STOP) -> 16#0; +typeid_to_compact(?tType_DOUBLE) -> 16#7; +typeid_to_compact(?tType_I8) -> 16#3; +typeid_to_compact(?tType_I16) -> 16#4; +typeid_to_compact(?tType_I32) -> 16#5; +typeid_to_compact(?tType_I64) -> 16#6; +typeid_to_compact(?tType_STRING) -> 16#8; +typeid_to_compact(?tType_STRUCT) -> 16#C; +typeid_to_compact(?tType_MAP) -> 16#B; +typeid_to_compact(?tType_SET) -> 16#A; +typeid_to_compact(?tType_LIST) -> 16#9. + +compact_to_typeid(16#0) -> ?tType_STOP; +compact_to_typeid(?CBOOL_FALSE) -> ?tType_BOOL; +compact_to_typeid(?CBOOL_TRUE) -> ?tType_BOOL; +compact_to_typeid(16#7) -> ?tType_DOUBLE; +compact_to_typeid(16#3) -> ?tType_I8; +compact_to_typeid(16#4) -> ?tType_I16; +compact_to_typeid(16#5) -> ?tType_I32; +compact_to_typeid(16#6) -> ?tType_I64; +compact_to_typeid(16#8) -> ?tType_STRING; +compact_to_typeid(16#C) -> ?tType_STRUCT; +compact_to_typeid(16#B) -> ?tType_MAP; +compact_to_typeid(16#A) -> ?tType_SET; +compact_to_typeid(16#9) -> ?tType_LIST. + +bool_to_cbool(Value) when Value -> ?CBOOL_TRUE; +bool_to_cbool(_) -> ?CBOOL_FALSE. +cbool_to_bool(Value) -> Value =:= ?CBOOL_TRUE. + +new(Transport) -> new(Transport, _Options = []). + +new(Transport, _Options) -> + State = #t_compact{transport = Transport}, + thrift_protocol:new(?MODULE, State). + +flush_transport(This = #t_compact{transport = Transport}) -> + {NewTransport, Result} = thrift_transport:flush(Transport), + {This#t_compact{transport = NewTransport}, Result}. + +close_transport(This = #t_compact{transport = Transport}) -> + {NewTransport, Result} = thrift_transport:close(Transport), + {This#t_compact{transport = NewTransport}, Result}. + +%%% +%%% instance methods +%%% + +write_field_begin(This0 = #t_compact{write_stack=[LastId|T]}, CompactType, Id) -> + IdDiff = Id - LastId, + This1 = This0#t_compact{write_stack=[Id|T]}, + case (IdDiff > 0) and (IdDiff < 16) of + true -> write(This1, {byte, (IdDiff bsl 4) bor CompactType}); + false -> + {This2, ok} = write(This1, {byte, CompactType}), + write(This2, {i16, Id}) + end. + +-spec to_zigzag(integer()) -> non_neg_integer(). +to_zigzag(Value) -> (Value bsl 1) bxor (Value bsr 63). + +-spec from_zigzag(non_neg_integer()) -> integer(). +from_zigzag(Value) -> (Value bsr 1) bxor -(Value band 1). + +-spec to_varint(non_neg_integer(), iolist()) -> iolist(). +to_varint(Value, Acc) when (Value < 16#80) -> [Acc, Value]; +to_varint(Value, Acc) -> + to_varint(Value bsr 7, [Acc, ((Value band 16#7F) bor 16#80)]). + +-spec read_varint(#t_compact{}, non_neg_integer(), non_neg_integer()) -> non_neg_integer(). +read_varint(This0, Acc, Count) -> + {This1, {ok, Byte}} = read(This0, byte), + case (Byte band 16#80) of + 0 -> {This1, {ok, (Byte bsl (7 * Count)) + Acc}}; + _ -> read_varint(This1, ((Byte band 16#7f) bsl (7 * Count)) + Acc, Count + 1) + end. + +write(This0, #protocol_message_begin{ + name = Name, + type = Type, + seqid = Seqid}) -> + {This1, ok} = write(This0, {byte, ?PROTOCOL_ID}), + {This2, ok} = write(This1, {byte, (?VERSION_1 band ?VERSION_MASK) bor (Type bsl ?TYPE_SHIFT_AMOUNT)}), + {This3, ok} = write(This2, {ui32, Seqid}), + {This4, ok} = write(This3, {string, Name}), + {This4, ok}; + +write(This, message_end) -> {This, ok}; + +write(This0, #protocol_field_begin{ + name = _Name, + type = Type, + id = Id}) +when (Type =:= ?tType_BOOL) -> {This0#t_compact{write_id = Id}, ok}; + +write(This0, #protocol_field_begin{ + name = _Name, + type = Type, + id = Id}) -> + write_field_begin(This0, typeid_to_compact(Type), Id); + +write(This, field_stop) -> write(This, {byte, ?tType_STOP}); + +write(This, field_end) -> {This, ok}; + +write(This0, #protocol_map_begin{ + ktype = _Ktype, + vtype = _Vtype, + size = Size}) +when Size =:= 0 -> + write(This0, {byte, 0}); + +write(This0, #protocol_map_begin{ + ktype = Ktype, + vtype = Vtype, + size = Size}) -> + {This1, ok} = write(This0, {ui32, Size}), + write(This1, {byte, (typeid_to_compact(Ktype) bsl 4) bor typeid_to_compact(Vtype)}); + +write(This, map_end) -> {This, ok}; + +write(This0, #protocol_list_begin{ + etype = Etype, + size = Size}) +when Size < 16#f -> + write(This0, {byte, (Size bsl 4) bor typeid_to_compact(Etype)}); + +write(This0, #protocol_list_begin{ + etype = Etype, + size = Size}) -> + {This1, ok} = write(This0, {byte, 16#f0 bor typeid_to_compact(Etype)}), + write(This1, {ui32, Size}); + +write(This, list_end) -> {This, ok}; + +write(This0, #protocol_set_begin{ + etype = Etype, + size = Size}) -> + write(This0, #protocol_list_begin{etype = Etype, size = Size}); + +write(This, set_end) -> {This, ok}; + +write(This = #t_compact{write_stack = Stack}, #protocol_struct_begin{}) -> + {This#t_compact{write_stack = [0|Stack]}, ok}; +write(This = #t_compact{write_stack = [_|T]}, struct_end) -> + {This#t_compact{write_stack = T}, ok}; + +write(This = #t_compact{write_id = ?ID_NONE}, {bool, Value}) -> + write(This, {byte, bool_to_cbool(Value)}); + +write(This0 = #t_compact{write_id = Id}, {bool, Value}) -> + {This1, ok} = write_field_begin(This0, bool_to_cbool(Value), Id), + {This1#t_compact{write_id = ?ID_NONE}, ok}; + +write(This, {byte, Value}) when is_integer(Value) -> + write(This, <<Value:8/big-signed>>); + +write(This, {i16, Value}) when is_integer(Value) -> write(This, to_varint(to_zigzag(Value), [])); +write(This, {ui32, Value}) when is_integer(Value) -> write(This, to_varint(Value, [])); +write(This, {i32, Value}) when is_integer(Value) -> + write(This, to_varint(to_zigzag(Value), [])); +write(This, {i64, Value}) when is_integer(Value) -> write(This, to_varint(to_zigzag(Value), [])); + +write(This, {double, Double}) -> + write(This, <<Double:64/big-signed-float>>); + +write(This0, {string, Str}) when is_list(Str) -> + % TODO: limit length + {This1, ok} = write(This0, {ui32, length(Str)}), + {This2, ok} = write(This1, list_to_binary(Str)), + {This2, ok}; + +write(This0, {string, Bin}) when is_binary(Bin) -> + % TODO: limit length + {This1, ok} = write(This0, {ui32, size(Bin)}), + {This2, ok} = write(This1, Bin), + {This2, ok}; + +%% Data :: iolist() +write(This = #t_compact{transport = Trans}, Data) -> + {NewTransport, Result} = thrift_transport:write(Trans, Data), + {This#t_compact{transport = NewTransport}, Result}. + +%% +%% + +read(This0, message_begin) -> + {This1, {ok, ?PROTOCOL_ID}} = read(This0, ubyte), + {This2, {ok, VerAndType}} = read(This1, ubyte), + ?VERSION_1 = VerAndType band ?VERSION_MASK, + {This3, {ok, SeqId}} = read(This2, ui32), + {This4, {ok, Name}} = read(This3, string), + {This4, #protocol_message_begin{ + name = binary_to_list(Name), + type = (VerAndType bsr ?TYPE_SHIFT_AMOUNT) band ?TYPE_BITS, + seqid = SeqId}}; + +read(This, message_end) -> {This, ok}; + +read(This = #t_compact{read_stack = Stack}, struct_begin) -> + {This#t_compact{read_stack = [0|Stack]}, ok}; +read(This = #t_compact{read_stack = [_H|T]}, struct_end) -> + {This#t_compact{read_stack = T}, ok}; + +read(This0 = #t_compact{read_stack = [LastId|T]}, field_begin) -> + {This1, {ok, Byte}} = read(This0, ubyte), + case Byte band 16#f of + CompactType = ?tType_STOP -> + {This1, #protocol_field_begin{type = CompactType}}; + CompactType -> + {This2, {ok, Id}} = case Byte bsr 4 of + 0 -> read(This1, i16); + IdDiff -> + {This1, {ok, LastId + IdDiff}} + end, + case compact_to_typeid(CompactType) of + ?tType_BOOL -> + {This2#t_compact{read_stack = [Id|T], read_value = cbool_to_bool(CompactType)}, + #protocol_field_begin{type = ?tType_BOOL, id = Id}}; + Type -> + {This2#t_compact{read_stack = [Id|T]}, + #protocol_field_begin{type = Type, id = Id}} + end + end; + +read(This, field_end) -> {This, ok}; + +read(This0, map_begin) -> + {This1, {ok, Size}} = read(This0, ui32), + {This2, {ok, KV}} = case Size of + 0 -> {This1, {ok, 0}}; + _ -> read(This1, ubyte) + end, + {This2, #protocol_map_begin{ktype = compact_to_typeid(KV bsr 4), + vtype = compact_to_typeid(KV band 16#f), + size = Size}}; +read(This, map_end) -> {This, ok}; + +read(This0, list_begin) -> + {This1, {ok, SizeAndType}} = read(This0, ubyte), + {This2, {ok, Size}} = case (SizeAndType bsr 4) band 16#f of + 16#f -> read(This1, ui32); + Else -> {This1, {ok, Else}} + end, + {This2, #protocol_list_begin{etype = compact_to_typeid(SizeAndType band 16#f), + size = Size}}; + +read(This, list_end) -> {This, ok}; + +read(This0, set_begin) -> + {This1, {ok, SizeAndType}} = read(This0, ubyte), + {This2, {ok, Size}} = case (SizeAndType bsr 4) band 16#f of + 16#f -> read(This1, ui32); + Else -> {This1, {ok, Else}} + end, + {This2, #protocol_set_begin{etype = compact_to_typeid(SizeAndType band 16#f), + size = Size}}; + +read(This, set_end) -> {This, ok}; + +read(This0, field_stop) -> + {This1, {ok, ?tType_STOP}} = read(This0, ubyte), + {This1, ok}; + +%% + +read(This0 = #t_compact{read_value = ?CBOOL_NONE}, bool) -> + {This1, {ok, Byte}} = read(This0, ubyte), + {This1, {ok, cbool_to_bool(Byte)}}; + +read(This0 = #t_compact{read_value = Bool}, bool) -> + {This0#t_compact{read_value = ?CBOOL_NONE}, {ok, Bool}}; + +read(This0, ubyte) -> + {This1, {ok, <<Val:8/integer-unsigned-big, _/binary>>}} = read_data(This0, 1), + {This1, {ok, Val}}; + +read(This0, byte) -> + {This1, Bytes} = read_data(This0, 1), + case Bytes of + {ok, <<Val:8/integer-signed-big, _/binary>>} -> {This1, {ok, Val}}; + Else -> {This1, Else} + end; + +read(This0, i16) -> + {This1, {ok, Zigzag}} = read_varint(This0, 0, 0), + {This1, {ok, from_zigzag(Zigzag)}}; + +read(This0, ui32) -> read_varint(This0, 0, 0); + +read(This0, i32) -> + {This1, {ok, Zigzag}} = read_varint(This0, 0, 0), + {This1, {ok, from_zigzag(Zigzag)}}; + +read(This0, i64) -> + {This1, {ok, Zigzag}} = read_varint(This0, 0, 0), + {This1, {ok, from_zigzag(Zigzag)}}; + +read(This0, double) -> + {This1, Bytes} = read_data(This0, 8), + case Bytes of + {ok, <<Val:64/float-signed-big, _/binary>>} -> {This1, {ok, Val}}; + Else -> {This1, Else} + end; + +% returns a binary directly, call binary_to_list if necessary +read(This0, string) -> + {This1, {ok, Sz}} = read(This0, ui32), + read_data(This1, Sz). + +-spec read_data(#t_compact{}, non_neg_integer()) -> + {#t_compact{}, {ok, binary()} | {error, _Reason}}. +read_data(This, 0) -> {This, {ok, <<>>}}; +read_data(This = #t_compact{transport = Trans}, Len) when is_integer(Len) andalso Len > 0 -> + {NewTransport, Result} = thrift_transport:read(Trans, Len), + {This#t_compact{transport = NewTransport}, Result}. + + +%%%% FACTORY GENERATION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% returns a (fun() -> thrift_protocol()) +new_protocol_factory(TransportFactory, _Options) -> + F = fun() -> + case TransportFactory() of + {ok, Transport} -> + thrift_compact_protocol:new( + Transport, + []); + {error, Error} -> + {error, Error} + end + end, + {ok, F}. diff --git a/lib/erl/src/thrift_protocol.erl b/lib/erl/src/thrift_protocol.erl index b63f95143..dc3bfef2e 100644 --- a/lib/erl/src/thrift_protocol.erl +++ b/lib/erl/src/thrift_protocol.erl @@ -149,8 +149,12 @@ read(IProto0, {list, Type}) -> read(IProto0, {map, KeyType, ValType}) -> {IProto1, #protocol_map_begin{size = Size, ktype = KType, vtype = VType}} = read(IProto0, map_begin), - {KType, KType} = {term_to_typeid(KeyType), KType}, - {VType, VType} = {term_to_typeid(ValType), VType}, + _ = case Size of + 0 -> 0; + _ -> + {KType, KType} = {term_to_typeid(KeyType), KType}, + {VType, VType} = {term_to_typeid(ValType), VType} + end, {List, IProto2} = lists:mapfoldl(fun(_, ProtoS0) -> {ProtoS1, {ok, Key}} = read(ProtoS0, KeyType), {ProtoS2, {ok, Val}} = read(ProtoS1, ValType), @@ -192,7 +196,8 @@ read_struct_loop(IProto0, SDict, RTuple) -> thrift_protocol:read(IProto0, field_begin), case {FType, Fid} of {?tType_STOP, _} -> - {IProto1, RTuple}; + {IProto2, ok} = read(IProto1, struct_end), + {IProto2, RTuple}; _Else -> case dict:find(Fid, SDict) of {ok, {Type, Index}} -> diff --git a/lib/erl/src/thrift_socket_server.erl b/lib/erl/src/thrift_socket_server.erl index 233b992cc..e9ad6f4c5 100644 --- a/lib/erl/src/thrift_socket_server.erl +++ b/lib/erl/src/thrift_socket_server.erl @@ -38,6 +38,7 @@ listen=null, acceptor=null, socket_opts=[{recv_timeout, 500}], + protocol=binary, framed=false, ssltransport=false, ssloptions=[] @@ -106,6 +107,9 @@ parse_options([{max, Max} | Rest], State) -> end, parse_options(Rest, State#thrift_socket_server{max=MaxInt}); +parse_options([{protocol, Proto} | Rest], State) when is_atom(Proto) -> + parse_options(Rest, State#thrift_socket_server{protocol=Proto}); + parse_options([{framed, Framed} | Rest], State) when is_boolean(Framed) -> parse_options(Rest, State#thrift_socket_server{framed=Framed}); @@ -176,14 +180,14 @@ new_acceptor(State=#thrift_socket_server{max=0}) -> State#thrift_socket_server{acceptor=null}; new_acceptor(State=#thrift_socket_server{listen=Listen, service=Service, handler=Handler, - socket_opts=Opts, framed=Framed, + socket_opts=Opts, framed=Framed, protocol=Proto, ssltransport=SslTransport, ssloptions=SslOptions }) -> Pid = proc_lib:spawn_link(?MODULE, acceptor_loop, - [{self(), Listen, Service, Handler, Opts, Framed, SslTransport, SslOptions}]), + [{self(), Listen, Service, Handler, Opts, Framed, SslTransport, SslOptions, Proto}]), State#thrift_socket_server{acceptor=Pid}. -acceptor_loop({Server, Listen, Service, Handler, SocketOpts, Framed, SslTransport, SslOptions}) +acceptor_loop({Server, Listen, Service, Handler, SocketOpts, Framed, SslTransport, SslOptions, Proto}) when is_pid(Server), is_list(SocketOpts) -> case catch gen_tcp:accept(Listen) of % infinite timeout {ok, Socket} -> @@ -197,7 +201,11 @@ acceptor_loop({Server, Listen, Service, Handler, SocketOpts, Framed, SslTranspor true -> thrift_framed_transport:new(SocketTransport); false -> thrift_buffered_transport:new(SocketTransport) end, - {ok, Protocol} = thrift_binary_protocol:new(Transport), + {ok, Protocol} = case Proto of + compact -> thrift_compact_protocol:new(Transport); + json -> thrift_json_protocol:new(Transport); + _ -> thrift_binary_protocol:new(Transport) + end, {ok, Protocol} end, thrift_processor:init({Server, ProtoGen, Service, Handler}); @@ -225,8 +233,11 @@ handle_cast({accepted, Pid}, handle_cast(stop, State) -> {stop, normal, State}. -terminate(_Reason, #thrift_socket_server{listen=Listen, port=Port}) -> +terminate(Reason, #thrift_socket_server{listen=Listen, port=Port}) -> gen_tcp:close(Listen), + {backtrace, Bt} = erlang:process_info(self(), backtrace), + error_logger:error_report({?MODULE, ?LINE, + {child_error, Reason, Bt}}), case Port < 1024 of true -> catch fdsrv:stop(), diff --git a/lib/erl/test/test_thrift_compact_protocol.erl b/lib/erl/test/test_thrift_compact_protocol.erl new file mode 100644 index 000000000..5da78c612 --- /dev/null +++ b/lib/erl/test/test_thrift_compact_protocol.erl @@ -0,0 +1,219 @@ +%% +%% 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. +%% + +-module(test_thrift_compact_protocol). +-include_lib("eunit/include/eunit.hrl"). +-include("thrift_constants.hrl"). +-include("thrift_protocol.hrl"). + + +new(Transport) -> thrift_compact_protocol:new(Transport). +new() -> + {ok, Transport} = thrift_membuffer_transport:new(), + thrift_compact_protocol:new(Transport). + +new_test() -> + new(thrift_membuffer_transport:new()). + +write(This, Value) -> thrift_protocol:write(This, Value). +read(This, Type) -> thrift_protocol:read(This, Type). + +str(This0, Value0) -> + {This1, ok} = write(This0, {string, Value0}), + {This2, {ok, Value1}} = read(This1, string), + ?assertEqual(Value0, binary_to_list(Value1)), + {This2, ok}. +string_test() -> + {ok, This0} = new(), + {This1, ok} = str(This0, "aaa"), + {This2, ok} = str(This1, ""), + {This2, ok}. + +round_trip(This0, Type, Value0) -> + {This1, ok} = write(This0, {Type, Value0}), + {This2, {ok, Value1}} = read(This1, Type), + ?assertEqual(Value0, Value1), + {This2, ok}. + +bool_test() -> + {ok, This0} = new(), + {This1, ok} = round_trip(This0, bool, true), + {This2, ok} = round_trip(This1, bool, false), + {This2, ok}. + +byte(This0, Value0) -> round_trip(This0, byte, Value0). +byte_test() -> + {ok, This0} = new(), + {This1, ok} = byte(This0, 0), + {This2, ok} = byte(This1, 42), + {This3, ok} = byte(This2, -1), + {This4, ok} = byte(This3, -128), + {This4, ok}. + +i16(This0, Value0) -> round_trip(This0, i16, Value0). +i16_test() -> + {ok, This0} = new(), + {This1, ok} = i16(This0, 0), + {This2, ok} = i16(This1, 42), + {This3, ok} = i16(This2, 30000), + {This4, ok} = i16(This3, -1), + {This5, ok} = i16(This4, -128), + {This6, ok} = i16(This5, -30000), + {This6, ok}. + +i32(This0, Value0) -> round_trip(This0, i32, Value0). +i32_test() -> + {ok, This0} = new(), + {This1, ok} = i32(This0, 0), + {This2, ok} = i32(This1, 42), + {This3, ok} = i32(This2, 30000), + {This4, ok} = i32(This3, 2000000002), + {This5, ok} = i32(This4, -1), + {This6, ok} = i32(This5, -128), + {This7, ok} = i32(This6, -30000), + {This8, ok} = i32(This7, -2000000002), + {This8, ok}. + +i64(This0, Value0) -> round_trip(This0, i64, Value0). +i64_test() -> + {ok, This0} = new(), + {This1, ok} = i64(This0, 0), + {This2, ok} = i64(This1, 42), + {This3, ok} = i64(This2, 30000), + {This4, ok} = i64(This3, 2000000002), + {This5, ok} = i64(This4, 100000000000000064), + {This6, ok} = i64(This5, -1), + {This7, ok} = i64(This6, -128), + {This8, ok} = i64(This7, -30000), + {This9, ok} = i64(This8, -2000000002), + {This10, ok} = i64(This9, -100000000000000064), + {This10, ok}. + +struct_test() -> + {ok, P0} = new(), + {P1, ok} = write(P0, #protocol_message_begin{ name = "Message1", type = ?tType_I8, seqid = 3}), + {P2, ok} = write(P1, #protocol_struct_begin{}), + {P3, ok} = write(P2, #protocol_field_begin{ name = "field1", type = ?tType_I8, id = 1}), + {P4, ok} = write(P3, {byte, 42}), + {P5, ok} = write(P4, field_end), + {P6, ok} = write(P5, #protocol_field_begin{ name = "field2", type = ?tType_I8, id = 14}), + {P7, ok} = write(P6, {byte, 3}), + {P8, ok} = write(P7, field_end), + {P9, ok} = write(P8, #protocol_field_begin{ name = "field3", type = ?tType_I8, id = 42}), + {P10, ok} = write(P9, {byte, 8}), + {P11, ok} = write(P10, field_end), + {P12, ok} = write(P11, field_stop), + {P13, ok} = write(P12, struct_end), + {P14, ok} = write(P13, message_end), + + {P15, #protocol_message_begin{ name = "Message1", type = ?tType_I8, seqid = 3}} = read(P14, message_begin), + {P16, ok} = read(P15, struct_begin), + {P17, #protocol_field_begin{ type = ?tType_I8, id = 1 }} = read(P16, field_begin), + {P18, {ok, 42}} = read(P17, byte), + {P19, ok} = read(P18, field_end), + {P20, #protocol_field_begin{ type = ?tType_I8, id = 14 }} = read(P19, field_begin), + {P21, {ok, 3}} = read(P20, byte), + {P22, ok} = read(P21, field_end), + {P23, #protocol_field_begin{ type = ?tType_I8, id = 42 }} = read(P22, field_begin), + {P24, {ok, 8}} = read(P23, byte), + {P25, ok} = read(P24, field_end), + {P26, #protocol_field_begin{ type = ?tType_STOP}} = read(P25, field_begin), + {P27, ok} = read(P26, struct_end), + {P28, ok} = read(P27, message_end), + {P28, ok}. + +bool_field_test() -> + {ok, P0} = new(), + {P1, ok} = write(P0, #protocol_message_begin{ name = "Message1", type = ?tType_I8, seqid = 3}), + {P2, ok} = write(P1, #protocol_struct_begin{}), + {P3, ok} = write(P2, #protocol_field_begin{ name = "field1", type = ?tType_BOOL, id = 1}), + {P4, ok} = write(P3, {bool, true}), + {P5, ok} = write(P4, field_end), + {P6, ok} = write(P5, #protocol_field_begin{ name = "field2", type = ?tType_BOOL, id = 14}), + {P7, ok} = write(P6, {bool, false}), + {P8, ok} = write(P7, field_end), + {P9, ok} = write(P8, #protocol_field_begin{ name = "field3", type = ?tType_BOOL, id = 42}), + {P10, ok} = write(P9, {bool, true}), + {P11, ok} = write(P10, field_end), + {P12, ok} = write(P11, field_stop), + {P13, ok} = write(P12, struct_end), + {P14, ok} = write(P13, message_end), + + {P15, #protocol_message_begin{ name = "Message1", type = ?tType_I8, seqid = 3}} = read(P14, message_begin), + {P16, ok} = read(P15, struct_begin), + {P17, #protocol_field_begin{ type = ?tType_BOOL, id = 1 }} = read(P16, field_begin), + {P18, {ok, true}} = read(P17, bool), + {P19, ok} = read(P18, field_end), + {P20, #protocol_field_begin{ type = ?tType_BOOL, id = 14 }} = read(P19, field_begin), + {P21, {ok, false}} = read(P20, bool), + {P22, ok} = read(P21, field_end), + {P23, #protocol_field_begin{ type = ?tType_BOOL, id = 42 }} = read(P22, field_begin), + {P24, {ok, true}} = read(P23, bool), + {P25, ok} = read(P24, field_end), + {P26, #protocol_field_begin{ type = ?tType_STOP}} = read(P25, field_begin), + {P27, ok} = read(P26, struct_end), + {P28, ok} = read(P27, message_end), + {P28, ok}. + +nesting_test() -> + {ok, P0} = new(), + {P1, ok} = write(P0, #protocol_message_begin{ name = "Message1", type = ?tType_I8, seqid = 3}), + {P2, ok} = write(P1, #protocol_struct_begin{}), + {P3, ok} = write(P2, #protocol_field_begin{ name = "field1", type = ?tType_BOOL, id = 14}), + {P4, ok} = write(P3, {bool, true}), + {P5, ok} = write(P4, field_end), + + {P6, ok} = write(P5, #protocol_field_begin{ name = "field2", type = ?tType_STRUCT, id = 28}), + {P7, ok} = write(P6, #protocol_struct_begin{}), + {P8, ok} = write(P7, #protocol_field_begin{ name = "field2_1", type = ?tType_BOOL, id = 30000}), + {P9, ok} = write(P8, {bool, false}), + {P10, ok} = write(P9, field_end), + {P11, ok} = write(P10, field_stop), + {P12, ok} = write(P11, struct_end), + {P13, ok} = write(P12, field_end), + + {P14, ok} = write(P13, #protocol_field_begin{ name = "field3", type = ?tType_BOOL, id = 42}), + {P15, ok} = write(P14, {bool, true}), + {P16, ok} = write(P15, field_end), + {P17, ok} = write(P16, field_stop), + {P18, ok} = write(P17, struct_end), + {P19, ok} = write(P18, message_end), + + {P20, #protocol_message_begin{ name = "Message1", type = ?tType_I8, seqid = 3}} = read(P19, message_begin), + {P21, ok} = read(P20, struct_begin), + {P22, #protocol_field_begin{ type = ?tType_BOOL, id = 14 }} = read(P21, field_begin), + {P23, {ok, true}} = read(P22, bool), + {P24, ok} = read(P23, field_end), + + {P25, #protocol_field_begin{ type = ?tType_STRUCT, id = 28 }} = read(P24, field_begin), + {P26, ok} = read(P25, struct_begin), + {P27, #protocol_field_begin{ type = ?tType_BOOL, id = 30000 }} = read(P26, field_begin), + {P28, {ok, false}} = read(P27, bool), + {P29, ok} = read(P28, field_end), + {P30, #protocol_field_begin{ type = ?tType_STOP }} = read(P29, field_begin), + {P31, ok} = read(P30, struct_end), + {P32, ok} = read(P31, field_end), + + {P33, #protocol_field_begin{ type = ?tType_BOOL, id = 42 }} = read(P32, field_begin), + {P34, {ok, true}} = read(P33, bool), + {P35, ok} = read(P34, field_end), + {P36, #protocol_field_begin{ type = ?tType_STOP }} = read(P35, field_begin), + {P37, ok} = read(P36, struct_end), + {P38, ok} = read(P37, message_end), + {P38, ok}. diff --git a/test/erl/src/test_client.erl b/test/erl/src/test_client.erl index 7b9efd6f4..f97bc8aaa 100644 --- a/test/erl/src/test_client.erl +++ b/test/erl/src/test_client.erl @@ -55,9 +55,8 @@ parse_args([Head | Rest], Opts) -> ,{keyfile, "../keys/server.key"} ]}, Opts#options{client_opts = [{ssltransport, true} | [SslOptions | Opts#options.client_opts]]}; - "--protocol=binary" -> - % TODO: Enable JSON protocol - Opts; + "--protocol=" ++ Proto -> + Opts#options{client_opts = [{protocol, list_to_atom(Proto)}]}; _Else -> erlang:error({bad_arg, Head}) end, @@ -91,23 +90,39 @@ start(Args) -> userMap = dict:from_list([{?THRIFT_TEST_NUMBERZ_FIVE, 5000}]), xtructs = [#'Xtruct'{ string_thing = <<"Truck">>, byte_thing = 8, i32_thing = 8, i64_thing = 8}]}, + error_logger:info_msg("testVoid"), {Client01, {ok, ok}} = thrift_client:call(Client0, testVoid, []), + error_logger:info_msg("testString"), {Client02, {ok, <<"Test">>}} = thrift_client:call(Client01, testString, ["Test"]), + error_logger:info_msg("testString"), {Client03, {ok, <<"Test">>}} = thrift_client:call(Client02, testString, [<<"Test">>]), + error_logger:info_msg("testByte"), {Client04, {ok, 63}} = thrift_client:call(Client03, testByte, [63]), + error_logger:info_msg("testI32"), {Client05, {ok, -1}} = thrift_client:call(Client04, testI32, [-1]), + error_logger:info_msg("testI32"), {Client06, {ok, 0}} = thrift_client:call(Client05, testI32, [0]), + error_logger:info_msg("testI64"), {Client07, {ok, -34359738368}} = thrift_client:call(Client06, testI64, [-34359738368]), + error_logger:info_msg("testDouble"), {Client08, {ok, -5.2098523}} = thrift_client:call(Client07, testDouble, [-5.2098523]), %% TODO: add testBinary() call + error_logger:info_msg("testStruct"), {Client09, {ok, DemoXtruct}} = thrift_client:call(Client08, testStruct, [DemoXtruct]), + error_logger:info_msg("testNest"), {Client10, {ok, DemoNest}} = thrift_client:call(Client09, testNest, [DemoNest]), + error_logger:info_msg("testMap"), {Client11, {ok, DemoDict}} = thrift_client:call(Client10, testMap, [DemoDict]), + error_logger:info_msg("testSet"), {Client12, {ok, DemoSet}} = thrift_client:call(Client11, testSet, [DemoSet]), + error_logger:info_msg("testList"), {Client13, {ok, [-1,2,3]}} = thrift_client:call(Client12, testList, [[-1,2,3]]), + error_logger:info_msg("testEnum"), {Client14, {ok, 1}} = thrift_client:call(Client13, testEnum, [?THRIFT_TEST_NUMBERZ_ONE]), + error_logger:info_msg("testTypedef"), {Client15, {ok, 309858235082523}} = thrift_client:call(Client14, testTypedef, [309858235082523]), + error_logger:info_msg("testInsanity"), {Client16, {ok, InsaneResult}} = thrift_client:call(Client15, testInsanity, [DemoInsane]), io:format("~p~n", [InsaneResult]), @@ -147,6 +162,7 @@ start(Args) -> %% Use deprecated erlang:now until we start requiring OTP18 %% Started = erlang:monotonic_time(milli_seconds), {_, StartSec, StartUSec} = erlang:now(), + error_logger:info_msg("testOneway"), {Client20, {ok, ok}} = thrift_client:call(Client19, testOneway, [1]), {_, EndSec, EndUSec} = erlang:now(), Elapsed = (EndSec - StartSec) * 1000 + (EndUSec - StartUSec) / 1000, diff --git a/test/erl/src/test_thrift_server.erl b/test/erl/src/test_thrift_server.erl index 884eb9e60..f504c734a 100644 --- a/test/erl/src/test_thrift_server.erl +++ b/test/erl/src/test_thrift_server.erl @@ -55,7 +55,8 @@ parse_args([Head | Rest], Opts) -> ,{keyfile, "../keys/server.key"} ]}, Opts#options{server_opts = [{ssltransport, true} | [SslOptions | Opts#options.server_opts]]}; - "--protocol=" ++ _ -> Opts; + "--protocol=" ++ Proto -> + Opts#options{server_opts = [{protocol, list_to_atom(Proto)} | Opts#options.server_opts]}; _Else -> erlang:error({bad_arg, Head}) end, diff --git a/test/known_failures_Linux.json b/test/known_failures_Linux.json index 12bf7e65f..a20209b5f 100644 --- a/test/known_failures_Linux.json +++ b/test/known_failures_Linux.json @@ -30,6 +30,8 @@ "csharp-cpp_json_framed-ip-ssl", "csharp-erl_binary_buffered-ip-ssl", "csharp-erl_binary_framed-ip-ssl", + "csharp-erl_compact_buffered-ip-ssl", + "csharp-erl_compact_framed-ip-ssl", "csharp-go_binary_buffered-ip-ssl", "csharp-go_binary_framed-ip-ssl", "csharp-go_compact_buffered-ip-ssl", @@ -46,13 +48,22 @@ "csharp-nodejs_json_buffered-ip-ssl", "csharp-nodejs_json_framed-ip", "csharp-nodejs_json_framed-ip-ssl", + "erl-cpp_compact_buffered-ip", + "erl-cpp_compact_buffered-ip-ssl", + "erl-cpp_compact_framed-ip", + "erl-cpp_compact_framed-ip-ssl", "erl-go_binary_buffered-ip-ssl", "erl-go_binary_framed-ip-ssl", + "erl-go_compact_buffered-ip-ssl", + "erl-go_compact_framed-ip-ssl", "erl-nodejs_binary_buffered-ip", + "erl-nodejs_compact_buffered-ip", "erl-rb_binary-accel_buffered-ip", "erl-rb_binary-accel_framed-ip", "erl-rb_binary_buffered-ip", "erl-rb_binary_framed-ip", + "erl-rb_compact_buffered-ip", + "erl-rb_compact_framed-ip", "go-dart_binary_framed-ip", "go-dart_json_framed-ip", "go-hs_json_buffered-ip", diff --git a/test/tests.json b/test/tests.json index 981f764ad..44040f2b0 100644 --- a/test/tests.json +++ b/test/tests.json @@ -388,7 +388,8 @@ "ip-ssl" ], "protocols": [ - "binary" + "binary", + "compact" ], "client": { "command": [ |