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

openSUSE Build Service is sponsored by