File 1891-Property-based-tests-for-the-binary-module.patch of Package erlang
From 4b675e57c3934b8fec05922686de0e08670ccf19 Mon Sep 17 00:00:00 2001
From: Maria Scott <maria-12648430@hnc-agency.org>
Date: Wed, 30 Aug 2023 16:54:52 +0200
Subject: [PATCH 1/4] Property-based tests for the binary module
Co-authored-by: Jan Uhlig <juhlig@hnc-agency.org>
---
.../test/binary_property_test_SUITE.erl | 134 ++-
lib/stdlib/test/property_test/binary_prop.erl | 896 +++++++++++++++++-
2 files changed, 1013 insertions(+), 17 deletions(-)
diff --git a/lib/stdlib/test/binary_property_test_SUITE.erl b/lib/stdlib/test/binary_property_test_SUITE.erl
index 4ec1ba67ab..87740a0dc5 100644
--- a/lib/stdlib/test/binary_property_test_SUITE.erl
+++ b/lib/stdlib/test/binary_property_test_SUITE.erl
@@ -22,7 +22,29 @@
-compile([export_all, nowarn_export_all]).
all() ->
- [encode_hex_case].
+ [at_case, at_invalid_case,
+ bin_to_list_1_case,
+ bin_to_list_2_3_case, bin_to_list_2_3_invalid_case,
+ compile_pattern_case, compile_pattern_invalid_case,
+ copy_case, copy_2_invalid_case,
+ decode_hex_case, decode_hex_invalid_case,
+ decode_unsigned_case,
+ encode_hex_case,
+ encode_unsigned_case, encode_unsigned_invalid_case,
+ first_case,
+ last_case,
+ list_to_bin_case, list_to_bin_invalid_case,
+ longest_common_prefix_case,
+ longest_common_suffix_case,
+ match_2_case,
+ match_3_case, match_3_invalid_case,
+ matches_2_case,
+ matches_3_case, matches_3_invalid_case,
+ part_case, part_invalid_case,
+ replace_3_case,
+ replace_4_case, replace_4_invalid1_case, replace_4_invalid2_case,
+ split_2_case,
+ split_3_case, split_3_invalid_case].
init_per_suite(Config) ->
ct_property_test:init_per_suite(Config).
@@ -30,6 +52,114 @@ init_per_suite(Config) ->
end_per_suite(Config) ->
Config.
+do_proptest(Prop, Config) ->
+ ct_property_test:quickcheck(binary_prop:Prop(), Config).
+
+at_case(Config) ->
+ do_proptest(prop_at, Config).
+
+at_invalid_case(Config) ->
+ do_proptest(prop_at_invalid, Config).
+
+bin_to_list_1_case(Config) ->
+ do_proptest(prop_bin_to_list_1, Config).
+
+bin_to_list_2_3_case(Config) ->
+ do_proptest(prop_bin_to_list_2_3, Config).
+
+bin_to_list_2_3_invalid_case(Config) ->
+ do_proptest(prop_bin_to_list_2_3_invalid, Config).
+
+compile_pattern_case(Config) ->
+ do_proptest(prop_compile_pattern, Config).
+
+compile_pattern_invalid_case(Config) ->
+ do_proptest(prop_compile_pattern_invalid, Config).
+
+copy_case(Config) ->
+ do_proptest(prop_copy, Config).
+
+copy_2_invalid_case(Config) ->
+ do_proptest(prop_copy_2_invalid, Config).
+
+decode_hex_case(Config) ->
+ do_proptest(prop_decode_hex, Config).
+
+decode_hex_invalid_case(Config) ->
+ do_proptest(prop_decode_hex_invalid, Config).
+
+decode_unsigned_case(Config) ->
+ do_proptest(prop_decode_unsigned, Config).
+
encode_hex_case(Config) ->
- ct_property_test:quickcheck(binary_prop:prop_hex_encode_2(), Config).
+ do_proptest(prop_encode_hex, Config).
+
+encode_unsigned_case(Config) ->
+ do_proptest(prop_encode_unsigned, Config).
+
+encode_unsigned_invalid_case(Config) ->
+ do_proptest(prop_encode_unsigned_invalid, Config).
+
+first_case(Config) ->
+ do_proptest(prop_first, Config).
+
+last_case(Config) ->
+ do_proptest(prop_last, Config).
+
+list_to_bin_case(Config) ->
+ do_proptest(prop_list_to_bin, Config).
+
+list_to_bin_invalid_case(Config) ->
+ do_proptest(prop_list_to_bin_invalid, Config).
+
+longest_common_prefix_case(Config) ->
+ do_proptest(prop_longest_common_prefix, Config).
+
+longest_common_suffix_case(Config) ->
+ do_proptest(prop_longest_common_suffix, Config).
+
+match_2_case(Config) ->
+ do_proptest(prop_match_2, Config).
+
+match_3_case(Config) ->
+ do_proptest(prop_match_3, Config).
+
+match_3_invalid_case(Config) ->
+ do_proptest(prop_match_3_invalid, Config).
+
+matches_2_case(Config) ->
+ do_proptest(prop_matches_2, Config).
+
+matches_3_case(Config) ->
+ do_proptest(prop_matches_3, Config).
+
+matches_3_invalid_case(Config) ->
+ do_proptest(prop_matches_3_invalid, Config).
+
+part_case(Config) ->
+ do_proptest(prop_part, Config).
+
+part_invalid_case(Config) ->
+ do_proptest(prop_part_invalid, Config).
+
+replace_3_case(Config) ->
+ do_proptest(prop_replace_3, Config).
+
+replace_4_case(Config) ->
+ do_proptest(prop_replace_4, Config).
+
+replace_4_invalid1_case(Config) ->
+ do_proptest(prop_replace_4_invalid1, Config).
+
+replace_4_invalid2_case(Config) ->
+ do_proptest(prop_replace_4_invalid2, Config).
+
+split_2_case(Config) ->
+ do_proptest(prop_split_2, Config).
+
+split_3_case(Config) ->
+ do_proptest(prop_split_3, Config).
+
+split_3_invalid_case(Config) ->
+ do_proptest(prop_split_3_invalid, Config).
diff --git a/lib/stdlib/test/property_test/binary_prop.erl b/lib/stdlib/test/property_test/binary_prop.erl
index 00efa77b8c..b07cf13f43 100644
--- a/lib/stdlib/test/property_test/binary_prop.erl
+++ b/lib/stdlib/test/property_test/binary_prop.erl
@@ -1,27 +1,856 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2021-2023. All Rights Reserved.
+%%
+%% 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.
+%%
+%% %CopyrightEnd%
+%%
-module(binary_prop).
--export([prop_hex_encode_2/0]).
+-include_lib("common_test/include/ct_property_test.hrl").
--compile([export_all, nowarn_export_all]).
+%%%%%%%%%%%%%%%%%%
+%%% Properties %%%
+%%%%%%%%%%%%%%%%%%
--include_lib("common_test/include/ct_property_test.hrl").
+%% --- at/2 -----------------------------------------------------------
+prop_at() ->
+ ?FORALL(
+ {Bin, Pos, Byte},
+ ?LET(
+ {B1, Bt, B2},
+ {binary(), byte(), binary()},
+ {<<B1/binary, Bt, B2/binary>>, byte_size(B1), Bt}
+ ),
+ Byte =:= binary:at(Bin, Pos)
+ ).
+
+prop_at_invalid() ->
+ ?FORALL(
+ {Bin, Pos},
+ ?LET(
+ B,
+ binary(),
+ {B, gen_index_invalid(B)}
+ ),
+ expect_error(fun binary:at/2, [Bin, Pos])
+ ).
+
+%% --- bin_to_list/1 --------------------------------------------------
+prop_bin_to_list_1() ->
+ ?FORALL(
+ {Bin, List},
+ ?LET(
+ L,
+ list(byte()),
+ {<< <<Bt>> || Bt <- L >>, L}
+ ),
+ List =:= binary:bin_to_list(Bin)
+ ).
+
+%% --- bin_to_list/2,3 ------------------------------------------------
+prop_bin_to_list_2_3() ->
+ ?FORALL(
+ {Bin, List, {Pos, Len}=PosLen},
+ ?LET(
+ {L1, L, L2},
+ {list(byte()), list(byte()), list(byte())},
+ {<< <<Bt>> || Bt <- L1 ++ L ++ L2 >>, L, {length(L1), length(L)}}
+ ),
+ List =:= binary:bin_to_list(Bin, PosLen) andalso
+ List =:= binary:bin_to_list(Bin, Pos, Len)
+ ).
+
+prop_bin_to_list_2_3_invalid() ->
+ ?FORALL(
+ {Bin, {Pos, Len}=PosLen},
+ ?LET(
+ B,
+ binary(),
+ {B, gen_part_invalid(B)}
+ ),
+ expect_error(fun binary:bin_to_list/2, [Bin, PosLen]) andalso
+ expect_error(fun binary:bin_to_list/3, [Bin, Pos, Len])
+ ).
+
+%% --- compile_pattern/1 ----------------------------------------------
+prop_compile_pattern() ->
+ ?FORALL(
+ P,
+ gen_patterns(),
+ is_tuple(binary:compile_pattern(P))
+ ).
+
+prop_compile_pattern_invalid() ->
+ ?FORALL(
+ P,
+ oneof([
+ <<>>,
+ [],
+ ?LET(
+ L,
+ non_empty(list(binary())),
+ case lists:any(fun(P) -> P =:= <<>> end, L) of
+ true ->
+ L;
+ false ->
+ [<<>> | L]
+ end)
+ ]),
+ expect_error(fun binary:compile_pattern/1, [P])
+ ).
+
+%% --- copy/1,2 -------------------------------------------------------
+prop_copy() ->
+ ?FORALL(
+ {Bin, N},
+ {binary(), ?SUCHTHAT(N, non_neg_integer(), N =/= 1)},
+ begin
+ Copy1 = binary:copy(Bin),
+ Copy2 = binary:copy(Bin, 1),
+ Bin =:= Copy1 andalso
+ not erts_debug:same(Bin, Copy1) andalso
+ Bin =:= Copy2 andalso
+ not erts_debug:same(Bin, Copy2) andalso
+ << <<Bin/binary>> || _ <- lists:seq(1, N) >> =:= binary:copy(Bin, N)
+ end
+ ).
+
+prop_copy_2_invalid() ->
+ ?FORALL(
+ {Bin, N},
+ {binary(), neg_integer()},
+ expect_error(fun binary:copy/2, [Bin, N])
+ ).
+
+%% --- decode_hex/1 ---------------------------------------------------
+prop_decode_hex() ->
+ ?FORALL(
+ {BR, BE},
+ ?LET(
+ L,
+ list({oneof([lower, upper]), range(16#0, 16#f)}),
+ lists:foldl(
+ fun
+ ({_, Nib}, {AccR, AccE}) when Nib < 10 ->
+ {<<AccR/bitstring, Nib:4>>, <<AccE/binary, ($0 + Nib)>>};
+ ({lower, Nib}, {AccR, AccE}) ->
+ {<<AccR/bitstring, Nib:4>>, <<AccE/binary, ($a + Nib - 10)>>};
+ ({upper, Nib}, {AccR, AccE}) ->
+ {<<AccR/bitstring, Nib:4>>, <<AccE/binary, ($A + Nib - 10)>>}
+ end,
+ {<<>>, <<>>},
+ case length(L) rem 2 of
+ 0 -> L;
+ 1 -> tl(L)
+ end
+ )
+ ),
+ BR =:= binary:decode_hex(BE)
+ ).
+
+prop_decode_hex_invalid() ->
+ ?FORALL(
+ Bin,
+ ?SUCHTHAT(B, binary(), not is_hex_bin(B)),
+ expect_error(fun binary:decode_hex/1, [Bin])
+ ).
+
+%% --- decode_unsigned/1,2 --------------------------------------------
+prop_decode_unsigned() ->
+ ?FORALL(
+ Bin,
+ binary(),
+ begin
+ Size = bit_size(Bin),
+ <<Big:Size/integer-unsigned-big>> = Bin,
+ <<Little:Size/integer-unsigned-little>> = Bin,
+ Big =:= binary:decode_unsigned(Bin) andalso
+ Big =:= binary:decode_unsigned(Bin, big) andalso
+ Little =:= binary:decode_unsigned(Bin, little)
+ end
+ ).
+
+%% --- encode_hex/1,2 -------------------------------------------------
+prop_encode_hex() ->
+ ?FORALL(
+ Bin,
+ binary(),
+ begin
+ LowerHex = binary:encode_hex(Bin, lowercase),
+ UpperHex = binary:encode_hex(Bin, uppercase),
+ UpperHex =:= binary:encode_hex(Bin) andalso
+ Bin =:= binary:decode_hex(LowerHex) andalso
+ Bin =:= binary:decode_hex(UpperHex) andalso
+ check_hex_encoded(Bin, UpperHex, LowerHex)
+ end
+ ).
+
+%% --- encode_unsigned/1,2 --------------------------------------------
+prop_encode_unsigned() ->
+ ?FORALL(
+ I,
+ non_neg_integer(),
+ begin
+ Size = max(8, int_bitsize(I)),
+ Big = <<I:Size/integer-unsigned-big>>,
+ Little = <<I:Size/integer-unsigned-little>>,
+ Big =:= binary:encode_unsigned(I) andalso
+ Big =:= binary:encode_unsigned(I, big) andalso
+ Little =:= binary:encode_unsigned(I, little)
+ end
+ ).
+
+prop_encode_unsigned_invalid() ->
+ ?FORALL(
+ I,
+ neg_integer(),
+ expect_error(fun binary:encode_unsigned/1, [I]) andalso
+ expect_error(fun binary:encode_unsigned/2, [I, big]) andalso
+ expect_error(fun binary:encode_unsigned/2, [I, little])
+ ).
+
+%% --- first/1 --------------------------------------------------------
+prop_first() ->
+ ?FORALL(
+ {Bin, Byte},
+ ?LET(
+ {B, Bt},
+ {binary(), byte()},
+ {<<Bt, B/binary>>, Bt}
+ ),
+ Byte =:= binary:first(Bin)
+ ).
+
+%% --- last/1 ---------------------------------------------------------
+prop_last() ->
+ ?FORALL(
+ {Bin, Byte},
+ ?LET(
+ {B, Bt},
+ {binary(), byte()},
+ {<<B/binary, Bt>>, Bt}
+ ),
+ Byte =:= binary:last(Bin)
+ ).
+
+%% --- list_to_bin/1 --------------------------------------------------
+prop_list_to_bin() ->
+ ?FORALL(
+ {List, Bin},
+ ?LET(
+ B,
+ binary(),
+ {[Bt || <<Bt>> <= B], B}
+ ),
+ Bin =:= binary:list_to_bin(List)
+ ).
+
+prop_list_to_bin_invalid() ->
+ ?FORALL(
+ List,
+ ?SUCHTHAT(
+ L,
+ non_empty(list(integer())),
+ lists:any(fun(I) -> I < 16#00 orelse I > 16#ff end, L)
+ ),
+ expect_error(fun binary:list_to_bin/1, [List])
+ ).
+
+%% --- longest_common_prefix/1 ----------------------------------------
+prop_longest_common_prefix() ->
+ ?FORALL(
+ Bins,
+ ?LET(
+ {C, Bs},
+ {binary(), non_empty(list(binary()))},
+ [<<C/binary, B/binary>> || B <- Bs]
+ ),
+ find_longest_common_prefix(Bins) =:= binary:longest_common_prefix(Bins)
+ ).
+
+%% --- longest_common_suffix/1 ----------------------------------------
+prop_longest_common_suffix() ->
+ ?FORALL(
+ Bins,
+ ?LET(
+ {C, Bs},
+ {binary(), non_empty(list(binary()))},
+ [<<B/binary, C/binary>> || B <- Bs]
+ ),
+ find_longest_common_suffix(Bins) =:= binary:longest_common_suffix(Bins)
+ ).
+
+%% --- match/2 --------------------------------------------------------
+prop_match_2() ->
+ ?FORALL(
+ {Bin, Pattern},
+ ?LET(
+ B,
+ binary(),
+ {B, gen_patterns(B)}
+ ),
+ begin
+ Match = binary:match(Bin, Pattern),
+ Match =:= binary:match(Bin, binary:compile_pattern(Pattern)) andalso
+ Match =:= find_match(Bin, Pattern)
+ end
+ ).
+
+%% --- match/3 --------------------------------------------------------
+prop_match_3() ->
+ ?FORALL(
+ {Bin, Pattern, Opts},
+ ?LET(
+ B,
+ binary(),
+ {B, gen_patterns(B), gen_opts([], [gen_scope_opt(B)])}
+ ),
+ begin
+ Match = binary:match(Bin, Pattern, Opts),
+ Match =:= binary:match(Bin, binary:compile_pattern(Pattern), Opts) andalso
+ Match =:= find_match(Bin, Pattern, Opts)
+ end
+ ).
+
+prop_match_3_invalid() ->
+ ?FORALL(
+ {Bin, Pattern, Opts},
+ ?LET(
+ B,
+ binary(),
+ {B, gen_patterns(), gen_opts([gen_scope_invalid_opt(B)], [])}
+ ),
+ expect_error(fun binary:match/3, [Bin, Pattern, Opts]) andalso
+ expect_error(fun binary:match/3, [Bin, binary:compile_pattern(Pattern), Opts])
+ ).
+
+%% --- matches/2 ------------------------------------------------------
+prop_matches_2() ->
+ ?FORALL(
+ {Bin, Pattern},
+ ?LET(
+ B,
+ binary(),
+ {B, gen_patterns(B)}
+ ),
+ begin
+ Match = lists:sort(binary:matches(Bin, Pattern)),
+ Match =:= lists:sort(binary:matches(Bin, binary:compile_pattern(Pattern))) andalso
+ Match =:= lists:sort(find_matches(Bin, Pattern))
+ end
+ ).
+
+%% --- matches/3 ------------------------------------------------------
+prop_matches_3() ->
+ ?FORALL(
+ {Bin, Pattern, Opts},
+ ?LET(
+ B,
+ binary(),
+ {B, gen_patterns(B), gen_opts([], [gen_scope_opt(B)])}
+ ),
+ begin
+ Match = lists:sort(binary:matches(Bin, Pattern, Opts)),
+ Match =:= lists:sort(binary:matches(Bin, binary:compile_pattern(Pattern), Opts)) andalso
+ Match =:= lists:sort(find_matches(Bin, Pattern, Opts))
+ end
+ ).
+
+prop_matches_3_invalid() ->
+ ?FORALL(
+ {Bin, Pattern, Opts},
+ ?LET(
+ B,
+ binary(),
+ {B, gen_patterns(), gen_opts([gen_scope_invalid_opt(B)], [])}
+ ),
+ expect_error(fun binary:matches/3, [Bin, Pattern, Opts]) andalso
+ expect_error(fun binary:matches/3, [Bin, binary:compile_pattern(Pattern), Opts])
+ ).
+
+%% --- part/2,3 -------------------------------------------------------
+prop_part() ->
+ ?FORALL(
+ {Bin, {Pos, Len}=PosLen},
+ ?LET(
+ B,
+ binary(),
+ {B, gen_part(B)}
+ ),
+ begin
+ {_, Part, _} = part_split(Bin, PosLen),
+ Part =:= binary:part(Bin, PosLen) andalso
+ Part =:= binary:part(Bin, Pos, Len) andalso
+ Part =:= erlang:binary_part(Bin, PosLen) andalso
+ Part =:= erlang:binary_part(Bin, Pos, Len)
+ end
+ ).
+
+prop_part_invalid() ->
+ ?FORALL(
+ {Bin, {Pos, Len}=PosLen},
+ ?LET(
+ B,
+ binary(),
+ {B, gen_part_invalid(B)}
+ ),
+ expect_error(fun binary:part/2, [Bin, PosLen]) andalso
+ expect_error(fun binary:part/3, [Bin, Pos, Len])
+ ).
+
+%% --- replace/3 ------------------------------------------------------
+prop_replace_3() ->
+ ?FORALL(
+ {Bin, Pattern, Replacement},
+ ?LET(
+ {B, R},
+ {binary(), oneof([binary(), function1(binary())])},
+ {B, gen_patterns(B), R}
+ ),
+ begin
+ Replaced = binary:replace(Bin, Pattern, Replacement),
+ Replaced =:= binary:replace(Bin, binary:compile_pattern(Pattern), Replacement) andalso
+ Replaced =:= do_replace(Bin, Pattern, Replacement)
+ end
+ ).
+
+%% --- replace/4 ------------------------------------------------------
+prop_replace_4() ->
+ ?FORALL(
+ {Bin, Pattern, Replacement, Opts},
+ ?LET(
+ {B, R},
+ {binary(), oneof([binary(), function1(binary())])},
+ {B, gen_patterns(B), R, gen_opts([], [global, gen_scope_opt(B), gen_insert_replaced_opt(R)])}
+ ),
+ begin
+ Replaced = binary:replace(Bin, Pattern, Replacement, Opts),
+ Replaced =:= binary:replace(Bin, binary:compile_pattern(Pattern), Replacement, Opts) andalso
+ Replaced =:= do_replace(Bin, Pattern, Replacement, Opts)
+ end
+ ).
+
+prop_replace_4_invalid1() ->
+ ?FORALL(
+ {Bin, Pattern, Replacement, Opts},
+ ?LET(
+ {B, R},
+ {binary(), oneof([binary(), function1(binary())])},
+ {B, gen_patterns(B), R, gen_opts([gen_scope_invalid_opt(B)], [global, gen_insert_replaced_opt(R)])}
+ ),
+ expect_error(fun binary:replace/4, [Bin, Pattern, Replacement, Opts]) andalso
+ expect_error(fun binary:replace/4, [Bin, binary:compile_pattern(Pattern), Replacement, Opts])
+ ).
+
+prop_replace_4_invalid2() ->
+ ?FORALL(
+ {Bin, Pattern, Replacement, Opts},
+ ?LET(
+ {B, R},
+ {binary(), binary()},
+ {B, gen_patterns(B), R, gen_opts([gen_insert_replaced_invalid_opt(R)], [global, gen_scope_opt(B)])}
+ ),
+ expect_error(fun binary:replace/4, [Bin, Pattern, Replacement, Opts]) andalso
+ expect_error(fun binary:replace/4, [Bin, binary:compile_pattern(Pattern), Replacement, Opts])
+ ).
+
+%% --- split/2 --------------------------------------------------------
+prop_split_2() ->
+ ?FORALL(
+ {Bin, Pattern},
+ ?LET(
+ B,
+ binary(),
+ {B, gen_patterns(B)}
+ ),
+ begin
+ Split = binary:split(Bin, Pattern),
+ Split =:= binary:split(Bin, binary:compile_pattern(Pattern)) andalso
+ Split =:= do_split(Bin, Pattern)
+ end
+ ).
+
+%% --- split/3 --------------------------------------------------------
+prop_split_3() ->
+ ?FORALL(
+ {Bin, Pattern, Opts},
+ ?LET(
+ B,
+ binary(),
+ {B, gen_patterns(B), gen_opts([], [global, trim, trim_all, gen_scope_opt(B)])}
+ ),
+ begin
+ Split = binary:split(Bin, Pattern, Opts),
+ Split =:= binary:split(Bin, binary:compile_pattern(Pattern), Opts) andalso
+ Split =:= do_split(Bin, Pattern, Opts)
+ end
+ ).
+
+prop_split_3_invalid() ->
+ ?FORALL(
+ {Bin, Pattern, Opts},
+ ?LET(
+ B,
+ binary(),
+ {B, gen_patterns(B), gen_opts([gen_scope_invalid_opt(B)], [global, trim, trim_all])}
+ ),
+ expect_error(fun binary:split/3, [Bin, Pattern, Opts]) andalso
+ expect_error(fun binary:split/3, [Bin, binary:compile_pattern(Pattern), Opts])
+ ).
+
+
+%%%%%%%%%%%%%%%%%%
+%%% Generators %%%
+%%%%%%%%%%%%%%%%%%
-prop_hex_encode_2() ->
- ?FORALL(Data, data(),
+%% Generator for lists of options, in random order.
+%% The options given in RequiredOpts will be present.
+%% The options given in MaybeOpts may or may not be present.
+gen_opts(RequiredOpts, MaybeOpts) ->
+ ?LET(
+ {UseOpts, Shuffles},
+ {vector(length(MaybeOpts), boolean()), vector(length(RequiredOpts) + length(MaybeOpts), integer())},
begin
- UpperHex = binary:encode_hex(Data, uppercase),
- LowerHex = binary:encode_hex(Data, lowercase),
- binary:decode_hex(LowerHex) =:= Data andalso
- binary:decode_hex(UpperHex) =:= Data andalso
- check_hex_encoded(Data, UpperHex, LowerHex)
- end).
+ UsedMaybeOpts = [Opt || {true, Opt} <- lists:zip(UseOpts, MaybeOpts)],
+ UsedOpts = RequiredOpts ++ UsedMaybeOpts,
+ ShuffledOpts = lists:sort(lists:zip(Shuffles, UsedOpts, trim)),
+ [Opt || {_, Opt} <- ShuffledOpts]
+ end
+ ).
-data() ->
- ?SIZED(Size, resize(Size * 2, binary())).
+%% Generator for scope options, valid for the binary given in Bin
+gen_scope_opt(Bin) ->
+ {scope, gen_part(Bin)}.
+%% Generator for scope options, invalid for the binary given in Bin
+gen_scope_invalid_opt(Bin) ->
+ {scope, gen_part_invalid(Bin)}.
+
+%% Generator for a part range, valid for the binary given in Bin
+gen_part(Bin) ->
+ Size = byte_size(Bin),
+ ?LET(
+ S,
+ range(0, Size),
+ {S, range(-S, Size-S)}
+ ).
+
+%% Generator for a part range, invalid for the binary given in Bin
+gen_part_invalid(Bin) ->
+ Size = byte_size(Bin),
+ ?SUCHTHAT(
+ {S, L},
+ {integer(), integer()},
+ S < 0 orelse S > Size orelse S + L < 0 orelse S + L > Size
+ ).
+
+%% Generator for an index within the binary given in Bin
+gen_index(Bin) ->
+ range(0, byte_size(Bin)).
+
+%% Generator for an index outside the binary given in Bin
+gen_index_invalid(Bin) ->
+ oneof([neg_integer(), range(byte_size(Bin) + 1, inf)]).
+
+%% Generator for insert_replaced options, valid for the binary given in Replacement
+gen_insert_replaced_opt(Replacement) when is_binary(Replacement) ->
+ {insert_replaced, oneof([gen_index(Replacement), list(gen_index(Replacement))])};
+gen_insert_replaced_opt(_Replacement) ->
+ {insert_replaced, oneof([non_neg_integer(), list(non_neg_integer())])}.
+
+%% Generator for insert_replaced options, invalid for the binary given in Replacement
+gen_insert_replaced_invalid_opt(Replacement) when is_binary(Replacement) ->
+ {insert_replaced, oneof([gen_index_invalid(Replacement), non_empty(list(gen_index_invalid(Replacement)))])};
+gen_insert_replaced_invalid_opt(_Replacement) ->
+ {insert_replaced, oneof([neg_integer(), non_empty(list(neg_integer()))])}.
+
+%% Generator for patterns, that is a single or a list of pattern binaries,
+%% which may or may not be present in the optionally binary given in Bin.
+gen_patterns() ->
+ oneof([gen_pattern(), non_empty(list(gen_pattern()))]).
+
+gen_patterns(Bin) ->
+ oneof([gen_pattern(Bin), non_empty(list(gen_pattern(Bin)))]).
+
+%% Generator for a single pattern.
+%% The pattern may or may not be present in the optionally binary given in Bin.
+gen_pattern() ->
+ non_empty(binary()).
+
+gen_pattern(<<>>) ->
+ non_empty(binary());
+gen_pattern(Bin) ->
+ oneof([non_empty(binary()),
+ ?LET(
+ S,
+ range(0, byte_size(Bin) - 1),
+ ?LET(
+ L,
+ range(1, byte_size(Bin) - S),
+ begin
+ <<_:S/binary, P:L/binary, _/binary>> = Bin,
+ P
+ end
+ )
+ )]).
+
+
+%%%%%%%%%%%%%%%
+%%% Helpers %%%
+%%%%%%%%%%%%%%%
+
+%% Determine the number of bits required to store the given integer in full bytes
+int_bitsize(0) ->
+ 0;
+int_bitsize(I) ->
+ 8 + int_bitsize(I div 16#100).
+
+%% Extract an indicator for whether or not to perform a global search from the given options list
+opts_to_how(Opts) ->
+ case lists:member(global, Opts) of
+ true ->
+ all;
+ false ->
+ first
+ end.
+
+%% Divide the given binary in the parts before, in, and after the scope specified in the given options list
+opts_to_scope_parts(Bin, Opts) ->
+ case lists:keyfind(scope, 1, Opts) of
+ false ->
+ {<<>>, Bin, <<>>};
+ {scope, PosLen} ->
+ part_split(Bin, PosLen)
+ end.
+
+%% Decide the trimming mode from the trim or trim_all options specified in the given options list
+opts_to_trim(Opts) ->
+ case {lists:member(trim_all, Opts), lists:member(trim, Opts)} of
+ {true, _} ->
+ all;
+ {_, true} ->
+ rear;
+ _ ->
+ none
+ end.
+
+%% Process a replacement based on the insert_replaced options specified in the given options list
+opts_to_replacement(Replacement, _Opts) when is_function(Replacement) ->
+ Replacement;
+opts_to_replacement(Replacement, Opts) ->
+ case lists:keyfind(insert_replaced, 1, Opts) of
+ false ->
+ Replacement;
+ {insert_replaced, []} ->
+ Replacement;
+ {insert_replaced, [_|_] = Positions} ->
+ split_replacement(Positions, Replacement);
+ {insert_replaced, Position} ->
+ <<Front:Position/binary, Rear/binary>> = Replacement,
+ [Front, Rear]
+ end.
+
+split_replacement(Inserts, Bin) ->
+ split_replacement1(Bin, lists:sort(Inserts), 0, []).
+
+split_replacement1(Bin, [], L, Acc) ->
+ lists:reverse([extract_part(Bin, L, byte_size(Bin))|Acc]);
+split_replacement1(Bin, [P|Ps], L, Acc) ->
+ split_replacement1(Bin, Ps, P, [extract_part(Bin, L, P)|Acc]).
+
+extract_part(Bin, S, E) ->
+ <<_:S/binary, P:(E-S)/binary, _/binary>> = Bin,
+ P.
+
+join_replacement(_Join, []) ->
+ <<>>;
+join_replacement(_Join, [P]) ->
+ P;
+join_replacement(Join, [P|Ps]) ->
+ <<P/binary, Join/binary, (join_replacement(Join, Ps))/binary>>.
+
+replace_match(Replacement, Match) when is_function(Replacement) ->
+ Replacement(Match);
+replace_match(Replacement, Match) when is_list(Replacement) ->
+ join_replacement(Match, Replacement);
+replace_match(Replacement, _Match) ->
+ Replacement.
+
+%% Split the given binary in the parts before, within, and after the scope
+%% defined by Pos and Len
+part_split(Bin, {Pos, Len}) when Len >= 0 ->
+ <<Front:Pos/binary, Middle:Len/binary, Rear/binary>> = Bin,
+ {Front, Middle, Rear};
+part_split(Bin, {Pos, Len}) ->
+ <<Front:(Pos + Len)/binary, Middle:(-Len)/binary, Rear/binary>> = Bin,
+ {Front, Middle, Rear}.
+
+%% Sort the given list of patterns by descending size.
+%% If a single pattern is given, wraps it in a list.
+sort_patterns(Patterns) when is_list(Patterns) ->
+ lists:sort(fun(P1, P2) -> byte_size(P1) >= byte_size(P2) end, Patterns);
+sort_patterns(Pattern) ->
+ [Pattern].
+
+%% Control implementation for longest_common_prefix/1
+find_longest_common_prefix([B|_]=Bs) ->
+ find_longest_common_prefix1(Bs, byte_size(B)).
+
+find_longest_common_prefix1(_Bs, 0) ->
+ 0;
+find_longest_common_prefix1([_B], N) ->
+ N;
+find_longest_common_prefix1([B1|[B2|_]=More], N) ->
+ find_longest_common_prefix1(More, min(N, find_longest_common_prefix2(B1, B2, 0))).
+
+find_longest_common_prefix2(<<C, B1/binary>>, <<C, B2/binary>>, N) ->
+ find_longest_common_prefix2(B1, B2, N + 1);
+find_longest_common_prefix2(_B1, _B2, N) ->
+ N.
+
+%% Control implementation for longest_common_suffix/1
+find_longest_common_suffix([B|_]=Bs) ->
+ find_longest_common_suffix1(Bs, byte_size(B)).
+
+find_longest_common_suffix1(_Bs, 0) ->
+ 0;
+find_longest_common_suffix1([_B], N) ->
+ N;
+find_longest_common_suffix1([B1|[B2|_]=More], N) ->
+ S1 = byte_size(B1),
+ S2 = byte_size(B2),
+ <<_:(max(0, S1 - S2))/bytes, B1_1/binary>> = B1,
+ <<_:(max(0, S2 - S1))/bytes, B2_1/binary>> = B2,
+ find_longest_common_suffix1(More, min(N, find_longest_common_suffix2(B1_1, B2_1, 0))).
+
+find_longest_common_suffix2(<<>>, <<>>, N) ->
+ N;
+find_longest_common_suffix2(<<C, B1/binary>>, <<C, B2/binary>>, N) ->
+ find_longest_common_suffix2(B1, B2, N + 1);
+find_longest_common_suffix2(<<_, B1/binary>>, <<_, B2/binary>>, _N) ->
+ find_longest_common_suffix2(B1, B2, 0).
+
+%% Control implementation for match/2
+find_match(Bin, Patterns) ->
+ find_match(Bin, Patterns, []).
+
+%% Control implementation for match/3
+find_match(Bin, Patterns, Opts) ->
+ case find_matches(Bin, Patterns, Opts) of
+ [] ->
+ nomatch;
+ [Match|_] ->
+ Match
+ end.
+
+%% Control implementation for matches/2
+find_matches(Bin, Patterns) ->
+ find_matches(Bin, Patterns, []).
+
+%% Control implementation for matches/3
+find_matches(Bin, Patterns, Opts) ->
+ {Front, SearchBin, _} = opts_to_scope_parts(Bin, Opts),
+ SortedPatterns = sort_patterns(Patterns),
+ Matches = find_matches1(SearchBin, SortedPatterns, SortedPatterns, 0, []),
+ Offset = byte_size(Front),
+ [{Pos + Offset, Len} || {Pos, Len} <- Matches].
+
+find_matches1(<<>>, _Patterns, _AllPatterns, _Pos, Acc) ->
+ lists:reverse(Acc);
+find_matches1(<<_, Bin/binary>>, [], AllPatterns, Pos, Acc) ->
+ find_matches1(Bin, AllPatterns, AllPatterns, Pos + 1, Acc);
+find_matches1(Bin, [Pattern|Patterns], AllPatterns, Pos, Acc) ->
+ case Bin of
+ <<Pattern:(byte_size(Pattern))/binary, Bin1/binary>> ->
+ find_matches1(Bin1, AllPatterns, AllPatterns, Pos + byte_size(Pattern), [{Pos, byte_size(Pattern)}|Acc]);
+ _ ->
+ find_matches1(Bin, Patterns, AllPatterns, Pos, Acc)
+ end.
+
+%% Control implementation for replace/3
+do_replace(Bin, Patterns, Replacement) ->
+ do_replace(Bin, Patterns, Replacement, []).
+
+%% Control implementation for replace/4
+do_replace(Bin, Patterns, Replacement, Opts) ->
+ How = opts_to_how(Opts),
+ {Front, ReplaceBin, Rear} = opts_to_scope_parts(Bin, Opts),
+ Replacement1 = opts_to_replacement(Replacement, Opts),
+ SortedPatterns = sort_patterns(Patterns),
+ Replaced = do_replace1(How, ReplaceBin, SortedPatterns, SortedPatterns, Replacement1, <<>>),
+ <<Front/binary, Replaced/binary, Rear/binary>>.
+
+do_replace1(_How, <<>>, _Patterns, _AllPatterns, _Replacement, Acc) ->
+ Acc;
+do_replace1(How, <<C, Bin/binary>>, [], AllPatterns, Replacement, Acc) ->
+ do_replace1(How, Bin, AllPatterns, AllPatterns, Replacement, <<Acc/binary, C>>);
+do_replace1(How, Bin, [Pattern|Patterns], AllPatterns, Replacement, Acc) ->
+ case Bin of
+ <<Pattern:(byte_size(Pattern))/binary, Bin1/binary>> ->
+ case How of
+ first ->
+ <<Acc/binary, (replace_match(Replacement, Pattern))/binary, Bin1/binary>>;
+ all ->
+ do_replace1(How, Bin1, AllPatterns, AllPatterns, Replacement, <<Acc/binary, (replace_match(Replacement, Pattern))/binary>>)
+ end;
+ _ ->
+ do_replace1(How, Bin, Patterns, AllPatterns, Replacement, Acc)
+ end.
+
+%% Control implementation for split/2
+do_split(Bin, Patterns) ->
+ do_split(Bin, Patterns, []).
+
+%% Control implementation for split/3
+do_split(Bin, Patterns, Opts) ->
+ How = opts_to_how(Opts),
+ {Front, SplitBin, Rear} = opts_to_scope_parts(Bin, Opts),
+ Trim = opts_to_trim(Opts),
+ SortedPatterns = sort_patterns(Patterns),
+ Splitted0 = do_split1(How, SplitBin, SortedPatterns, SortedPatterns, <<>>, []),
+ Splitted1 = [<<Front/binary, (hd(Splitted0))/binary>> | tl(Splitted0)],
+ Splitted2 = lists:droplast(Splitted1) ++ [<<(lists:last(Splitted1))/binary, Rear/binary>>],
+ case Trim of
+ none ->
+ Splitted2;
+ all ->
+ lists:filter(fun(Part) -> Part =/= <<>> end, Splitted2);
+ rear ->
+ lists:reverse(lists:dropwhile(fun(Part) -> Part =:= <<>> end, lists:reverse(Splitted2)))
+ end.
+
+do_split1(_How, <<>>, _Patterns, _AllPatterns, AccB, AccL) ->
+ lists:reverse([AccB | AccL]);
+do_split1(How, <<C, Bin/binary>>, [], AllPatterns, AccB, AccL) ->
+ do_split1(How, Bin, AllPatterns, AllPatterns, <<AccB/binary, C>>, AccL);
+do_split1(How, Bin, [Pattern|Patterns], AllPatterns, AccB, AccL) ->
+ case Bin of
+ <<Pattern:(byte_size(Pattern))/binary, Bin1/binary>> ->
+ case How of
+ first ->
+ [AccB, Bin1];
+ all ->
+ do_split1(How, Bin1, AllPatterns, AllPatterns, <<>>, [AccB | AccL])
+ end;
+ _ ->
+ do_split1(How, Bin, Patterns, AllPatterns, AccB, AccL)
+ end.
%% @doc Credit to the <a href="https://github.com/erlang/otp/pull/6297#discussion_r1034771450">comment</a> of @Maria-12648430
--spec check_hex_encoded(Data :: binary(), UpperHex :: binary(), LoweHex :: binary()) -> boolean().
+-spec check_hex_encoded(Data :: binary(), UpperHex :: binary(), LowerHex :: binary()) -> boolean().
check_hex_encoded(<<I1:4, I2:4, Ins/binary>>, <<U1:8, U2:8, UCs/binary>>, <<L1:8, L2:8, LCs/binary>>) ->
check_hex_chars_match(I1, U1, L1) andalso
check_hex_chars_match(I2, U2, L2) andalso
@@ -34,4 +863,41 @@ check_hex_encoded(_, _, _) ->
check_hex_chars_match(X, U, L) when X < 10 ->
(U =:= $0 + X) andalso (L =:= $0 + X);
check_hex_chars_match(X, U, L) ->
- (U =:= $A + X -10) andalso (L =:= $a + X -10).
+ (U =:= $A + X - 10) andalso (L =:= $a + X - 10).
+
+%% Determine whether the given binary consist of an even number of
+%% bytes, with each byte in one of the ranges of $0..$9, $a..$f, $A..$F
+is_hex_bin(<<>>) ->
+ true;
+is_hex_bin(<<C1, C2, Bin/binary>>) ->
+ is_hex_char(C1) andalso
+ is_hex_char(C2) andalso
+ is_hex_bin(Bin);
+is_hex_bin(_) ->
+ false.
+
+%% Check if the given byte is a hex digit
+is_hex_char(C) when C >= $0, C =< $9 ->
+ true;
+is_hex_char(C) when C >= $a, C =< $f ->
+ true;
+is_hex_char(C) when C >= $A, C =< $F ->
+ true;
+is_hex_char(_) ->
+ false.
+
+%% Returns whether an error exception was raised when the
+%% given function is applied to the given arguments
+expect_error(Fun, Args) when is_function(Fun, length(Args)) ->
+ try
+ erlang:apply(Fun, Args)
+ of
+ _ ->
+ false
+ catch
+ error:_ ->
+ true;
+ _:_ ->
+ false
+ end.
+
--
2.35.3