Testing Erlang-OTP with QuickCheck CRYSTAL CHANG DIN

advertisement
Testing Erlang-OTP with QuickCheck
Master of Science Thesis in the Programme Software Engineering and Technology
CRYSTAL CHANG DIN
Department of Computer Science and Engineering
CHALMERS UNIVERSITY OF TECHNOLOGY
UNIVERSITY OF GOTHENBURG
Göteborg, Sweden, June 2009
Supervisor: John Hughes
Department of Computer Science and Engineering
Chalmers University of Technology
SE-412 96 Göteborg
Sweden
Telephone + 46 (0)31-772 1000
Copyright © Crystal Chang Din, June 2009.
E-mail: crystal@student.chalmers.se
Contents
List of Figures .......................................................................................................................... iii
List of Tables............................................................................................................................ iv
Abstract ..................................................................................................................................... v
Acknowledgement ................................................................................................................... vi
1. Introduction .......................................................................................................................... 1
1.1 Purpose ......................................................................................................................... 1
1.2 Scope ............................................................................................................................. 1
1.3 Incompleteness............................................................................................................. 1
1.4 Tool ............................................................................................................................... 2
1.5 Contribution ................................................................................................................ 2
2. QuickCheck........................................................................................................................... 3
2.1 Generators ................................................................................................................... 3
2.2 Properties ..................................................................................................................... 4
2.3 Symbolic Representation ............................................................................................ 5
3. Testing Pure Functions ........................................................................................................ 6
3.1 Data Generators .......................................................................................................... 6
3.2 Data Structure Generators ......................................................................................... 7
3.3 Model Based Testing ................................................................................................... 9
3.4 Information Hiding ................................................................................................... 10
3.5 Case Study.................................................................................................................. 10
Case 1: ........................................................................................................................ 10
Case 2: ........................................................................................................................ 12
3.6 Findings ...................................................................................................................... 15
4. QuickCheck State Machine ............................................................................................... 24
4.1 Initial state ................................................................................................................. 24
4.2 Function “command”................................................................................................ 25
4.3 Function “precondition” ........................................................................................... 25
4.4 Function “postcondition” ......................................................................................... 25
4.5 Function “next_state” ............................................................................................... 25
5. Testing Imperative Functions ........................................................................................... 27
5.1 Module digraph ......................................................................................................... 27
Bug1 ............................................................................................................................ 27
i
Bug2 ............................................................................................................................ 32
Bug3 ............................................................................................................................ 33
5.2 Module dets ................................................................................................................ 34
Bug4 ............................................................................................................................ 34
Bibliography ........................................................................................................................... 39
Appendix ................................................................................................................................. 40
A. User Manu ................................................................................................................... 40
A-1 Pure Functions ................................................................................................... 40
A-2 Imperative functions .......................................................................................... 55
B. Complete Code ............................................................................................................ 56
ii
List of Figures
3-1: Transformation from decimal to Erlang number.......….…….…………………..9
3-2: Transformation from queue to list.…………………..…………………………10
4-1: A complete cycle of a QuickCheck state machine test case……………………24
4-2: Process during generation time.……………………………….……………….26
4-3: Process during run time.……………………………………………………..…26
5-1: Edge added.……………………………………………………………….....…31
5-2: Edge modified.……………………………………………………………....…31
iii
List of Tables
3-1: Size functions………………………………………...………………..…………11
3-2: Containment functions…………………………………………………………...14
3-3: Deletion functions………………………………………………………..………23
3-4: Comparison of (==) and (=:=).……...…………………………………………...23
5-1: Entire process of QuickCheck state machine (digraph)………………………….28
5-2: Entire process of QuickCheck state machine (dets)…..…………………………35
iv
Abstract
Erlang/OTP is a concurrent functional language developed and maintained by Ericsson
AB, and was released as open source in 1998. Though Erlang/OTP has been tested for
many years, it is still possible to find more bugs since some test cases have not been
written yet. The commercial version of QuickCheck for Erlang was recently developed
by Quviq AB. It is a tool replacing conventional test cases by writing specification of
properties under test that the tested function should fulfill. Moreover, QuickCheck also
provides libraries for testing operations containing side-effects, which are specified via
an abstract state machine. Based on that, QuickCheck is a testing tool mainly applied
in this thesis that focuses on Erlang/OTP data-structures.
In the first part, it starts with analyzing properties of pure functions in various datastructure modules. After that, we develop extensive test suites. As long as the
functions having the same property, they can be tested through the same property.
In the second part, we test imperative functions of the rest of data-structure modules
by using QuickCheck state machine. The bugs which were found in Erlang/OTP as
well as the method which was used to identify the bugs will also be described in the
later part.
v
Acknowledgement
This work will not be possible without kindly support from many people.
First of all, I would like to deeply thank my supervisor, professor Dr. John Hughes,
who guided me into such an exciting field. His broad and deep knowledge in
QuickCheck and software verification inspired me so much. I would like to express
my sincere thank to Nick Smallbone, who constantly gave me brilliant advices and
support when I met difficulties.
Professor Reiner Hähnle and professor Wolfgang Ahrendt provided me knowledge in
software verification field which is critical for QuickCheck. I want to thank Angela
Wallenburg who shared her experience of working in software verification. I would
like to thank Mary Sheeran who introduced me to ProTest project and QuickCheck. I
really appreciate the training provided by professor Thomas Arts about QuickCheck.
I would like to thank my dear family, who warmly encourage me to pursue my dream
in Sweden and support me with unconditional and infinite love. I also appreciate their
sincerely prayer. I would like to thank all my friends in Taiwan and in Sweden.
Because of them, I do not feel lonely abroad. I would like to particularly thank Cathy
Ni, who is my best friend and sister in Lord and a good company especially in Sweden.
Though our research fields are totally different, we worked together on our thesis and
supported each other from the beginning. Finally, I would like to thank my dear Lord,
who loves me and participates in my life in every moment. All I have is from Him.
vi
1. Introduction
1.1 Purpose
Message passing is the main idea in Erlang, which is used to develop telecom and
internet products such as switches, email gateways, instant messaging services, semistructured databases and game servers. Therefore, to design different data structures
for installing information wisely and make sure all of them operate as they should be
is a very challenging subject. Otherwise, several unpleasant situations could occur, e.g.
saved data are not consistent with the order of operations.
1.2 Scope
This thesis is separated into two parts for testing data-structures under the application
stdlib-1.16.1 of Erlang/OTP. Each data structure is implemented as an Erlang module.
The first part is about testing the modules consisting of pure functions.

key-value structure: dict module, orddict module and gb_trees module.

set structure: sets module, ordsets module and gb_sets module.

list structure: lists module, proplists module and string module

queue structure: queue module

array structure: array module
The second part is about testing the modules consisting of imperative functions.

directed-graph structure: digraph module

sets, ordsets, bag and duplicate_bag structure: ets module

sets, bag and duplicate_bag structure: dets module
1.3 Incompleteness
A data-structure is expected to perform some tasks in a specific manner. If the datastructure does not behave the way it is intended to, errors occur accordingly.
Therefore, to design an effective set of test cases that enable uncovering maximum
errors was the way been used to improve the quality of Erlang/OTP. However, writing
all test cases manually is not easy, especially when the complexity of requirements
increases. Some errors might still have not been found.
1
1.4 Tool
QuickCheck is the tool mainly used in this thesis. Together with a new phenomenon,
property-based test, the coverage of testing cases is increased enormously. In the
following chapters, we will describe how to write property-based test, how to
implement QuickCheck generators and how to define QuickCheck state machine for
testing imperative functions.
1.5 Contribution
Several libraries provide alternative implementations of a very similar interface,
enabling the same property specification used by more than one library. Before giving
an example, we introduce a way to present a function 1 .It says which module the
function belongs to and how many arguments the function has.
When we look through Erlang/OTP, it is not hard to notice the functionality, size of a
data structure, is included both in array module and in queue module. One is
array:size/1 and another is queue:len/1 respectively. We will show how to test them
by the same property specification in chapter 3.5. Based on this goal, we can develop
an extensive property-based test suite for as many pure functions as practicable.
Furthermore, we also find some interesting bugs of imperative functions in module
digraph and dets by using QuickCheck state machine. The bugs will be presented and
explained in chapter 4.
1
ModuleName:FunctionName∕NumberOfFunctionArgument.
2
2. QuickCheck
QuickCheck is a testing tool produced by the company Quviq, which was recently
founded by John Hughes and Thomas Arts. It provides the ability to test software in
an entirely different way from previous techniques. Instead of writing test cases that
map to use cases in a one-to-one fashion, QuickCheck enables programmers to
specify the expected behavior of the tested system. It is realized by writing
specification of properties as well as generators that automatically generates a
multitude of behaviors that the system might have. The automation of the test case
generation and execution can then be effectively checked against the properties that
ought to hold. A many-to-one approach of testing is therefore accomplished by
applying QuickCheck. In addition, the benefit from using QuickCheck is not end here.
As long as an error is found, QuickCheck also returns a minimum counterexample of
the error. It is called shrinking strategy. Based on that, the source of error becomes
easily identified.
2.1 Generators
Generators generate random pieces of data. QuickCheck provides a set of generators
of basic data types, which are defined by Quviq AB [5]. For instance,

binary() generates random binary values.

bool() generates Boolean values.

char() generates random characters.

list(G::gen(A)) generates a list of defined-type elements.

oneof(Gs::list(gen(A))) generates an element from the list arguments.

function1(G::gen(A)) generates a one-argument function with result generated
by G.
Besides, programmers can also specify their own generators. By the definition of
string type, a list of characters, a string generator can be defined as the following.
string() → list(char()).
Some possible results are also presented.

“f*”

[87,213,104,194,145,76]

