To be a more productive reader when rereading a book, it is very convenient to create small rebar3 projects based on books’ samples and ideas. Here’s what I’ve already made.
Exercise 2
I add output and then run it to see it execute.
There is peace of my code with output:
increments([Head | Tail]) -> increments(Head, Tail).
increments(_, []) ->
io:format("~n"),
true;
increments(N, [Head | Tail]) when Head == N + 1 ->
io:format("~p", [Head]),
if length(Tail) > 0 -> io:format(", ");
true -> io:format("|")
end,
increments(Head, Tail);
increments(_, _) -> false.
Erlang
(page 88)
code/CustomGenerators/erlang/pbt/test/prop_generators.erl
I would like to share inner step calculation while gathering values (by groups of 10). I was confused a litter how is the calculation going.
I have inserted an expression to print the parameters.
prop_collect2() ->
?FORALL(Bin, (binary()),
(collect(to_range(10, byte_size(Bin)),
is_binary(Bin)))).
%%%%%%%%%%%%%%%
%%% Helpers %%%
%%%%%%%%%%%%%%%
to_range(M, N) ->
Base = N div M,
io:format("~2B = ~2B div ~B | {~2B, ~B} = {~2B * ~B, (~B + 1)*~B~n",
[Base, N, M, Base * M, (Base + 1) * M, Base, M, Base, M]),
{Base * M, (Base + 1) * M}.
Command:
rebar3 proper -p prop_collect2
Output (with the intermediate calculations):
0 = 0 div 10 | { 0, 10} = { 0 * 10, (0 + 1)*10
. 0 = 1 div 10 | { 0, 10} = { 0 * 10, (0 + 1)*10
. 0 = 0 div 10 | { 0, 10} = { 0 * 10, (0 + 1)*10
. 0 = 0 div 10 | { 0, 10} = { 0 * 10, (0 + 1)*10
...
. 3 = 38 div 10 | {30, 40} = { 3 * 10, (3 + 1)*10
. 3 = 33 div 10 | {30, 40} = { 3 * 10, (3 + 1)*10
. 1 = 15 div 10 | {10, 20} = { 1 * 10, (1 + 1)*10
. 2 = 22 div 10 | {20, 30} = { 2 * 10, (2 + 1)*10
OK: Passed 100 test(s).
56.00% {0,10}
28.00% {10,20}
9.00% {20,30}
7.00% {30,40}
===>
1/1 properties passed
Erlang
(page 89)
code/CustomGenerators/erlang/pbt/test/prop_generators.erl
While running property based test prop_dupes
you will get the same logging info.
prop_dupes() ->
?FORALL(KV, list({key(), val()}),
begin
M = maps:from_list(KV),
_ = [maps:get(K, M) || {K, _V} <- KV], % crash if K's not in map
collect(
{dupes, to_range(5, length(KV) - length(lists:ukeysort(1,KV)))},
true
)
end).
%%%%%%%%%%%%%%%
%%% Helpers %%%
%%%%%%%%%%%%%%%
key() -> integer().
val() -> term().
to_range(M, N) ->
Base = N div M,
io:format("~2B = ~2B div ~B | {~2B, ~2B} = {~2B * ~B, (~B + 1)*~B~n",
[Base, N, M, Base * M, (Base + 1) * M, Base, M, Base, M]),
{Base * M, (Base + 1) * M}.
Full code project
Erlang
(page 91)
code/CustomGenerators/erlang/pbt/test/prop_generators.erl
I have compared results alternative PropEr generators - oneof, union and elements. Here’s what I noticed. The first two generators after 10 launches in the resulting report give 3-4 lines, and the third only 3.
Source code projects:
-
ex05_oneof
82.00% {dupes,{0,5}}
11.00% {dupes,{5,10}}
5.00% {dupes,{10,15}}
2.00% {dupes,{15,20}}
===>
1/1 properties passed -
ex06_union
83.00% {dupes,{0,5}}
10.00% {dupes,{5,10}}
5.00% {dupes,{10,15}}
2.00% {dupes,{15,20}}
===>
1/1 properties passed -
ex07_elements
82.00% {dupes,{0,5}}
13.00% {dupes,{5,10}}
5.00% {dupes,{10,15}}
===>
1/1 properties passed
Erlang
(page 92)
code/CustomGenerators/erlang/pbt/test/prop_generators.erl
prop_aggregate() ->
Suits = [club, diamond, heart, spade],
?FORALL(Hand, (vector(5, {oneof(Suits), choose(1, 13)})),
begin
% io:format("~p~n", [Hand]),
aggregate(Hand, true)
end). % `true' makes it always pass
If you would like to know what info contains Hand
variable, just uncomment logging line.
Note that what kind of PropEr API here is. There are generators and wrapper that can be used to collect statistics on the distribution of test data:
- Generators:
– vector/2
– oneof/1
– choose/2 - Wrapper
– aggregate/2
Erlang
(page 97)
code/CustomGenerators/erlang/pbt/test/prop_generators.erl
To read more about resize(Size, Generator)
function, please go to the documentation page.
Erlang
(page 98)
code/CustomGenerators/erlang/pbt/test/prop_generators.erl
Note that in the property prop_profile1
proplists module is used.
Erlang
(page 99)
code/CustomGenerators/erlang/pbt/test/prop_generators.erl
Note that macros ?SIZED(VarName, Expression)
is contained in the include/proper_common.hrl
file.
It is proper_types:sized(fun(VarName) -> Gen end))
function call.
Erlang
(page 100)
code/CustomGenerators/erlang/pbt/test/prop_generators.erl
Note that when you lounch propterties in a command line there should be no spaces between names in the property name list -p prop_profile1,prop_profile2
:
$ rebar3 proper -m prop_generators -p prop_profile1,prop_profile2
Otherwise, you will receive an error message with the following content:
===> Property [] does not belong to any module in ["prop_generators"]
Erlang
(page 100)
code/CustomGenerators/erlang/pbt/test/prop_generators.erl
Let’s take a closer look at the expression ?SIZED(Size,resize(min(100,Size)*35,string()))
.
Note that there is erlang:min
function is used. To ensure maximum size without imposing the maximum value you can use in the erlang:max
function:
OK: Passed 100 test(s).
27.00% {name,{0,10}}
12.50% {name,{10,20}}
8.00% {name,{20,30}}
5.50% {bio,{2400,2700}}
5.50% {bio,{3000,3300}}
5.00% {bio,{0,300}}
5.00% {bio,{300,600}}
5.00% {bio,{1800,2100}}
4.50% {bio,{1500,1800}}
3.50% {bio,{600,900}}
3.50% {bio,{900,1200}}
3.50% {bio,{1200,1500}}
3.50% {bio,{2700,3000}}
3.00% {bio,{2100,2400}}
2.50% {bio,{3300,3600}}
2.50% {name,{30,40}}
===>
1/1 properties passed
Erlang
(page 102)
code/CustomGenerators/erlang/pbt/test/prop_generators.erl
Note that binding is the main purpose of this macro ?LET
. It is implemented using a function proper_types:bind(RawType,fun(X) -> Gen end,false)
.
Definition ?LET
macro contains in include/proper_common.hrl
file.
More information related to queue
module you can see in the Erlang documentation.
Macros ?LET
helps create new generator queue
.
Erlang
(page 103)
code/CustomGenerators/erlang/pbt/test/prop_generators.erl
Pay attention to the peculiarity in the implementation of the macro ?SUCHTHAT
. It is a function call
-define(SUCHTHAT(X,RawType,Condition),
proper_types:add_constraint(RawType,fun(X) -> Condition end,true)).
Note that in your code (test/prop_generators.erl
) you can not define non_empty
generator because it is already defined in ProEr API. If you try to do it we will get an error message:
===> Compiling test/prop_generators.erl failed
test/prop_generators.erl:19: defining imported function non_empty/1
Let’s see how non_empty/1
generator can be used - rebar3 project.
Erlang
(page 104)
code/CustomGenerators/erlang/pbt/test/prop_generators.erl
Explore Filtering
It was interesting for me to check (reproduce) the error described by the author - the unsuccessful creation of the generator when filtering using ?SUCHTHAT
macro.
- Making mistakes is fun.
- Messages contain more details to help you with testing. Here is the full text of one of the possible errors:
$ rebar3 proper -p prop_list_content_integer
Error: Couldn't produce an instance that satisfies all strict constraints from (prop_generators:check_list_content_integer/1) after 50 tries.
===>
0/1 properties passed, 1 failed
===> Failed test cases:
prop_generators:prop_list_content_integer() -> {error,
{cant_generate,
[{prop_generators,
check_list_content_integer,
1}]}}
Code examples to help you reproduce this error.
Erlang
(page 104)
test/prop_generators.erl
In order to better understand the difference between macros ?LET
and ?SUCHTHAT
, try to implement of your own examples and you will see this difference.
Practice at work is very important, and doing exercises such as this is a wonderful solution.
Here is a project that demonstrates the use of these macros.
Erlang
(page 105)
test/prop_generators.erl
Latin1
When you start running property filtering Latint1 characters
rebar3 proper -p prop_latin1_string
you, likely, received a message from PropEr:
Error: Couldn't produce an instance that satisfies all strict constraints from (prop_generators:latin1_string2/0) after 50 tries.
To increase the number of attempts to 80 (this number of attempts should be enough), use the PropEr option --constraint_tries
.
The complete command will look like this:
rebar3 proper -p prop_latin1_string --constraint_tries 80
To simplify the problem being solved - generating encoded Latin1 strings, it would be better to set a numerical segment and generate numbers (codes by symbols) only from printable segment:
prop_latin1_string2() ->
?FORALL(String, latin1_string2(),
is_list(String)
).
latin1_string2() ->
?SUCHTHAT(S, ?SIZED(Size,vector(Size,integer(33, 126))), io_lib:printable_latin1_list(S)).
Now this property does not need any additional parameters of the testing framework PropEr.
Erlang
(page 105)
test/prop_generators.erl
Unicode strings
For generating Unicode strings, the function proper_unicode:utf8() is quite handy.
To suppress the generation of empty strings, use the non_empty/1 generator.
You will find the implementation of the ideas for generating Unicode strings, which the author described, in the project.
Erlang
(page 106)
test/prop_generators.erl
For implementing predictable tests, the generator frequency is well suited for probability management.
Examples of using this this generator in the property based tests can be found in the rebar3 project.
Erlang
(page 108)
test/prop_generators.erl
It is best to view the result of the methods’ work directly in the property code. I made it.
To make the test more convenient for creating properties, I added non_empty generator.
Erlang
(page 112)
test/prop_generators.erl
To view the results of generating paths, it is very convenient to use the function io:format/2
.
[left,up,up,up]
.[right,up,left,up,left,up,left,down,down,right,down,left,left]
.[down,down,left]
...
.[down,left,down,down,left,down,down]
.[down]
.[up,left,down,down,down,right,up,right,up]
.[right,right,down,down,down,down,down,left,down,down,right,up,right,down,down,
down]
OK: Passed 100 test(s).
===>
1/1 properties passed
Property with generators into rebar3 project.