File 4121-Property-based-tests-for-the-base64-module.patch of Package erlang

From c43bac27631e9bc56732b76d442e3d133ebcd742 Mon Sep 17 00:00:00 2001
From: Maria Scott <maria-12648430@hnc-agency.org>
Date: Mon, 16 May 2022 21:27:46 +0200
Subject: [PATCH] Property-based tests for the base64 module

---
 lib/stdlib/test/Makefile                      |   1 +
 .../test/base64_property_test_SUITE.erl       |  86 ++++
 lib/stdlib/test/property_test/base64_prop.erl | 429 ++++++++++++++++++
 3 files changed, 516 insertions(+)
 create mode 100644 lib/stdlib/test/base64_property_test_SUITE.erl
 create mode 100644 lib/stdlib/test/property_test/base64_prop.erl

diff --git a/lib/stdlib/test/Makefile b/lib/stdlib/test/Makefile
index 9e6df4c542..f030235046 100644
--- a/lib/stdlib/test/Makefile
+++ b/lib/stdlib/test/Makefile
@@ -8,6 +8,7 @@ include $(ERL_TOP)/make/$(TARGET)/otp.mk
 MODULES= \
 	array_SUITE \
 	base64_SUITE \
+	base64_property_test_SUITE \
 	beam_lib_SUITE \
 	binary_module_SUITE \
 	binref \