[]
3
2.2 Properties
A property is written in a logical way in order to describe what a function should
fulfill. Let‟s start with an example of testing lists:any/2, which is defined as folloing
[3].
lists:any(Pred, List) → bool()
Returns true if Pred(Elem) returns true for at least one element Elem in List.
Types: Pred=fun(Elem) → bool() ; Elem=term() ; List=[term()]
It is obviously complicated to test this function by manually designing different lists
of terms as well as propositional functions. However, what we do with QuickCheck is
just writing the specification of this function in Code 2-1 saying what ought to hold
and also the generators of test data.
prop_any ()→
?FORALL ({F, L}, {function1 (bool ()), list(term ())},
lists:any (F,L)==not totally_false ([F(E)||E←L])).
Code 2-1: Property specification of lists:any/2
totally_false ([])→
true;
totally_false ([H|T]) →
case H of
true→false;
false→totally_false (T)
end.
Code 2-2: Totally false
This property can be read as: For all lists of terms and for all one-argument functions
returning Boolean values, it should hold that the result of lists:any/2 is equivalent to
not all element E in list L make Pred(E) false. We start by checking this property with
QuickCheck, which returns true after passing 100 automatically generated test cases.
1> eqc:quickcheck(lists_test:prop_any()).
…………………………………………………………………………………….
OK, passed 100 tests
true
In Code 2-3, we will show what a failing test looks like. In case we misread the lists
documentation and come to believe that lists:keydelete/3 deletes all the tuples from a
list which have the same key as the given one, then it should hold that the given key
cannot be found by applying lists:keymember/3 after deletion.
4
prop_deleteKey()->
?FORALL({Key, L} , {key(), list({key(),int()})},
lists:keymember(Key,1,lists:keydelete(Key,1,L))==false).
Code 2-3: Failing property specification
However, the property specification failed.
1>eqc:quickcheck(guess:prop_deleteKey()).
……………………………………………Failed! After 36 tests.
{e,[{a,2},{e,2},{f,9},{e,-7}]}
Shrinking…. (4 times)
{e,[{e,0},{e,0}]}
false
This shows that prop_deleteKey() does not behave as expected, i.e. the given key still
exists in the list after executing lists:keydelete/3, and the test failed after 36 test cases.
The counterexample is {e,[{a,2},{e,2},{f,9},{e,-7}]}, and the minimal
counterexample
after
shrinking,
is
{e,[{e,0},{e,0}]},
i.e.
lists:keymember(e,1,lists:keydelete(e,1,[{e,0},{e,0}])) does not return false. Further
investigation reveals that lists:keydelete/3 only deletes the first occurrence of a tuple
whose Nth element, which is the first one in this case, compares equal to the given
key, namely lists:keydelete(e,1,[{e,0},{e,0}])==[{e,0}].
2.3 Symbolic Representation
While function calls are the element of generators, symbolic representation of the
functions is used here for the purpose of debugging. An example will be shown in
chapter 3.2. It is basically Erlang tuple in the form {call, module, function,
[arguments]}, which clearly defines the module of function as well as the function
arguments. This symbolic representation can be evaluated later by QuickCheck eval
function, which replaces symbolic representation with the result of corresponding
function call.
5
3. Testing Pure Functions
This chapter will introduce the reader how to test pure functions of the data structure
in Erlang/OTP as well as explain the results after testing. A pure function always
evaluates the same result by the same argument value(s). The function result do not
depend on the state that may change as program execution proceeds, nor depend on
any external input from I/O devices.
3.1 Data Generators
QuickCheck provides a group of basic generators for testing data. Based on that,
programmers can design their own generators by need. An Erlang term is defined by
Joe Armstrong as a general Erlang data structure in Erlang/OTP. It can be number,
atom, reference, fun, port, pid, tuple, list, and binary [1]. We use it as an example to
show how to generate data generators. However, only number, atom, tuple, list, and
binary are chosen for the return types of the term generator in this thesis.
term()→
?SIZED(Size, term(Size)).
term(Size) →
oneof( [int()] ++
[real()] ++
[char()] ++
[bool()] ++
[list(term(Size-1)) || Size>0] ++
[?LET(List, list(term(Size-1)), list_to_binary(List)) || Size>0] ++
[?LET(List, list(term(Size-1)), list_to_tuple(List)) || Size>0]).
Code 3-1: Term generator
Here ?SIZED and ?LET are defined in QuickCheck 1.162 [5] as Erlang
macros. ?SIZED(Size, Generator) control the size of generation from 1 to about 40 so
that the longest list or tuple can up to length 40. Generator list(term(Size-1)) cannot
be directly used by function list_to_binary/1 or list_to_tuple/1 without binding it to an
actual value with ?LET(Pat,G1,G2), which generates a value from G1, binds it to Pat,
then generates a value from G2, which may refer to the variables bound in Pat.
Generator term() can generate one single element of number, character or Boolean
value; as well as lists, binaries and tuples by using recursive calls. The elements of the
lists, which binaries and tuples are transformed from, can be any types of term.



64
{14, true}
[[-55.686, 208], 3, 5.4, [<<192,102>>, false]]
6
3.2 Data Structure Generators
In order to test the functionality of a data-structure called D, it is necessary to
implement a generator for all possible data-structure D. In an Erlang module, the
return value of functions can be variant types. However, only the functions whose
return type is the same as the tested module are chosen to compose the data structure
generator. We would like to show an example of a simple dict-generator, which
consists of three kinds of commands.

dict: new()→Dict
Creates a new dictionary [3].

dict: store(Key, Value, Dict1)→Dict2
Stores a Key-Value pair in a dictionary [3].

dict: from_list(List)→Dict
Converts the key/value list List to a dictionary [3].
A dict-generator is defined in Code 3-2.
dict1()->
?SIZED(Size,dict1(Size)).
dict1(0) ->
dict:new();
dict1(Size) ->
?LET(Dict_,dict1(Size-1),
begin
oneof( [dict:new()] ++
[?LET({Key,Value},{key(),term()},dict:store(Key,Value,Dict_)) || Size>0 ] ++
[?LET(L,list({key(),term()}),dict:from_list(L))]) end).
Code 3-2: Simple dict structure generator 1
However, a possible dict is generated in the following format, which is very hard to
be understood. Also, the information about the sequence of generated commands is
lost.
1>eqc_gen:sample(test:dict1()).
{dict,3,16,16,8,80,48,
{[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]},
{{[],
[[a,1,[2],<<>>,{}]],
[],
[[c|-7250470656]],
[[d|3]],
[],[],[],[],[],[],[],[],[],[],[]}}}
7
Instead, we rewrite this dict-generator with symbolic representation of commands.
dict2()->
?SIZED(Size,dict2(Size)).
dict2(0) ->
[{call,dict,new,[]}];
dict2(Size) ->
?LET(Dict_,dict2(Size-1),
begin
?LAZY(
oneof( [{call,dict,new,[]}] ++
[{call,dict,store,[key(),term(Size),Dict_]} || Size>0 ] ++
[{call,dict,from_list,[list({key(),term()})]}])) end).
Code 3-3: Simple dict structure generator 2
Macro ?LAZY is used for lazy evaluation so that symbolic function call
{call,dict,new,[]} will not be evaluated every time dict2/1 function is called.
2>eqc_gen:sample(test:dict2()).
{call,dict,store,
[e,{2.3},{call,dict,from_list,[[{f,<<34,249,182,220,20>>}]]}]}
This time, the generated result shows a dict generated recursively first by calling
dict:from_list/1 with a list, [{f,<<34,249,182,220,20>>}], as the argument, then by
calling dict:store/3 with a new key “e”, and a new value “{2.3}”, as the other two
arguments.
To evaluate this symbolic representation, we simply use the function, eqc_gen:eval/1,
in QuickCheck library.
3>eqc_gen:eval({call,dict,store,[e,{2.3},{call,dict,from_list,[[{f,<<34,249,182,220,20>>}]]}]}).
{dict,2,16,16,8,80,48,
{[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]},
{{[],[],[],[],[],
[[e|{2.3}]],
[[f|<<32,249,182,220,20>>]],
[],[],[],[],[],[],[],[],[]}}}
Now, the result is clearly to be seen. According to this method, a complete dictgenerator can be defined by applying all the libraries whose return type is dict. All
possible dictionaries can be generated afterwards.
8
3.3 Model Based Testing
To make the testing trusty and easier, we use model based testing. Says we have a self
defined module called decimal which consists of mathematic functions. Also, there is
a way to transform a decimal number into Erlang number. Here is the test:
D1, D2  decimal 2
[sum(D1,D2)] ≡ [D1] + [D2],
[sub(D1,D2)] ≡ [D1] - [D2],
[mult(D1,D2)] ≡ [D1] * [D2],
[div(D1,D2)] ≡ [D1] / [D2],
[lt(D1,D2)] ≡ [D1] < [D2].
Figure 3-1: Transformation from decimal to Erlang number
We use Erlang numbers to model the decimal numbers. For example the function sum
has the same effect as plus(+). By this way, we can check if the function works
correctly by comparing the result with the based testing model. The methodology is
based on [2].
In this thesis, we choose module lists in application stdlib and module erlang in
application erts as the based testing model. A model-based transforming function of
queue module can be defined as the following, which transforms a queue into a list.
model(Queue) ->
queue:to_list(Queue).
Code 3-4: Model-based transforming function of queue module
As testing any function of queue module, the corresponding lists function is called.
Code3-5 shows how to test queue:in/2.
queue:in(Item, Q1) -> Q2
Inserts Item at the rear of queue Q1. Returns the resulting queue Q2 [3].
2 [D1]
means the transformation from a decimal number to an Erlang number.
9
prop_queue_in() ->
?FORALL({Item,Q} , {term(),queue()},
model(queue:in(Item,Q)) == model(Q)++[Item]).
Code 3-5: Property specification of queue:in/2
Figure 3-2: Transformation from queue to list
In this case, [Q] means the transformation from a queue to a list.
3.4 Information Hiding
Several functions in different modules provide alternative implementations of a very
similar interface and have the same property. Therefore, it is efficient to reuse the
same property specification for as many functions as possible. According to this goal,
saving the main differences between various modules in Erlang records is the strategy
we use. So that it is really easy to change from testing one function to another by
passing different Erlang records. Read examples in the following Case Study.
3.5 Case Study
A more complex model-based transforming function is defined in Code 3-6, in which
function Fun transforms either key-value structure into a list of keys or set structure
into a list of all set elements. It depends on the purpose.
model(Module, Fun, Original)->
Module:Fun(Original).
Code 3-6: Model-based transforming function of variant modules
Case 1:
The functionality of getting the size of a data structure is included in eight different
modules of Erlang/OTP, which are dict, orddict, gb_trees, sets, ordsets, gb_sets,
queue and array. In the following, it shows how to test these libraries with the same
property and use the concepts which have been presented, i.e. model based testing and
information hiding.
10
Table 3-1: Size functions
Module
Function
Definitions of Function
1) dict
size
Returns the number of elements in a Dict.
2) orddict
size
Returns the number of elements in an Orddict.
3) gb_trees
size
Returns the number of nodes in Tree.
4) sets
size
Returns the number of elements in Set.
5) ordsets
size
Returns the number of elements in Ordset.
6) gb_sets
size
Returns the number of elements in Set.
7) queue
len
Calculates and returns the length of queue Q.
8) array
size
Get the number of entries in the array Array.
We save the differences between teated mofules in an Erlang record, module. Record
field, name, holds the name of modules; record field, gen, holds the data structure
generators; and record field, length, holds the length of the data structure. This Enlang
record can be defined like this:
-record(module,{name, gen, length}).
This Erlang record is named module. Name, gen and length are three fields of the
record. An instance of module record for dict structure can be defined by assigning
dict to name, dict() to gen, and size to length.

dictr()->
#module{name=dict, gen=dict(), length=size}.
So does other seven structures.

orddictr()->
#module{name=orddict, gen=orddict(), length=size}.

gb_treesr()->
#module{name=gb_trees, gen=gb_trees(), length=size}.

setsr()->
#module{name=sets, gen=sets(), length=size}.

ordsetsr()->
#module{name=ordsets, gen=ordsets(), length=size}.

gb_setsr()->
#module{name=gb_sets, gen=gb_sets(), length=size}.
11

queuer()->
#module{name=queue, gen=queue(), length=len}.

arrayr()->
#module{name=array, gen=array(), length=size}.
Code 3-7: Variation in modules 1
The property test is written in Code 3-8, where erlang:length/1 of based testing model
is used to get the length of list.
prop_size(R)->
#module{name=Module, gen=Gen, length=Length}=R,
?FORALL(D_,Gen,
begin
D=eval(D_),
Module:Length(D) == erlang:length( model(Module,to_list,D) )
end).
Code 3-8: Property specification of the size of tested data structure
Now, we can test these eight functions by changing different Erlang records. 100
automatically generated test cases for each are passed.
1> eqc:quickcheck(combine:prop_size(combine:dictr())).
…………………………………………………………………………………….
OK, passed 100 tests
true
2> eqc:quickcheck(combine:prop_size(combine:orddictr())).
…………………………………………………………………………………….
OK, passed 100 tests
true
Case 2:
Though there are more differences between tested modules than one can imagine. We
only save the information, which can be reused also by other property specifications,
in Erlang records, and leave the rest differences assigned through the arguments of
property.
In this case, the Erlang record is extended for more information about the modules.
The field, mapping, stores a function name. Which the function transforms the data
structure into a type of list. For example, dict:fetch_keys/1 returns a list of all keys in
the dictionary but sets:to_list/1 returns a list of all set elements. The function stored in
the field, compare, is used to find out the existence of the given target in a list. Reader
can ignore the meaning of it at this moment.
12
-record(module,{name, gen, length, mapping, compare}).

dictr()->
#module{name=dict, gen=dict(), length=size, mapping=fetch_keys, compare=fun
match/2}.

orddictr()->
#module{name=orddict,gen=orddict(), length=size, mapping=fetch_keys, compare=fun
equal/2}.

gb_treesr()->
#module{name=gb_trees, gen=gb_trees(), length=size, mapping=keys, compare=fun
equal/2}.

