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