diff --git a/lib/stdlib/test/base64_property_test_SUITE.erl b/lib/stdlib/test/base64_property_test_SUITE.erl
new file mode 100644
index 0000000000..3802b68ce3
--- /dev/null
+++ b/lib/stdlib/test/base64_property_test_SUITE.erl
@@ -0,0 +1,86 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2021. 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(base64_property_test_SUITE).
+
+-include_lib("common_test/include/ct.hrl").
+-compile([export_all, nowarn_export_all]).
+
+all() ->
+    [
+     encode_case,
+     encode_to_string_case,
+     decode_case,
+     decode_malformed_case,
+     decode_noisy_case,
+     decode_to_string_case,
+     decode_to_string_malformed_case,
+     decode_to_string_noisy_case,
+     mime_decode_case,
+     mime_decode_malformed_case,
+     mime_decode_to_string_case,
+     mime_decode_to_string_malformed_case
+    ].
+
+init_per_suite(Config) ->
+    ct_property_test:init_per_suite(Config).
+
+end_per_suite(Config) ->
+    Config.
+
+encode_case(Config) ->
+    do_proptest(prop_encode, Config).
+
+encode_to_string_case(Config) ->
+    do_proptest(prop_encode_to_string, Config).
+
+decode_case(Config) ->
+    do_proptest(prop_decode, Config).
+
+decode_malformed_case(Config) ->
+    do_proptest(prop_decode_malformed, Config).
+
+decode_noisy_case(Config) ->
+    do_proptest(prop_decode_noisy, Config).
+
+decode_to_string_case(Config) ->
+    do_proptest(prop_decode_to_string, Config).
+
+decode_to_string_malformed_case(Config) ->
+    do_proptest(prop_decode_to_string_malformed, Config).
+
+decode_to_string_noisy_case(Config) ->
+    do_proptest(prop_decode_to_string_noisy, Config).
+
+mime_decode_case(Config) ->
+    do_proptest(prop_mime_decode, Config).
+
+mime_decode_malformed_case(Config) ->
+    do_proptest(prop_mime_decode_malformed, Config).
+
+mime_decode_to_string_case(Config) ->
+    do_proptest(prop_mime_decode_to_string, Config).
+
+mime_decode_to_string_malformed_case(Config) ->
+    do_proptest(prop_mime_decode_to_string_malformed, Config).
+
+do_proptest(Prop, Config) ->
+    ct_property_test:quickcheck(
+        base64_prop:Prop(),
+        Config).
diff --git a/lib/stdlib/test/property_test/base64_prop.erl b/lib/stdlib/test/property_test/base64_prop.erl
new file mode 100644
index 0000000000..6ab7e4c68a
--- /dev/null
+++ b/lib/stdlib/test/property_test/base64_prop.erl
@@ -0,0 +1,429 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2021. 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(base64_prop).
+
+-compile([export_all, nowarn_export_all]).
+
+-proptest(eqc).
+-proptest([triq, proper]).
+
+-ifndef(EQC).
+-ifndef(PROPER).
+-ifndef(TRIQ).
+-define(EQC, true).
+-endif.
+-endif.
+-endif.
+
+-ifdef(EQC).
+-include_lib("eqc/include/eqc.hrl").
+-define(MOD_eqc,eqc).
+
+-else.
+-ifdef(PROPER).
+-include_lib("proper/include/proper.hrl").
+-define(MOD_eqc,proper).
+
+-else.
+-ifdef(TRIQ).
+-define(MOD_eqc,triq).
+-include_lib("triq/include/triq.hrl").
+
+-endif.
+-endif.
+-endif.
+
+%%%%%%%%%%%%%%%%%%
+%%% Properties %%%
+%%%%%%%%%%%%%%%%%%
+
+prop_encode() ->
+    ?FORALL(
+        Str,
+        oneof([list(byte()), binary()]),
+        begin
+            Enc = base64:encode(Str),
+            Dec = base64:decode(Enc),
+            is_b64_binary(Enc) andalso str_equals(Str, Dec)
+        end
+    ).
+
+prop_encode_to_string() ->
+    ?FORALL(
+        Str,
+        oneof([list(byte()), binary()]),
+        begin
+            Enc = base64:encode_to_string(Str),
+            Dec = base64:decode_to_string(Enc),
+            is_b64_string(Enc) andalso str_equals(Str, Dec)
+        end
+    ).
+
+prop_decode() ->
+    ?FORALL(
+        {NormalizedB64, WspedB64},
+        wsped_b64(),
+        begin
+            Dec = base64:decode(WspedB64),
+            Enc = base64:encode(Dec),
+            is_binary(Dec) andalso b64_equals(NormalizedB64, Enc)
+        end
+    ).
+
+prop_decode_malformed() ->
+    common_decode_malformed(wsped_b64(), fun base64:decode/1).
+
+prop_decode_noisy() ->
+    common_decode_noisy(fun base64:decode/1).
+
+prop_decode_to_string() ->
+    ?FORALL(
+        {NormalizedB64, WspedB64},
+        wsped_b64(),
+        begin
+            Dec = base64:decode_to_string(WspedB64),
+            Enc = base64:encode(Dec),
+            is_bytelist(Dec) andalso b64_equals(NormalizedB64, Enc)
+        end
+    ).
+
+prop_decode_to_string_malformed() ->
+    common_decode_malformed(wsped_b64(), fun base64:decode_to_string/1).
+
+prop_decode_to_string_noisy() ->
+    common_decode_noisy(fun base64:decode_to_string/1).
+
+prop_mime_decode() ->
+    ?FORALL(
+        {NormalizedB64, NoisyB64},
+        noisy_b64(),
+        begin
+            Dec = base64:mime_decode(NoisyB64),
+            Enc = base64:encode(Dec),
+            is_binary(Dec) andalso b64_equals(NormalizedB64, Enc)
+        end
+    ).
+
+prop_mime_decode_malformed() ->
+    common_decode_malformed(noisy_b64(), fun base64:mime_decode/1).
+
+prop_mime_decode_to_string() ->
+    ?FORALL(
+        {NormalizedB64, NoisyB64},
+        noisy_b64(),
+        begin
+            Dec = base64:mime_decode_to_string(NoisyB64),
+            Enc = base64:encode(Dec),
+            is_bytelist(Dec) andalso b64_equals(NormalizedB64, Enc)
+        end
+    ).
+
+prop_mime_decode_to_string_malformed() ->
+    common_decode_malformed(noisy_b64(), fun base64:mime_decode_to_string/1).
+
+common_decode_noisy(Fn) ->
+    ?FORALL(
+        {_, NoisyB64},
+        ?SUCHTHAT({NormalizedB64, NoisyB64}, noisy_b64(), NormalizedB64 =/= NoisyB64),
+        try
+            Fn(NoisyB64)
+        of
+            _ ->
+                false
+        catch
+            error:_ ->
+                true
+        end
+    ).
+
+common_decode_malformed(Gen, Fn) ->
+    ?FORALL(
+        MalformedB64,
+        ?LET(
+            {{NormalizedB64, NoisyB64}, Malformings},
+            {
+                Gen,
+                oneof(
+                    [
+                        [b64_char()],
+                        [b64_char(), b64_char()],
+                        [b64_char(), b64_char(), b64_char()]
+                    ]
+                )
+            },
+            {NormalizedB64, insert_noise(NoisyB64, Malformings)}
+        ),
+        try
+            Fn(MalformedB64)
+        of
+            _ ->
+                false
+        catch
+            error:_ ->
+                true
+        end
+    ).
+
+%%%%%%%%%%%%%%%%%%
+%%% Generators %%%
+%%%%%%%%%%%%%%%%%%
+
+%% Generate a single character from the base64 alphabet.
+b64_char() ->
+    oneof(b64_chars()).
+
+%% Generate a string of characters from the base64 alphabet,
+%% including padding if needed.
+b64_string() ->
+    ?LET(
+        {L, Filler},
+        {list(b64_char()), b64_char()},
+        case length(L) rem 4 of
+            0 -> L;
+            1 -> L ++ [Filler, $=, $=];
+            2 -> L ++ [$=, $=];
+            3 -> L ++ [$=]
+        end
+    ).
+
+%% Generate a binary of characters from the base64 alphabet,
+%% including padding if needed.
+b64_binary() ->
+    ?LET(
+        L,
+        b64_string(),
+        list_to_binary(L)
+    ).
+
+%% Generate a string or binary of characters from the
+%% base64 alphabet, including padding if needed.
+b64() ->
+    oneof([b64_string(), b64_binary()]).
+
+%% Generate a string or binary of characters from the
+%% base64 alphabet, including padding if needed, with
+%% whitespaces inserted at random indexes.
+wsped_b64() ->
+    ?LET(
+        {B64, Wsps},
+        {b64(), list(oneof([$\t, $\r, $\n, $\s]))},
+        {B64, insert_noise(B64, Wsps)}
+    ).
+
+%% Generate a single character outside of the base64 alphabet.
+%% As whitespaces are allowed but ignored in base64, this generator
+%% will produce no whitespaces, either.
+non_b64_char() ->
+    oneof(lists:seq(16#00, 16#FF) -- b64_allowed_chars()).
+
+%% Generate a string or binary of characters from the
+%% base64 alphabet, including padding if needed, with
+%% whitespaces and non-base64 ("invalid") characters
+%% inserted at random indexes.
+noisy_b64() ->
+    ?LET(
+        {{B64, WspedB64}, Noise},
+        {wsped_b64(), non_empty(list(non_b64_char()))},
+        {B64, insert_noise(WspedB64, Noise)}
+    ).
+
+%%%%%%%%%%%%%%%
+%%% Helpers %%%
+%%%%%%%%%%%%%%%
+
+%% The characters of the base64 alphabet.
+%% "=" is not included, as it is special in that it
+%% may only appear at the end of a base64 encoded string
+%% for padding.
+b64_chars() ->
+    lists:seq($0, $9) ++
+    lists:seq($a, $z) ++
+    lists:seq($A, $Z) ++
+    [$+, $/].
+
+%% In addition to the above, the whitespace characters
+%% HTAB, CR, LF and SP are allowed to appear in a base64
+%% encoded string and should be ignored.
+b64_allowed_chars() ->
+    [$\t, $\r, $\n, $\s | b64_chars()].
+
+%% Insert the given list of noise characters at random
+%% places into the given base64 string.
+insert_noise(B64, []) ->
+    B64;
+insert_noise([], Noise) ->
+    Noise;
+insert_noise(<<>>, Noise) ->
+    list_to_binary(Noise);
+insert_noise([B|Bs] = B64, [N|Ns] = Noise) ->
+    case rand:uniform(2) of
+        1 ->
+            [B|insert_noise(Bs, Noise)];
+        2 ->
+            [N|insert_noise(B64, Ns)]
+    end;
+insert_noise(<<B, Bs/binary>> = B64, [N|Ns] = Noise) ->
+    case rand:uniform(2) of
+        1 ->
+            <<B, (insert_noise(Bs, Noise))/binary>>;
+        2 ->
+            <<N, (insert_noise(B64, Ns))/binary>>
+    end.
+
+%% Check if the given character is in the base64 alphabet.
+%% This does not include the padding character "=".
+is_b64_char($+) ->
+    true;
+is_b64_char($/) ->
+    true;
+is_b64_char(C) when C >= $0, C =< $9 ->
+    true;
+is_b64_char(C) when C >= $A, C =< $Z ->
+    true;
+is_b64_char(C) when C >= $a, C =< $z ->
+    true;
+is_b64_char(_) ->
+    false.
+
+%% Check if the given argument is a base64 binary,
+%% ie that it consists of quadruplets of characters
+%% from the base64 alphabet, whereas the last quadruplet
+%% may be padded with one or two "="s
+is_b64_binary(B) ->
+    is_b64_binary(B, 0).
+
+is_b64_binary(<<>>, N) ->
+    N rem 4 =:= 0;
+is_b64_binary(<<$=>>, N) ->
+    N rem 4 =:= 3;
+is_b64_binary(<<$=, $=>>, N) ->
+    N rem 4 =:= 2;
+is_b64_binary(<<C, More/binary>>, N) ->
+    case is_b64_char(C) of
+        true ->
+            is_b64_binary(More, N + 1);
+        false ->
+            false
+    end.
+
+%% Check if the given argument is a base64 string
+%% (see is_b64_binary/1)
+is_b64_string(S) ->
+    is_b64_binary(list_to_binary(S)).
+
+%% Check if the argument is a list of bytes.
+is_bytelist(L) ->
+    lists:all(
+        fun (B) ->
+            is_integer(B) andalso B >= 16#00 andalso B =< 16#FF
+        end,
+        L
+    ).
+
+%% Check two byte-lists or binaries for equality.
+str_equals(Str1, Str2) when is_list(Str1) ->
+    str_equals(list_to_binary(Str1), Str2);
+str_equals(Str1, Str2) when is_list(Str2) ->
+    str_equals(Str1, list_to_binary(Str2));
+str_equals(Str1, Str2) when is_binary(Str1), is_binary(Str2) ->
+    Str1 =:= Str2.
+
+%% Check two base64-encoded byte-lists or binaries for equality.
+%% Assumes that the given arguments are in a normalized form,
+%% ie that they consist only of characters from the base64
+%% alphabet and possible padding ("=").
+b64_equals(L, B) when is_list(L) ->
+    b64_equals(list_to_binary(L), B);
+b64_equals(B, L) when is_list(L) ->
+    b64_equals(B, list_to_binary(L));
+b64_equals(B1, B2) when is_binary(B1), is_binary(B2) ->
+    b64_equals1(B1, B2).
+
+b64_equals1(<<Eq:4/bytes>>, <<Eq:4/bytes>>) ->
+    is_b64_binary(Eq);
+b64_equals1(<<Eq:4/bytes, More1/binary>>, <<Eq:4/bytes, More2/binary>>) ->
+    case lists:all(fun is_b64_char/1, binary_to_list(Eq)) of
+        true ->
+            b64_equals1(More1, More2);
+        false ->
+            false
+    end;
+b64_equals1(<<Eq, B1, $=, $=>>, <<Eq, B2, $=, $=>>) ->
+    %% If the encoded string ends with "==", there exist multiple
+    %% possibilities for the character preceding the "==" as only the
+    %% 3rd and 4th bits of the encoded byte represented by that
+    %% character are significant.
+    %%
+    %% For example, all of the encoded strings "QQ==", "QR==", ..., "QZ=="
+    %% decode to the string "A", since all the bytes represented by Q to Z
+    %% are the same in the significant 3rd and 4th bit.
+    case is_b64_char(Eq) of
+        true ->
+            Normalize = fun
+                (C) when C >= $A, C =< $P -> $A;
+                (C) when C >= $Q, C =< $Z -> $Q;
+                (C) when C >= $a, C =< $f -> $Q;
+                (C) when C >= $g, C =< $v -> $g;
+                (C) when C >= $w, C =< $z -> $w;
+                (C) when C >= $0, C =< $9 -> $w;
+                ($+) -> $w;
+                ($/) -> $w
+            end,
+            Normalize(B1) =:= Normalize(B2);
+        false ->
+            false
+    end;
+b64_equals1(<<Eq:2/bytes, B1, $=>>, <<Eq:2/bytes, B2, $=>>) ->
+    %% Similar to the above, but with the encoded string ending with a
+    %% single "=" the 3rd to 6th bits of the encoded byte are significant,
+    %% such that, for example, all the encoded strings "QUE=" to "QUH="
+    %% decode to the same string "AA".
+    <<Eq1, Eq2>> = Eq,
+    case is_b64_char(Eq1) andalso is_b64_char(Eq2) of
+        true ->
+            Normalize = fun
+                (C) when C >= $A, C =< $D -> $A;
+                (C) when C >= $E, C =< $H -> $E;
+                (C) when C >= $I, C =< $L -> $I;
+                (C) when C >= $M, C =< $P -> $M;
+                (C) when C >= $Q, C =< $T -> $Q;
+                (C) when C >= $U, C =< $X -> $U;
+                (C) when C >= $Y, C =< $Z -> $Y;
+                (C) when C >= $a, C =< $b -> $Y;
+                (C) when C >= $c, C =< $f -> $c;
+                (C) when C >= $g, C =< $j -> $g;
+                (C) when C >= $k, C =< $n -> $k;
+                (C) when C >= $o, C =< $r -> $o;
+                (C) when C >= $s, C =< $v -> $s;
+                (C) when C >= $w, C =< $z -> $w;
+                (C) when C >= $0, C =< $3 -> $0;
+                (C) when C >= $4, C =< $7 -> $4;
+                (C) when C >= $8, C =< $9 -> $8;
+                ($+) -> $8;
+                ($/) -> $8
+            end,
+            Normalize(B1) =:= Normalize(B2);
+        false ->
+            false
+    end;
+b64_equals1(<<>>, <<>>) ->
+    true;
+b64_equals1(_, _) ->
+    false.
-- 
2.35.3

openSUSE Build Service is sponsored by