setsr()->
#module{name=sets, gen=sets(), length=size, mapping=to_list, compare=fun match/2}.

ordsetsr()->
#module{name=ordsets, gen=ordsets(), length=size, mapping=to_list, compare=fun
equal/2}.

gb_setsr()->
#module{name=gb_sets, gen=gb_sets(), length=size, mapping=to_list, compare=fun
equal/2}.
Code 3-9: Variation in modules 2
match(_,[])->
false;
match(K,[H|T])->
case K=:=H of
true-> true;
false-> match(K,T)
end.
Code 3-10: Matching equal function
equal(_,[])->
false;
equal(K,[H|T])->
case K==H of
true-> true;
false-> equal(K,T)
end.
Code 3-11: Comparing equal function
Now we want to test the following seven functions.
13
Table 3-2: Containment functions
Module
Function
Definitions of Function
1) dict
is_key
This function tests if Key is contained in the dictionary Dict.
2) orddict
is_key
This function tests if Key is contained in the dictionary Orddict.
3) gb_trees is_defined
Returns true if Key is present in Tree, otherwise false.
4) sets
is_element
Returns true if Element is an element of Set, otherwise false.
5) ordsets
is_element
Returns true if Element is an element of Ordset, otherwise false.
6) gb_sets
is_element
Returns true if Element is an element of Set, otherwise false.
7) gb_sets
is_member
Returns true if Element is an element of Set, otherwise false.
The functionalities of these seven functions are quite similar. Therefore, we will try to
test them by the same property specification. However, those different function names
are used nowhere else than this property specification, so we just defined them
through the arguments of property.
prop_contain(R,Fun)->
#module{name=Module, gen=Gen, mapping=Mapping, compare=Compare}=R,
?FORALL({K,D_},{term(),Gen},
begin
D=eval(D_),
Module:Fun(K,D) == Compare(K,model(Module,Mapping,D))
end).
Code 3-12: Property specification of containment functionality
These seven functions were tested by changing R as the related Erlang records and
Fun as the name of tested functions. 100 randomly generated test cases for each are
passed.
1> eqc:quickcheck(combine:prop_contain(combine:dictr(),is_key)).
…………………………………………………………………………………….
OK, passed 100 tests
true
2> eqc:quickcheck(combine:prop_contain(combine:gb_treesr(),is_defined)).
…………………………………………………………………………………….
OK, passed 100 tests
true
3> eqc:quickcheck(combine:prop_contain(combine:setsr(),is_element)).
…………………………………………………………………………………….
OK, passed 100 tests
14
true
4> eqc:quickcheck(combine:prop_contain(combine:gb_setsr(),is_member)).
…………………………………………………………………………………….
OK, passed 100 tests
true
3.6 Findings
In order to make a test case used by as many libraries as possible, the characteristic of
each library should be analyzed first. Afterwards, the libraries are grouped into
several types according to testing strategy. In this process, it shows how libraries were
designed and what the Erlang/OTP users should be careful about.
Testing the functionality of deletion for module dict, orddict, gb_trees, sets, ordsets,
and gb_sets shows an example of this idea. In these cases, lists:delete/2,
lists:keymember/3 and lists:keydelete/3 are the functions used in model-based testing.
lists: delete(Elem, List1) -> List2
Returns a copy of List1 where the first element matching (=:=) Elem is deleted, if
there is such an element [3].
lists: keymember(Key, N, TupleList) -> bool()
Returns true if there is a tuple in TupleList whose Nth element compares equal (==) to
Key, otherwise false [3].
lists: keydelete(Key, N, TupleList1) -> TupleList2
Returns a copy of TupleList1 where the first occurrence of a tuple whose Nth element
compares equal (==) to Key is deleted, if there is such a tuple [3].
In Code 3-13, we write a property specification for both orddict:erase/2 and
gb_trees:delete_any/2 by their specification.
orddict:erase(Key, Orddict1) -> Orddict2
This function erases all items with a given key from a dictionary if the key is present
in the orddict, otherwise does nothing; returns new orddict [3].
gb_trees:delete_any(Key, Tree1) -> Tree2
Removes the node with key Key from Tree1 if the key is present in the tree, otherwise
does nothing; returns new tree [3].
15
prop_delete1(R, Fun) ->
#module{name=Module, gen=Gen}=R,
?FORALL({K,S1_},{term(),Gen},
begin
S1=eval(S1_),
S2=Module:Fun(K,S1),
L=model(Module,to_list,S1),
case lists:keymember(K,1,L) of
true ->model(Module,to_list,S2)==lists:keydelete(K,1,L);
false->model(Module,to_list,S2)==L
end
end).
Code 3-13: Property specification of Deletion 1
First we transform the orddict or gb_trees to a key-value list, and then check if the key
presents in the list. If it returns true, all the items with the same key are deleted from
the list. Otherwise, an unchanged list is returned. Meanwhile, we transform the
orddict or gb_trees, which is the result after deletion, to a key-value list. Finally, we
compare these two lists. If the comparison is always true, orddict:erase/2 and
gb_trees:delete_any/2 were implemented correctly.
1>eqc:quickcheck(combine:prop_delete1(combine:orddictr(),erase)).
…………………………………………………………………………………….
OK, passed 100 tests
true
2>eqc:quickcheck(combine:prop_delete1(combine:gb_treesr(),delete_any)).
…………………………………………………………………………………….
OK, passed 100 tests
true
Next, let‟s look at the definition of dict: erase/2.
dict: erase(Key, Dict1) -> Dict2
This function erases all items with a given key from a dictionary if the key is present
in the dict, otherwise does nothing; returns new tree [3].
It seems like dict:erase/2 has the same property with orddict:erase/2 and
gb_trees:delete_any/2. However, as we test it with the same test case, error message
arises immediately.
eqc:quickcheck(combine:prop_delete1(combine:dictr(),erase)).
……………………………..…Failed! After 17 tests
{3.0, {call,dict,store,[3,[-2524017590],{call,dict,new,[]}]}}
Shrinking. (1 times)
{3.0, {call,dict,store,[3,[],{call,dict,new,[]}]}}
false
16
What happened? The reason is that there are two kinds of comparison used in the
Erlang/OTP. One is comparing equal (==) and another is matching equal (=:=).
Number 3 is the same as number 3.0 with comparing equal (==) but they are different
with matching equal(=:=). In orddict module and gb_trees module, comparing equal is
used for comparing keys. However, dict module uses matching equal. From this
minimal counterexample, dict:erase/2 tries to delete key 3.0 from the dict,
{call,dict,store,[3,[],{call,dict,new,[]}]}, where the only key is 3. The result should be an
unchanged dict. However, by applying Code 3-13, lists:keymember/3 checks if key
3.0 is presented in the dict with comparing equal. True is returned. So that it continues
to use lists:keydelete/3, which also uses comparing equal, in order to delete the item.
Finally, the result is an empty dict and it is different from the expected unchanged dict.
To fix the problem, we implement a helper function, deletekeyvaluefromlist/2, in
which lists:member/2 is called to find the given key in this dict by using matching
equal. So does other parts of deletekeyvalueformlist/2, where matching equal is the
only use for key-comparison.
deletekeyvaluefromlist(K,L)->
case lists:member(K, keys_from_list(L)) of
true ->deletekeyvaluefromlist(K,L,[]);
false->L
end.
deletekeyvaluefromlist(_,[],Acc)->
Acc;
deletekeyvaluefromlist(Key,[{K,V}|T],Acc)->
case Key=:=K of
true ->Acc++T;
false->deletekeyvaluefromlist(Key,T,Acc++[{K,V}])
end.
Code 3-14: Delete Key-Value by Matching Equal
Accordingly, we modify the property specification into a new version.
prop_delete2(R)->
#module{name=Module, gen=Gen}=R,
?FORALL({E,S1_},{term(),Gen},
begin
S1=eval(S1_),
S2=Module:erase(E,S1),
model(Module,to_list,S2)==deletekeyvaluefromlist(E,model(Module,to_list,S1))
end).
Code 3-15: Property specification of Deletion 2
17
1>eqc:quickcheck(combine:prop_delete2(combine:dictr())).
…………………………………………………………………………………….
OK, passed 100 tests
true
Finally, 100 test cases are passed against this new property. Next, let‟s look at the
definition of gb_trees:delete/2
gb_trees:delete(Key, Tree1) -> Tree2
Removes the node with key Key from Tree1; returns new tree. Assume that the Key is
present in the tree, crashes otherwise [3].
The only different functionality from the former ones is that an error message will
arise as the given key is not present in the gb_trees. However, this reason is sufficient
for writing another property specification, which is presented in Code 3-16.
prop_delete3(R) ->
#module{name=Module,gen=Gen,compare=Compare}=R,
?FORALL({K,D1_},{term(),Gen},
begin
D1=eval(D1_),
case catch(Module:delete(K,D1)) of
{'EXIT',_}->
not equal(K,model(Module,keys,D1));
D2->
model(Module,to_list,D2)==lists:keydelete(K,1,model(Module,to_list,D1))
end
end).
Code 3-16: Property specification of Deletion 3
1>eqc:quickcheck(combine:prop_delete3(combine:gb_treesr())).
…………………………………………………………………………………….
OK, passed 100 tests
true
Up to now, we have tested all the delete functions of key-value structures. In the
following, we will start testing delete functions of sets structures. In Code3-17, we
write a property for sets:del_element/2.
sets:del_element(Element, Set1) -> Set2
Returns a new set formed from Set1 with Element removed. If Element is not an
element in Set1, nothing is changed [3].
18
prop_delete4(R, Fun) ->
#module{name=Module, gen=Gen}=R,
?FORALL({E,S1_},{term(),Gen},
begin
S1=eval(S1_),
S2=Module:Fun(E,S1),
L=model(Module,to_list,S1),
case lists:member(E,L) of
true ->model(Module,to_list,S2)==lists:delete(E,L);
false->model(Module,to_list,S2)==L
end
end).
Code 3-17: Property specification of Deletion 4
First, we transform the set to a list called L and remove the element from it if the
element is present in L. Otherwise, keep the list unchanged. Meanwhile, we transform
the set, which is the result after deletion, to a list. Finally, we compare these two lists.
If the comparison is always true, sets:del_element/2 is designed correctly.
1>eqc:quickcheck(combine:prop_delete4(combine:setsr(),del_element)).
…………………………………………………………………………………….
OK, passed 100 tests
true
100 test cases are passed against this property. Next, let‟s test ordsets:del_element/2,
gb_sets:del_element/2 and gb_sets:delete_any/2.
ordsets:del_element(Element, Ordset1) -> Ordset2
Returns a new ordset formed from Ordset1 with Element removed. If Element is not
an element in Ordset1, nothing is changed [3].
gb_sets:del_element(Element, Set1) -> Set2
gb_sets:delete_any(Element, Set1) -> Set2
Returns a new gb_set formed from Set1 with Element removed. If Element is not an
element in Set1, nothing is changed [3].
It seems like ordsets:del_element/2, gb_sets:del_element/2 and gb_sets:delete_any/2
have the same functionality with sets:del_element/2. However, as we test it with the
same test case, error messages arise immediately.
19
1>eqc:quickcheck(combine:prop_delete4(combine:ordsetsr(),del_element)).
……………………………………Failed! After 80 tests.
{3, {call, ordsets, from_list, [[3.0,24,3]]}}
Shrinking. (1 times)
{3, {call, ordsets, from_list, [[3.0]]}}
false
2>eqc:quickcheck(combine:prop_delete4(combine:gb_setsr(),del_element)).
……………………………………Failed! After 55 tests.
{3, {call, gb_sets, insert, [3.0, {call, gb_sets, from_list, [[72,77,<<>>]]}]}}
Shrinking. (1 times)
{3, {call, gb_sets, insert, [3.0, {call, gb_sets, from_list, [[]]}]}}
false
3>eqc:quickcheck(combine:prop_delete4(combine:gb_setsr(),delete_any)).
……………………………………Failed! After 23 tests.
{3, {call, gb_sets, balance, [{1, {3.0, nil, nil}}]}}
false
The reason is similar to the former key-value structure cases. In module ordsets,
comparing equal is used for comparing elements. Therefore, to delete 3 from ordsets,
where 3.0 is the only one element, the result should be an empty ordsets. However, in
Code 3-17, it checks if key 3 is presented in the ordsets by using lists:member/2,
which uses matching equal. Since number 3 is different from number 3.0, an
unchanged ordset is returned. Therefore, the result is different from the expected
empty ordsets. This problem is the same in module gb_sets.
To fix the problem, we implement a helper function deletefromlist/2, in which
comparing equal is the only use for element-comparison.
deletefromlist(E,L)->
case equal(E,L) of
true ->deletefromlist(E,L,[]);
false->L
end.
deletefromlist(_,[],Acc)->
Acc;
deletefromlist(E,[H|T],Acc)->
case E==H of
true ->Acc++T;
false->deletefromlist(E,T,Acc++[H])
end.
Code 3-18: Delete Element by Comparing Equal
20
Afterwards, we modify prop_delete4() into a new version in Code 3-19, which can
even test the function sets:del_element/2. Fun1 is the name of tested functions and
Fun2 is the functions used for deletion.
prop_delete5(R,Fun1,Fun2) ->
#module{name=Module, gen=Gen, compare=Compare}=R,
?FORALL({E,S1_},{term(),Gen},
begin
S1=eval(S1_),
S2=Module:Fun1(E,S1),
L=model(Module,to_list,S1),
case Compare(E,L) of
true ->model(Module,to_list,S2)==Fun2(E,L);
false->model(Module,to_list,S2)==L
end
end).
Code 3-19: Property specification of Deletion 5
1>eqc:quickcheck(combine:prop_delete5(combine:setsr(),del_element,
fun lists:delete/2)).
…………………………………………………………………………………….
OK, passed 100 tests
true
2>eqc:quickcheck(combine:prop_delete5(combine:ordsetsr(),del_element,
fun combine:deletefromlist/2)).
…………………………………………………………………………………….
OK, passed 100 tests
true
3>eqc:quickcheck(combine:prop_delete5(combine:gb_setsr(),del_element,
fun combine:deletefromlist/2)).
…………………………………………………………………………………….
OK, passed 100 tests
true
4>eqc:quickcheck(combine:prop_delete5(combine:gb_setsr(),delete_any,
fun combine:deletefromlist/2)).
…………………………………………………………………………………….
OK, passed 100 tests
true
21
Finally, 100 test cases for each are passed against this new property.
The last delete function in gb_sets module will be tested as the following.
gb_sets: delete(Element, Set1) -> Set2
Removes the element from Set1; returns new set. Assume that the Element is present
in the set, crashes otherwise [3].
The only different strategy from Code 3-19 is that an error message will arise as the
element is not present in the set. However, this reason is sufficient for writing another
property specification, which is presented in Code 3-20.
prop_delete6(R) ->
#module{name=Module,gen=Gen,compare=Compare}=R,
?FORALL({E,D1_},{term(),gb_sets()},
begin
D1=eval(D1_),
case catch(Module:delete(E,D1)) of
{'EXIT',_}->
not equal(E,model(Module,to_list,D1));
D2->
model(Module,to_list,D2)==deletefromlist(E,model(Module,to_list,D1))
end
end).
Code 3-20: Property specification of Deletion 6
100 test cases are passed against this property.
1>eqc:quickcheck(combine:prop_delete6(combine:gb_setsr())).
…………………………………………………………………………………….
OK, passed 100 tests
true
22
Table 3-3: Deletion Functions
Module
Function
1) orddict
erase
2) gb_trees
delete_any
3) dict
erase
4) gb_trees
delete
5) sets
del_element
6) ordsets
del_element
7) gb_sets
del_element
8) gb_sets
delete_any
9) gb_sets
delete
Table 3-3 presents these nine tested functions. There are some suggestions for
Erlang/OTP users.
Comparing equal is used in the modules whose data is saved in order, i.e. orddcit,
gb_trees, ordsets and gb_sets. However, the other two modules, dict and sets, do not
have this need. So that matching equal is applied to compare keys and elements
respectively. Nevertheless, it seems hard to explain the reason of using matching
equal for lists:member/2 and lists:delete/2 but comparing equal for lists:keymember/3
and lists:keydelete/3. Consequently, Erlang/OTP users should apply those functions
carefully according to this detail.
Table 3-4: Comparison of (==) and (=:=)
Comparing numbers
Matching
comparison
Equal
comparison
Module
3 is different from dict,
3.0
sets
3 is the same as 3.0
orddict,
gb_trees,
ordsets,
gb_sets
23
Model based testing
functions
lists:member/2,
lists:delete/2
lists:keymember/3,
lists:keydelete/3
4. QuickCheck State Machine
QuickCheck state machine generates imperative command sequence and executes
them by applying five main functions, which will be introduced in the following.
Figure 4-1: A complete cycle of a QuickCheck state machine test case [7]
4.1 Initial state
Just like the conventional testing where a function is always called to initialize test
cases, QuickCheck state machine provides several techniques to initialize the state
prior to both generation and execution of a command sequence. However, we only
introduce the one used in this thesis.
What is state? Vertices and edges are two main components of a directed graph which
can be either cyclic or acyclic. Except the cyclicity is defined for a digraph in the
beginning, vertices and edges can be added, deleted or modified by each invocated
operation, which can change the state of digraph. We define an Erlang record to trace
states. This record in the following is named state and contains three fields: vertex for
a list of vertices, edge for a list of edges, and cyclicity for either atom cyclic or acyclic.
-record(state, {vertex, edge, cyclicity}).
Says the tested digraph is cyclic. In the beginning, there should be no vertex and edge.
Hence, we can initialize the state as #state{vertex=[],edge=[],cyclicity=cyclic}. The
first two lines of Code 4-1 shows how to generate a command sequence based on this
initial state.
24
prop_statem_correct (Cyclicity)→
?FORALL (Cmds, commands (?MODULE, #state{vertex=[], edge=[], cyclicity=Cyclicity}),
begin
G=digraph:new ([Cyclicity]),
{H,S,Res} = run_commands(?MODULE,Cmds,[{digraph,G}]),
?WHENFAIL (io:format("~p\n~p\n~p\n", [H, S, Res]), Res==ok)
end).
Code 4-1: property of digraph module
A cyclic digraph can then be tested from the console by typing:
eqc:quickcheck(digraph_test:prop_statem_correct(cyclic)).
So does acyclic digraph.
eqc:quickcheck(digraph_test:prop_statem_correct(acyclic)).
4.2 Function “command”
The function command is called after initializing states during generation-time. It
generates command sequence in symbolic representation. New commands can be
recursively added into the sequence by the dependency of previous state and the
requirements of commands in the sequence. More examples will be shown in chapter
5.
4.3 Function “precondition”
After executing command generator during generation time, function precondition is
used to filter out commands by returning Boolean value. If the precondition for the
specific command is true, this command will be kept in the test case sequence.
Otherwise, it will be excluded.
4.4 Function “postcondition”
The function postcondition is called during execution-time after executing the
commands created during generation-time. It is used to determine whether the return
value of each executed commands in the test case sequence corresponding to the
specification of the tested imperative functions.
4.5 Function “next_state”
The state is changed by executing from one command to another. The next_state
25
function is called twice. If the precondition returns true, the state is updated with new
symbolic values in the generation-time; if the postcondition returns true, the state is
modified with actual values in the execution-time. Figure4-2 and figure4-3 are
designed according to [6]. The dialogue box stands for the states before and after each
tested command presented in square boxes.
Figure 4-2: Process during generation time
Figure 4-3: Process during run time
During generation time, QuickCheck only generate symbolic command. However,
during execution time each command is invocated.
26
5. Testing Imperative Functions
Different from pure functions, program state can be changed by a sequence of
imperative function commands. In this chapter, the bugs found by using QuickCheck
state machine in Erlang/OTP will be presented and explained to the reader.
5.1 Module digraph
The digraph module in Erlang/OTP implements imperative labeled directed graph.
The digraph module is defined by Ericsson AB [3] and presented as following.
Every vertex and edge can be annotated with additional information which is called
label. All the edges are directed. An edge e = (v1, v2) is said to emanate from vertex
v1 and to be incident on vertex v2. Therefore, edge e1=(v1, v2) is opposite to edge
e2=(v2, v1). Multiple edges between vertices are allowed. If the new created vertex or
edge already exists, the old one will be modified so that all the vertices and edges are
unique [3].
If there is an edge e emanating from vertex v1 to incident on vertex v2, v1 is an inneighbor of v2 and v2 is an out-neighbor of v1. The out-degree of a vertex is a
number of out-neighbors of that vertex. The in-degree of a vertex is a number of inneighbors of that vertex [3].
A path P from v[1] to v[k] in digraph (E, V) is a non-empty sequence
v[1], v[2], ..., v[k] of vertices in V such that there is an edge (v[i], v[i+1]) in E for
1 <= i < k. The length of the path P is k-1. P is a cycle if the length of P is not zero
and v[1] = v[k]. An acyclic digraph is a digraph that has no cycles [3].
Bug1
Let‟s test acyclic digraph by running this command.
1> eqc:quickcheck(digraph_test:prop_statem_correct(acyclic)).
………………………………………………………………………..
Shrinking------------(16 times)
[{init, {state, [], [], acyclic}},
{set, {var, 5}, {call, digraph, add_vertex, [{var, digraph}]}},
{set, {var, 7}, {call, digraph, add_vertex, [{var, digraph}]}},
{set, {var, 8}, {call, digraph, add_vertex, [{var, digraph}]}},
{set, {var, 19}, {call, digraph, add_edge, [{var, digraph}, {var, 5}, {var, 8}, 0]}},
{set, {var, 30}, {call, digraph, add_edge, [{var, digraph}, {var, 19}, {var, 7}, {var, 5}, []]}},
{set, {var, 32}, {call, digraph, edges, [{var, digraph}, {var, 5}]}}]
27
[{{state, [], [], acyclic}, [‘$v’|0]},
{{state, [{[‘$v’|0], []}], [], acyclic}, [‘$v’|1]},
{{state, [{[‘$v’|0], []}, {[‘$v’|1]}, []], [], acyclic}, [‘$v’|2]},
{{state, [{[‘$v’|0], []}, {[‘$v’|1], []}, {[‘$v’|2], []}], [], acyclic}, [‘$e’|0]},
{{state, [{[‘$v’|0], []}, {[‘$v’|1], []}, {[‘$v’|2], []}], [{[‘$e’|0], [‘$v’|0], [‘$v’|2], 0}], acyclic},
[‘$e’|0]},
{{state, [{[‘$v’|0], []}, {[‘$v’|1], []}, {[‘$v’|2], []}], [{[‘$e’|0], [‘$v’|1], [‘$v’|0], 0}], acyclic},
[[‘$e’|0], [‘$e’|0]]}]
{postcondition, false}
false
Before explaining this failing test case, let‟s see the definition of function
digraph:add_vertex/1, digraph:add_edge/4, digraph:add_edge/5 and digraph:edges/2.
digraph:add_vertex(G) -> vertex()
Creates a vertex using the empty list as label, and returns the created vertex. The
created vertex is represented by the term ['$v' | N], where N is an integer >= 0 [3].
digraph:add_edge(G, E, V1, V2, Label) -> edge() | {error, Reason}
Creates (or modifies) the edge E of the digraph G, using Label as the (new) label of
the edge. The edge is emanating from V1 and incident on V2. Returns E [3].
digraph:add_edge(G, V1, V2, Label) -> edge() | {error, Reason}
Creates an edge which is represented by the term ['$e' | N], where N is an integer >= 0
[3].
digraph:edges(G, V) -> Edges
Returns a list of all edges emanating from or incident on V of the digraph G, in some
unspecified order [3].
In Table 5-1, it goes through this failing test case by seeing the entire process in
QuickCheck state machine.
Table 5-1: Entire process of QuickCheck state machine (digraph)
Command generation time:
Cycle 1
Cycle 2
Cycle 3
Initial state
Command
Precondition
Next state
Command
Precondition
Next state
Command
Precondition
Next state
{state, [], [], acyclic}
{call, digraph, add_vertex, [{var, digraph}]}
true
{state, [{{var,5},[]}], [], acyclic}
{call, digraph, add_vertex, [{var, digraph}]}
true
{state, [{{var,5},[]},{{var,7},[]}], [], acyclic}
{call, digraph, add_vertex, [{var, digraph}]}
true
{state, [{{var,5},[]},{{var,7},[]},{{var,8},[]}], [], acyclic}
28
Cycle 4
{call, digraph, add_edge, [{var, digraph}, {var, 5}, {var, 8},
0]}
true; (the edge would not create a cycle in this acyclic
digraph and both {var,5} and {var,8} are vertices of the
digraph {var,digraph})
{state, [{{var,5},[]},{{var,7},[]},{{var,8},[]}], [{{var,19},
{var,5}, {var,8}, 0}], acyclic}
{call, digraph, add_edge, [{var, digraph}, [„$e‟|0], {var, 7},
{var, 5}, []]}
true; (the edge would not create a cycle in this acyclic
digraph and both {var,7} and {var,5} are vertices of the
digraph {var,digraph})
{state, [{{var,5},[]},{{var,7},[]},{{var,8},[]}], [{{var,19},
{var,7}, {var,5}, []}], acyclic}
{call, digraph, edges, [{var, digraph}, {var, 5}]}
true
unchanged
Command
Precondition
Next state
Cycle 5
Command
Precondition
Next state
Cycle 6
Command
Precondition
Next state
Command execution time:
Cycle 1
Initial state
Invocation
Postcondition
Cycle 2
Next state
Invocation
Postcondition
Cycle 3
Next state
Invocation
Postcondition
Cycle 4
Next state
Invocation
Postcondition
Next state
Cycle 5
Invocation
Postcondition
Next state
Cycle 6
Invocation
Postcondition
{state, [], [], acyclic}
digraph:add_vertex(G)
true
{state, [{[„$v‟|0],[]}], [], acyclic}
digraph:add_vertex(G)
true
{state, [{[„$v‟|0],[]},{[„$v‟|1],[]}], [], acyclic}
digraph:add_vertex(G)
true
{state, [{[„$v‟|0],[]},{[„$v‟|1],[]},{[„$v‟|2],[]}], [], acyclic}
digraph:add_edge(G,[„$v‟|0],[„$v‟|2],0)
true
{state, [{[„$v‟|0],[]},{[„$v‟|1],[]},{[„$v‟|2],[]}],
[{[„$e‟|0],[„$v‟|0],[„$v‟|2], 0}], acyclic}
digraph:add_edge(G,[„$e‟|0],[„$v‟|1],[„$v‟|0],[])
true
{state, [{[„$e‟|0],[]},{[„$v‟|1],[]},{[„$v‟|2],[]}], [{[„$e‟|0],
[„$v‟|1],[„$v‟|0], []}], acyclic}
digraph:edges(G,[„$v‟|0])
false
An Erlang record, -record(state, {vertex, edge, cyclicity}), is used to store the state of
this state machine with a list of vertices, a list of edges and the cyclicity of the digraph.
In the beginning, {init, {state, [], [], acyclic}} defines the initial state as an empty
acyclic digraph. At the end, {postcondition, false} shows that it fails during verifying
29
the specification for executed command, which is digraph:edges/2 in this case. The
middle part shows the detail of how the postcondition failed. It can be divided into
three parts. First is the history of the test command sequence. {set, {var, 5}, {call,
digraph, add_vertex, [{var, digraph}]}} means to set the symbolic representation,
{call, digraph, add_vertex, [{var, digraph}]}, to the variable {var, 5}. We can see the
failed sequence starts with function add_vertex/1 and then add_vertex/1, add_vertex/1,
add_edge/4, add_edge/5, finally edges/2. The second part shows the sequence of
changed states. The state is always modified as executing next command. For
example, the first half part of {{state, [], [], acyclic}, [„$v‟|0]} shows the pre state for
executing command {set, {var, 5}, {call, digraph, add_vertex, [{var, digraph}]}} and
the second half part shows the return value of this add_vertex function. However, we
can see the state is actually changed by adding a new element {[„$v‟|0], []} to the
vertex list from {{state, [{[„$v‟|0], []}], [], acyclic}, [„$v‟|1]} when executing next
command.
Because the result of digraph:edges/2 is [[„$e‟|0], [„$e‟|0]]. It reveals function
digraph:add_edge/5 did not correctly modify the same named edge and keep [„$e‟|0]
unique.
To clarify the actual function of digraph:add_edge/5, the following example is chosen
in this case.
First, we need an empty digraph G.
1>G=digraph:new().
{digraph,53261,57358,61455,true}
Four vertices are added into digraph.
2>digraph:add_vertex(G).
['$v'|0]
3>digraph:add_vertex(G).
['$v'|1]
4>digraph:add_vertex(G).
['$v'|2]
5>digraph:add_vertex(G).
['$v'|3]
Add edge ['$e'|0], which emanates from vertex [„$v‟|0] and incident on vertex [„$v‟|1]
without label, by using the function digraph:add_edge/5.
4>digraph:add_edge(G,['$e'|0],['$v'|0],['$v'|1],{}).
['$e'|0]
Up to now, the digraph G can be showed as Figure 5-1.
30
Figure 5-1: Edge added
Figure 5-2: Edge modified
Next, add a directed edge emanates from vertex [„$v‟|2] and incident on vertex [„$v‟|3]
without label. As all the vertices and edges in the digraph should be unique, edge
[„$e‟|0] is modified by new assignments.
5>digraph:add_edge(G,['$e'|0],['$v'|2],['$v'|3],{}).
['$e'|0]
Base on the principle of uniqueness, digraph G should be changed to Figure 5-2
afterwards.
Now, we can test the characteristics of digraph G.
The number of edges in total.
6>digraph:no_edges(G).
1
The two vertices which edge [„$e‟|0] is between.
7>digraph:edge(G,[‘$e’|0]).
[[‘$e’|0],[‘$v’|2],[‘$v’|3],{}]
The out-degree of vertex [„$v‟|2]
8>digraph:out_degree(G,['$v'|2]).
1
All the edges which emanate from vertex [„$v‟|2]
9>digraph:out_edges(G,['$v'|2]).
[[‘$e’|0]]
All the out-neighbours of vertex [„$v‟|2]
10>digraph:out_neighbours(G,[‘$v’|2]).
[[‘$v’|3]]
The in-degree of vertex [„$v‟|3]
11>digraph:in_degree(G,['$v'|3]).
1
31
All the edges which are incident on vertex [„$v‟|3]
12>digraph:in_edges(G,['$v'|3]).
[[‘$e’|0]]
All the in-neighbours of vertex [„$v‟|3]
13>digraph:in_neighbours(G,[‘$v’|3]).
[[‘$v’|2]]
Up to now, all the test cases return correct results. However, bugs are found in the
following cases.
The out-degree of vertex [„$v‟|0]
14>digraph:out_degree(G,['$v'|0]).
1
All the edges which emanate from vertex [„$v‟|0]
15>digraph:out_edges(G,['$v'|0]).
[[‘$e’|0]]
All the out-neighbours of vertex [„$v‟|0]
16>digraph:out_neighbours(G,[‘$v’|0]).
[[‘$v’|3]]
The in-degree of vertex [„$v‟|1]
17>digraph:in_degree(G,['$v'|1]).
1
All the edges which are incident on vertex [„$v‟|1]
18>digraph:in_edges(G,['$v'|1]).
[[‘$e’|0]]
All the in-neighbours of vertex [„$v‟|1]
19>digraph:in_neighbours(G,[‘$v’|1]).
[[‘$v’|2]]
Obviously, edge [„$e‟|0] has been completely modified. However, it is still recorded
as an out-edge of vertex [„$v‟|0] and an in-edge of vertex [„$v‟|1]. Therefore, [„$v‟|3]
is an out-neighbor of [„$v‟|0] and [„$v‟|2] is an in-neighbor of [„$v‟|1]. This bug also
exists in cyclic digraph.
The same bug makes another case crushed. Bug2 presents this.
Bug2
1>G=digraph:new().
{digraph,40976,45073,49170,true}
32
2>digraph:add_vertex(G).
[‘$v’|0]
3>digraph:add_vertex(G).
[‘$v’|1]
4>digraph:add_edge(G,[‘$e’|0],[‘$v’|0],[‘$v’|0],{}).
[‘$e’|0]
5>digraph:add_edge(G,[‘$e’|0],[‘$v’|1],[‘$v’|0],{}).
[‘$e’|0]
6>digraph:del_edge(G,[‘$e’|0]).
true
7>digraph:out_neighbours(G,['$v'|1]).
[]
8>digraph:out_neighbours(G,['$v'|0]).
** exception error: bad argument
in function ets:lookup_element/3
called as ets:lookup_element (45073,['$e'|0],3)
in call from digraph:collect_elems/4
[„$e‟|0] is still recorded as an our-edge of [„$v‟|0] after being modified. Therefore, as
trying to get the out-neighbours of [„$v‟|0], the system intends to find it out in the
opposite of [„$e‟|0], which, however, has been deleted. Hence, exception arises
accordingly.
Bug3
The function digraph:del_path(G,V1,V2) -> true deletes edges from the digraph G
until there are no path from the vertex V1 to the vertex V2 [3]. However, it cannot
work at all.
1>D=digraph:new().
{digraph,40973,45070,49167,true}
2>digraph:add_vertex(D).
[‘$v’|0]
3>digraph:add_vertex(D).
[‘$v’|1]
4>digraph:add_edge(D,[‘$v’|0],[‘$v’|1]).
[‘$e’|0]
digraph:get_path(G, V1, V2) -> Vertices | false
Tries to find a simple path from the vertex V1 to the vertex V2 of the digraph G.
Returns the path as a list [V1,....., V2] of vertices, or false if no simple path from V1
to V2 of length one or more exists [3].
5>digraph:get_path(D,[‘$v’|0],[‘$v’|1]).
[[‘$v’|0],[‘$v’|1]]
33
However, an exception arises by deleting this path. The process which controls the
digraph terminates.
6>digraph:del_path(D,[‘$v’|0],[‘$v’|1]).
** exception error: bad argument
in function ets:lookup_element/3
called as ets:lookup_element (45070,['$e'0|],3)
in call from digraph:collect_elems/4
in call from digraph:get_path/3
in call from digraph:del_path/3
5.2 Module dets
Module dets provides a term storage on file. It is a collection of objects, which are
tuples, with the key at the same position. Tables are divided into three different types,
set, bag and duplicate_bag. A set can only have one object associated with each key.
A bag or duplicate_bag can have many objects associated with each key. But the same
object can be more than one in duplicate_bag.
Bug4
This bug appears in testing type duplicate_bag under module dets. We can simplify
the test cases by assign 1 as the key-position in the tuple.
1>eqc:quickcheck(dets_tests:prop_statem_correct(duplicate_bag,1)).
……………………………………………………………………….
{postcondition, false}
Shrinking------------------(10 times)
[{init, {state, duplicate_bag, 1, []}},
{set, {var,13}, {call, dets, insert, [{var, tab}, {3, 0}]}},
{set, {var,14}, {call, dets, insert_new, [{var,tab}, [{3, 0}, {3, 0}]]}},
{set, {var,15}, {call, dets, lookup, [{var, tab}, 3}}]
[{{state, duplicate_bag, 1, []}, ok},
{{state, duplicate_bag, 1, [{3, 0}]}, false},
{{state, duplicate_bag, 1, [{3, 0}]}, [{3, 0}, {3, 0}]}]
{state, duplicate_bag, 1, [{3, 0}]}
{postcondition, false}
false
Before explaining this test case, let‟s see the definition of function dets:insert/2,
dets:insert_new/2 and dets:lookup/2.
34
dets:insert(TableName, Objects) -> ok | {error, Reason}
Inserts one or more objects into the table Name. If there already exists an object with
a key matching the key of some of the given objects and the table type is set, the old
object will be replaced [3].
dets:insert_new(TableName, Objects) -> Bool
Inserts one or more objects into the table Name. If there already exists some object
with a key matching the key of any of the given objects the table is not updated and
false is returned, otherwise the objects are inserted and true returned [3].
dets:lookup(TableName, Key) -> [Object] | {error, Reason}
Returns a list of all objects with the key stored in the table [3].
In Table 5-2, it goes through this failing test case by seeing the entire process of
QuickCheck state machine.
Table 5-2: Entire process of QuickCheck state machine (dets)
Command generation time:
Cycle 1
Cycle 2
Cycle 3
Initial state
Command
Precondition
Next state
Command
Precondition
Next state
Command
Precondition
Next state
{state, duplicate_bag, 1, []}
{call, dets, insert, [{var, tab}, {3, 0}]}
true
{state, duplicate_bag, 1, [{3,0}]}
{call, dets, insert_new, [{var,tab}, [{3, 0}, {3, 0}]]}
true
{state, duplicate_bag, 1, [{3,0}]}
{set, {var,15}, {call, dets, lookup, [{var, tab}, 3}}
true
unchanged
Command execution time:
Cycle 1
Cycle 2
Cycle 3
Initial state
Invocation
Postcondition
Next state
Invocation
Postcondition
Next state
Invocation
Postcondition
{state, duplicate_bag, 1, []}
dets:insert(D,{3,0})
true
{state, duplicate_bag, 1, [{3, 0}]}
dets:insert_new(D,[{3,0},{3,0}])
true; (If there already exists some object with a key
matching the key of any of the given objects the table is
not updated and false is returned, otherwise the objects are
inserted and true returned.)
{state, duplicate_bag, 1, [{3, 0}]}
dets:lookup(D,3)
false
35
An Erlang record, -record(state, {type, keypos, list}), is used to store the state of this
state machine with the type of tested data-structure, the key position of tuple and a list
of latest tuples in the actual table. In the beginning, {init, {state, duplicate_bag, 1, []}}
defines the initial state as an empty duplicate_bag with the key position at 1. At the
end, {postcondition, false} shows that it fails during verifying the specification for
executed command, which is dets:lookup/2 in this case. The middle part shows the
detail of how the postcondition failed. It can be divided into three parts. First is the
history of the test command sequence. {set, {var,13}, {call, dets, insert, [{var, tab},
{3, 0}]}} means to set the symbolic representation, {call, dets, insert, [{var, tab}, {3,
0}]}, to the variable {var,13}. We can see the failed sequence starts with function
dets:insert/2 and then dets:insert_new/2, finally dets:lookup/2. The second part shows
the sequence of changed states. The state is always modified as executing next
command. For example, the first half part of {{state, duplicate_bag, 1, []}, ok} shows
the pre state for executing command {call, dets, insert, [{var, tab}, {3, 0}]} and the
second half part shows the return value of this insert function. However, we can see
the state is actually changed by adding a new element {3, 0} to the list from {{state,
duplicate_bag, 1, [{3, 0}]}, false} as executing the next command.
As 3 is already the key of stored element {3, 0}, the list of elements [{3, 0}, {3, 0}]
will not be inserted into the table by calling function insert_new. Therefore, it returns
false, which can be seen in the second part of {{state, duplicate_bag, 1, [{3, 0}]},
false}. Though {{state, duplicate_bag, 1, [{3, 0}]}, [{3, 0}, {3, 0}]} shows the state is
not changed accordingly, function lookup returns [{3, 0}, {3, 0}] but not the expected
value [{3, 0}].
Now, we test this command sequence directly from the console.
1> dets:open_file(tab,[{type,duplicate_bag}]).
{ok,tab}
2> dets:insert(tab,{3,0}).
ok
3> dets:insert_new(tab,[{3,0},{3,0}]).
false
4> dets:lookup(tab,3).
[{3,0}]
What a surprise!! dets:lookup/2 returns correct answer from the console. According to
the observation, the delayed time between each command by typing them one by one
from the console is actually much longer than executing the same commands in a
sequence by QuickCheck. It is probably the source of error? Based on this guessing,
we write Code 5-1 and execute it by setting different delayed time between executing
dets:insert/2 and dets:insert_new2.
36
weird(Delay) ->
dets:open_file(tabName, [{type, duplicate_bag}]),
dets:delete_all_objects(tabName),
dets:insert(tabName, {3,0}),
receive after Delay -> ok end,
dets:insert_new(tabName, [{3,0},{3,0}]),
X = dets:lookup(tabName, 3),
dets:close(tabName),
X.
Code 5-1: Testing delayed error
Now, let‟s set different delayed time.
1> dets_test:weird(2500).
[{3,0},{3,0}]
2> dets_test:weird(2750).
[{3,0},{3,0}]
3> dets_test:weird(2900).
[{3,0},{3,0}]
4> dets_test:weird(2950).
[{3,0},{3,0}]
5> dets_test:weird(2999).
[{3,0},{3,0}]
6> dets_test:weird(3000).
[{3,0}]
This bug is even stranger because we don't need to use a concurrent program to find it.
As the delayed time becomes longer than 3000 milliseconds, function lookup returns
the correct answer. Therefore, delayed time is the source of error. However, we
cannot explain this.
This error is caused not only according to the delayed time between executing
dets:insert/2 and dets:insert_new/2. The same thing happens if we use
dets:insert_new/2 instead of dets:insert/2.
37
6. Conclusion
QuickCheck increases the possibility of finding failing test cases by self defined
generators and property-based testing. Through applying QuickCheck, we have tested
almost all the pure functions in dict module, orddict module, gb_trees module, sets
module, ordsets module, gb_sets module, lists module, proplists module, string
module, queue module and array module; as well as the imperative functions in
digraph module, ets module and dets module. All the pure functions that have been
tested by QuickCheck got the passing results, which provides Erlang programmers
confidence of using those libraries. As well as, we also implemented generic
properties for more than one tested functions. Furthermore, the found bugs among
imperative functions have been reported to the Erlang/OTP programming team.
38
Bibliography
[1] Armstrong, J. (2007). Programming Erlang. United States of America: The
Progmatic Programmers.
[2] Arts, T., Castro, L. M., & Hughes, J. (2008). Testing Erlang Data Types with
Quviq QuickCheck. Erlang'08. Victoria, BC, Canada: ACM.
[3] Ericsson AB. (2009). STDLIB Reference Manual. Retrieved 2009, from
Erlang/OTP R13B: http://erlang.org/doc/index.html
[4] Granberg, A., & Jernberg, D. (2007). NBAP message construction using
QuickCheck. Kista: Linköpings Universitet.
[5] Quviq AB. (2009). QuickCheck 1.162 . Quviq.
[6] Quviq AB. (2008). QuickCheck for Erlang Users. Stockholm: Quviq.
[7] Wang, J., & Yeoh, S. S. (2009). Testing Software Block with QuickCheck.
Göteborg: Chalmers University and Technology.
39
Appendix
A. User Manu
A-1 Pure Functions
dict module (combine.erl)
Function names
append
append_list
erase
fetch
fetch_keys
filter
find
from_list
is_key
map
merge
size
store
update/3
update/4
update_counter
QuickCheck testing commands
eqc:quickcheck(combine:prop_append1(combine:dictr()))
eqc:quickcheck(combine:prop_append_list1(combine:dictr()))
eqc:quickcheck(combine:prop_delete2(combine:dictr()))
eqc:quickcheck(combine:prop_fetch_get(combine:dictr() , fetch))
eqc:quickcheck(combine:prop_fetch_keys(combine:dictr() , fetch_keys))
eqc:quickcheck(combine:prop_filter1(combine:dictr()))
eqc:quickcheck(combine:prop_get_value(combine:dictr() , fetch , find))
eqc:quickcheck(combine:prop_from_list1(combine:dictr()))
eqc:quickcheck(combine:prop_contain(combine:dictr() , is_key))
eqc:quickcheck(combine:prop_map(combine:dictr()))
eqc:quickcheck(combine:prop_merge(combine:dictr()))
eqc:quickcheck(combine:prop_size(combine:dictr()))
eqc:quickcheck(combine:prop_insert1(combine:dictr() , store ,
fun combine:insertkeyvaluefromlist/4))
eqc:quickcheck(combine:prop_update3(combine:dictr()))
eqc:quickcheck(combine:prop_update4(combine:dictr()))
eqc:quickcheck(combine:prop_update_counter(combine:dictr()))
40
orddict module (combine.erl)
Function names
QuickCheck testing commands
eqc:quickcheck(combine:prop_dict_is_ordered(combine:orddictr()))
append
append_list
erase
fetch
fetch_keys
filter
find
from_list
is_key
map
merge
size
store
update/3
update/4
update_counter
eqc:quickcheck(combine:prop_append2(combine:orddictr()))
eqc:quickcheck(combine:prop_append_list2(combine:orddictr()))
eqc:quickcheck(combine:prop_delete1(combine:orddictr() , erase))
eqc:quickcheck(combine:prop_fetch_get(combine:orddictr() , fetch))
eqc:quickcheck(combine:prop_fetch_keys(combine:orddictr() ,
fetch_keys))
eqc:quickcheck(combine:prop_filter1(combine:orddictr()))
eqc:quickcheck(combine:prop_get_value(combine:orddictr() , fetch ,
find))
eqc:quickcheck(combine:prop_from_list1(combine:orddictr()))
eqc:quickcheck(combine:prop_contain(combine:orddictr() ,is_key))
eqc:quickcheck(combine:prop_map(combine:orddictr()))
eqc:quickcheck(combine:prop_merge(combine:orddictr()))
eqc:quickcheck(combine:prop_size(combine:orddictr()))
eqc:quickcheck(combine:prop_insert1(combine:orddictr() , store ,
fun lists:keystore/4))
eqc:quickcheck(combine:prop_update3(combine:orddictr()))
eqc:quickcheck(combine:prop_update4(combine:orddictr()))
eqc:quickcheck(combine:prop_update_counter(combine:orddictr()))
41
gb_trees module (combine.erl)
Function names
QuickCheck testing commands
eqc:quickcheck(combine:prop_dict_is_ordered(combine:gb_treesr()))
delete
delete_any
enter
from_orddict
get
is_defined
is_empty
keys
lookup
size
take_largest,
largest
take_smallest,
smallest
values
eqc:quickcheck(combine:prop_delete3(combine:gb_treesr()))
eqc:quickcheck(combine:prop_delete1(combine:gb_treesr() , delete_any))
eqc:quickcheck(combine:prop_insert1(combine:gb_treesr() , enter ,
fun lists:keystore/4))
eqc:quickcheck(combine:prop_from_list3(combine:gb_treesr() ,
from_orddict))
eqc:quickcheck(combine:prop_fetch_get(combine:gb_treesr() , get))
eqc:quickcheck(combine:prop_contain(combine:gb_treesr() , is_defined))
eqc:quickcheck(combine:prop_is_empty(combine:gb_treesr()))
eqc:quickcheck(combine:prop_fetch_keys(combine:gb_treesr() , keys))
eqc:quickcheck(combine:prop_get_value(combine:gb_treesr() , get ,
lookup))
eqc:quickcheck(combine:prop_size(combine:gb_treesr()))
eqc:quickcheck(combine:prop_smallest1(combine:gb_treesr()))
eqc:quickcheck(combine:prop_smallest1(combine:gb_treesr()))
eqc:quickcheck(combine:prop_values(combine:gb_treesr()))
42
sets module (combine.erl)
Function names
add_element
del_element
filter
from_list
intersection/1
intersection/2
is_element
is_set
is_subset
size
subtract
union/1
union/2
QuickCheck testing commands
eqc:quickcheck(combine:prop_insert2(combine:setsr() , add_element))
eqc:quickcheck(combine:prop_delete5(combine:setsr(), del_element ,
fun lists:delete/2))
eqc:quickcheck(combine:prop_filter2(combine:setsr()))
eqc:quickcheck(combine:prop_from_list2(combine:setsr()))
eqc:quickcheck(combine:prop_intersection_many_sets(combine:setsr()))
eqc:quickcheck(combine:prop_intersection_two_sets(combine:setsr()))
eqc:quickcheck(combine:prop_contain(combine:setsr() , is_element))
eqc:quickcheck(combine:prop_is_set(combine:setsr()))
eqc:quickcheck(combine:prop_is_subset(combine:setsr()))
eqc:quickcheck(combine:prop_size(combine:setsr()))
eqc:quickcheck(combine:prop_subtract1(combine:setsr()))
eqc:quickcheck(combine:prop_union_many_sets(combine:setsr()))
eqc:quickcheck(combine:prop_union_two_sets(combine:setsr()))
43
ordsets module (combine.erl)
Function
names
QuickCheck testing commands
eqc:quickcheck(combine:prop_set_is_ordered(combine:ordsetsr()))
add_element
del_element
filter
from_list
intersection/1
intersection/2
is_element
is_set
is_subset
size
subtract
union/1
union/2
eqc:quickcheck(combine:prop_insert2(combine:ordsetsr() , add_element))
eqc:quickcheck(combine:prop_delete5(combine:ordsetsr() , del_element ,
fun combine:deletefromlist/2))
eqc:quickcheck(combine:prop_filter2(combine:ordsetsr()))
eqc:quickcheck(combine:prop_from_list2(combine:ordsetsr()))
eqc:quickcheck(combine:prop_intersection_many_sets(combine:ordsetsr()))
eqc:quickcheck(combine:prop_intersection_two_sets(combine:ordsetsr()))
eqc:quickcheck(combine:prop_contain(combine:ordsetsr() , is_element))
eqc:quickcheck(combine:prop_is_set(combine:ordsetsr()))
eqc:quickcheck(combine:prop_is_subset(combine:ordsetsr()))
eqc:quickcheck(combine:prop_size(combine:ordsetsr()))
eqc:quickcheck(combine:prop_subtract2(combine:ordsetsr() , subtract))
eqc:quickcheck(combine:prop_union_many_sets(combine:ordsetsr()))
eqc:quickcheck(combine:prop_union_two_sets(combine:ordsetsr()))
44
gb_sets module (combine.erl)
Function
names
QuickCheck testing commands
eqc:quickcheck(combine:prop_set_is_ordered(combine:gb_setsr()))
add
add_element
del_element
delete_any
delete
difference
filter
from_list
from_ordset
insert
intersection/1
intersection/2
is_element
is_empty
is_member
is_set
is_subset
size
singleton
subtract
take_largest,
largest
take_smallest,
smallest
union/1
union/2
eqc:quickcheck(combine:prop_insert2(combine:gb_setsr() , add))
eqc:quickcheck(combine:prop_insert2(combine:gb_setsr() , add_element))
eqc:quickcheck(combine:prop_delete5(combine:gb_setsr() , del_element ,
fun combine:deletefromlist/2))
eqc:quickcheck(combine:prop_delete5(combine:gb_setsr() , delete_any ,
fun combine:deletefromlist/2))
eqc:quickcheck(combine:prop_delete6(combine:gb_setsr()))
eqc:quickcheck(combine:prop_subtract2(combine:gb_setsr() , difference))
eqc:quickcheck(combine:prop_filter2(combine:gb_setsr()))
eqc:quickcheck(combine:prop_from_list2(combine:gb_setsr()))
eqc:quickcheck(combine:prop_from_list3(combine:gb_setsr() ,
from_ordset))
eqc:quickcheck(combine:prop_insert3(combine:gb_setsr()))
eqc:quickcheck(combine:prop_intersection_many_sets(combine:gb_setsr()))
eqc:quickcheck(combine:prop_intersection_two_sets(combine:gb_setsr()))
eqc:quickcheck(combine:prop_contain(combine:gb_setsr() , is_element))
eqc:quickcheck(combine:prop_is_empty(combine:gb_setsr()))
eqc:quickcheck(combine:prop_contain(combine:gb_setsr() , is_member))
eqc:quickcheck(combine:prop_is_set(combine:gb_setsr()))
eqc:quickcheck(combine:prop_is_subset(combine:gb_setsr()))
eqc:quickcheck(combine:prop_size(combine:gb_setsr()))
eqc:quickcheck(combine:prop_singleton(combine:gb_setsr()))
eqc:quickcheck(combine:prop_subtract2(combine:gb_setsr() , subtract))
eqc:quickcheck(combine:prop_smallest2(combine:gb_setsr()))
eqc:quickcheck(combine:prop_smallest2(combine:gb_setsr()))
eqc:quickcheck(combine:prop_union_many_sets(combine:gb_setsr()))
eqc:quickcheck(combine:prop_union_two_sets(combine:gb_setsr()))
45
queue module (queue_test.erl ; combine.erl)
Function names
QuickCheck testing commands
out
eqc:quickcheck(queue_test:prop_out())
out_r
eqc:quickcheck(queue_test:prop_out_r())
snoc
eqc:quickcheck(queue_test:prop_snoc())
in
eqc:quickcheck(queue_test:prop_in())
join
eqc:quickcheck(queue_test:prop_join())
split
eqc:quickcheck(queue_test:prop_split())
reverse
eqc:quickcheck(queue_test:prop_reverse())
from_list
eqc:quickcheck(combine:prop_from_list3(combine:queuer() , from_list))
size
eqc:quickcheck(combine:prop_size(combine:queuer()))
filter
eqc:quickcheck(combine:prop_filter2(combine:queuer()))
is_empty
eqc:quickcheck(combine:prop_is_empty(combine:queuer()))
in_r
eqc:quickcheck(combine:prop_insert_front(in_r))
cons
eqc:quickcheck(combine:prop_insert_front(cons))
get
eqc:quickcheck(combine:prop_front_item(get))
head
eqc:quickcheck(combine:prop_front_item(head))
last
eqc:quickcheck(combine:prop_end_item(last))
daeh
eqc:quickcheck(combine:prop_end_item(daeh))
get_r
eqc:quickcheck(combine:prop_end_item(get_r))
peek
eqc:quickcheck(combine:prop_peek(peek , fun erlang:hd/1))
peek_r
eqc:quickcheck(combine:prop_peek(peek_r , fun lists:last/1))
drop
eqc:quickcheck(combine:prop_except_front(drop))
tail
eqc:quickcheck(combine:prop_except_front(tail))
init
eqc:quickcheck(combine:prop_except_end(init))
lait
eqc:quickcheck(combine:prop_except_end(lait))
drop_r
eqc:quickcheck(combine:prop_except_end(drop_r))
46
array module (array_test.erl ; combine.erl)
Function names
QuickCheck testing commands
fix
eqc:quickcheck(array_test:prop_fix())
from_list
eqc:quickcheck(array_test:prop_from_list())
from_orddict
eqc:quickcheck(array_test:prop_from_orddict())
get
eqc:quickcheck(array_test:prop_get())
is_fix
eqc:quickcheck(array_test:prop_is_fix())
map
eqc:quickcheck(array_test:prop_map())
relax
eqc:quickcheck(array_test:prop_relax())
reset
eqc:quickcheck(array_test:prop_reset())
resize/1
eqc:quickcheck(array_test:prop_resize1())
resize/2
eqc:quickcheck(array_test:prop_resize2())
set
eqc:quickcheck(array_test:prop_set())
sparse_map
eqc:quickcheck(array_test:prop_sparse_map())
sparse_size
eqc:quickcheck(array_test:prop_sparse_size())
sparse_to_list
eqc:quickcheck(array_test:prop_sparse_to_list())
sparse_to_orddict
eqc:quickcheck(array_test:prop_sparse_to_orddict())
size
eqc:quickcheck(combine:prop_size(combine:arrayr()))
47
proplists module (proplists_test.erl)
Function names
QuickCheck testing commands
append_values
eqc:quickcheck(proplists_test:prop_append_values())
compact
eqc:quickcheck(proplists_test:prop_compact())
delete
eqc:quickcheck(proplists_test:prop_delete())
expand
eqc:quickcheck(proplists_test:prop_expand())
get_all_values
eqc:quickcheck(proplists_test:prop_get_all_values())
get_bool
eqc:quickcheck(proplists_test:prop_get_bool())
get_keys
eqc:quickcheck(proplists_test:prop_get_keys())
get_value
eqc:quickcheck(proplists_test:prop_get_value())
is_defined
eqc:quickcheck(proplists_test:prop_is_defined())
lookup
eqc:quickcheck(proplists_test:prop_lookup())
lookup_all
eqc:quickcheck(proplists_test:prop_lookup_all())
property/1
eqc:quickcheck(proplists_test:prop_property1())
property/2
eqc:quickcheck(proplists_test:prop_property2())
split
eqc:quickcheck(proplists_test:prop_split())
substitute_aliases
eqc:quickcheck(proplists_test:prop_substitute_aliases())
substitute_negations
eqc:quickcheck(proplists_test:prop_substitute_negations())
unfold
eqc:quickcheck(proplists_test:prop_unfold())
48
lists module (lists_test.erl)
Function names
QuickCheck testing commands
all
eqc:quickcheck(lists_test:prop_all())
any
eqc:quickcheck(lists_test:prop_any())
append/1
eqc:quickcheck(lists_test:prop_append1())
append/2
eqc:quickcheck(lists_test:prop_append2())
delete
eqc:quickcheck(lists_test:prop_delete())
dropwhile
eqc:quickcheck(lists_test:prop_dropwhile())
duplicate
eqc:quickcheck(lists_test:prop_duplicate())
filter
eqc:quickcheck(lists_test:prop_filter())
flatmap
eqc:quickcheck(lists_test:prop_flatmap())
keydelete
eqc:quickcheck(lists_test:prop_keydelete())
keyfind
eqc:quickcheck(lists_test:prop_keyfind())
keymap
eqc:quickcheck(lists_test:prop_keymap())
keymember
eqc:quickcheck(lists_test:prop_keymember())
keymerge
eqc:quickcheck(lists_test:prop_keymerge())
keyreplace
eqc:quickcheck(lists_test:prop_keyreplace())
keysearch
eqc:quickcheck(lists_test:prop_keysearch())
keysort
eqc:quickcheck(lists_test:prop_keysort())
keytake
eqc:quickcheck(lists_test:prop_keytake())
last
eqc:quickcheck(lists_test:prop_last())
map
eqc:quickcheck(lists_test:prop_map())
max
eqc:quickcheck(lists_test:prop_max())
member
eqc:quickcheck(lists_test:prop_member())
merge/1
eqc:quickcheck(lists_test:prop_merge_many())
merge/2
eqc:quickcheck(lists_test:prop_merge())
merge3
eqc:quickcheck(lists_test:prop_merge3())
min
eqc:quickcheck(lists_test:prop_min())
nth
eqc:quickcheck(lists_test:prop_nth())
nthtail
eqc:quickcheck(lists_test:prop_nthtail())
partition
eqc:quickcheck(lists_test:prop_partition())
prefix
eqc:quickcheck(lists_test:prop_prefix())
reverse/1
eqc:quickcheck(lists_test:prop_reverse1())
49
reverse/2
eqc:quickcheck(lists_test:prop_reverse2())
seq/2
eqc:quickcheck(lists_test:prop_seq1())
seq/3
eqc:quickcheck(lists_test:prop_seq2())
sort
eqc:quickcheck(lists_test:prop_sort())
split
eqc:quickcheck(lists_test:prop_split())
splitwith
eqc:quickcheck(lists_test:prop_splitwith())
sublist/2
eqc:quickcheck(lists_test:prop_sublist1())
sublist/3
eqc:quickcheck(lists_test:prop_sublist2())
subtract
eqc:quickcheck(lists_test:prop_subtract())
suffix
eqc:quickcheck(lists_test:prop_suffix())
sum
eqc:quickcheck(lists_test:prop_sum())
takewhile
eqc:quickcheck(lists_test:prop_takewhile())
ukeymerge
eqc:quickcheck(lists_test:prop_ukeymerge())
ukeysort
eqc:quickcheck(lists_test:prop_ukeysort())
umerge/1
eqc:quickcheck(lists_test:prop_umerge_many())
umerge/2
eqc:quickcheck(lists_test:prop_umerge())
umerge3
eqc:quickcheck(lists_test:prop_umerge3())
uzip
eqc:quickcheck(lists_test:prop_uzip())
uzip3
eqc:quickcheck(lists_test:prop_uzip3())
usort
eqc:quickcheck(lists_test:prop_usort())
zip
eqc:quickcheck(lists_test:prop_zip())
zip3
eqc:quickcheck(lists_test:prop_zip3())
zipwith
eqc:quickcheck(lists_test:prop_zipwith())
zipwith3
eqc:quickcheck(lists_test:prop_zipwith3())
erlang module (lists_test.erl)
Function names
QuickCheck testing commands
length
eqc:quickcheck(lists_test:prop_length())
50
string module (string_test.erl)
Function names
QuickCheck testing commands
chars
eqc:quickcheck(string_test:prop_chars())
chr
eqc:quickcheck(string_test:prop_chr_rchr(chr))
concat
eqc:quickcheck(string_test:prop_concat())
copies
eqc:quickcheck(string_test:prop_copies())
equal
eqc:quickcheck(string_test:prop_equal())
left
eqc:quickcheck(string_test:prop_left())
len
eqc:quickcheck(string_test:prop_len())
rchr
eqc:quickcheck(string_test:prop_chr_rchr(rchr))
right
eqc:quickcheck(string_test:prop_right())
sub_string
eqc:quickcheck(string_test:prop_sub_string())
sub_word
eqc:quickcheck(string_test:prop_sub_word())
substr
eqc:quickcheck(string_test:prop_substr())
words
eqc:quickcheck(string_test:prop_words())
51
The functions in module dict, orddict, gb_trees, sets, ordsets, gb_sets, queue and array,
are grouped for testing. As long as the functions having the same property, they can
be tested through the same property-based test case, which is written in the file,
combine.erl.
Property specification
Module
Function
Complete property specification
prop_is_set(R)
set
is_set
prop_is_set(combine:setsr())
ordset
is_set
prop_is_set(combine:ordsetsr())
gb_set
is_set
prop_is_set(combine:gb_setsr())
dict
size
prop_size(combine:dictr())
orddict
size
prop_size(combine:orddictr())
gb_trees
size
prop_size(combine:gb_treesr())
sets
size
prop_size(combine:setsr())
ordsets
size
prop_size(combine:ordsetsr())
gb_sets
size
prop_size(combine:gb_setsr())
queue
len
prop_size(combine:queuer())
array
size
prop_size(combine:arrayr())
dict
from_list
prop_from_list1(combine:dictr())
orddict
from_list
prop_from_list1(combine:orddictr())
sets
from_list
prop_from_list2(combine:setsr())
ordsets
from_list
prop_from_list2(combine:ordsetsr())
gb_sets
from_list
prop_from_list2(combine:gb_setsr())
gb_trees
from_orddict
prop_from_list3(combine:gb_treesr() , from_orddict)
gb_sets
from_ordset
prop_from_list3(combine:gb_setsr() , from_ordset)
queue
from_list
prop_from_list3(combine:queuer() , from_list)
dict
is_key
prop_contain(combine:dictr() , is_key)
orddict
is_key
prop_contain(combine:orddictr() ,is_key)
gb_trees
is_defined
prop_contain(combine:gb_treesr() , is_defined)
sets
is_element
prop_contain(combine:setsr() , is_element)
ordsets
is_element
prop_contain(combine:ordsetsr() , is_element)
gb_sets
is_element
prop_contain(combine:gb_setsr() ,is_element)
gb_sets
is_member
prop_contain(combine:gb_setsr() , is_member)
dict
store
prop_insert1(combine:dictr() , store ,
prop_size(R)
prop_from_list1(R)
prop_from_list2(R)
prop_from_list3(R,Fun)
prop_contain(R,Fun)
prop_insert1(R,Fun ,Fun2)
fun combine:insertkeyvaluefromlist/4)
orddict
store
prop_insert1(combine:orddictr() ,store ,
fun lists:keystore/4)
gb_trees
enter
prop_insert1(combine:gb_treesr() , enter ,
fun lists:keystore/4)
prop_insert2(R,Fun)
sets
add_element
prop_insert2(combine:setsr() , add_element)
ordsets
add_element
prop_insert2(combine:ordsetsr() ,add_element)
52
prop_delete1(R,Fun)
gb_sets
add_element
prop_insert2(combine:gb_setsr() ,add_element)
gb_sets
add
prop_insert2(combine:gb_setsr() , add)
orddict
erase
prop_delete1(combine:orddictr() , erase)
gb_trees
delet_any
prop_delete1(combine:gb_treesr() ,delete_any)
sets
del_element
prop_delete5(combine:setsr(), del_element ,
prop_delete5(R,Fun1, Fun2)
fun lists:delete/2)
ordsets
del_element
prop_delete5(combine:ordsetsr() , del_element ,
fun combine:deletefromlist/2)
gb_sets
del_element
prop_delete5(combine:gb_setsr() , del_element ,
fun combine:deletefromlist/2)
gb_sets
delete_any
prop_delete5(combine:gb_setsr() , delete_any ,
fun combine:deletefromlist/2)
prop_intersection_two_sets(R)
prop_intersection_many_sets(R)
prop_union_two_sets(R)
prop_union_many_sets(R)
prop_filter1(R)
prop_filter2(R)
prop_is_subset(R)
prop_subtract2(R,Fun)
prop_is_empty(R)
sets
intersection/2
prop_intersection_two_sets(combine:setsr())
ordsets
intersection/2
prop_intersection_two_sets(combine:ordsetsr())
gb_sets
intersection/2
prop_intersection_two_sets(combine:gb_setsr())
sets
intersection/1
prop_intersection_many_sets(ombine:setsr())
ordsets
intersection/1
prop_intersection_many_sets(combine:ordsetsr())
gb_sets
intersection/1
prop_intersection_many_sets(combine:gb_setsr())
sets
union/2
prop_union_two_sets(combine:setsr())
ordsets
union/2
prop_union_two_sets(combine:ordsetsr())
gb_sets
union/2
prop_union_two_sets(combine:gb_setsr())
sets
union/1
prop_union_many_sets(combine:setsr())
ordsets
union/1
prop_union_many_sets(combine:ordsetsr())
gb_sets
union/1
prop_union_many_sets(combine:gb_setsr())
dict
filter
prop_filter1(combine:dictr())
orddict
filter
prop_filter1(combine:orddictr())
sets
filter
prop_filter2(combine:setsr())
ordsets
filter
prop_filter2(combine:ordsetsr())
gb_sets
filter
prop_filter2(combine:gb_setsr())
queue
filter
prop_filter2(combine:queuer())
sets
is_subset
prop_is_subset(combine:setsr())
ordsets
is_subset
prop_is_subset(combine:ordsetsr())
gb_stes
is_subset
prop_is_subset(combine:gb_setsr())
ordsets
subtract
prop_subtract2(combine:ordsetsr() , subtract)
gb_sets
subtract
prop_subtract2(combine:gb_setsr() , subtract)
gb_sets
difference
prop_subtract2(combine:gb_setsr() ,difference)
gb_trees
is_empty
prop_is_empty(combine:gb_treesr())
gb_sets
is_empty
prop_is_empty(combine:gb_setsr())
queue
is_empty
prop_is_empty(combine:queuer())
53
prop_fetch_get(R,Fun)
prop_get_value(R,Fun1, Fun2)
prop_fetch_keys(R , Fun)
prop_merge(R)
prop_update3(R)
prop_update4(R)
prop_update_counter(R)
prop_map(R)
prop_insert_front(Fun)
prop_front_item(Fun)
prop_end_item(Fun)
prop_peek(Fun1,Fun2)
prop_except_front(Fun)
prop_except_end(Fun)
dict
fetch
prop_fetch_get(combine:dictr(),fetch)
orddict
fetch
prop_fetch_get(combine:orddictr(),fetch)
gb_trees
get
prop_fetch_get(combine:gb_treesr(),get)
dict
find
prop_get_value(combine:dictr() , fetch , find)
orddict
find
prop_get_value(combine:orddictr(),fetch,find)
gb_trees
lookup
prop_get_value(combine:gb_treesr(),get,lookup)
dict
fetch_keys
prop_fetch_keys(combine:dictr() , fetch_keys)
orddict
fetch_keys
prop_fetch_keys(combine:orddictr() , fetch_keys)
gb_trees
keys
prop_fetch_keys(combine:gb_treesr() , keys)
dict
merge
prop_merge(combine:dictr())
orddict
merge
prop_merge(combine:orddictr())
dict
update/3
prop_update3(combine:dictr())
orddict
update/3
prop_update3(combine:orddictr())
dict
update/4
prop_update4(combine:dictr())
orddict
update/4
prop_update4(combine:orddictr())
dict
update_counter
prop_update_counter(combine:dictr())
orddict
update_counter
prop_update_counter(combine:orddictr())
dict
map
prop_map(combine:dictr())
orddict
map
prop_map(combine:orddictr())
queue
in_r
prop_insert_front(in_r)
cons
prop_insert_front(cons)
get
prop_front_item(get)
head
prop_front_item(head)
last
prop_end_item(last)
daeh
prop_end_item(daeh)
get_r
prop_end_item(get_r)
peek
prop_peek(peek,fun erlang:hd/1)
peek_r
prop_peek(peek_r,fun lists:last/1)
drop
prop_except_front(drop)
tail
prop_except_front(tail)
init
prop_except_end(init)
drop_r
prop_except_end(drop_r)
liat
prop_except_end(liat)
queue
queue
queue
queue
queue
54
A-2 Imperative functions
digraph module (digraph_test.erl)
Type of digraph
QuickCheck testing commands
cyclic digraph
eqc:quickcheck(digraph_test:prop_statem_correct(cyclic))
acyclic digraph
eqc:quickcheck(digraph_test:prop_statem_correct(acyclic))
ets module (ets_test.erl)
Types of
QuickCheck testing commands
structure
set
eqc:quickcheck(ets_test:prop_statem_correct(set,int()))
ordered_set
eqc:quickcheck(ets_test:prop_statem_correct(orderedset,int()))
bag
eqc:quickcheck(ets_test:prop_statem_correct(bag,int()))
duplicate_bag
eqc:quickcheck(ets_test:prop_statem_correct(duplicate_bag,int()))
int()>=1, which defines the key position in the tested structure
dets module (dets_test.erl)
Types of
QuickCheck testing commands
structure
set
eqc:quickcheck(ets_test:prop_statem_correct(set,int()))
bag
eqc:quickcheck(ets_test:prop_statem_correct(bag,int()))
duplicate_bag
eqc:quickcheck(ets_test:prop_statem_correct(duplicate_bag,int()))
int()>=1, which defines the key position in the tested structure
As testing module dets a named file is generated and initialized by the parameters of
the testing command from console. This file should always be deleted by hand from
the disk before testing next command with different parameters.
55
B. Complete Code
http://www.cse.chalmers.se/~nicsma/code.zip
56
Download