File 3904-Add-property-based-tests.patch of Package erlang

From 8b46593e97478451374c24f20ec633204fb78657 Mon Sep 17 00:00:00 2001
From: Dan Gudmundsson <dgud@erlang.org>
Date: Tue, 17 Feb 2026 10:21:12 +0100
Subject: [PATCH 4/5] Add property based tests

And remove comments with property tests in src.

Add missing simple regression tests of mapfold
---
 lib/stdlib/src/array.erl                     | 128 +----
 lib/stdlib/test/array_SUITE.erl              | 184 ++++++-
 lib/stdlib/test/property_test/array_prop.erl | 521 +++++++++++++++++++
 3 files changed, 702 insertions(+), 131 deletions(-)
 create mode 100644 lib/stdlib/test/property_test/array_prop.erl

diff --git a/lib/stdlib/src/array.erl b/lib/stdlib/src/array.erl
index 39b626b2e8..3cc3451d89 100644
--- a/lib/stdlib/src/array.erl
+++ b/lib/stdlib/src/array.erl
@@ -115,9 +115,6 @@ beyond the last set entry:
 -moduledoc(#{ authors => [~"Richard Carlsson <carlsson.richard@gmail.com>",
                           ~"Dan Gudmundsson <dgud@erix.ericsson.se>"] }).
 
-%% -define(PROPER_NO_IMPORTS, 1).
-%% -include_lib("proper/include/proper.hrl").
-
 %% Developers:
 %%
 %% The key to speed is to minimize the number of tests, on
@@ -873,17 +870,6 @@ to_list(Array) ->
     %% eqwalizer:ignore ambiguous_union
     foldr(fun (_I, V, A) -> [V|A] end, [], Array).
 
-%% -spec prop_to_list() -> term().
-%% prop_to_list() ->
-%%     ?FORALL(Low, proper_types:non_neg_integer(),
-%%             ?FORALL(Delta, proper_types:non_neg_integer(),
-%%                     begin
-%%                         L = lists:seq(0,Low+Delta),
-%%                         L =:= to_list(array:from_list(L))
-%%                     end)
-%%            ).
-
-
 -doc """
 Converts the array to a list, skipping default-valued entries.
 
@@ -1302,8 +1288,11 @@ See also `foldl/3`, `sparse_foldl/5`.
       Function :: fun((Index :: array_indx(), Value :: Type, Acc :: A) -> A),
       Array :: array(Type).
 
-foldl(Low, High, Function, Acc, #array{size = N, zero = Z, cache = C, cache_index = CI, elements = E, default = D, bits = S})
-  when is_integer(Low), Low >= 0, is_integer(High), is_function(Function, 3), is_integer(N), High < N, is_integer(Z), is_integer(CI), is_integer(S) ->
+foldl(Low, High, Function, Acc,
+      #array{size = N, zero = Z, cache = C, cache_index = CI, elements = E, default = D, bits = S})
+  when is_integer(Low), Low >= 0, is_integer(High), is_integer(N), High < N,
+       is_function(Function, 3),
+       is_integer(Z), is_integer(CI), is_integer(S) ->
     if Low =< High ->
             E1 = set_leaf(CI, S, E, C),
             foldl_1(Low + Z, High + Z, Low, S, E1, D, Function, Acc);
@@ -1313,17 +1302,6 @@ foldl(Low, High, Function, Acc, #array{size = N, zero = Z, cache = C, cache_inde
 foldl(_, _, _, _, _) ->
     erlang:error(badarg).
 
-%% -spec prop_foldl1() -> term().
-%% prop_foldl1() ->
-%%     Fun = fun(I,_X,A)-> [I|A] end,
-%%     ?FORALL(Low, proper_types:non_neg_integer(),
-%%             ?FORALL(Delta, proper_types:non_neg_integer(),
-%%                     begin
-%%                         Arr = array:from_list(lists:seq(0,Low+Delta)),
-%%                         #array{elements = E, default = D, bits = S} = Arr,
-%%                         lists:reverse(foldl_1(Low, Low+Delta, Low, S, E, D, Fun, [])) =:= lists:seq(Low, Low+Delta)
-%%                     end)
-%%            ).
 
 foldl_1(Low, High, Ix, S, ?EMPTY, D, F, A) ->
     foldl_4(Low, High, Ix, S, D, F, A);
@@ -1382,20 +1360,6 @@ foldl_6(Low, High, Ix, D, F, A) when Low =< High ->
 foldl_6(_Low, _High, _Ix, _D, _F, A) ->
     A.
 
-%% -spec prop_foldl4() -> term().
-%% prop_foldl4() ->
-%%     Fun = fun(I,_X,A)-> [I|A] end,
-%%     ?FORALL(Low, proper_types:non_neg_integer(),
-%%             ?FORALL(Delta, proper_types:non_neg_integer(),
-%%                     begin
-%%                         Arr = array:new(Low+Delta+1),
-%%                         #array{elements = E, default = D, bits = S} = Arr,
-%%                         S = E-4,
-%%                         lists:reverse(foldl_4(Low, Low+Delta, Low, S, D, Fun, [])) =:= lists:seq(Low, Low+Delta)
-%%                     end)
-%%            ).
-
-
 -doc """
 Folds the array elements using the specified function and initial accumulator
 value, skipping default-valued entries. The elements are visited in order from
