From 6f49a634a7ce310624748b90299d23ccdbf7f743 Mon Sep 17 00:00:00 2001
From: Hans Nilsson
Date: Wed, 11 Dec 2019 13:00:04 +0100
Subject: common_test: Presentation function
---
lib/common_test/src/ct_property_test.erl | 300 ++++++++++++++++++++++++++++++-
1 file changed, 298 insertions(+), 2 deletions(-)
diff --git a/lib/common_test/src/ct_property_test.erl b/lib/common_test/src/ct_property_test.erl
index 93642a0970..5acf380367 100644
--- a/lib/common_test/src/ct_property_test.erl
+++ b/lib/common_test/src/ct_property_test.erl
@@ -30,12 +30,35 @@
-module(ct_property_test).
-%% API
+%%% API
+%% Main functions
-export([init_per_suite/1,
- quickcheck/2]).
+ quickcheck/2
+ ]).
+%% Result presentation
+-export([present_result/4, present_result/5,
+ title/2, title/3,
+ sequential_parallel/1,
+ cmnd_names/1,
+ num_calls/1,
+ print_frequency_ranges/0,
+ print_frequency/0
+ ]).
+
+%%% Mandatory include
-include_lib("common_test/include/ct.hrl").
+%%%================================================================
+%%%
+%%% API
+%%%
+
+%%%----------------------------------------------------------------
+%%%
+%%% Search for a property tester in the lib path, and if found, compile
+%%% the property tests
+%%%
init_per_suite(Config) ->
case which_module_exists([eqc,proper,triq]) of
{ok,ToolModule} ->
@@ -66,12 +89,71 @@ init_per_suite(Config) ->
{skip, "No property testing tool found"}
end.
+%%%----------------------------------------------------------------
+%%%
+%%% Call the found property tester (if any)
+%%%
quickcheck(Property, Config) ->
Tool = proplists:get_value(property_test_tool,Config),
F = function_name(quickcheck, Tool),
mk_ct_return( Tool:F(Property), Tool ).
+%%%----------------------------------------------------------------
+%%%
+%%% Present a nice table of the statem result
+%%%
+present_result(Module, Cmds, Triple, Config) ->
+ present_result(Module, Cmds, Triple, Config, []).
+
+present_result(Module, Cmds, {H,Sf,Result}, Config, Options0) ->
+ DefSpec =
+ if
+ is_tuple(Cmds) ->
+ [{"Distribution sequential/parallel", fun sequential_parallel/1}];
+ is_list(Cmds) ->
+ []
+ end
+ ++ [{"Function calls", fun cmnd_names/1},
+ {"Length of command sequences", fun print_frequency_ranges/0, fun num_calls/1}
+ ],
+ Options = add_default_options(Options0,
+ [{print_fun, fun ct:log/2},
+ {spec, DefSpec}
+ ]),
+ do_present_result(Module, Cmds, H, Sf, Result, Config, Options).
+
+
+title(Str, Fun) ->
+ title(Str, Fun, fun io:format/2).
+
+title(Str, Fun, PrintFun) ->
+ fun(L) -> PrintFun("~n~s~n~n~s~n", [Str,Fun(L)]) end.
+
+print_frequency() ->
+ fun(L) ->
+ [io_lib:format("~5.1f% ~p~n",[Pcnt,V])
+ || {V,_Num,Pcnt} <-
+ with_percentage(get_frequencies_no_range(L), length(L))
+ ]
+ end.
+
+print_frequency_ranges() ->
+ print_frequency_ranges([{ngroups,10}]).
+
+print_frequency_ranges(Options0) ->
+ fun([]) ->
+ io_lib:format('Empty list!~n',[]);
+ (L ) ->
+ try
+ Options = set_default_print_freq_range_opts(Options0, L),
+ do_print_frequency_ranges(L, Options)
+ catch
+ C:E:S ->
+ ct:pal("~p:~p ~p:~p~n~p~n~p",[?MODULE,?LINE,C,E,S,L])
+ end
+ end.
+
%%%================================================================
%%%
%%% Local functions
@@ -155,3 +237,217 @@ macro_def(triq) -> [{d, 'TRIQ'}].
function_name(quickcheck, triq) -> check;
function_name(F, _) -> F.
+
+%%%================================================================
+%%%================================================================
+%%%================================================================
+%%%
+%%% Result presentation part
+%%%
+do_present_result(_Module, Cmds, _H, _Sf, ok, Config, Options) ->
+ [PrintFun, Spec] = [proplists:get_value(K,Options) || K <- [print_fun,spec]],
+ Tool = proplists:get_value(property_test_tool,Config),
+ AGGREGATE = function_name(aggregate, Tool),
+ lists:foldr(fun({Title, FreqFun, CollecFun}, Result) ->
+ Tool:AGGREGATE(title(Title, FreqFun(), PrintFun),
+ CollecFun(Cmds),
+ Result);
+ ({Title, CollecFun}, Result) ->
+ Tool:AGGREGATE(title(Title, print_frequency(), PrintFun),
+ CollecFun(Cmds),
+ Result)
+ end, true, Spec);
+
+do_present_result(Module, Cmds, H, Sf, Result, _Config, Options) ->
+ [PrintFun] = [proplists:get_value(K,Options) || K <- [print_fun]],
+ PrintFun("Module = ~p,~n"
+ "Commands = ~p,~n"
+ "History = ~p,~n"
+ "FinalDynState = ~p,~n"
+ "Result = ~p",
+ [Module, Cmds, H, Sf, Result]),
+ Result == ok. % Proper dislikes non-boolean results while eqc treats non-true as false.
+
+%%%================================================================
+cmnd_names(Cs) -> traverse_commands(fun cmnd_name/1, Cs).
+cmnd_name(L) -> [F || {set,_Var,{call,_Mod,F,_As}} <- L].
+
+num_calls(Cs) -> traverse_commands(fun num_call/1, Cs).
+num_call(L) -> [length(L)].
+
+sequential_parallel(Cs) ->
+ traverse_commands(fun(L) -> dup_module(L, sequential) end,
+ fun(L) -> [dup_module(L1, mkmod("parallel",num(L1,L))) || L1<-L] end,
+ Cs).
+dup_module(L, ModName) -> lists:duplicate(length(L), ModName).
+mkmod(PfxStr,N) -> list_to_atom(PfxStr++"_"++integer_to_list(N)).
+
+%% Meta functions for the aggregate functions
+traverse_commands(Fun, L) when is_list(L) -> Fun(L);
+traverse_commands(Fun, {Seq, ParLs}) -> Fun(lists:append([Seq|ParLs])).
+
+traverse_commands(Fseq, _Fpar, L) when is_list(L) -> Fseq(L);
+traverse_commands(Fseq, Fpar, {Seq, ParLs}) -> lists:append([Fseq(Seq)|Fpar(ParLs)]).
+
+%%%================================================================
+-define(middle_dot, 0183).
+
+set_default_print_freq_range_opts(Opts0, L) ->
+ add_default_options(Opts0, [{ngroups, 10},
+ {min, 0},
+ {max, max_in_list(L)}
+ ]).
+
+add_default_options(Opts0, DefaultOpts) ->
+ [set_def_opt(Key,DefVal,Opts0) || {Key,DefVal} <- DefaultOpts].
+
+set_def_opt(Key, DefaultValue, Opts) ->
+ {Key, proplists:get_value(Key, Opts, DefaultValue)}.
+
+max_in_list(L) ->
+ case lists:last(L) of
+ Max when is_integer(Max) -> Max;
+ {Max,_} -> Max
+ end.
+
+do_print_frequency_ranges(L0, Options) ->
+ [N,Min,Max] = [proplists:get_value(K,Options) || K <- [ngroups, min, max]],
+ L = if
+ N>Max ->
+ %% There will be less than the demanded number of classes,
+ %% insert one last with zero values in it. That will force
+ %% the generation of N classes.
+ L0++[{N,0}];
+ N=
+ L0
+ end,
+ try
+ Interval = round((Max-Min)/N),
+ IntervalLowerLimits = lists:seq(Min,Max,Interval),
+ Ranges = [{I,I+Interval-1} || I <- IntervalLowerLimits],
+ Acc0 = [{Rng,0} || Rng <- Ranges],
+ Fs0 = get_frequencies(L, Acc0),
+ SumVal = lists:sum([V||{_,V}<-Fs0]),
+ Fs = with_percentage(Fs0, SumVal),
+ DistInfo = [{"min", lists:min(L)},
+ {"mean", mean(L)},
+ {"median", median(L)},
+ {"max", lists:max(L)}],
+
+ Npos_value = num_digits(SumVal),
+ Npos_range = num_digits(Max),
+ [%% Table heading:
+ io_lib:format("Range~*s: ~s~n",[2*Npos_range-2,"", "Number in range"]),
+ %% Line under heading:
+ io_lib:format("~*c:~*c~n",[2*Npos_range+3,$-, max(16,Npos_value+10),$- ]),
+ %% Lines with values:
+ [io_lib:format("~*w - ~*w: ~*w ~5.1f% ~s~n",
+ [Npos_range,Rlow,
+ Npos_range,Rhigh,
+ Npos_value,Val,
+ Percent,
+ cond_prt_vals(DistInfo, Interv)
+ ])
+ || {Interv={Rlow,Rhigh},Val,Percent} <- Fs],
+ %% Line under the table for the total number of values:
+ io_lib:format('~*c ~*c~n',[2*Npos_range,32, Npos_value+3,$-]),
+ %% The total number of values:
+ io_lib:format('~*c ~*w~n',[2*Npos_range,32, Npos_value,SumVal])
+ ]
+ catch
+ C:E ->
+ ct:pal('*** Failed printing (~p:~p) for~n~p~n',[C,E,L])
+ end.
+
+cond_prt_vals(LVs, CurrentInterval) ->
+ [prt_val(Label, Value, CurrentInterval) || {Label,Value} <- LVs].
+
+prt_val(Label, Value, CurrentInterval) ->
+ case in_interval(Value, CurrentInterval) of
+ true ->
+ io_lib:format(" <-- ~s=" ++ if
+ is_float(Value) -> "~.1f";
+ true -> "~p"
+ end,
+ [Label,Value]);
+ false ->
+ ""
+ end.
+
+get_frequencies([{I,Num}|T], [{{Lower,Upper},Cnt}|Acc]) when Lower=
+ get_frequencies(T, [{{Lower,Upper},Cnt+Num}|Acc]);
+get_frequencies(L=[{I,_Num}|_], [Ah={{_Lower,Upper},_Cnt}|Acc]) when I>Upper ->
+ [Ah | get_frequencies(L,Acc)];
+get_frequencies([I|T], Acc) when is_integer(I) ->
+ get_frequencies([{I,1}|T], Acc);
+get_frequencies([], Acc) ->
+ Acc.
+
+get_frequencies_no_range([]) ->
+ io_lib:format("No values~n", []);
+get_frequencies_no_range(L) ->
+ [H|T] = lists:sort(L),
+ get_frequencies_no_range(T, H, 1, []).
+
+get_frequencies_no_range([H|T], H, N, Acc) ->
+ get_frequencies_no_range(T, H, N+1, Acc);
+get_frequencies_no_range([H1|T], H, N, Acc) ->
+ get_frequencies_no_range(T, H1, 1, [{H,N}|Acc]);
+get_frequencies_no_range([], H, N, Acc) ->
+ lists:reverse(
+ lists:keysort(2, [{H,N}|Acc])).
+
+%% get_frequencies_percent(L) ->
+%% with_percentage(get_frequencies_no_range(L), length(L)).
+
+
+with_percentage(Fs, Sum) ->
+ [{Rng,Val,100*Val/Sum} || {Rng,Val} <- Fs].
+
+
+num_digits(I) -> 1+trunc(math:log(I)/math:log(10)).
+
+num(Elem, List) -> length(lists:takewhile(fun(E) -> E /= Elem end, List)) + 1.
+
+%%%---- Just for naming an operation for readability
+is_odd(I) -> (I rem 2) == 1.
+
+in_interval(Value, {Rlow,Rhigh}) ->
+ try
+ Rlow= false
+ end.
+
+%%%================================================================
+%%% Statistical functions
+
+%%%---- Mean value
+mean(L = [X|_]) when is_number(X) ->
+ lists:sum(L) / length(L);
+mean(L = [{_Value,_Weight}|_]) ->
+ SumOfWeights = lists:sum([W||{_,W}<-L]),
+ WeightedSum = lists:sum([W*V||{V,W}<-L]),
+ WeightedSum / SumOfWeights;
+mean(_) ->
+ undefined.
+
+%%%---- Median
+median(L = [X|_]) when is_number(X) ->
+ Len = length(L),
+ case is_odd(Len) of
+ true ->
+ hd(lists:nthtail(Len div 2, L));
+ false ->
+ %% 1) L has at least one element (the one in the is_number test).
+ %% 2) Length is even.
+ %% => Length >= 2
+ [M1,M2|_] = lists:nthtail((Len div 2)-1, L),
+ (M1+M2) / 2
+ end;
+%% integer Weights...
+median(L = [{_Value,_Weight}|_]) ->
+ median( lists:append([lists:duplicate(W,V) || {V,W} <- L]) );
+median(_) ->
+ undefined.
+
--
cgit v1.2.1
From 855912f5056fc2963e6921bd5360481901c2f551 Mon Sep 17 00:00:00 2001
From: Hans Nilsson
Date: Mon, 16 Dec 2019 11:55:01 +0100
Subject: common_test: Remove warning for experimental code
---
lib/common_test/doc/src/ct_property_test.xml | 12 ++----------
lib/common_test/src/ct_property_test.erl | 10 ----------
2 files changed, 2 insertions(+), 20 deletions(-)
diff --git a/lib/common_test/doc/src/ct_property_test.xml b/lib/common_test/doc/src/ct_property_test.xml
index 1e01d9a5d7..0fd641a6f3 100644
--- a/lib/common_test/doc/src/ct_property_test.xml
+++ b/lib/common_test/doc/src/ct_property_test.xml
@@ -33,15 +33,12 @@
ct_property_test.xml
ct_property_test
- EXPERIMENTAL support in Common Test for calling
+ Support in Common Test for calling
property-based tests.
- EXPERIMENTAL support in Common Test for calling property-based
- tests.
-
- This module is a first step to run property-based tests in the
+
This module helps running property-based tests in the
Common Test framework. A property testing tool like QuickCheck
or PropEr is assumed to be installed.
@@ -70,11 +67,6 @@
Config
).
-
- This is experimental code that can be changed or removed anytime
- without any warning.
-
-
diff --git a/lib/common_test/src/ct_property_test.erl b/lib/common_test/src/ct_property_test.erl
index 5acf380367..47e6be30f6 100644
--- a/lib/common_test/src/ct_property_test.erl
+++ b/lib/common_test/src/ct_property_test.erl
@@ -18,16 +18,6 @@
%% %CopyrightEnd%
%%
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%%% %%%
-%%% WARNING %%%
-%%% %%%
-%%% This is experimental code which may be changed or removed %%%
-%%% anytime without any warning. %%%
-%%% %%%
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-
-module(ct_property_test).
%%% API
--
cgit v1.2.1
From fe7c44eb8a92bb30ba7ca5b1e23bfaaf4acff221 Mon Sep 17 00:00:00 2001
From: Hans Nilsson
Date: Mon, 16 Dec 2019 14:55:24 +0100
Subject: common_test: Add .hrl for support macros and -include of proptest
tool
---
lib/common_test/include/ct_property_test.hrl | 40 ++++++++++++++++++++++++++++
lib/common_test/src/Makefile | 3 ++-
2 files changed, 42 insertions(+), 1 deletion(-)
create mode 100644 lib/common_test/include/ct_property_test.hrl
diff --git a/lib/common_test/include/ct_property_test.hrl b/lib/common_test/include/ct_property_test.hrl
new file mode 100644
index 0000000000..9d5933fde3
--- /dev/null
+++ b/lib/common_test/include/ct_property_test.hrl
@@ -0,0 +1,40 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2004-2019. 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%
+%%
+%%
+
+-ifndef(CT_PROPERTY_TEST_HRL).
+ -define(CT_PROPERTY_TEST_HRL, true).
+
+ -ifdef(EQC).
+ -define(MOD_eqc, eqc).
+ -include_lib("eqc/include/eqc.hrl").
+ -else.
+ -ifdef(PROPER).
+ -define(MOD_eqc, proper).
+ -include_lib("proper/include/proper.hrl").
+ -else.
+ -ifdef(TRIQ).
+ -define(MOD_eqc, triq).
+ -include_lib("triq/include/triq.hrl").
+ -endif.
+ -endif.
+ -endif.
+
+-endif.
diff --git a/lib/common_test/src/Makefile b/lib/common_test/src/Makefile
index 76689dab8c..ffdef8ec39 100644
--- a/lib/common_test/src/Makefile
+++ b/lib/common_test/src/Makefile
@@ -96,7 +96,8 @@ HRL_FILES = \
ct_netconfc.hrl
EXTERNAL_HRL_FILES = \
../include/ct.hrl \
- ../include/ct_event.hrl
+ ../include/ct_event.hrl \
+ ../include/ct_property_test.hrl
EXTERNAL_INC_PATH = ../include
--
cgit v1.2.1
From eb52758f7fc76dd967f261d66c55c81c10a612de Mon Sep 17 00:00:00 2001
From: Hans Nilsson
Date: Mon, 16 Dec 2019 16:41:28 +0100
Subject: common_test: ct_property_test test case
---
lib/common_test/test/Makefile | 3 ++-
lib/common_test/test/ct_property_test_SUITE.erl | 24 +++++++++++++++++++++
lib/common_test/test/property_test/ct_prop.erl | 28 +++++++++++++++++++++++++
3 files changed, 54 insertions(+), 1 deletion(-)
create mode 100644 lib/common_test/test/ct_property_test_SUITE.erl
create mode 100644 lib/common_test/test/property_test/ct_prop.erl
diff --git a/lib/common_test/test/Makefile b/lib/common_test/test/Makefile
index e510b74d6a..fae7ce0eb5 100644
--- a/lib/common_test/test/Makefile
+++ b/lib/common_test/test/Makefile
@@ -76,7 +76,8 @@ MODULES= \
ct_unicode_SUITE \
ct_auto_clean_SUITE \
ct_util_SUITE \
- ct_tc_repeat_SUITE
+ ct_tc_repeat_SUITE \
+ ct_property_test_SUITE
ERL_FILES= $(MODULES:%=%.erl)
HRL_FILES= test_server_test_lib.hrl
diff --git a/lib/common_test/test/ct_property_test_SUITE.erl b/lib/common_test/test/ct_property_test_SUITE.erl
new file mode 100644
index 0000000000..1f8c9e08cf
--- /dev/null
+++ b/lib/common_test/test/ct_property_test_SUITE.erl
@@ -0,0 +1,24 @@
+-module(ct_property_test_SUITE).
+
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
+
+all() -> [prop_sort
+ ].
+
+%%% First prepare Config and compile the property tests for the found tool:
+init_per_suite(Config) ->
+ ct_property_test:init_per_suite(Config).
+
+end_per_suite(Config) ->
+ Config.
+
+%%%================================================================
+%%% Test suites
+%%%
+prop_sort(Config) ->
+ ct_property_test:quickcheck(
+ ct_prop:prop_sort(),
+ Config
+ ).
diff --git a/lib/common_test/test/property_test/ct_prop.erl b/lib/common_test/test/property_test/ct_prop.erl
new file mode 100644
index 0000000000..559c33da74
--- /dev/null
+++ b/lib/common_test/test/property_test/ct_prop.erl
@@ -0,0 +1,28 @@
+-module(ct_prop).
+-export([prop_sort/0]).
+
+-include_lib("common_test/include/ct_property_test.hrl").
+
+prop_sort() ->
+ ?FORALL(UnSorted, list(),
+ is_sorted(lists:sort(UnSorted))
+ ).
+
+
+is_sorted([]) ->
+ true;
+is_sorted([_]) ->
+ true;
+is_sorted(Sorted) ->
+ try
+ lists:foldl(fun chk_sorted_pair/2, hd(Sorted), tl(Sorted))
+ of
+ _ ->
+ true
+ catch
+ throw:false ->
+ false
+ end.
+
+chk_sorted_pair(A, B) when A>=B -> A;
+chk_sorted_pair(_, _) -> throw(false).
--
cgit v1.2.1
From 610e8bb1d9bcf7f31e2839a178e5ef3df0b541cb Mon Sep 17 00:00:00 2001
From: Hans Nilsson
Date: Tue, 17 Dec 2019 15:47:10 +0100
Subject: common_test: Add 'prop_tools' option to Config for ct_property_test
---
lib/common_test/src/ct_property_test.erl | 3 ++-
lib/common_test/test/property_test/ct_prop.erl | 18 ++++--------------
2 files changed, 6 insertions(+), 15 deletions(-)
diff --git a/lib/common_test/src/ct_property_test.erl b/lib/common_test/src/ct_property_test.erl
index 47e6be30f6..251a0a4896 100644
--- a/lib/common_test/src/ct_property_test.erl
+++ b/lib/common_test/src/ct_property_test.erl
@@ -50,7 +50,8 @@
%%% the property tests
%%%
init_per_suite(Config) ->
- case which_module_exists([eqc,proper,triq]) of
+ ToolsToCheck = proplists:get_value(prop_tools, Config, [eqc,proper,triq]),
+ case which_module_exists(ToolsToCheck) of
{ok,ToolModule} ->
case code:where_is_file(lists:concat([ToolModule,".beam"])) of
non_existing ->
diff --git a/lib/common_test/test/property_test/ct_prop.erl b/lib/common_test/test/property_test/ct_prop.erl
index 559c33da74..67ab3f3e6b 100644
--- a/lib/common_test/test/property_test/ct_prop.erl
+++ b/lib/common_test/test/property_test/ct_prop.erl
@@ -8,21 +8,11 @@ prop_sort() ->
is_sorted(lists:sort(UnSorted))
).
-
is_sorted([]) ->
true;
is_sorted([_]) ->
true;
-is_sorted(Sorted) ->
- try
- lists:foldl(fun chk_sorted_pair/2, hd(Sorted), tl(Sorted))
- of
- _ ->
- true
- catch
- throw:false ->
- false
- end.
-
-chk_sorted_pair(A, B) when A>=B -> A;
-chk_sorted_pair(_, _) -> throw(false).
+is_sorted([H1,H2|SortedTail]) when H1 =< H2 ->
+ is_sorted([H2|SortedTail]);
+is_sorted(_) ->
+ false.
--
cgit v1.2.1
From d697201421023112c708b5710d4b2818af27bb57 Mon Sep 17 00:00:00 2001
From: Hans Nilsson
Date: Wed, 11 Dec 2019 15:13:21 +0100
Subject: common_test: Document property test reporting
---
lib/common_test/doc/src/Makefile | 1 +
lib/common_test/doc/src/ct_property_test.xml | 229 ++++++++++++++++---
.../doc/src/ct_property_test_chapter.xml | 249 +++++++++++++++++++++
lib/common_test/doc/src/part.xml | 1 +
4 files changed, 446 insertions(+), 34 deletions(-)
create mode 100644 lib/common_test/doc/src/ct_property_test_chapter.xml
diff --git a/lib/common_test/doc/src/Makefile b/lib/common_test/doc/src/Makefile
index b5acdc6f95..ae06572752 100644
--- a/lib/common_test/doc/src/Makefile
+++ b/lib/common_test/doc/src/Makefile
@@ -74,6 +74,7 @@ XML_CHAPTER_FILES = \
ct_master_chapter.xml \
event_handler_chapter.xml \
ct_hooks_chapter.xml \
+ ct_property_test_chapter.xml \
dependencies_chapter.xml \
notes.xml
diff --git a/lib/common_test/doc/src/ct_property_test.xml b/lib/common_test/doc/src/ct_property_test.xml
index 0fd641a6f3..687da15b0a 100644
--- a/lib/common_test/doc/src/ct_property_test.xml
+++ b/lib/common_test/doc/src/ct_property_test.xml
@@ -33,27 +33,39 @@
ct_property_test.xml
ct_property_test
- Support in Common Test for calling
- property-based tests.
+ Support in Common Test for running property-based tests.
This module helps running property-based tests in the
- Common Test framework. A property testing tool like QuickCheck
- or PropEr is assumed to be installed.
+ Common Test framework. One (or more) of the property testing tools
+
+
+ - QuickCheck,
+ - PropEr or
+ - Triq
+
+
+ is assumed to be installed.
+
- The idea is to have a Common Test test suite calling a property
- testing tool with special property test suites as defined by that tool.
- The usual Erlang application directory structure is assumed. The tests
- are collected in the test directory of the application. The
- test directory has a subdirectory property_test, where
- everything needed for the property tests is collected.
+ The idea with this module is to have a Common Test test suite calling
+ a property testing tool with special property test suites as defined by that tool.
+ The tests
+ are collected in the test directory of the application. The
+ test directory has a subdirectory property_test, where
+ everything needed for the property tests are collected.
+ The usual Erlang application directory structure is assumed.
+
A typical Common Test test suite using ct_property_test
- is organized as follows:
+ is organized as follows:
-
- -include_lib("common_test/include/ct.hrl").
+
+-module(my_prop_test_SUITE).
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
all() -> [prop_ftp_case].
@@ -63,46 +75,195 @@
%%%---- test case
prop_ftp_case(Config) ->
ct_property_test:quickcheck(
- ftp_simple_client_server:prop_ftp(Config),
+ ftp_simple_client_server:prop_ftp(),
Config
- ).
+ ).
+ and the the property test module (in this example ftp_simple_client_server.erl)
+ as almost a usual property testing module
+ (More examples are in the User's Guide):
+
+-module(ftp_simple_client_server).
+-export([prop_ftp/0...]).
+
+-include_lib("common_test/include/ct_property_test.hrl").
+prop_ftp() ->
+ ?FORALL( ....
+
init_per_suite(Config) -> Config | {skip, Reason}
- Initializes Config for property testing.
+ Initializes and extends Config for property testing.
- Initializes Config for property testing.
+ Initializes and extends Config for property based testing.
This function investigates if support is available for either
- Quickcheck, PropEr, or Triq. The options
- {property_dir,AbsPath} and {property_test_tool,Tool}
- are set in the Config returned.
+ QuickCheck,
+ PropEr
+ or Triq and compiles the
+ properties with the first tool found.
+ It is supposed to be called in the init_per_suite/1 function
+ in a CommonTest test suite.
+
+ Which tools to check for, and in which order could be set with the option
+ {prop_tools, list(eqc|proper|triq)}
+ in the CommonTest configuration Config. The default value is
+ [eqc, proper, triq] with eqc being the first one searched for.
+
+ If no support is found for any tool, this function returns
+ {skip, Explanation}.
+
+ If support is found, the option {property_test_tool,ToolModule} with
+ the selected tool main module name (eqc, proper or triq)
+ is added to the list Config which then is returned.
+
+ The property tests are assumed to be in a subdirectory named
+ property_test.
+ All found Erlang files in that directory are compiled with one of the macros
+ 'EQC', 'PROPER' or 'TRIQ' set, depending on which tool
+ that is first found.
+
+ The file(s) in the property_test subdirectory could, or should,
+ include the ct_property_test include file:
+
+
+-include_lib("common_test/include/ct_property_test.hrl").
+
+ This included file will:
+
+
+ - Include the correct tool's include file
+ - Set the macro 'MOD_eqc' to the correct module name for the
+ selected tool, that is eqc, proper or triq.
+
+
+
+
- The function is intended to be called in function
- init_per_suite in the test suite.
+
+ quickcheck(Property, Config) -> true | {fail, Reason}
+ Calls quickcheck and returns the result in a form suitable for
+ Common Test.
+
+ Calls the selected tool's function for running the Property. It is usually and
+ by historical reasons called quickcheck, and that is why that name is used in
+ this module (ct_property_test).
+
+ The result is returned in a form suitable for Common Test test suites.
+
+ This function is intended to be called in test cases in test suites.
+
+
+
- The property tests are assumed to be in subdirectory
- property_test.
+
+ present_result(Module, Cmds, Triple, Config) -> Result
+ Presents the result of statem property testing
+
+ Same as present_result(Module, Cmds, Triple, Config, [])
+
- quickcheck(Property, Config) -> true | {fail, Reason}
- Calls quickcheck and returns the result in a form suitable for
- Common Test.
-
- Calls quickcheck and returns the result in a form suitable for
- Common Test.
+ present_result(Module, Cmds, Triple, Config, Options) -> Result
+ Presents the result of statem property testing
+
+ Module = module()
+
+
+ Cmds =
+ the list of commands generated by the property testing tool, for example
+ by proper:commands/1 or by proper:parallel_commands/1
+
+
+ Triple =
+ the output from for example proper:run_commands/2 or proper:run_parallel_commands/2
+
+ Config =
+ the Common Test Config in test cases.
+
+ Options = [present_option()]
+ present_option() = {print_fun, fun(Format,Args)}
+ | {spec, StatisticsSpec}
+ The print_fun defines which function to do the actual printout. The default is
+ ct:log/2.
+ The spec defines what statistics are to be printed
+
+
+ Result = boolean()
+ Is false if the test failed and is true if the test passed
+
+
+ Presents the result of stateful (statem) property testing using the aggregate function in
+ PropEr, QuickCheck or other similar property testing tool.
+
+ It is assumed to be called inside the property called by
+ quickcheck/2:
+
+...
+RunResult = run_parallel_commands(?MODULE, Cmds),
+ct_property_test:present_result(?MODULE, Cmds, RunResult, Config)
+...
+
+ See the User's Guide for
+ an example of the usage and of the default printout.
+
+ The StatisticsSpec is a list of the tuples:
+
+ - {Title::string(), CollectFun::fun/1}
+ - {Title::string(), FrequencyFun::/0, CollectFun::fun/1}
+
+ Each tuple will produce one table in the order of their places in the list.
+
+ - Title will be the title of one result table
- This function is intended to be called in the test cases in the
- test suite.
+ - CollectFun is called with one argument: the Cmds. It should return
+ a list of the values to be counted. The following pre-defined functions exist:
+
+ - ct_property_test:cmnd_names/1 returns a list of commands (function calls) generated in the Cmnd
+ sequence, without Module, Arguments and other details.
+ - ct_property_test:num_calls/1 returns a list of the length of commands lists
+ - ct_property_test:sequential_parallel/1 returns a list with information about sequential and
+ parallel parts from Tool:parallel_commands/1,2
+
+
+
+ - FrequencyFun/0 returns a fun/1 which is supposed to take a list of items as input,
+ and return an iolist wich will be printed as the table. Per default, the number of each item is counted
+ and the percentage is printed for each. The list [a,b,a,a,c] could for example return
+
+ ["a 60%\n","b 20%\n","c 20%\n"]
+ which will be printed by the print_fun.
+ The default print_fun will print it as:
+
+ a 60%
+ b 20%
+ c 20%
+
+
+ The default StatisticsSpec is:
+
+ - For sequential commands:
+
+[{"Function calls", fun cmnd_names/1},
+ {"Length of command sequences", fun print_frequency_ranges/0,
+ fun num_calls/1}]
+
+ - For parallel commands:
+
+[{"Distribution sequential/parallel", fun sequential_parallel/1},
+ {"Function calls", fun cmnd_names/1},
+ {"Length of command sequences", fun print_frequency_ranges/0,
+ fun num_calls/1}]
+
+
+
-
-
diff --git a/lib/common_test/doc/src/ct_property_test_chapter.xml b/lib/common_test/doc/src/ct_property_test_chapter.xml
new file mode 100644
index 0000000000..8cc273d2fc
--- /dev/null
+++ b/lib/common_test/doc/src/ct_property_test_chapter.xml
@@ -0,0 +1,249 @@
+
+
+
+
+
+
+ 20192019
+ Ericsson AB. 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.
+
+
+
+ Common Test's Property Testing Support: ct_property_test
+ Hans Nilsson
+
+
+
+ ct_property_test_chapter.xml
+
+
+
+
+ General
+
+ The Common Test Property Testing Support (ct_property_test)
+ is an aid to run property based testing tools in Common Test test suites.
+
+
+ Basic knowledge of property based testing is assumed in the following.
+ It is also assumed that at least one of the following property based
+ testing tools is installed and available in the library path:
+
+
+ - QuickCheck,
+ - PropEr or
+ - Triq
+
+
+
+
+
+ What Is Supported?
+ The ct_property_test module
+ does the following:
+
+
+ - Compiles the files with property tests in the subdirectory property_test
+
+ - Tests properties in those files using the first found Property Testing Tool.
+
+ - Saves the results - that is the printouts - in the usual Common Test Log
+
+
+
+
+
+
+ Introductory Example
+ Assume that we want to test the lists:sort/1 function.
+
+ We need a property to test the function. In normal way, we create
+ property_test/test_sort.erl module in the test directory
+ in our application:
+
+
+
+-module(test_sort).
+-export([prop_sort/0]).
+
+%%% This will include the .hrl file for the installed testing tool:
+-include_lib("common_test/include/ct_property_test.hrl").
+
+%%% The property we want to check:
+%%% For all possibly unsorted lists,
+%%% the result of lists:sort/1 is sorted.
+prop_sort() ->
+ ?FORALL(UnSorted, list(),
+ is_sorted(lists:sort(UnSorted))
+ ).
+
+%%% Function to check that a list is sorted:
+is_sorted([]) ->
+ true;
+is_sorted([_]) ->
+ true;
+is_sorted([H1,H2|SortedTail]) when H1 =< H2 ->
+ is_sorted([H2|SortedTail]);
+is_sorted(_) ->
+ false.
+
+
+ We also need a CommonTest test suite:
+
+
+-module(ct_property_test_SUITE).
+-compile(export_all). % Only in tests!
+
+-include_lib("common_test/include/ct.hrl").
+
+all() -> [prop_sort
+ ].
+
+%%% First prepare Config and compile the property tests for the found tool:
+init_per_suite(Config) ->
+ ct_property_test:init_per_suite(Config).
+
+end_per_suite(Config) ->
+ Config.
+
+%%%================================================================
+%%% Test suites
+%%%
+prop_sort(Config) ->
+ ct_property_test:quickcheck(
+ ct_prop:prop_sort(),
+ Config
+ ).
+
+
+ We run it as usual, for example with ct_run in the OS shell:
+
+..../test$ ct_run -suite ct_property_test_SUITE
+.....
+Common Test: Running make in test directories...
+
+TEST INFO: 1 test(s), 1 case(s) in 1 suite(s)
+
+Testing lib.common_test.ct_property_test_SUITE: Starting test, 1 test cases
+
+----------------------------------------------------
+2019-12-18 10:44:46.293
+Found property tester proper
+at "/home/X/lib/proper/ebin/proper.beam"
+
+
+----------------------------------------------------
+2019-12-18 10:44:46.294
+Compiling in "/home/..../test/property_test"
+ Deleted: ["ct_prop.beam"]
+ ErlFiles: ["ct_prop.erl"]
+ MacroDefs: [{d,'PROPER'}]
+
+Testing lib.common_test.ct_property_test_SUITE: TEST COMPLETE, 1 ok, 0 failed of 1 test cases
+
+....
+
+
+
+
+
+
+ A stateful testing example
+ Assume a test that generates some parallel stateful commands, and runs 300 tests:
+
+prop_parallel(Config) ->
+ numtests(300,
+ ?FORALL(Cmds, parallel_commands(?MODULE),
+ begin
+ RunResult = run_parallel_commands(?MODULE, Cmds),
+ ct_property_test:present_result(?MODULE, Cmds, RunResult, Config)
+ end)).
+
+ The
+ ct_property_test:present_result/4
+ is a help function for printing some statistics in the CommonTest log file.
+ Our example test could for example be a simple test of an ftp server, where we perform get, put
+ and delete requests, some of them in parallel. Per default, the result has three sections:
+
+
+*** User 2019-12-11 13:28:17.504 ***
+
+Distribution sequential/parallel
+
+ 57.7% sequential
+ 28.0% parallel_2
+ 14.3% parallel_1
+
+
+
+*** User 2019-12-11 13:28:17.505 ***
+
+Function calls
+
+ 44.4% get
+ 39.3% put
+ 16.3% delete
+
+
+
+*** User 2019-12-11 13:28:17.505 ***
+
+Length of command sequences
+
+Range : Number in range
+-------:----------------
+ 0 - 4: 8 2.7% <-- min=3
+ 5 - 9: 44 14.7%
+10 - 14: 74 24.7%
+15 - 19: 60 20.0% <-- mean=18.7 <-- median=16.0
+20 - 24: 38 12.7%
+25 - 29: 26 8.7%
+30 - 34: 19 6.3%
+35 - 39: 19 6.3%
+40 - 44: 8 2.7%
+45 - 49: 4 1.3% <-- max=47
+ ------
+ 300
+
+ The first part - Distribution sequential/parallel - shows the distribution in the
+ sequential and parallel part of the result of parallel_commands/1. See any property testing tool for
+ an explanation of this function.
+ The table shows that of all commands (get and put in our case),
+ 57.7% are executed in the sequential part prior to the parallel part,
+ 28.0% are executed in the first parallel list and the rest in the second parallel list.
+
+
+ The second part - Function calls - shows the distribution of the three calls in the
+ generated command lists. We see that all of the three calls are executed. If it was so that we
+ thought that we also generated a fourth call, a table like this shows that we failed with that.
+
+
+ The third and final part - Length of command sequences - show statistics of the
+ generated command sequences. We see that the shortest list has three elementes while the longest
+ has 47 elements. The mean and median values are also shown. Further we could for example see that
+ only 2.7% of the lists (that is eight lists) only has three or four elements.
+
+
+
+
+
+
diff --git a/lib/common_test/doc/src/part.xml b/lib/common_test/doc/src/part.xml
index 000eb06b82..66dcf75258 100644
--- a/lib/common_test/doc/src/part.xml
+++ b/lib/common_test/doc/src/part.xml
@@ -48,6 +48,7 @@
+
--
cgit v1.2.1
From f1869655e76e876b19201095b104b394b8faea36 Mon Sep 17 00:00:00 2001
From: Hans Nilsson
Date: Wed, 8 Jan 2020 18:05:38 +0100
Subject: common_test: Comments from doc review
---
lib/common_test/doc/src/ct_property_test.xml | 7 +++++--
lib/common_test/doc/src/ct_property_test_chapter.xml | 4 ++--
2 files changed, 7 insertions(+), 4 deletions(-)
diff --git a/lib/common_test/doc/src/ct_property_test.xml b/lib/common_test/doc/src/ct_property_test.xml
index 687da15b0a..1690e9962a 100644
--- a/lib/common_test/doc/src/ct_property_test.xml
+++ b/lib/common_test/doc/src/ct_property_test.xml
@@ -123,7 +123,9 @@ prop_ftp() ->
property_test.
All found Erlang files in that directory are compiled with one of the macros
'EQC', 'PROPER' or 'TRIQ' set, depending on which tool
- that is first found.
+ that is first found. This could make parts of the Erlang property tests
+ code to be included or excluded with the macro directives
+ -ifdef(Macro). or -ifndef(Macro)..
The file(s) in the property_test subdirectory could, or should,
include the ct_property_test include file:
@@ -136,7 +138,8 @@ prop_ftp() ->
- Include the correct tool's include file
- Set the macro 'MOD_eqc' to the correct module name for the
- selected tool, that is eqc, proper or triq.
+ selected tool. That is, the macro 'MOD_eqc' is set to either
+ eqc, proper or triq.
diff --git a/lib/common_test/doc/src/ct_property_test_chapter.xml b/lib/common_test/doc/src/ct_property_test_chapter.xml
index 8cc273d2fc..131f3a962d 100644
--- a/lib/common_test/doc/src/ct_property_test_chapter.xml
+++ b/lib/common_test/doc/src/ct_property_test_chapter.xml
@@ -71,12 +71,12 @@
Assume that we want to test the lists:sort/1 function.
We need a property to test the function. In normal way, we create
- property_test/test_sort.erl module in the test directory
+ property_test/ct_prop.erl module in the test directory
in our application:
--module(test_sort).
+-module(ct_prop).
-export([prop_sort/0]).
%%% This will include the .hrl file for the installed testing tool:
--
cgit v1.2.1