Add property test for AMQP serializer and parser
This commit is contained in:
parent
9f42e40346
commit
6018155e9b
|
@ -14,12 +14,12 @@
|
|||
/logs/
|
||||
/plugins/
|
||||
/plugins.lock
|
||||
/rebar.config
|
||||
/rebar.lock
|
||||
/sbin/
|
||||
/sbin.lock
|
||||
/test/ct.cover.spec
|
||||
/xrefr
|
||||
_build
|
||||
|
||||
/amqp10_common.d
|
||||
/*.plt
|
||||
|
|
|
@ -110,10 +110,12 @@ dialyze(
|
|||
|
||||
rabbitmq_suite(
|
||||
name = "binary_generator_SUITE",
|
||||
size = "small",
|
||||
)
|
||||
|
||||
rabbitmq_suite(
|
||||
name = "binary_parser_SUITE",
|
||||
size = "small",
|
||||
)
|
||||
|
||||
rabbitmq_suite(
|
||||
|
@ -121,6 +123,13 @@ rabbitmq_suite(
|
|||
size = "small",
|
||||
)
|
||||
|
||||
rabbitmq_suite(
|
||||
name = "prop_SUITE",
|
||||
deps = [
|
||||
"//deps/rabbitmq_ct_helpers:erlang_app",
|
||||
],
|
||||
)
|
||||
|
||||
assert_suites()
|
||||
|
||||
alias(
|
||||
|
|
|
@ -26,6 +26,7 @@ endef
|
|||
|
||||
DIALYZER_OPTS += --src -r test -DTEST
|
||||
BUILD_DEPS = rabbit_common
|
||||
TEST_DEPS = rabbitmq_ct_helpers proper
|
||||
|
||||
# Variables and recipes in development.*.mk are meant to be used from
|
||||
# any Git clone. They are excluded from the files published to Hex.pm.
|
||||
|
|
|
@ -110,3 +110,13 @@ def test_suite_beam_files(name = "test_suite_beam_files"):
|
|||
app_name = "amqp10_common",
|
||||
erlc_opts = "//:test_erlc_opts",
|
||||
)
|
||||
erlang_bytecode(
|
||||
name = "prop_SUITE_beam_files",
|
||||
testonly = True,
|
||||
srcs = ["test/prop_SUITE.erl"],
|
||||
outs = ["test/prop_SUITE.beam"],
|
||||
hdrs = ["include/amqp10_framing.hrl"],
|
||||
app_name = "amqp10_common",
|
||||
erlc_opts = "//:test_erlc_opts",
|
||||
deps = ["@proper//:erlang_app"],
|
||||
)
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
{profiles,
|
||||
[{test, [{deps, [proper
|
||||
]}]}
|
||||
]
|
||||
}.
|
|
@ -40,7 +40,7 @@
|
|||
{symbol, binary()} |
|
||||
{binary, binary()} |
|
||||
{list, [amqp10_type()]} |
|
||||
{map, [{amqp10_prim(), amqp10_prim()}]} | %% TODO: make map a map
|
||||
{map, [{amqp10_prim(), amqp10_prim()}]} |
|
||||
{array, amqp10_ctor(), [amqp10_type()]}.
|
||||
|
||||
-type amqp10_described() ::
|
||||
|
@ -113,16 +113,20 @@ generate1({long, V}) when V<128 andalso V>-129 -> <<16#55,V:8/signed>>;
|
|||
generate1({long, V}) -> <<16#81,V:64/signed>>;
|
||||
generate1({float, V}) -> <<16#72,V:32/float>>;
|
||||
generate1({double, V}) -> <<16#82,V:64/float>>;
|
||||
generate1({char, V}) -> <<16#73,V:4/binary>>;
|
||||
generate1({char,V}) when V>=0 andalso V=<16#10ffff -> <<16#73,V:32>>;
|
||||
%% AMQP timestamp is "64-bit two's-complement integer representing milliseconds since the unix epoch".
|
||||
%% For small integers (i.e. values that can be stored in a single word),
|
||||
%% Erlang uses two’s complement to represent the signed integers.
|
||||
generate1({timestamp,V}) -> <<16#83,V:64/signed>>;
|
||||
generate1({uuid, V}) -> <<16#98,V:16/binary>>;
|
||||
|
||||
generate1({utf8, V}) when size(V) < ?VAR_1_LIMIT -> [16#a1, size(V), V];
|
||||
generate1({utf8, V}) -> [<<16#b1, (size(V)):32>>, V];
|
||||
generate1({symbol, V}) -> [16#a3, size(V), V];
|
||||
generate1({utf8, V}) when size(V) =< ?VAR_1_LIMIT -> [16#a1, size(V), V];
|
||||
generate1({utf8, V}) -> [<<16#b1, (size(V)):32>>, V];
|
||||
generate1({symbol, V}) when size(V) =< ?VAR_1_LIMIT -> [16#a3, size(V), V];
|
||||
generate1({symbol, V}) -> [<<16#b3, (size(V)):32>>, V];
|
||||
generate1({binary, V}) ->
|
||||
Size = iolist_size(V),
|
||||
case Size < ?VAR_1_LIMIT of
|
||||
case Size =< ?VAR_1_LIMIT of
|
||||
true ->
|
||||
[16#a0, Size, V];
|
||||
false ->
|
||||
|
@ -195,6 +199,7 @@ constructor(uuid) -> 16#98;
|
|||
constructor(null) -> 16#40;
|
||||
constructor(boolean) -> 16#56;
|
||||
constructor(array) -> 16#f0; % use large array type for all nested arrays
|
||||
constructor(binary) -> 16#b0;
|
||||
constructor(utf8) -> 16#b1;
|
||||
constructor({described, Descriptor, Primitive}) ->
|
||||
[16#00, generate1(Descriptor), constructor(Primitive)].
|
||||
|
@ -202,10 +207,13 @@ constructor({described, Descriptor, Primitive}) ->
|
|||
% returns io_list
|
||||
generate2(symbol, {symbol, V}) -> [<<(size(V)):32>>, V];
|
||||
generate2(utf8, {utf8, V}) -> [<<(size(V)):32>>, V];
|
||||
generate2(binary, {binary, V}) -> [<<(size(V)):32>>, V];
|
||||
generate2(boolean, true) -> 16#01;
|
||||
generate2(boolean, false) -> 16#00;
|
||||
generate2(boolean, {boolean, true}) -> 16#01;
|
||||
generate2(boolean, {boolean, false}) -> 16#00;
|
||||
generate2(null, null) -> 16#40;
|
||||
generate2(char, {char,V}) when V>=0 andalso V=<16#10ffff -> <<V:32>>;
|
||||
generate2(ubyte, {ubyte, V}) -> V;
|
||||
generate2(byte, {byte, V}) -> <<V:8/signed>>;
|
||||
generate2(ushort, {ushort, V}) -> <<V:16/unsigned>>;
|
||||
|
@ -214,6 +222,10 @@ generate2(uint, {uint, V}) -> <<V:32/unsigned>>;
|
|||
generate2(int, {int, V}) -> <<V:32/signed>>;
|
||||
generate2(ulong, {ulong, V}) -> <<V:64/unsigned>>;
|
||||
generate2(long, {long, V}) -> <<V:64/signed>>;
|
||||
generate2(float, {float, V}) -> <<V:32/float>>;
|
||||
generate2(double, {double, V}) -> <<V:64/float>>;
|
||||
generate2(timestamp, {timestamp,V}) -> <<V:64/signed>>;
|
||||
generate2(uuid, {uuid, V}) -> <<V:16/binary>>;
|
||||
generate2({described, D, P}, {described, D, V}) ->
|
||||
generate2(P, V);
|
||||
generate2(array, {array, Type, List}) ->
|
||||
|
|
|
@ -33,7 +33,6 @@
|
|||
|
||||
-export_type([opts/0]).
|
||||
|
||||
|
||||
%% Parses only the 1st AMQP type (including possible nested AMQP types).
|
||||
-spec parse(binary()) ->
|
||||
{amqp10_binary_generator:amqp10_type(), BytesParsed :: non_neg_integer()}.
|
||||
|
@ -64,7 +63,7 @@ parse(<<16#61, V:16/signed, _/binary>>, B) -> {{short, V}, B+3};
|
|||
parse(<<16#70, V:32/unsigned, _/binary>>, B) -> {{uint, V}, B+5};
|
||||
parse(<<16#71, V:32/signed, _/binary>>, B) -> {{int, V}, B+5};
|
||||
parse(<<16#72, V:32/float, _/binary>>, B) -> {{float, V}, B+5};
|
||||
parse(<<16#73, Utf32:4/binary,_/binary>>, B) -> {{char, Utf32}, B+5};
|
||||
parse(<<16#73, V:32, _/binary>>, B) -> {{char, V}, B+5};
|
||||
parse(<<?CODE_ULONG, V:64/unsigned, _/binary>>, B) -> {{ulong, V},B+9};
|
||||
parse(<<16#81, V:64/signed, _/binary>>, B) -> {{long, V}, B+9};
|
||||
parse(<<16#82, V:64/float, _/binary>>, B) -> {{double, V}, B+9};
|
||||
|
@ -110,15 +109,6 @@ parse(<<16#94, V:128, _/binary>>, B) ->
|
|||
parse(<<Type, _/binary>>, B) ->
|
||||
throw({primitive_type_unsupported, Type, {position, B}}).
|
||||
|
||||
parse_array_primitive(16#40, <<_:8/unsigned, _/binary>>) -> {null, 1};
|
||||
parse_array_primitive(16#41, <<_:8/unsigned, _/binary>>) -> {true, 1};
|
||||
parse_array_primitive(16#42, <<_:8/unsigned, _/binary>>) -> {false, 1};
|
||||
parse_array_primitive(16#43, <<_:8/unsigned, _/binary>>) -> {{uint, 0}, 1};
|
||||
parse_array_primitive(16#44, <<_:8/unsigned, _/binary>>) -> {{ulong, 0}, 1};
|
||||
parse_array_primitive(ElementType, Data) ->
|
||||
{Val, B} = parse(<<ElementType, Data/binary>>),
|
||||
{Val, B-1}.
|
||||
|
||||
%% array structure is {array, Ctor, [Data]}
|
||||
%% e.g. {array, symbol, [<<"amqp:accepted:list">>]}
|
||||
parse_array(UnitSize, Bin) ->
|
||||
|
@ -150,7 +140,9 @@ parse_array2(Count, Type, Bin, Acc) ->
|
|||
|
||||
parse_constructor(?CODE_SYM_8) -> symbol;
|
||||
parse_constructor(?CODE_SYM_32) -> symbol;
|
||||
parse_constructor(16#a0) -> binary;
|
||||
parse_constructor(16#a1) -> utf8;
|
||||
parse_constructor(16#b0) -> binary;
|
||||
parse_constructor(16#b1) -> utf8;
|
||||
parse_constructor(16#50) -> ubyte;
|
||||
parse_constructor(16#51) -> byte;
|
||||
|
@ -158,15 +150,29 @@ parse_constructor(16#60) -> ushort;
|
|||
parse_constructor(16#61) -> short;
|
||||
parse_constructor(16#70) -> uint;
|
||||
parse_constructor(16#71) -> int;
|
||||
parse_constructor(16#72) -> float;
|
||||
parse_constructor(16#73) -> char;
|
||||
parse_constructor(16#82) -> double;
|
||||
parse_constructor(?CODE_ULONG) -> ulong;
|
||||
parse_constructor(16#81) -> long;
|
||||
parse_constructor(16#40) -> null;
|
||||
parse_constructor(16#56) -> boolean;
|
||||
parse_constructor(16#f0) -> array;
|
||||
parse_constructor(16#83) -> timestamp;
|
||||
parse_constructor(16#98) -> uuid;
|
||||
parse_constructor(0) -> described; %%TODO add test with descriptor in constructor
|
||||
parse_constructor(X) ->
|
||||
exit({failed_to_parse_constructor, X}).
|
||||
|
||||
parse_array_primitive(16#40, <<_:8/unsigned, _/binary>>) -> {null, 1};
|
||||
parse_array_primitive(16#41, <<_:8/unsigned, _/binary>>) -> {true, 1};
|
||||
parse_array_primitive(16#42, <<_:8/unsigned, _/binary>>) -> {false, 1};
|
||||
parse_array_primitive(16#43, <<_:8/unsigned, _/binary>>) -> {{uint, 0}, 1};
|
||||
parse_array_primitive(16#44, <<_:8/unsigned, _/binary>>) -> {{ulong, 0}, 1};
|
||||
parse_array_primitive(ElementType, Data) ->
|
||||
{Val, B} = parse(<<ElementType, Data/binary>>),
|
||||
{Val, B-1}.
|
||||
|
||||
mapify([]) ->
|
||||
[];
|
||||
mapify([Key, Value | Rest]) ->
|
||||
|
@ -220,8 +226,8 @@ pm(<<16#c1, S:8,CountAndValue:S/binary,R/binary>>, O, B) ->
|
|||
%% We avoid guard tests: they improve readability, but result in worse performance.
|
||||
%%
|
||||
%% In server mode:
|
||||
%% * stop when we reach the message body (data or amqp-sequence or amqp-value section).
|
||||
%% * include number of bytes left for properties and application-properties sections.
|
||||
%% * Stop when we reach the message body (data or amqp-sequence or amqp-value section).
|
||||
%% * Include byte positions for parsed bare message sections.
|
||||
pm(<<?DESCRIBED, ?CODE_SMALL_ULONG, ?DESCRIPTOR_CODE_DATA, _Rest/binary>>, true, B) ->
|
||||
reached_body(B, ?DESCRIPTOR_CODE_DATA);
|
||||
pm(<<?DESCRIBED, ?CODE_SMALL_ULONG, ?DESCRIPTOR_CODE_AMQP_SEQUENCE, _Rest/binary>>, true, B) ->
|
||||
|
@ -288,7 +294,7 @@ pm(<<16#60, V:16/unsigned, R/binary>>, O, B) -> [{ushort, V} | pm(R, O, B+3)];
|
|||
pm(<<16#61, V:16/signed, R/binary>>, O, B) -> [{short, V} | pm(R, O, B+3)];
|
||||
pm(<<16#71, V:32/signed, R/binary>>, O, B) -> [{int, V} | pm(R, O, B+5)];
|
||||
pm(<<16#72, V:32/float, R/binary>>, O, B) -> [{float, V} | pm(R, O, B+5)];
|
||||
pm(<<16#73, Utf32:4/binary,R/binary>>, O, B) -> [{char, Utf32} | pm(R, O, B+5)];
|
||||
pm(<<16#73, V:32, R/binary>>, O, B) -> [{char, V} | pm(R, O, B+5)];
|
||||
pm(<<16#81, V:64/signed, R/binary>>, O, B) -> [{long, V} | pm(R, O, B+9)];
|
||||
pm(<<16#82, V:64/float, R/binary>>, O, B) -> [{double, V} | pm(R, O, B+9)];
|
||||
pm(<<16#83, TS:64/signed, R/binary>>, O, B) -> [{timestamp, TS} | pm(R, O, B+9)];
|
||||
|
|
|
@ -113,7 +113,7 @@ utf8(_Config) ->
|
|||
ok.
|
||||
|
||||
char(_Config) ->
|
||||
roundtrip({char, <<$A/utf32>>}),
|
||||
roundtrip({char, $🎉}),
|
||||
ok.
|
||||
|
||||
list(_Config) ->
|
||||
|
|
|
@ -0,0 +1,436 @@
|
|||
%% This Source Code Form is subject to the terms of the Mozilla Public
|
||||
%% License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
%%
|
||||
%% Copyright (c) 2007-2024 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved.
|
||||
|
||||
-module(prop_SUITE).
|
||||
-compile([export_all, nowarn_export_all]).
|
||||
|
||||
-include_lib("proper/include/proper.hrl").
|
||||
-include("amqp10_framing.hrl").
|
||||
|
||||
-import(rabbit_ct_proper_helpers, [run_proper/3]).
|
||||
|
||||
all() ->
|
||||
[{group, tests}].
|
||||
|
||||
groups() ->
|
||||
[
|
||||
{tests, [parallel],
|
||||
[
|
||||
prop_single_primitive_type_parse,
|
||||
prop_single_primitive_type_parse_many,
|
||||
prop_many_primitive_types_parse,
|
||||
prop_many_primitive_types_parse_many,
|
||||
prop_annotated_message,
|
||||
prop_server_mode_body,
|
||||
prop_server_mode_bare_message
|
||||
]}
|
||||
].
|
||||
|
||||
%%%%%%%%%%%%%%%%%%
|
||||
%%% Properties %%%
|
||||
%%%%%%%%%%%%%%%%%%
|
||||
|
||||
prop_single_primitive_type_parse(_Config) ->
|
||||
run_proper(
|
||||
fun() -> ?FORALL(Val,
|
||||
oneof(primitive_types()),
|
||||
begin
|
||||
Bin = iolist_to_binary(amqp10_binary_generator:generate(Val)),
|
||||
equals({Val, size(Bin)}, amqp10_binary_parser:parse(Bin))
|
||||
end)
|
||||
end, [], 10_000).
|
||||
|
||||
prop_single_primitive_type_parse_many(_Config) ->
|
||||
run_proper(
|
||||
fun() -> ?FORALL(Val,
|
||||
oneof(primitive_types()),
|
||||
begin
|
||||
Bin = iolist_to_binary(amqp10_binary_generator:generate(Val)),
|
||||
equals([Val], amqp10_binary_parser:parse_many(Bin, []))
|
||||
end)
|
||||
end, [], 10_000).
|
||||
|
||||
prop_many_primitive_types_parse(_Config) ->
|
||||
run_proper(
|
||||
fun() -> ?FORALL(Vals,
|
||||
list(oneof(primitive_types())),
|
||||
begin
|
||||
Bin = iolist_to_binary([amqp10_binary_generator:generate(V) || V <- Vals]),
|
||||
PosValList = parse(Bin, 0, []),
|
||||
equals(Vals, [Val || {_Pos, Val} <- PosValList])
|
||||
end)
|
||||
end, [], 1000).
|
||||
|
||||
prop_many_primitive_types_parse_many(_Config) ->
|
||||
run_proper(
|
||||
fun() -> ?FORALL(Vals,
|
||||
list(oneof(primitive_types())),
|
||||
begin
|
||||
Bin = iolist_to_binary([amqp10_binary_generator:generate(V) || V <- Vals]),
|
||||
equals(Vals, amqp10_binary_parser:parse_many(Bin, []))
|
||||
end)
|
||||
end, [], 1000).
|
||||
|
||||
prop_annotated_message(_Config) ->
|
||||
run_proper(
|
||||
fun() -> ?FORALL(Sections,
|
||||
annotated_message(),
|
||||
begin
|
||||
Bin = iolist_to_binary([amqp10_framing:encode_bin(S) || S <- Sections]),
|
||||
equals(Sections, amqp10_framing:decode_bin(Bin))
|
||||
end)
|
||||
end, [], 1000).
|
||||
|
||||
prop_server_mode_body(_Config) ->
|
||||
run_proper(
|
||||
fun() -> ?FORALL(Sections,
|
||||
annotated_message(),
|
||||
begin
|
||||
{value,
|
||||
FirstBodySection} = lists:search(
|
||||
fun(#'v1_0.data'{}) -> true;
|
||||
(#'v1_0.amqp_sequence'{}) -> true;
|
||||
(#'v1_0.amqp_value'{}) -> true;
|
||||
(_) -> false
|
||||
end, Sections),
|
||||
|
||||
Bin = iolist_to_binary([amqp10_framing:encode_bin(S) || S <- Sections]),
|
||||
%% Invariant 1: Decoder should us return the correct
|
||||
%% byte position of the first body section.
|
||||
Decoded = amqp10_framing:decode_bin(Bin, [server_mode]),
|
||||
{value,
|
||||
{{pos, Pos},
|
||||
{body, Code}}} = lists:search(fun(({{pos, _Pos}, {body, _Code}})) ->
|
||||
true;
|
||||
(_) ->
|
||||
false
|
||||
end, Decoded),
|
||||
FirstBodySectionBin = binary_part(Bin, Pos, size(Bin) - Pos),
|
||||
{Section, _NumBytes} = amqp10_binary_parser:parse(FirstBodySectionBin),
|
||||
%% Invariant 2: Decoder should have returned the
|
||||
%% correct descriptor code of the first body section.
|
||||
{described, {ulong, Code}, _Val} = Section,
|
||||
equals(FirstBodySection, amqp10_framing:decode(Section))
|
||||
end)
|
||||
end, [], 1000).
|
||||
|
||||
prop_server_mode_bare_message(_Config) ->
|
||||
run_proper(
|
||||
fun() -> ?FORALL(Sections,
|
||||
annotated_message(),
|
||||
begin
|
||||
{value,
|
||||
FirstBareMsgSection} = lists:search(
|
||||
fun(#'v1_0.properties'{}) -> true;
|
||||
(#'v1_0.application_properties'{}) -> true;
|
||||
(#'v1_0.data'{}) -> true;
|
||||
(#'v1_0.amqp_sequence'{}) -> true;
|
||||
(#'v1_0.amqp_value'{}) -> true;
|
||||
(_) -> false
|
||||
end, Sections),
|
||||
|
||||
Bin = iolist_to_binary([amqp10_framing:encode_bin(S) || S <- Sections]),
|
||||
%% Invariant: Decoder should us return the correct
|
||||
%% byte position of the first bare message section.
|
||||
Decoded = amqp10_framing:decode_bin(Bin, [server_mode]),
|
||||
{value,
|
||||
{{pos, Pos}, _Sect}} = lists:search(fun(({{pos, _Pos}, _Sect})) ->
|
||||
true;
|
||||
(_) ->
|
||||
false
|
||||
end, Decoded),
|
||||
FirstBareMsgSectionBin = binary_part(Bin, Pos, size(Bin) - Pos),
|
||||
{Section, _NumBytes} = amqp10_binary_parser:parse(FirstBareMsgSectionBin),
|
||||
equals(FirstBareMsgSection, amqp10_framing:decode(Section))
|
||||
end)
|
||||
end, [], 1000).
|
||||
|
||||
%%%%%%%%%%%%%%%
|
||||
%%% Helpers %%%
|
||||
%%%%%%%%%%%%%%%
|
||||
|
||||
parse(Bin, Parsed, PosVal)
|
||||
when size(Bin) =:= Parsed ->
|
||||
lists:reverse(PosVal);
|
||||
parse(Bin, Parsed, PosVal)
|
||||
when size(Bin) > Parsed ->
|
||||
BinPart = binary_part(Bin, Parsed, size(Bin) - Parsed),
|
||||
{Val, NumBytes} = amqp10_binary_parser:parse(BinPart),
|
||||
parse(Bin, Parsed + NumBytes, [{Parsed, Val} | PosVal]).
|
||||
|
||||
%%%%%%%%%%%%%%%%%%
|
||||
%%% Generators %%%
|
||||
%%%%%%%%%%%%%%%%%%
|
||||
|
||||
primitive_types() ->
|
||||
fixed_and_variable_width_types() ++
|
||||
compound_types() ++
|
||||
[amqp_array()].
|
||||
|
||||
fixed_and_variable_width_types() ->
|
||||
[
|
||||
amqp_null(),
|
||||
amqp_boolean(),
|
||||
amqp_ubyte(),
|
||||
amqp_ushort(),
|
||||
amqp_uint(),
|
||||
amqp_ulong(),
|
||||
amqp_byte(),
|
||||
amqp_short(),
|
||||
amqp_int(),
|
||||
amqp_long(),
|
||||
amqp_float(),
|
||||
amqp_double(),
|
||||
amqp_char(),
|
||||
amqp_timestamp(),
|
||||
amqp_uuid(),
|
||||
amqp_binary(),
|
||||
amqp_string(),
|
||||
amqp_symbol()
|
||||
].
|
||||
|
||||
compound_types() ->
|
||||
[
|
||||
amqp_list(),
|
||||
amqp_map()
|
||||
].
|
||||
|
||||
amqp_null() ->
|
||||
null.
|
||||
|
||||
amqp_boolean() ->
|
||||
boolean().
|
||||
|
||||
amqp_ubyte() ->
|
||||
{ubyte, integer(0, 16#ff)}.
|
||||
|
||||
amqp_ushort() ->
|
||||
{ushort, integer(0, 16#ff_ff)}.
|
||||
|
||||
amqp_uint() ->
|
||||
Lim = 16#ff_ff_ff_ff,
|
||||
{uint, oneof([
|
||||
integer(0, Lim),
|
||||
?SIZED(Size, resize(Size * 100, integer(0, Lim)))
|
||||
])}.
|
||||
|
||||
amqp_ulong() ->
|
||||
Lim = 16#ff_ff_ff_ff_ff_ff_ff_ff,
|
||||
{ulong, oneof([
|
||||
integer(0, Lim),
|
||||
?SIZED(Size, resize(Size * 100_000, integer(0, Lim)))
|
||||
])}.
|
||||
|
||||
amqp_byte() ->
|
||||
Lim = 16#ff div 2,
|
||||
{byte, integer(-Lim - 1, Lim)}.
|
||||
|
||||
amqp_short() ->
|
||||
Lim = 16#ff_ff div 2,
|
||||
{short, integer(-Lim - 1, Lim)}.
|
||||
|
||||
amqp_int() ->
|
||||
Lim = 16#ff_ff_ff_ff div 2,
|
||||
{int, oneof([
|
||||
integer(-Lim - 1, Lim),
|
||||
?SIZED(Size, resize(Size * 100, integer(-Lim - 1, Lim)))
|
||||
])}.
|
||||
|
||||
amqp_long() ->
|
||||
Lim = 16#ff_ff_ff_ff_ff_ff_ff_ff div 2,
|
||||
{long, oneof([
|
||||
integer(-Lim - 1, Lim),
|
||||
?SIZED(Size, resize(Size * 100, integer(-Lim - 1, Lim)))
|
||||
])}.
|
||||
|
||||
%% AMQP float is 32-bit whereas Erlang float is 64-bit.
|
||||
%% Therefore, 32-bit encoding any Erlang float will lose precision.
|
||||
%% Hence, we use some static floats where we know that they can be represented precisely using 32 bits.
|
||||
amqp_float() ->
|
||||
{float, oneof([-1.5, -1.0, 0.0, 1.0, 1.5, 100.0])}.
|
||||
|
||||
%% AMQP double and Erlang float are both 64-bit.
|
||||
amqp_double() ->
|
||||
{double, float()}.
|
||||
|
||||
amqp_char() ->
|
||||
{char, char()}.
|
||||
|
||||
amqp_timestamp() ->
|
||||
Now = erlang:system_time(millisecond),
|
||||
YearMillis = 1000 * 60 * 60 * 24 * 365,
|
||||
TimestampMillis1950 = -631_152_000_000,
|
||||
TimestampMillis2200 = 7_258_118_400_000,
|
||||
{timestamp, oneof([integer(Now - YearMillis, Now + YearMillis),
|
||||
integer(TimestampMillis1950, TimestampMillis2200)
|
||||
])}.
|
||||
|
||||
amqp_uuid() ->
|
||||
{uuid, binary(16)}.
|
||||
|
||||
amqp_binary() ->
|
||||
{binary, oneof([
|
||||
binary(),
|
||||
?SIZED(Size, resize(Size * 10, binary()))
|
||||
])}.
|
||||
|
||||
amqp_string() ->
|
||||
{utf8, utf8()}.
|
||||
|
||||
amqp_symbol() ->
|
||||
{symbol, ?LET(L,
|
||||
?SIZED(Size, resize(Size * 10, list(ascii_char()))),
|
||||
list_to_binary(L))}.
|
||||
|
||||
ascii_char() ->
|
||||
integer(0, 127).
|
||||
|
||||
amqp_list() ->
|
||||
{list, list(prefer_simple_type())}.
|
||||
|
||||
amqp_map() ->
|
||||
{map, ?LET(KvList,
|
||||
list({prefer_simple_type(),
|
||||
prefer_simple_type()}),
|
||||
lists:uniq(fun({K, _V}) -> K end, KvList)
|
||||
)}.
|
||||
|
||||
amqp_array() ->
|
||||
Gens = fixed_and_variable_width_types(),
|
||||
?LET(N,
|
||||
integer(1, length(Gens)),
|
||||
begin
|
||||
Gen = lists:nth(N, Gens),
|
||||
?LET(Instance,
|
||||
Gen,
|
||||
begin
|
||||
Constructor = case Instance of
|
||||
{T, _V} -> T;
|
||||
null -> null;
|
||||
V when is_boolean(V) -> boolean
|
||||
end,
|
||||
{array, Constructor, list(Gen)}
|
||||
end)
|
||||
end).
|
||||
|
||||
prefer_simple_type() ->
|
||||
frequency([{200, oneof(fixed_and_variable_width_types())},
|
||||
{1, ?LAZY(oneof(compound_types()))},
|
||||
{1, ?LAZY(amqp_array())}
|
||||
]).
|
||||
|
||||
zero_or_one(Section) ->
|
||||
oneof([
|
||||
[],
|
||||
[Section]
|
||||
]).
|
||||
|
||||
optional(Field) ->
|
||||
oneof([
|
||||
undefined,
|
||||
Field
|
||||
]).
|
||||
|
||||
annotated_message() ->
|
||||
?LET(H,
|
||||
zero_or_one(header_section()),
|
||||
?LET(DA,
|
||||
zero_or_one(delivery_annotation_section()),
|
||||
?LET(MA,
|
||||
zero_or_one(message_annotation_section()),
|
||||
?LET(P,
|
||||
zero_or_one(properties_section()),
|
||||
?LET(AP,
|
||||
zero_or_one(application_properties_section()),
|
||||
?LET(B,
|
||||
body(),
|
||||
?LET(F,
|
||||
zero_or_one(footer_section()),
|
||||
lists:append([H, DA, MA, P, AP, B, F])
|
||||
))))))).
|
||||
|
||||
%% "The body consists of one of the following three choices: one or more data sections,
|
||||
%% one or more amqp-sequence sections, or a single amqp-value section." [§3.2]
|
||||
body() ->
|
||||
oneof([
|
||||
non_empty(list(data_section())),
|
||||
non_empty(list(amqp_sequence_section())),
|
||||
[amqp_value_section()]
|
||||
]).
|
||||
|
||||
header_section() ->
|
||||
#'v1_0.header'{
|
||||
durable = optional(amqp_boolean()),
|
||||
priority = optional(amqp_ubyte()),
|
||||
ttl = optional(milliseconds()),
|
||||
first_acquirer = optional(amqp_boolean()),
|
||||
delivery_count = optional(amqp_uint())}.
|
||||
|
||||
delivery_annotation_section() ->
|
||||
#'v1_0.delivery_annotations'{content = annotations()}.
|
||||
|
||||
message_annotation_section() ->
|
||||
#'v1_0.message_annotations'{content = annotations()}.
|
||||
|
||||
properties_section() ->
|
||||
#'v1_0.properties'{
|
||||
message_id = optional(message_id()),
|
||||
user_id = optional(amqp_binary()),
|
||||
to = optional(address_string()),
|
||||
subject = optional(amqp_string()),
|
||||
reply_to = optional(address_string()),
|
||||
correlation_id = optional(message_id()),
|
||||
content_type = optional(amqp_symbol()),
|
||||
content_encoding = optional(amqp_symbol()),
|
||||
absolute_expiry_time = optional(amqp_timestamp()),
|
||||
creation_time = optional(amqp_timestamp()),
|
||||
group_id = optional(amqp_string()),
|
||||
group_sequence = optional(sequence_no()),
|
||||
reply_to_group_id = optional(amqp_string())}.
|
||||
|
||||
application_properties_section() ->
|
||||
Gen = ?LET(KvList,
|
||||
list({amqp_string(),
|
||||
oneof(fixed_and_variable_width_types() -- [amqp_null()])}),
|
||||
lists:uniq(fun({K, _V}) -> K end, KvList)),
|
||||
#'v1_0.application_properties'{content = Gen}.
|
||||
|
||||
data_section() ->
|
||||
#'v1_0.data'{content = binary()}.
|
||||
|
||||
amqp_sequence_section() ->
|
||||
#'v1_0.amqp_sequence'{content = list(oneof(primitive_types() -- [amqp_null()]))}.
|
||||
|
||||
amqp_value_section() ->
|
||||
#'v1_0.amqp_value'{content = oneof(primitive_types())}.
|
||||
|
||||
footer_section() ->
|
||||
#'v1_0.footer'{content = annotations()}.
|
||||
|
||||
annotations() ->
|
||||
?LET(KvList,
|
||||
list({oneof([amqp_symbol(),
|
||||
amqp_ulong()]),
|
||||
prefer_simple_type()}),
|
||||
begin
|
||||
KvList1 = lists:uniq(fun({K, _V}) -> K end, KvList),
|
||||
lists:filter(fun({_K, V}) -> V =/= null end, KvList1)
|
||||
end).
|
||||
|
||||
sequence_no() ->
|
||||
amqp_uint().
|
||||
|
||||
milliseconds() ->
|
||||
amqp_uint().
|
||||
|
||||
message_id() ->
|
||||
oneof([amqp_ulong(),
|
||||
amqp_uuid(),
|
||||
amqp_binary(),
|
||||
amqp_string()]).
|
||||
|
||||
address_string() ->
|
||||
amqp_string().
|
|
@ -3656,8 +3656,8 @@ footer_checksum(FooterOpt, Config) ->
|
|||
#'v1_0.data'{content = <<"m6 b">>},
|
||||
#'v1_0.footer'{
|
||||
content = [
|
||||
{{symbol, <<"x-opt-rabbit">>}, {char, unicode:characters_to_binary("🐇", utf8, utf32)}},
|
||||
{{symbol, <<"x-opt-carrot">>}, {char, unicode:characters_to_binary("🥕", utf8, utf32)}}
|
||||
{{symbol, <<"x-opt-rabbit">>}, {char, $🐇}},
|
||||
{{symbol, <<"x-opt-carrot">>}, {char, $🥕}}
|
||||
]}]),
|
||||
ok = amqp10_client:send_msg(Sender, M6),
|
||||
ok = wait_for_settlement(<<"t6">>),
|
||||
|
@ -3665,8 +3665,8 @@ footer_checksum(FooterOpt, Config) ->
|
|||
{ok, Msg6} = amqp10_client:get_msg(Receiver),
|
||||
?assertEqual([<<"m6 a">>, <<"m6 b">>], amqp10_msg:body(Msg6)),
|
||||
?assertMatch(#{ExpectedKey := _,
|
||||
<<"x-opt-rabbit">> := <<"🐇"/utf32>>,
|
||||
<<"x-opt-carrot">> := <<"🥕"/utf32>>},
|
||||
<<"x-opt-rabbit">> := $🐇,
|
||||
<<"x-opt-carrot">> := $🥕},
|
||||
amqp10_msg:footer(Msg6)),
|
||||
|
||||
%% We only sanity check here that the footer annotation we received from the server
|
||||
|
|
Loading…
Reference in New Issue