From 96537a2b56e4805f2ba96aae0e30362ee84e315a Mon Sep 17 00:00:00 2001 From: Sergey Prokhorov Date: Tue, 1 Oct 2019 01:44:07 +0200 Subject: [PATCH 1/4] Add proper tests for some types --- rebar.config | 8 +++++++ src/pg_date.erl | 23 ++++++++++++++++-- src/pg_uuid.erl | 2 +- test/prop_boolean.erl | 7 ++++++ test/prop_date.erl | 6 +++++ test/prop_hstore.erl | 39 ++++++++++++++++++++++++++++++ test/prop_inet.erl | 30 +++++++++++++++++++++++ test/prop_int.erl | 14 +++++++++++ test/prop_json.erl | 53 +++++++++++++++++++++++++++++++++++++++++ test/prop_time.erl | 6 +++++ test/prop_timestamp.erl | 13 ++++++++++ test/prop_uuid.erl | 34 ++++++++++++++++++++++++++ test/proper_lib.erl | 48 +++++++++++++++++++++++++++++++++++++ 13 files changed, 280 insertions(+), 3 deletions(-) create mode 100644 test/prop_boolean.erl create mode 100644 test/prop_date.erl create mode 100644 test/prop_hstore.erl create mode 100644 test/prop_inet.erl create mode 100644 test/prop_int.erl create mode 100644 test/prop_json.erl create mode 100644 test/prop_time.erl create mode 100644 test/prop_timestamp.erl create mode 100644 test/prop_uuid.erl create mode 100644 test/proper_lib.erl diff --git a/rebar.config b/rebar.config index a0e58e5..0c23534 100644 --- a/rebar.config +++ b/rebar.config @@ -6,3 +6,11 @@ {pg_codec, decode_text, 4}, {pg_codec, encode, 4}, {pg_codec, init_mods, 2}]}. + +%% `rebar3 as test proper` +{profiles, + [{test, + [{deps, [{proper, "1.3.0"}]}, + {plugins, [rebar3_proper]} + ]}] +}. diff --git a/src/pg_date.erl b/src/pg_date.erl index 1277c54..f3cc5af 100644 --- a/src/pg_date.erl +++ b/src/pg_date.erl @@ -14,10 +14,29 @@ init(_Opts) -> {[<<"date_send">>], []}. encode(Date, _TypeInfo) -> - [<<4:?int32>>, encode_date(Date)]. + [<<4:?int32>>, <<(encode_date(Date) - ?POSTGRESQL_GD_EPOCH):?int32>>]. decode(<>, _TypeInfo) -> - calendar:gregorian_days_to_date(Date + ?POSTGRESQL_GD_EPOCH). + decode_date(Date + ?POSTGRESQL_GD_EPOCH). + +%% Julian <-> Gregorian +decode_date(N) -> + J = N + 32044, + Q1 = J div 146097, + Extra = (J - Q1 * 146097) * 4 + 3, + J2 = J + 60 + Q1 * 3 + Extra div 146097, + Q2 = J2 div 1461, + J3 = J2 - Q2 * 1461, + Y = J3 * 4 div 1461, + J4 = case Y of + 0 -> ((J3 + 306) rem 366) + 123; + _ -> ((J3 + 305) rem 365) + 123 + end, + Year = (Y + Q2 * 4) - 4800, + Q3 = J4 * 2141 div 65536, + Day = J4 - 7834 * Q3 div 256, + Month = (Q3 + 10) rem 12 + 1, + {Year, Month, Day}. encode_date({Y, M, D}) -> M2 = case M > 2 of diff --git a/src/pg_uuid.erl b/src/pg_uuid.erl index 51a247e..4feaec5 100644 --- a/src/pg_uuid.erl +++ b/src/pg_uuid.erl @@ -40,5 +40,5 @@ decode(Uuid, #type_info{config=binary}) -> decode(<>, #type_info{config=string}) -> Format = "~8.16.0b-~4.16.0b-~4.16.0b-~4.16.0b-~12.16.0b", iolist_to_binary(io_lib:format(Format, [U0, U1, U2, U3, U4])); -decode(<>, #type_info{config=integer}) -> +decode(<>, #type_info{config=integer}) -> Uuid. diff --git a/test/prop_boolean.erl b/test/prop_boolean.erl new file mode 100644 index 0000000..e805d22 --- /dev/null +++ b/test/prop_boolean.erl @@ -0,0 +1,7 @@ +-module(prop_boolean). +-include_lib("proper/include/proper.hrl"). + + +prop_codec() -> + ?FORALL(Val, proper_types:boolean(), + proper_lib:codec(pg_boolean, [], Val)). diff --git a/test/prop_date.erl b/test/prop_date.erl new file mode 100644 index 0000000..df8a367 --- /dev/null +++ b/test/prop_date.erl @@ -0,0 +1,6 @@ +-module(prop_date). +-include_lib("proper/include/proper.hrl"). + +prop_codec() -> + ?FORALL(Val, proper_lib:date_gen(), + proper_lib:codec(pg_date, [], Val)). diff --git a/test/prop_hstore.erl b/test/prop_hstore.erl new file mode 100644 index 0000000..b751e30 --- /dev/null +++ b/test/prop_hstore.erl @@ -0,0 +1,39 @@ +-module(prop_hstore). +-include_lib("proper/include/proper.hrl"). + +prop_binary_codec() -> + ?FORALL(Val, bin_map(), + proper_lib:codec(pg_hstore, [], Val)). + +bin_map() -> + ?LET(KV, + proper_types:list( + {proper_types:binary(), + proper_types:binary()} + ), + maps:from_list(KV)). + +prop_strkey_codec() -> + ?FORALL(Val, strkey_map(), + proper_lib:codec(pg_hstore, [], Val, fun canonical_keys/1)). + +strkey_map() -> + ?LET(KV, + proper_types:list( + {proper_types:oneof( + [proper_types:atom(), + proper_types:list(proper_types:byte()), + proper_types:binary()]), + proper_types:binary()} + ), + maps:from_list(KV)). + +canonical_keys(Map) -> + maps:fold( + fun(K, V, Acc) when is_list(K) -> + Acc#{list_to_binary(K) => V}; + (K, V, Acc) when is_atom(K) -> + Acc#{atom_to_binary(K, utf8) => V}; + (K, V, Acc) -> + Acc#{K => V} + end, #{}, Map). diff --git a/test/prop_inet.erl b/test/prop_inet.erl new file mode 100644 index 0000000..178f359 --- /dev/null +++ b/test/prop_inet.erl @@ -0,0 +1,30 @@ +-module(prop_inet). +-include_lib("proper/include/proper.hrl"). + +prop_ip4_codec() -> + ?FORALL(Val, ipv4(), + proper_lib:codec(pg_inet, [], Val)). + +prop_ip6_codec() -> + ?FORALL(Val, ipv6(), + proper_lib:codec(pg_inet, [], Val)). + +prop_ip4_cidr_codec() -> + ?FORALL(Val, {ipv4(), proper_types:integer(0, 32)}, + proper_lib:codec(pg_inet, [], Val)). + +prop_ip6_cidr_codec() -> + ?FORALL(Val, {ipv6(), proper_types:integer(4, 128)}, + proper_lib:codec(pg_inet, [], Val)). + + +ipv4() -> + {proper_types:byte(), + proper_types:byte(), + proper_types:byte(), + proper_types:byte()}. + +ipv6() -> + E = proper_types:integer(0, 65535), + {E, E, E, E, + E, E, E, E}. diff --git a/test/prop_int.erl b/test/prop_int.erl new file mode 100644 index 0000000..5b9429f --- /dev/null +++ b/test/prop_int.erl @@ -0,0 +1,14 @@ +-module(prop_int). +-include_lib("proper/include/proper.hrl"). + +prop_int2_codec() -> + ?FORALL(Val, proper_lib:int16(), + proper_lib:codec(pg_int2, [], Val)). + +prop_int4_codec() -> + ?FORALL(Val, proper_lib:int32(), + proper_lib:codec(pg_int4, [], Val)). + +prop_int8_codec() -> + ?FORALL(Val, proper_lib:int64(), + proper_lib:codec(pg_int8, [], Val)). diff --git a/test/prop_json.erl b/test/prop_json.erl new file mode 100644 index 0000000..33a136c --- /dev/null +++ b/test/prop_json.erl @@ -0,0 +1,53 @@ +-module(prop_json). +-include_lib("proper/include/proper.hrl"). + +%% Fake JSON encoder +-export([decode/2, encode/2]). + +prop_json_codec() -> + ?FORALL(Val, json_str(), + proper_lib:codec(pg_json, [], Val)). + +prop_jsonb_codec() -> + ?FORALL(Val, json_str(), + proper_lib:codec(pg_jsonb, [], Val)). + +prop_custom_json_codec() -> + Config = #{json_config => {?MODULE, [compressed], []}}, + ?FORALL(Val, json_struct(), + proper_lib:codec(pg_json, Config, Val)). + +prop_custom_jsonb_codec() -> + Config = #{json_config => {?MODULE, [compressed], []}}, + ?FORALL(Val, json_struct(), + proper_lib:codec(pg_jsonb, Config, Val)). + + +%% Fake JSON codec +decode(Bin, _) -> + binary_to_term(Bin). + +encode(Term, Opts) -> + term_to_binary(Term, Opts). + +%% Generators + +json_str() -> + proper_types:binary(). + + +json_struct() -> + ?LET(KV, + proper_types:list({j_string(), j_literal()}), + maps:from_list(KV)). + +j_string() -> + proper_types:string(). + +j_literal() -> + proper_types:oneof( + [j_string(), + proper_types:integer(1, 10), + proper_types:float(), + proper_types:boolean(), + null]). diff --git a/test/prop_time.erl b/test/prop_time.erl new file mode 100644 index 0000000..cfdb375 --- /dev/null +++ b/test/prop_time.erl @@ -0,0 +1,6 @@ +-module(prop_time). +-include_lib("proper/include/proper.hrl"). + +prop_int_sec_codec() -> + ?FORALL(Val, proper_lib:int_time_gen(), + proper_lib:codec(pg_time, [], Val)). diff --git a/test/prop_timestamp.erl b/test/prop_timestamp.erl new file mode 100644 index 0000000..55e9b2e --- /dev/null +++ b/test/prop_timestamp.erl @@ -0,0 +1,13 @@ +-module(prop_timestamp). +-include_lib("proper/include/proper.hrl"). + +prop_int_sec_codec() -> + ?FORALL(Val, int_timestamp(), + proper_lib:codec(pg_timestamp, [], Val)). + +prop_tz_int_sec_codec() -> + ?FORALL(Val, int_timestamp(), + proper_lib:codec(pg_timestampz, [], Val)). + +int_timestamp() -> + {proper_lib:date_gen(), proper_lib:int_time_gen()}. diff --git a/test/prop_uuid.erl b/test/prop_uuid.erl new file mode 100644 index 0000000..faa7679 --- /dev/null +++ b/test/prop_uuid.erl @@ -0,0 +1,34 @@ +-module(prop_uuid). +-include_lib("proper/include/proper.hrl"). + +prop_binary_codec() -> + ?FORALL(Val, proper_types:binary(16), + proper_lib:codec(pg_uuid, #{uuid_format => binary}, Val)). + +prop_string_codec() -> + ?FORALL(Val, proper_types:binary(16), + proper_lib:codec( + pg_uuid, #{uuid_format => string}, + lists:flatten(strfmt(Val)), + fun canonic_str/1)). + +prop_bin_string_codec() -> + ?FORALL(Val, proper_types:binary(16), + proper_lib:codec( + pg_uuid, #{uuid_format => string}, + iolist_to_binary(strfmt(Val)), + fun canonic_str/1)). + +prop_integer_codec() -> + ?FORALL(Val, proper_types:integer( + -170141183460469231731687303715884105728, + 170141183460469231731687303715884105728), + proper_lib:codec(pg_uuid, #{uuid_format => integer}, Val)). + + +strfmt(<>) -> + Format = "~8.16.0b-~4.16.0b-~4.16.0b-~4.16.0b-~12.16.0b", + io_lib:format(Format, [U0, U1, U2, U3, U4]). + +canonic_str(Str) when is_list(Str) -> Str; +canonic_str(Bin) when is_binary(Bin) -> binary_to_list(Bin). diff --git a/test/proper_lib.erl b/test/proper_lib.erl new file mode 100644 index 0000000..a1d19e3 --- /dev/null +++ b/test/proper_lib.erl @@ -0,0 +1,48 @@ +-module(proper_lib). + +-export([codec/3, codec/4]). + +-export([int16/0, int32/0, int64/0, + date_gen/0, int_time_gen/0]). + +-include_lib("proper/include/proper.hrl"). +-include_lib("stdlib/include/assert.hrl"). +-include("pg_types.hrl"). + +codec(Mod, Opts, Data) -> + codec(Mod, Opts, Data, fun(V) -> V end). + +codec(Mod, Opts, Data, Canonical) -> + {_, Config} = Mod:init(Opts), + TypeInfo = #type_info{config = Config}, + <> = iolist_to_binary(Mod:encode(Data, TypeInfo)), + ?assertEqual(Canonical(Data), Canonical(Mod:decode(Encoded, TypeInfo)), + {Mod, Opts}), + true. + + +%% +%% Generators +%% + +int16() -> + proper_types:integer(-32768, 32256). + +int32() -> + proper_types:integer(-2147483648, 2130706432). + +int64() -> + proper_types:integer(-9223372036854775808, 9151314442816847872). + +date_gen() -> + ?SUCHTHAT( + Date, + {proper_types:integer(-9999, 9999), + proper_types:integer(1, 12), + proper_types:integer(1, 31)}, + calendar:valid_date(Date)). + +int_time_gen() -> + {proper_types:integer(0, 23), + proper_types:integer(0, 59), + proper_types:integer(0, 59)}. From 994b075afc45331febee901a193520691e33656e Mon Sep 17 00:00:00 2001 From: Sergey Prokhorov Date: Tue, 1 Oct 2019 02:01:30 +0200 Subject: [PATCH 2/4] Add float64 --- test/prop_float.erl | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 test/prop_float.erl diff --git a/test/prop_float.erl b/test/prop_float.erl new file mode 100644 index 0000000..2faff0b --- /dev/null +++ b/test/prop_float.erl @@ -0,0 +1,14 @@ +-module(prop_float). +-include_lib("proper/include/proper.hrl"). + +%% TODO: fix truncation problem +%% prop_float4_codec() -> +%% ?FORALL(Val, proper_types:float(-999.99, 990.99), +%% proper_lib:codec(pg_float4, [], Val, fun canonical_float4/1)). +%% +%% canonical_float4(N) -> +%% round(N * 100). + +prop_float8_codec() -> + ?FORALL(Val, proper_types:float(), + proper_lib:codec(pg_float8, [], Val)). From 893cf66fa7a75c5a8cab75da74773e9455eb2363 Mon Sep 17 00:00:00 2001 From: Sergey Prokhorov Date: Wed, 2 Oct 2019 00:10:53 +0200 Subject: [PATCH 3/4] Add tests for geometry types --- test/prop_geometry.erl | 58 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 test/prop_geometry.erl diff --git a/test/prop_geometry.erl b/test/prop_geometry.erl new file mode 100644 index 0000000..4377a6f --- /dev/null +++ b/test/prop_geometry.erl @@ -0,0 +1,58 @@ +%% https://www.postgresql.org/docs/current/datatype-geometric.html +-module(prop_geometry). +-include_lib("proper/include/proper.hrl"). + +prop_point_codec() -> + ?FORALL(Val, point(), + proper_lib:codec(pg_point, [], Val)). + + +prop_line_codec() -> + ?FORALL(Val, line(), + proper_lib:codec(pg_line, [], Val)). + + +prop_line_segment_codec() -> + ?FORALL({P1, P2}, {point(), point()}, + proper_lib:codec(pg_line_segment, [], + #{point1 => P1, point2 => P2})). + +%% prop_box_codec() -> +%% not_implemented. + +prop_path_codec() -> + ?FORALL({Open, Points}, + {proper_types:boolean(), + proper_types:list(point())}, + proper_lib:codec(pg_path, [], + #{open => Open, points => Points})). + + +prop_polygon_codec() -> + ?FORALL(Vertices, + proper_types:list(point()), + proper_lib:codec(pg_polygon, [], + #{vertices => Vertices})). + +prop_circle_codec() -> + ?FORALL({Center, Radius}, + {point(), coord()}, + proper_lib:codec(pg_circle, [], + #{center => Center, radius => Radius})). +%% +%% Types +%% + +point() -> + ?LET({X, Y}, {coord(), coord()}, + #{x => X, y => Y}). + +line() -> + ?LET({A, B, C}, + {coord(), + coord(), + coord()}, + #{a => A, b => B, c => C}). + +coord() -> + proper_types:float(). From acdd9f744d8f3feed21c6c5a7bfff2888012040f Mon Sep 17 00:00:00 2001 From: Sergey Prokhorov Date: Wed, 2 Oct 2019 01:07:15 +0200 Subject: [PATCH 4/4] More types tests * binaries * macaddr * name * tsvector * float extended --- test/prop_bin.erl | 11 +++++++++++ test/prop_float.erl | 9 ++++++++- test/prop_macaddr.erl | 10 ++++++++++ test/prop_name.erl | 13 +++++++++++++ test/prop_tsvector.erl | 25 +++++++++++++++++++++++++ 5 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 test/prop_bin.erl create mode 100644 test/prop_macaddr.erl create mode 100644 test/prop_name.erl create mode 100644 test/prop_tsvector.erl diff --git a/test/prop_bin.erl b/test/prop_bin.erl new file mode 100644 index 0000000..9117fa2 --- /dev/null +++ b/test/prop_bin.erl @@ -0,0 +1,11 @@ +-module(prop_bin). +-include_lib("proper/include/proper.hrl"). + + +prop_raw_codec() -> + ?FORALL(Val, proper_types:binary(), + proper_lib:codec(pg_raw, [], Val)). + +prop_bitstring_codec() -> + ?FORALL(Val, proper_types:bitstring(), + proper_lib:codec(pg_bit_string, [], Val)). diff --git a/test/prop_float.erl b/test/prop_float.erl index 2faff0b..20f7044 100644 --- a/test/prop_float.erl +++ b/test/prop_float.erl @@ -10,5 +10,12 @@ %% round(N * 100). prop_float8_codec() -> - ?FORALL(Val, proper_types:float(), + ?FORALL(Val, float64(), proper_lib:codec(pg_float8, [], Val)). + +float64() -> + proper_types:frequency( + [{85, proper_types:float()}, + {5, plus_infinity}, + {5, minus_infinity}, + {5, nan}]). diff --git a/test/prop_macaddr.erl b/test/prop_macaddr.erl new file mode 100644 index 0000000..573afab --- /dev/null +++ b/test/prop_macaddr.erl @@ -0,0 +1,10 @@ +-module(prop_macaddr). +-include_lib("proper/include/proper.hrl"). + + +prop_codec() -> + B = proper_types:byte(), + ?FORALL(Val, + {B, B, B, + B, B, B}, + proper_lib:codec(pg_macaddr, [], Val)). diff --git a/test/prop_name.erl b/test/prop_name.erl new file mode 100644 index 0000000..524705c --- /dev/null +++ b/test/prop_name.erl @@ -0,0 +1,13 @@ +-module(prop_name). +-include_lib("proper/include/proper.hrl"). + + +prop_codec() -> + ?FORALL(Val, bin64(), + proper_lib:codec(pg_name, [], Val)). + + +bin64() -> + ?LET(Size, + proper_types:integer(0, 63), + proper_types:binary(Size)). diff --git a/test/prop_tsvector.erl b/test/prop_tsvector.erl new file mode 100644 index 0000000..b893338 --- /dev/null +++ b/test/prop_tsvector.erl @@ -0,0 +1,25 @@ +-module(prop_tsvector). +-include_lib("proper/include/proper.hrl"). + + +prop_codec() -> + ?FORALL(Val, tsvector(), + proper_lib:codec(pg_tsvector, [], Val)). + +tsvector() -> + proper_types:list(lexeme()). + +lexeme() -> + {str(), % word + proper_types:list(position())}. % positions + +position() -> + {proper_types:integer(0, 8192), % position + proper_types:oneof(['A', 'B', 'C', null])}. % weight + +str() -> + ?LET( + NoZeroStr, + ?SUCHTHAT(S, proper_types:list(proper_types:byte()), + not lists:member(0, S)), + list_to_binary(NoZeroStr)).