@@ -1485,18 +1449,6 @@ foldr(Low, High, Function, Acc, #array{size = N, zero = Z, cache = C,
 foldr(_, _, _, _, _) ->
     erlang:error(badarg).
 
-%% -spec prop_foldr1() -> term().
-%% prop_foldr1() ->
-%%     Fun = fun(I,_X,A)-> [I|A] end,
-%%     ?FORALL(Low, proper_types:non_neg_integer(),
-%%             ?FORALL(Delta, proper_types:non_neg_integer(),
-%%                     begin
-%%                         Arr = array:from_list(lists:seq(0,Low+Delta)),
-%%                         #array{elements = E, default = D, bits = S} = Arr,
-%%                         foldr_1(Low, Low+Delta, Low+Delta, S, E, D, Fun, []) =:= lists:seq(Low, Low+Delta)
-%%                     end)
-%%            ).
-
 foldr_1(Low, High, Ix, S, ?EMPTY, D, F, A) ->
     foldr_4(Low, High, Ix, S, D, F, A);
 foldr_1(Low, High, Ix, 0, E, _D, F, A) ->
@@ -1550,20 +1502,6 @@ foldr_6(Low, High, Ix, D, F, A) when Low =< High ->
 foldr_6(_Low, _High, _Ix, _D, _F, A) ->
     A.
 
-%% -spec prop_foldr4() -> term().
-%% prop_foldr4() ->
-%%     Fun = fun(I,_X,A)-> [I|A] end,
-%%     ?FORALL(Low, proper_types:non_neg_integer(),
-%%             ?FORALL(Delta, proper_types:non_neg_integer(),
-%%                     begin
-%%                         Arr = array:new(Low+Delta+1),
-%%                         #array{elements = E, default = D, bits = S} = Arr,
-%%                         S = E-4,
-%%                         foldr_4(Low, Low+Delta, Low+Delta, S, D, Fun, []) =:= lists:seq(Low, Low+Delta)
-%%                     end)
-%%            ).
-
-
 -doc """
 Folds the array elements right-to-left using the specified function and initial
 accumulator value, skipping default-valued entries. The elements are visited in
@@ -1719,34 +1657,6 @@ unfold(S, _D) when S > 0 ->
 unfold(_S, D) ->
     ?NEW_LEAF(D).
 
-%% -spec prop_mapfoldl1() -> term().
-%% prop_mapfoldl1() ->
-%%     Fun = fun(I,X,A)-> {X+10000, [I|A]} end,
-%%     ?FORALL(From, proper_types:non_neg_integer(),
-%%         ?FORALL(Length, proper_types:non_neg_integer(),
-%%                 ?FORALL(Tail, proper_types:non_neg_integer(),
-%%                     begin
-%%                         Max = From+Length-1+Tail,
-%%                         Arr = array:from_list(lists:seq(0,Max)),
-%%                         {Arr1, L} = mapfoldl(From, From+Length-1, Fun, [], Arr),
-%%                         lists:reverse(L) =:= lists:seq(From, From+Length-1) andalso to_list(Arr1) =:= lists:seq(0, From-1) ++ lists:seq(10000+From, 10000+From+Length-1) ++ lists:seq(From+Length, Max)
-%%                     end))
-%%            ).
-
-%% -spec prop_mapfoldl4() -> term().
-%% prop_mapfoldl4() ->
-%%     Fun = fun(I,_,A)-> {I+10000, [I|A]} end,
-%%     ?FORALL(From, proper_types:non_neg_integer(),
-%%         ?FORALL(Length, proper_types:non_neg_integer(),
-%%                 ?FORALL(Tail, proper_types:non_neg_integer(),
-%%                     begin
-%%                         Max = From+Length-1+Tail,
-%%                         Arr = array:new(Max+1),
-%%                         {Arr1, L} = mapfoldl(From, From+Length-1, Fun, [], Arr),
-%%                         lists:reverse(L) =:= lists:seq(From, From+Length-1) andalso to_list(Arr1) =:= lists:duplicate(From, undefined) ++ lists:seq(10000+From, 10000+From+Length-1) ++ lists:duplicate(Tail, undefined)
-%%                     end))
-%%            ).
-
 
 -doc """
 Like `mapfoldl/3` but skips default-valued entries.
@@ -1926,34 +1836,6 @@ mapfoldr_3_1(Low, High, Ix, [E|Es], F, A, Es1) when Low =< High ->
 mapfoldr_3_1(_Low, _High, _Ix, Es, _F, A, Es1) ->
     {list_to_tuple(lists:reverse(Es, Es1)), A}.
 
-%% -spec prop_mapfoldr1() -> term().
-%% prop_mapfoldr1() ->
-%%     Fun = fun(I,X,A)-> {X+10000, [I|A]} end,
-%%     ?FORALL(From, proper_types:non_neg_integer(),
-%%         ?FORALL(Length, proper_types:non_neg_integer(),
-%%                 ?FORALL(Tail, proper_types:non_neg_integer(),
-%%                     begin
-%%                         Max = From+Length-1+Tail,
-%%                         Arr = array:from_list(lists:seq(0,Max)),
-%%                         {Arr1, L} = mapfoldr(From, From+Length-1, Fun, [], Arr),
-%%                         L =:= lists:seq(From, From+Length-1) andalso to_list(Arr1) =:= lists:seq(0, From-1) ++ lists:seq(10000+From, 10000+From+Length-1) ++ lists:seq(From+Length, Max)
-%%                     end))
-%%            ).
-
-%% -spec prop_mapfoldr4() -> term().
-%% prop_mapfoldr4() ->
-%%     Fun = fun(I,_,A)-> {I+10000, [I|A]} end,
-%%     ?FORALL(From, proper_types:non_neg_integer(),
-%%         ?FORALL(Length, proper_types:non_neg_integer(),
-%%                 ?FORALL(Tail, proper_types:non_neg_integer(),
-%%                     begin
-%%                         Max = From+Length-1+Tail,
-%%                         Arr = array:new(Max+1),
-%%                         {Arr1, L} = mapfoldr(From, From+Length-1, Fun, [], Arr),
-%%                         L =:= lists:seq(From, From+Length-1) andalso to_list(Arr1) =:= lists:duplicate(From, undefined) ++ lists:seq(10000+From, 10000+From+Length-1) ++ lists:duplicate(Tail, undefined)
-%%                     end))
-%%            ).
-
 
 -doc """
 Like `mapfoldr/3` but skips default-valued entries.
diff --git a/lib/stdlib/test/array_SUITE.erl b/lib/stdlib/test/array_SUITE.erl
index 35f86cc21b..703251e22d 100644
--- a/lib/stdlib/test/array_SUITE.erl
+++ b/lib/stdlib/test/array_SUITE.erl
@@ -54,8 +54,17 @@
          sparse_foldl_test/1,
          foldr_test/1,
          sparse_foldr_test/1,
+         mapfoldl_test/1,
          import_export/1,
-         doctests/1
+         doctests/1,
+         %% Property tests
+         prop_new/1, prop_is_array/1, prop_set_get/1, prop_size/1,
+         prop_sparse_size/1, prop_default/1, prop_fix_relax/1,
+         prop_resize/1, prop_reset/1, prop_to_list/1, prop_from_list/1,
+         prop_to_orddict/1, prop_from_orddict/1, prop_map/1,
+         prop_foldl/1, prop_foldr/1, prop_shift/1, prop_slice/1,
+         prop_append_prepend/1, prop_concat/1, prop_mapfoldl/1, prop_mapfoldr/1,
+         prop_sparse_mapfoldl/1, prop_sparse_mapfoldr/1
         ]).
 
 
@@ -68,7 +77,8 @@
          from_list/1, from_list/2, to_orddict/1, sparse_to_orddict/1,
          from_orddict/1, from_orddict/2, map/2, sparse_map/2, foldl/3,
          foldr/3, sparse_foldl/3, sparse_foldr/3, fix/1, relax/1, is_fix/1,
-         resize/1, resize/2]).
+         resize/1, resize/2, mapfoldl/3, mapfoldr/3, sparse_mapfoldl/3,
+         sparse_mapfoldr/3]).
 
 %%
 %% all/1
@@ -85,17 +95,38 @@ all() ->
      concat_test, to_orddict_test, sparse_to_orddict_test,
      from_orddict_test, map_test, sparse_map_test,
      foldl_test, sparse_foldl_test, foldr_test, sparse_foldr_test,
-     import_export, doctests].
+     mapfoldl_test,
+     import_export, doctests,
+     {group, property}].
 
 groups() ->
-    [].
-
-init_per_suite(Config) ->
-    Config.
+    [{property, [],
+      [prop_new, prop_is_array, prop_set_get, prop_size,
+       prop_sparse_size, prop_default, prop_fix_relax,
+       prop_resize, prop_reset, prop_to_list, prop_from_list,
+       prop_to_orddict, prop_from_orddict, prop_map,
+       prop_foldl, prop_foldr, prop_shift, prop_slice,
+       prop_append_prepend, prop_concat, prop_mapfoldl, prop_mapfoldr,
+       prop_sparse_mapfoldl, prop_sparse_mapfoldr]}].
+
+init_per_suite(Config0) ->
+    case ct_property_test:init_per_suite(Config0) of
+        Config when is_list(Config) ->
+            Config;
+        {skip, _} -> Config0;
+        Fail -> Fail
+    end.
 
 end_per_suite(_Config) ->
     ok.
 
+init_per_group(property, Config) ->
+    case proplists:get_value(property_test_tool, Config, none) of
+        none ->
+            {skip, "No known property based tool found"};
+        _ ->
+            Config
+    end;
 init_per_group(_GroupName, Config) ->
     Config.
 
@@ -536,7 +567,8 @@ append_test(_Config) ->
      ?assertEqual(6, array:get(5, append(6, fix(from_list(lists:seq(1,5)))))),
      ?assertEqual(5, array:get(4, append(6, from_list(lists:seq(1,5))))),
      ?assertEqual([1,2,3,4,5,6], to_list(append(6, from_list(lists:seq(1,5))))),
-     ?assertEqual(lists:seq(1,?LEAFSIZE+1), to_list(append(?LEAFSIZE+1, from_list(lists:seq(1,?LEAFSIZE)))))
+     ?assertEqual(lists:seq(1,?LEAFSIZE+1), to_list(append(?LEAFSIZE+1, from_list(lists:seq(1,?LEAFSIZE))))),
+     ?assertError(badarg, append(5, foo))
     ].
 
 concat_test(_Config) ->
@@ -753,6 +785,7 @@ foldl_test(_Config) ->
            end,
     [?assertError(badarg, foldl([], 0, new())),
      ?assertError(badarg, foldl([], 0, new(10))),
+     ?assertError(badarg, foldl([], 0, not_an_array)),
      ?assert(foldl(Count, 0, new()) =:= 0),
      ?assert(foldl(Count, 0, new(1)) =:= 1),
      ?assert(foldl(Count, 0, new(10)) =:= 10),
@@ -779,6 +812,7 @@ sparse_foldl_test(_Config) ->
            end,
     [?assertError(badarg, sparse_foldl([], 0, new())),
      ?assertError(badarg, sparse_foldl([], 0, new(10))),
+     ?assertError(badarg, sparse_foldl([], 0, not_an_array)),
      ?assert(sparse_foldl(Count, 0, new()) =:= 0),
      ?assert(sparse_foldl(Count, 0, new(1)) =:= 0),
      ?assert(sparse_foldl(Count, 0, new(10,{default,1})) =:= 0),
@@ -862,6 +896,58 @@ sparse_foldr_test(_Config) ->
                                    set(0,0,new())))))
     ].
 
+mapfoldl_test(_Config) ->
+    N0 = ?LEAFSIZE,
+    Inc = fun(_, X, Acc) -> {X+1, Acc+X} end,
+    Double = fun(K, X, Acc) -> {X*2, Acc+K} end,
+    
+    %% mapfoldl
+    ?assertError(badarg, mapfoldl([], 0, new())),
+    ?assertError(badarg, mapfoldl([], 0, not_an_array)),
+
+    ?assert(begin {A1, 0} = mapfoldl(Inc, 0, new()),
+                  to_list(A1) =:= [] end),
+    ?assert(begin {A3, 10} = mapfoldl(Inc, 0, from_list([1,2,3,4])),
+                  to_list(A3) =:= [2,3,4,5] end),
+    ?assert(begin {A4, 55} = mapfoldl(Inc, 0, from_list(lists:seq(0,10))),
+                  to_list(A4) =:= lists:seq(1,11) end),
+    ?assert(begin {A5, 45} = mapfoldl(Double, 0, from_list(lists:seq(1,10))),
+                  to_list(A5) =:= [2,4,6,8,10,12,14,16,18,20] end),
+    
+    %% mapfoldr
+    ?assertError(badarg, mapfoldr([], 0, new())),
+    ?assertError(badarg, mapfoldr([], 0, not_an_aray)),
+    ?assert(begin {A6, 0} = mapfoldr(Inc, 0, new()),
+                  to_list(A6) =:= [] end),
+    ?assert(begin {A7, 10} = mapfoldr(Inc, 0, from_list([1,2,3,4])),
+                  to_list(A7) =:= [2,3,4,5] end),
+    ?assert(begin {A8, 55} = mapfoldr(Inc, 0, from_list(lists:seq(0,10))),
+                  to_list(A8) =:= lists:seq(1,11) end),
+
+    %% sparse_mapfoldl
+    ?assertError(badarg, sparse_mapfoldl([], 0, new())),
+    ?assertError(badarg, sparse_mapfoldl([], 0, not_an_aray)),
+    ?assert(begin {A9, 0} = sparse_mapfoldl(Inc, 0, new()), to_list(A9) =:= [] end),
+    ?assert(begin {A11, 10} = sparse_mapfoldl(Inc, 0, from_list([0,1,2,3,4],0)),
+                  to_list(A11) =:= [0,2,3,4,5] end),
+    ?assert(begin {A12, 45} = sparse_mapfoldl(Inc, 0, from_list(lists:seq(0,9),0)),
+                  to_list(A12) =:= [0,2,3,4,5,6,7,8,9,10] end),
+    
+    %% sparse_mapfoldr
+    ?assertError(badarg, sparse_mapfoldr([], 0, new())),
+    ?assertError(badarg, sparse_mapfoldr([], 0, not_an_aray)),
+    ?assert(begin {A13, 0} = sparse_mapfoldr(Inc, 0, new()), to_list(A13) =:= [] end),
+    ?assert(begin {A14, 10} = sparse_mapfoldr(Inc, 0, from_list([0,1,2,3,4],0)),
+                  to_list(A14) =:= [0,2,3,4,5] end),
+    ?assert(begin {A15, 45} = sparse_mapfoldr(Inc, 0, from_list(lists:seq(0,9),0)),
+                  to_list(A15) =:= [0,2,3,4,5,6,7,8,9,10] end),
+    
+    %% Test with sparse array
+    ?assert(begin {A16, 2} = sparse_mapfoldl(Inc, 0, set(N0*2+1,1,set(0,1,new()))),
+            sparse_to_orddict(A16) =:= [{0,2},{N0*2+1,2}] end),
+    
+    ok.
+
 import_export(_Config) ->
     %% Some examples of usages
     FloatBin = << <<N:32/float-native>> || N <- lists:seq(1, 20000)>>,
@@ -890,3 +976,85 @@ import_export(_Config) ->
 
 doctests(Config) when is_list(Config) ->
     shell_docs:test(array, []).
+
+
+%%
+%% Property-based tests
+%%
+
+prop_new(Config) ->
+    do_proptest(prop_new, Config).
+
+prop_is_array(Config) ->
+    do_proptest(prop_is_array, Config).
+
+prop_set_get(Config) ->
+    do_proptest(prop_set_get, Config).
+
+prop_size(Config) ->
+    do_proptest(prop_size, Config).
+
+prop_sparse_size(Config) ->
+    do_proptest(prop_sparse_size, Config).
+
+prop_default(Config) ->
+    do_proptest(prop_default, Config).
+
+prop_fix_relax(Config) ->
+    do_proptest(prop_fix_relax, Config).
+
+prop_resize(Config) ->
+    do_proptest(prop_resize, Config).
+
+prop_reset(Config) ->
+    do_proptest(prop_reset, Config).
+
+prop_to_list(Config) ->
+    do_proptest(prop_to_list, Config).
+
+prop_from_list(Config) ->
+    do_proptest(prop_from_list, Config).
+
+prop_to_orddict(Config) ->
+    do_proptest(prop_to_orddict, Config).
+
+prop_from_orddict(Config) ->
+    do_proptest(prop_from_orddict, Config).
+
+prop_map(Config) ->
+    do_proptest(prop_map, Config).
+
+prop_foldl(Config) ->
+    do_proptest(prop_foldl, Config).
+
+prop_foldr(Config) ->
+    do_proptest(prop_foldr, Config).
+
+prop_shift(Config) ->
+    do_proptest(prop_shift, Config).
+
+prop_slice(Config) ->
+    do_proptest(prop_slice, Config).
+
+prop_append_prepend(Config) ->
+    do_proptest(prop_append_prepend, Config).
+
+prop_concat(Config) ->
+    do_proptest(prop_concat, Config).
+
+prop_mapfoldl(Config) ->
+    do_proptest(prop_mapfoldl, Config).
+
+prop_mapfoldr(Config) ->
+    do_proptest(prop_mapfoldr, Config).
+
+prop_sparse_mapfoldl(Config) ->
+    do_proptest(prop_sparse_mapfoldl, Config).
+
+prop_sparse_mapfoldr(Config) ->
+    do_proptest(prop_sparse_mapfoldr, Config).
+
+do_proptest(Prop, Config) ->
+    ct_property_test:quickcheck(
+        array_prop:Prop(),
+        Config).
diff --git a/lib/stdlib/test/property_test/array_prop.erl b/lib/stdlib/test/property_test/array_prop.erl
new file mode 100644
index 0000000000..8f2910aa33
--- /dev/null
+++ b/lib/stdlib/test/property_test/array_prop.erl
@@ -0,0 +1,521 @@
+%%
+%% %CopyrightBegin%
+%%
+%% SPDX-License-Identifier: Apache-2.0
+%%
+%% Copyright Ericsson AB 2026. 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(array_prop).
+
+-include_lib("common_test/include/ct_property_test.hrl").
+
+-export([prop_new/0, prop_is_array/0, prop_set_get/0, prop_size/0,
+         prop_sparse_size/0, prop_default/0, prop_fix_relax/0,
+         prop_resize/0, prop_reset/0, prop_to_list/0, prop_from_list/0,
+         prop_to_orddict/0, prop_from_orddict/0, prop_map/0,
+         prop_foldl/0, prop_foldr/0, prop_shift/0, prop_slice/0,
+         prop_append_prepend/0, prop_concat/0, prop_mapfoldl/0, prop_mapfoldr/0,
+         prop_sparse_mapfoldl/0, prop_sparse_mapfoldr/0]).
+
+%%%%%%%%%%%%%%%%%%
+%%% Generators %%%
+%%%%%%%%%%%%%%%%%%
+
+%% Generate a small non-negative integer for array indices
+small_nat() ->
+    ?LET(N, nat(), N rem 520).
+
+safe_any() ->  %% Just integers for now
+    int().
+
+%% Generate array with list model
+
+array_with_list() ->
+    ?LET(Type, safe_any(),
+         ?LET(Def, Type,
+              ?SIZED(Size, array_with_list(Size, Type, [], array:new({default, Def}))))).
+
+array_with_list(Type) ->
+    ?LET(Def, Type,
+         ?SIZED(Size, array_with_list(Size, Type, [], array:new({default, Def})))).
+
+array_with_list(0, _Type, List, A) ->
+    {List, A};
+array_with_list(N, Type, ListAcc, ArrAcc) ->
+    RC = fun({L, A}) -> array_with_list(N-1, Type, L, A) end,
+    oneof([ %% Set/append/prepend many at end/beginning
+            ?LET(List, list(Type),
+                 RC(array_list_append(List, ListAcc, ArrAcc))),
+            ?LET(List, list(Type),
+                 RC(array_list_set(List, ListAcc, ArrAcc))),
+            ?LET(List, list(Type),
+                 RC(array_list_prepend(List, ListAcc, ArrAcc))),
+            ?LET(List, list(Type),
+                 RC(array_list_concat(List, ListAcc, ArrAcc))),
+            %% Set and reset random position single
+            ?LET({I, V}, {small_nat(), Type},
+                 RC(array_list_set(I,V,ListAcc, ArrAcc))),
+            ?LET(I, small_nat(),
+                 RC(array_list_reset(I, ListAcc, ArrAcc))),
+            %% Resize, shift
+            ?LET(I, small_nat(),
+                 RC(array_list_resize(I, ListAcc, ArrAcc))),
+            ?LET(I, int(),
+                 RC(array_list_shift(I, ListAcc, ArrAcc)))
+          ]).
+
+array_list_append(List, ListAcc, ArrAcc) ->
+    try
+        ListAcc = array:to_list(ArrAcc), %% assert
+        L = ListAcc ++ List,
+        A = lists:foldl(fun array:append/2, ArrAcc, List),
+        L = array:to_list(A), %% assert
+        {L, A}
+    catch Class:Err:St ->
+            io:format("~w~n  ~w~n ~w~n", [List,ListAcc,ArrAcc]),
+            erlang:raise(Class, Err, St)
+    end.
+
+array_list_set(List, ListAcc, ArrAcc) ->
+    try
+        ListAcc = array:to_list(ArrAcc), %% assert
+        ArraySet = fun(V, A) -> array:set(array:size(A), V, A) end,
+        L = ListAcc ++ List,
+        A = lists:foldl(ArraySet, ArrAcc, List),
+        L = array:to_list(A), %% assert
+        {L, A}
+    catch Class:Err:St ->
+            io:format("~w~n  ~w~n ~w~n", [List,ListAcc,ArrAcc]),
+            erlang:raise(Class, Err, St)
+    end.
+
+array_list_prepend(List, ListAcc, ArrAcc) ->
+    try
+        ListAcc = array:to_list(ArrAcc), %% assert
+        L = List ++ ListAcc,
+        A = lists:foldl(fun array:prepend/2, ArrAcc, lists:reverse(List)),
+        L = array:to_list(A), %% assert
+        {L, A}
+    catch Class:Err:St ->
+            io:format("~w~n  ~w~n ~w~n", [List,ListAcc,ArrAcc]),
+            erlang:raise(Class, Err, St)
+    end.
+
+array_list_concat(List, ListAcc, ArrAcc) ->
+    try
+        ListAcc = array:to_list(ArrAcc), %% assert
+        L = List ++ ListAcc,
+        A = array:concat(array:from_list(List, array:default(ArrAcc)), ArrAcc),
+        L = array:to_list(A), %% assert
+        {L, A}
+    catch Class:Err:St ->
+            io:format("~w~n  ~w~n ~w~n", [List,ListAcc,ArrAcc]),
+            erlang:raise(Class, Err, St)
+    end.
+
+array_list_set(I, V, L0, A0) ->
+    try
+        L0 = array:to_list(A0), %% assert
+        Size = array:size(A0),
+        {L, A} =
+            if I < Size ->
+                    {L1, [_|L2]} = lists:split(I, L0),
+                    {L1 ++ [V|L2], array:set(I, V, A0)};
+               I == Size ->
+                    {L0 ++ [V], array:set(I, V, A0)};
+               true ->
+                    Def = array:default(A0),
+                    Idx = Size + (I rem (Size+17)),  %% Allow up to next bucket
+                    L2 = lists:duplicate(Idx-Size, Def) ++ [V],
+                    {L0++L2, array:set(Idx, V, A0)}
+            end,
+        L = array:to_list(A), %% assert
+        {L, A}
+    catch Class:Err:St ->
+            io:format("~w ~w~n  ~w~n ~w~n", [I,V,L0,A0]),
+            erlang:raise(Class, Err, St)
+    end.
+
+array_list_reset(I, L0, A0) ->
+    try
+        L0 = array:to_list(A0), %% assert
+        Size = array:size(A0),
+        {L,A} = if I < Size ->
+                        Def = array:default(A0),
+                        {L1, [_|L2]} = lists:split(I, L0),
+                        {L1 ++ [Def|L2], array:reset(I, A0)};
+                   true ->
+                        {L0, array:reset(I, A0)}
+                end,
+        L = array:to_list(A), %% assert
+        {L, A}
+    catch Class:Err:St ->
+            io:format("~w~n  ~w~n ~w~n", [I,L0,A0]),
+            erlang:raise(Class, Err, St)
+    end.
+
+array_list_resize(NewSz0, L0, A0) ->
+    try
+        L0 = array:to_list(A0), %% assert
+        Size = array:size(A0),
+        {L, A} =
+            if NewSz0 < Size ->
+                    {L1, _L2} = lists:split(NewSz0, L0),
+                    {L1, array:resize(NewSz0, A0)};
+               true ->
+                    %% Limit NewSize
+                    Extend = (NewSz0 rem (Size+17)),
+                    Def = array:default(A0),
+                    L1 = lists:duplicate(Extend, Def),
+                    {L0 ++ L1, array:resize(Size+Extend, A0)}
+            end,
+        L = array:to_list(A), %% assert
+        {L, A}
+    catch Class:Err:St ->
+            io:format("~w~n  ~w~n ~w~n", [NewSz0,L0,A0]),
+            erlang:raise(Class, Err, St)
+    end.
+
+array_list_shift(I0, L0, A0) ->
+    try
+        L0 = array:to_list(A0), %% assert
+        Size = array:size(A0),
+        Steps = if Size =< 1 ->
+                        -abs(I0 rem 10);
+                   true ->
+                        I1 = I0 rem (Size-1),
+                        I1 - Size div 2
+                end,
+        Atemp = array:shift(Steps, A0),  %% We need to reset entries in the array
+        {L1,A1} = case Steps >= 0 of     %% to be equal to a list after another shift
+                      true ->
+                          {lists:sublist(L0, Steps+1, Size-Steps),
+                           lists:foldl(fun array:reset/2, Atemp, lists:seq(Size-Steps, Size))};
+                      false ->
+                          {lists:duplicate(abs(Steps), array:default(A0)) ++ L0,
+                           lists:foldl(fun array:reset/2, Atemp, lists:seq(0, -Steps-1))}
+                  end,
+        {L1,Steps} = {array:to_list(A1), Steps}, %% Assert
+        {L1, A1}
+    catch Class:Err:St ->
+            io:format("~w~n  ~w~n ~w~n", [I0,L0,A0]),
+            erlang:raise(Class, Err, St)
+    end.
+
+%% Generate a simple array
+array_gen() ->
+    array_gen(safe_any()).
+
+array_gen(Type) ->
+    ?LET({List, Default},
+         {list(Type), Type},
+         array:from_list(List, Default)).
+
+%% Generate non-array term
+non_array() ->
+    ?SUCHTHAT(T, safe_any(), not array:is_array(T)).
+
+%%%%%%%%%%%%%%%%%%
+%%% Properties %%%
+%%%%%%%%%%%%%%%%%%
+
+prop_new() ->
+    array:is_array(array:new()).
+
+prop_is_array() ->
+    ?FORALL({IsArray, Term},
+            oneof([{true, array_gen()},
+                   {false, non_array()}]),
+            IsArray =:= array:is_array(Term)).
+
+prop_set_get() ->
+    ?FORALL({{List, A}, I, V},
+            {array_with_list(), small_nat(), safe_any()},
+            begin
+                case I < length(List) of
+                    true ->
+                        A1 = array:set(I, V, A),
+                        array:get(I, A1) =:= V;
+                    false ->
+                        %% Setting beyond size grows array
+                        A1 = array:relax(A),
+                        A2 = array:set(I, V, A1),
+                        array:get(I, A2) =:= V
+                end
+            end).
+
+prop_size() ->
+    ?FORALL({List, A}, array_with_list(),
+            array:size(A) =:= length(List)).
+
+prop_sparse_size() ->
+    ?FORALL({List, A}, array_with_list(),
+            begin
+                SparseSize = array:sparse_size(A),
+                Size = array:size(A),
+                Strip = fun(X) -> X == array:default(A) end,
+                Expected = lists:reverse(lists:dropwhile(Strip, lists:reverse(List))),
+                SparseSize >= 0 andalso SparseSize =< Size
+                    andalso SparseSize =:= length(Expected)
+            end).
+
+prop_default() ->
+    ?FORALL({Default, Size},
+            {safe_any(), small_nat()},
+            begin
+                A = array:new([{size, Size}, {default, Default}]),
+                array:default(A) =:= Default
+            end).
+
+prop_fix_relax() ->
+    ?FORALL(A, array_gen(),
+            begin
+                A1 = array:fix(A),
+                I = array:size(A),
+                try array:set(I, x, A1), false catch _:_ -> true end
+                    andalso array:is_fix(A1)
+                    andalso begin
+                                A2 = array:relax(A1),
+                                try array:set(I, x, A), true catch _:_ -> false end
+                                    andalso not array:is_fix(A2)
+                            end
+            end).
+
+prop_resize() ->
+    ?FORALL({A, NewSize},
+            {array_gen(), small_nat()},
+            ?TIMEOUT(60_000,
+                     begin
+                         A1 = array:resize(NewSize, A),
+                         array:size(A1) =:= NewSize andalso
+                             length(array:to_list(A1)) == NewSize
+                     end)).
+
+prop_reset() ->
+    ?FORALL({{List, A}, I},
+            {array_with_list(), small_nat()},
+            begin
+                case I < length(List) orelse not array:is_fix(A) of
+                    true ->
+                        Default = array:default(A),
+                        A1 = array:set(I, some_value, A),
+                        A2 = array:reset(I, A1),
+                        array:get(I, A2) =:= Default;
+                    false ->
+                        try array:reset(I, A), false catch _:_ -> true end
+                end
+            end).
+
+prop_to_list() ->
+    ?FORALL({List, A}, array_with_list(),
+            array:to_list(A) =:= List).
+
+prop_from_list() ->
+    ?FORALL(L, list(safe_any()),
+            begin
+                A = array:from_list(L),
+                array:is_array(A) andalso
+                    array:size(A) =:= length(L) andalso
+                    array:to_list(A) =:= L
+            end).
+
+prop_to_orddict() ->
+    ?FORALL({List, A}, array_with_list(),
+            begin
+                OD = array:to_orddict(A),
+                Expected = [{I, V} || I <:- lists:seq(0, length(List)-1) && V <:- List],
+                OD =:= Expected
+            end).
+
+prop_from_orddict() ->
+    ?FORALL(L, list({small_nat(), safe_any()}),
+            begin
+                OD = lists:ukeysort(1, L),
+                A = array:from_orddict(OD),
+                array:is_array(A) andalso
+                    array:sparse_to_orddict(A) =:= OD
+            end).
+
+prop_map() ->
+    ?FORALL({List, A}, array_with_list(),
+            begin
+                FL = fun(X) -> {mapped, X} end,
+                FA = fun(_I, X) -> {mapped, X} end,
+                A1 = array:map(FA, A),
+                Expected = lists:map(FL, List),
+                array:to_list(A1) =:= Expected
+            end).
+
+prop_foldl() ->
+    ?FORALL({{List, A}, I0, I1}, {array_with_list(int()), small_nat(), small_nat()},
+            case array:size(A) of
+                0 ->
+                    true;
+                Size ->
+                    Stop  = min(Size-1, max(I0, I1)),
+                    Start = min(Stop, min(I0, I1)),
+                    Sum1 = array:foldl(Start, Stop, fun(_I, V, Acc) -> Acc + V end, 0, A),
+                    Split = lists:sublist(List, Start+1, 1+Stop-Start),
+                    Sum2 = lists:foldl(fun(V, Acc) -> Acc + V end, 0, Split),
+
+                    Sum3 = array:foldl(fun(_I, V, Acc) -> Acc + V end, 0, A),
+                    Sum4 = lists:foldl(fun(V, Acc) -> Acc + V end, 0, List),
+                    Sum1 =:= Sum2 andalso Sum3 =:= Sum4
+            end).
+
+prop_foldr() ->
+    ?FORALL({{List, A}, I0, I1}, {array_with_list(int()), small_nat(), small_nat()},
+            case array:size(A) of
+                0 ->
+                    true;
+                Size ->
+                    Stop  = min(Size-1, max(I0, I1)),
+                    Start = min(Stop, min(I0, I1)),
+                    Sum1 = array:foldr(Start, Stop, fun(_I, V, Acc) -> Acc + V end, 0, A),
+                    Split = lists:sublist(List, Start+1, 1+Stop-Start),
+                    Sum2 = lists:foldr(fun(V, Acc) -> Acc + V end, 0, Split),
+
+                    Sum3 = array:foldr(fun(_I, V, Acc) -> Acc + V end, 0, A),
+                    Sum4 = lists:foldr(fun(V, Acc) -> Acc + V end, 0, List),
+                    Sum1 =:= Sum2 andalso Sum3 =:= Sum4
+            end).
+
+prop_shift() ->
+    ?FORALL({{List, A}, Steps},
+            {array_with_list(), ?LET(N, int(), N rem 20)},
+            begin
+                Size = length(List),
+                case Steps =< Size of
+                    true ->
+                        Expected =
+                            case Steps > 0 of
+                                true ->
+                                    lists:sublist(List, Steps+1, Size-Steps);
+                                false ->
+                                    lists:duplicate(abs(Steps), array:default(A)) ++ List
+                            end,
+                        A1 = array:shift(Steps, A),
+                        array:is_array(A1) andalso
+                            array:size(A1) =:= max(0, Size - Steps) andalso
+                            array:to_list(A1) =:= Expected;
+                    false ->
+                        true
+                end
+            end).
+
+prop_slice() ->
+    ?FORALL({{List, A}, I, Len},
+            {array_with_list(), small_nat(), small_nat()},
+            begin
+                Size = length(List),
+                case I < Size of
+                    true ->
+                        ActualLen = min(Len, Size - I),
+                        A1 = array:slice(I, ActualLen, A),
+                        Expected = lists:sublist(List, I+1, ActualLen),
+                        array:to_list(A1) =:= Expected;
+                    false ->
+                        true
+                end
+            end).
+
+prop_append_prepend() ->
+    ?FORALL({{List, A}, V},
+            {array_with_list(), safe_any()},
+            begin
+                A1 = array:append(V, A),
+                array:to_list(A1) =:= List ++ [V] andalso
+                    begin
+                        A2 = array:prepend(V, A),
+                        array:to_list(A2) =:= [V | List]
+                    end
+            end).
+
+prop_concat() ->
+    ?FORALL({{List1, A1}, {List2, A2}},
+            {array_with_list(), array_with_list()},
+            begin
+                A3 = array:concat(A1, A2),
+                array:to_list(A3) =:= List1 ++ List2
+            end).
+
+prop_mapfoldl() ->
+    ?FORALL({List, A}, array_with_list(int()),
+            begin
+                F = fun(_I, V, Acc) -> {{mapped, V}, Acc + V} end,
+                {A1, Sum1} = array:mapfoldl(F, 0, A),
+                Sum2 = lists:sum(List),
+                Expected = lists:map(fun(V) -> {mapped, V} end, List),
+                array:to_list(A1) =:= Expected andalso Sum1 =:= Sum2
+            end).
+
+prop_mapfoldr() ->
+    ?FORALL({List, A}, array_with_list(int()),
+            begin
+                F = fun(_I, V, Acc) -> {{mapped, V}, Acc + V} end,
+                {A1, Sum1} = array:mapfoldr(F, 0, A),
+                Sum2 = lists:sum(List),
+                Expected = lists:map(fun(V) -> {mapped, V} end, List),
+                array:to_list(A1) =:= Expected andalso Sum1 =:= Sum2
+            end).
+
+prop_sparse_mapfoldl() ->
+    ?FORALL({{List, A}, I0, I1},
+            {array_with_list(int()), small_nat(), small_nat()},
+            case array:size(A) of
+                0 ->
+                    true;
+                Size ->
+                    High = min(Size-1, max(I0, I1)),
+                    Low = min(High, min(I0, I1)),
+
+                    F = fun(_I, V, Acc) -> {{mapped, V}, Acc + V} end,
+                    {A1, Sum1} = array:sparse_mapfoldl(Low, High, F, 0, A),
+                    SubList = lists:sublist(List, Low+1, 1+High-Low),
+                    Def = array:default(A1),
+                    F2 = fun(V, Acc) -> case V == Def of
+                                            false -> {{mapped, V}, Acc + V};
+                                            true  -> {V, Acc}
+                                        end
+                         end,
+                    {ExpectedSub, Sum2} = lists:mapfoldl(F2, 0, SubList),
+                    ResultSub = lists:sublist(array:to_list(A1), Low+1, High-Low+1),
+                    ResultSub =:= ExpectedSub andalso Sum1 =:= Sum2
+            end).
+
+prop_sparse_mapfoldr() ->
+    ?FORALL({{List, A}, I0, I1},
+            {array_with_list(int()), small_nat(), small_nat()},
+            case array:size(A) of
+                0 ->
+                    true;
+                Size ->
+                    High = min(Size-1, max(I0, I1)),
+                    Low = min(High, min(I0, I1)),
+                    F = fun(_I, V, Acc) -> {{mapped, V}, Acc + V} end,
+                    {A1, Sum1} = array:sparse_mapfoldr(Low, High, F, 0, A),
+                    SubList = lists:sublist(List, Low+1, High-Low+1),
+                    Def = array:default(A1),
+                    F2 = fun(V, Acc) -> case V == Def of
+                                            false -> {{mapped, V}, Acc + V};
+                                            true  -> {V, Acc}
+                                        end
+                         end,
+                    {ExpectedSub, Sum2} = lists:mapfoldr(F2, 0, SubList),
+                    ResultSub = lists:sublist(array:to_list(A1), Low+1, High-Low+1),
+                    ResultSub =:= ExpectedSub andalso Sum1 =:= Sum2
+            end).
-- 
2.51.0

openSUSE Build Service is sponsored by