=Paper= {{Paper |id=None |storemode=property |title=Improving the Automatic Test Generation process for Coverage Analysis using CBMC |pdfUrl=https://ceur-ws.org/Vol-589/paper04.pdf |volume=Vol-589 |dblpUrl=https://dblp.org/rec/conf/aiia/AngelettiGNPPS09 }} ==Improving the Automatic Test Generation process for Coverage Analysis using CBMC== https://ceur-ws.org/Vol-589/paper04.pdf
Improving the Automatic Test Generation process
      for Coverage Analysis using CBMC

       Damiano Angeletti1 , Enrico Giunchiglia2 , Massimo Narizzano2 ,
        Gabriele Palma2 , Alessandra Puddu2 , and Salvatore Sabina1
                                     1
                                    Ansaldo STS
                     Via Paolo Mantovani, 3 16151 Genova, Italy
                          name.surname@ansaldo-sts.com
                                      2
                                         DIST
                                University of Genoa
                      Via all’Opera Pia 13 16145 Genova, Italy
                              name.surname@unige.it

                                         Abstract
          Software Testing via Coverage Analysis is the most used technique
      for software verification in industry, but, since manual generation is
      involved, remains one of the most expensive process of the software
      development. Many tools have been proposed for the automatic test
      generation: on one hand they reduce the cost of the testing generation
      process, on the other hand the quality of the test set so generated is
      lower than the quality of the test set manually generated by domain
      experts. In fact, most of the time, the test set automatically generated
      contains redundant tests, i.e. test that does not contribute to reach
      the property of 100% of coverage: if they are eliminated by the set,
      the property still holds. Indeed, these redundant tests are useless from
      the perspective of the coverage, are not easy to detect and then to
      eliminate a posteriori, and, if maintained, imply additional costs during
      the verification process.
          In this paper we present a methodology for the automatic genera-
      tion of test set for Coverage Analysis, containing only non redundant
      test. We experimented it on a subset of modules of the ERTMS/ETCS
      source code, an industrial system for the control of the traffic railway,
      provided by Ansaldo STS, where we were able to verify completely
      31 different modules of the ERTMS: On 20 modules we were able to
      generate a set of minimal test covering the 100% of the code for each
      function in each module; on 7 of them we generate a set of tests that
      does not cover the 100% of the branches due to unreachable code; the
      last 4 modules were not completely verified due to CBMC exponential
      blow-up explosion. The use of our methodology for test generation
      led to a dramatic increase in the productivity of the entire Software
      Development process by substantially reducing the number of tests
      generated and thus the costs of the testing phase.




Proceedings of the 16th International RCRA workshop (RCRA 2009):
Experimental Evaluation of Algorithms for Solving Problems with Combinatorial Explosion
Reggio Emilia, Italy, 12 December 2009
                          Figure 1: testing process


1    Introduction
Testing [8] is the most used technique for software verification: it is easy to
use and even if no error is found, it can release a set of tests certifying the
(partial) correctness of the compiled system. The most used technique for
test generation is random testing since it is automatic and simple to apply.
However, it does not ensure an extensive test of the code: since it merely
relies on probability it has quite low chance of finding semantically small
faults. Thus, in order to increase the confidence of the correctness of the
compiled system, it is often required that the provided set of tests covers
100% of the code. This requirement, however, substantially increases the
costs associated to the testing phase, since it may involve the manual gen-
eration of tests. In the literature many techniques have been proposed to
automatically generate test for coverage analysis [9], [10], [7]: on one hand
they reduce the cost of the testing generation process, on the other hand
most of the times the quality of the test set so generated is lower than the
quality of the test set manually generated by domain experts. For instance,
in a previous paper [7] we have shown how it is possible to automatically
generate test sets covering 100% of the code using a bounded model checker
in an industrial setting: Such test set met the desired coverage properties
previously reached only by manual generation, but on the other hand it
contained many redundant tests, i.e., tests not contributing to covering not
otherwise covered portions of the code. Indeed, these redundant tests are
useless from the perspective of the coverage, are not easy to detect and then
to eliminate a posteriori, and, if maintained, imply additional costs during
the verification process. Determine which test set to use is still a major
problem in program testing. One technique that is in widespread use is to
take the control flow graph from each of the program function and calculate
a set of independent paths, i.e. a set of paths through the functions that
are linearly independent. From this set, that has to cover the 100% of the
branches in the control flow graph, it is possible to construct a test exer-
cising that path, unless the path is unfeasible. In this paper we present a
methodology for the automatic generation of a set of non redundant test,
through the construction of a set of independent paths covering the 100% of
the branches in the control flow graph of the function under test. In other
words, given a program to test, for each function we firstly construct the


                                      2
control flow graph from which we deduce an independent set of paths. Then
for each path we generate a test that, when the program (function) is exe-
cuted, it explores exactly that path. As a test generator we use CBMC [1]
a Bounded Model Checker for low-level ANSI-C programs. CBMC checks
safety properties such as the correctness of pointer constructs, array bounds,
and user-provided assertions. CBMC, as showed in [7], can also be used as
a test generator, if it can take as input an instrumented code. However, the
methodology proposed in [7] does not generate a set of tests without redun-
dancy: usually the path explored to generate a test is chosen by CBMC in
a heuristic way, thus the set of paths associated to the set of tests generated
by CBMC is not guarantee to be non redundant. In this paper we show also
how to instrument a function in order to force CBMC to generate a test fol-
lowing a given path. In this way we can generate a set of tests that satisfies
the 100% of the branch coverage. We experimented our methodology on a
subset of modules of the ERTMS/ETCS [5] source code, an industrial sys-
tem for the control of the traffic railway, provided by Ansaldo STS. ERTMS
is an initiative from the European Community to create a unique signal-
ing standard as a cornerstone for the achievement of the interoperability
of the trans-European rail network and is likely to be adopted by the rest
of the world as well. Ansaldo STS, an industry leader in Europe for the
railway system, provides its implementation of the ERTMS/ETCS written
in standard ANSI-C, which consists of thousands of code lines. In the field
of railway signaling systems the CENELEC EN50128 [3] guidelines for soft-
ware development recommend the use of formal methods where it is possible
and a 100% of testing coverage where it is impossible. In this paper the case
study provided by Ansaldo STS has been verified with CBMC obtaining
the target 100% code coverage, requested by CENELEC guidelines. The
use of our methodology for test generation led to a dramatic increase in the
productivity of the entire Software Development process by substantially
reducing the number of tests generated and thus the costs of the testing
phase. The paper is structured as follows: we firstly describe a simple and
naive algorithm for test generation from a set of independent path, making
the assumption of not having unfeasible paths in the program. Then we
describe how to modify the algorithm in order to release the assumption
and we finally show the experimental results and the conclusions.


2    Branch Coverage Via Path Generation
In this section we first present some basic notions used through the paper,
then the basic algorithm is presented, under the assumption of not having
to test program with unfeasible paths and finally we present the advanced
algorithm taking into account also the unfeasible paths.



                                      3
2.1    Basic Definitions
A flow graph is a directed graph C = (N,E,ns ,ne ) where

   • N is a set of nodes

   • E is a binary relation on N (a subset of N x N ), referred to a set of
     edges;

   • ns ∈ N and ne ∈ N are unique entry (start) and unique exit nodes
     respectively.

A control flow graph is a representation, using graph notation, of all paths
that might be traversed by a program during its execution. Each node in the
graph represents a basic block, i.e. a piece of code without any jump or jump
target; jump targets start a block and jumps end a block. Directed edges are
used to represent jumps in the control flow. In most representations there
are two specially designated blocks: the entry block, through which control
enters into the flow graph, and the exit block, through which all the control
flows leave. Moreover, for each edge, it is also annotated which branch
condition, if any, it represents. We say that a branch predicate guards a basic
block if the true value of the predicate implies the execution of the block.
Given a control flow graph G, a path p in G is a sequence p = {n1 , n2 , ...nk } of
nodes such that n1 = ns and nk = ne and for all i, 1 ≤ i ≤ k, (ni , ni+1 ) ∈ E.
Notice that an assignment to the input variables determines a path along
the control flow graph, but the contrary is not always true. We say that a
path is feasible if it exists an assignment to the program’s input X for which
the path is traversed during the program execution, otherwise the path is
unfeasible. Given a set of path of a control flow graph, an independent path is
a path such that: there is at least an edge in the path that does not occur in
any other path in the set. A branch is defined as any decision outcome in the
source code. This is reflected in the unwinded flow graph: for every decision
we have one or more nodes with two or more outgoing edges, representing
the branches. Different edges in the unwinded graph may refer to the same
original branch. Covering a branch requires producing at least a test that
contains at least an edge which referes to that branch. CBMC is a Bounded
Model Checker for software verification [1], that takes as input a low-level
C program and it checks safety properties such as the correctness of pointer
constructs, array bounds, and user-provided assertions. Given a program D,
and a property P , the verification is done translating both the program and
the property into a Boolean formula in Conjunctive Normal Form (CNF)
and giving the result to a SAT solver like MiniSat [4]. If the SAT solver
returns false then the property holds, otherwise the property does not hold.
The conversion from a C program into a CNF consists of three steps:

   1. Each function call should be replaced by its function body;

                                        4
  2. Each loop should be unwound, i.e. the body has to be duplicated
     k times, where k is a parameter given in input (goto statements are
     unwound in a similar way). Notice that each copy of the body is
     guarded by an if statement that uses the same condition of the loop
     statement.

  3. The program and the property are rewritten into an equivalent pro-
     gram in Single Static Assignment (SSA) form [2] that is an Intermedi-
     ate Representation (IR) where each variable is assigned exactly once.
     In the original IR existing variables are split into versions, new vari-
     ables are typically indicated by the original name with a subscript, so
     that every definition gets its own version.

Given the property P and a program D both in SSA form, the formula
D ∧ ¬P is first converted into a bit-vector equation, i.e. each variable is rep-
resented by a bit-vector of fixed size, and the operations are converted into
bit-vector operations. Finally, it is converted into a propositional formula
in Conjunctive Normal Form (CNF), by adding intermediate variables. If
applying a SAT solver to the CNF, the equation is satisfiable, then the prop-
erty does not hold, and an assignment to the variables making the formula
true is returned. Starting from this assignment, it is possible to construct
an error trace showing where the property does not hold in the program.
Otherwise, if the resulting CNF is false the property holds. However, we
can not conclude by saying that the program is verified, but we can say that
it is verified for a given k. Choosing another k 0 > k does not ensure that
the property holds. In order to use CBMC as a test generator we need to
modify the code in input as firstly presented in [7]: the code under test is
instrumented with an assert(0) after each branch; the instrumented code
is given as input to CBMC with an assert activated at the time. For each
assert, CBMC returns an assignment to the input variables (a test) violating
the property (assert(0)). The asserts are inserted in a naive way without
any reasoning, this would generate more tests than necessary. Let’s take the
following example:

if a == 10 then B1
else B2
if a 6= 5 then B3
else B4

given the methodology in [7], it will generate four different program codes
adding an assert(0) at the end of each block, namely B1 , B2 , B3 , B4 . The
set of tests
                            T = {t1 , t2 , t3 , t4 }
generated covers the 100% of the branches for construction, and for example


                                       5
                                           path genApath(n,Au )
                                           5   if n ≡ ne then return {n}
                                           6   maxn = succ(n).first();
                                           7   foreach si ∈ succ(n) do
                                           8     if |Au (maxs )| < |Au (si )| then
                                           9       maxs = si
 set of paths pathGenerator(C)             10    else if |Au (maxs )| == |Au (si )| then
 0    P = {};                              11      if E(n, maxs ) ∈/ Au then
 1    Au = update(P,C);                    12        maxs = si
 2    while |Au | =
                  6 0 do                   13    Au = update(P,C);
 3      P = P ∪ genApath(ns ,Au );         14 return {n} ∪
 4    return P;                                      genApath(maxs ,Au )


                    Figure 2: Generate Paths Functions.

                     set of tests ATGviaCBMC(P,D)
                     15 T = {};
                     16 foreach p ∈ P do
                     17     D0 = instrument(D,p)
                     18     T = T ∪ CBMCcall(D0 );
                     19 return T;



                     Figure 3: Test Generation function


they could be:
              t1 : (a = 10), t2 : (a = 6), t3 : (a = 5), t4 : (a = 4)
However it may be the case that eliminating one or two test from the set the
100% of branch are still covered, for example also the test set T 0 = {t1 , t3 }
covers the 100% of the branches. In the naive algorithm we always generate
more test of the ones needed to grant the 100% of branch coverage. This
may increase the costs associated to the testing phase, in particular during
the regression phase. In order to reduce the size of the test set we first con-
struct a set of independent paths covering the 100% of the branches of the
control flow graph. Then, given a path in the set, it is possible to instrument
CBMC in order to generate a test following the path.

2.2   Basic Algorithm
The testing process is presented in figure 1. It assumes that all the paths
are feasible. This is a big restriction that is made only for a better compre-
hension of the algorithm. In the next subsections the algorithm that takes


                                       6
                                              int Test1(int[ ] a, int size)
                                              s0    int b = 0; int c = 0;
                                              b1    assume(c < size);
                                              b3    assume(a[c] < 0);
                                              s4      b++;
                                              s4      c++;
      int Test1(int[ ] a, int size)           b1    assume(c < size);
      s0           int b = 0; int c = 0;      b3    assume(!(a[c] < 0));
      b1 , b2 , s1 for ( ; (c < size); c++)   s4      c++;
      b3 , b4        if (a[c] < 0)            b1    assume(!(c < size));
      s2               b++;                         assert();
      s3           return b;                  s13   return b;


Figure 4: An example function, left, and its instrumentation on the right.

into account unfeasible paths is presented.
     The process in figure 1 firstly analyzes the code under test and it con-
structs a set of independent paths covering the 100% of the branches in the
control flow graph associated to the program under test (pathGenerator),
i.e. each path covers at least a branch not covered by any other path. Then,
from each path, the module ATG via CBMC will generate a test in a way
similar to the one presented in [7]. For each path, the original code is in-
strumented and it is given to CBMC, which will return an assignment to
the input variables, a test, soliciting the path through the code. The pro-
cess is composed by three main functions: pathGenerator, genApath,
ATGviaCBMC, as shown in figure 2 and 3.

pathGenerator is a function presented into details in figure 2, left, that
takes as input the control flow graph representation of the program under
test (C). It returns a set of basis paths (P) covering the 100% of the branches
in C. P is initially empty. Au is the set of branches in C not yet covered
by any path in P. Au initially contains all the branches in C. Given a node
n ∈ C, Au (n) is a set of branches not yet covered in C, that can be covered
from n. Given a node n, Au (n) is defined recursively as follow:
Au (ne ) = {}                                                   
           S|succ(n)|                {E(n, si )} : E(n, si ) ∈ Au
Au (n) = i=1            Au (si ) ∪
                                     {}          : E(n, si ) ∈
                                                             / Au
where ne is the end node of the control flow graph, si is a successor of n,
E(n1 , n2 ) is the branch from n1 to n2 and succ(n) is the set containing all
the successors of n. So for example, given a control flow graph C, s0 a
root node and s1 is a successor of s0 connected with a branch b1 . Let be
s2 a successor of s1 connected with a branch b2 , then Au (s0 ) = {b1 , b2 }.
Notice that Au = Au (ns ). Looking at the algorithm in figure 2, left, that

                                         7
 int Test1(int[ ] a, int size)
 s0          int b = 0; int c = 0;
 s1 ,b1 ,b2 if (c < size)
 s2 ,b3 ,b4    if (a[c] < 0)
 s3              b++;
 s4            c++;
 s5 ,b1 ,b2    if (c < size)
 s6 ,b3 ,b4      if (a[c] < 0)
 s7                 b++;
 s8              c++;
 s9 ,b1 ,b2      if (c < size)
 s10 ,b3 ,b4        if (a[c] < 0)
 s11                    b++;
 s12                c++;
 s13         return b;


      Figure 5: The example code and the corresponding flow graph


generates a set of paths, it starts from an empty set of paths (line 0) and
a set of uncovered branches containing all the branches of C (line 1). The
function UPDATE(P,C) returns all the branches of C not yet covered by P.
Moreover for each node n in C, it will update Au (n). Then, while there are
still uncovered branches in C(line 2), a new path is generated and added to
P (line 3).

genApath is a recursive function that given a set of uncovered branches
(Au ) and a starting node (n) generates a path containing at least an un-
covered branch in Au . It always starts from the beginning of the control
flow graph (line 3) from the node ns . The idea is: starting from a node
n, the algorithm explores successors of n, si , and it chooses the one having
the greatest number of uncovered branches (from line 8 till 12). If a tie is
found between two successors of n (line 10), n1 and n2 , then the one having
E(n, ni ) ∈ Au , in lexicographic order, is chosen. The algorithm stops when-
ever the end of the control flow graph (ne ) is reached line 5. Then the path
is constructed in a backtrack way, starting from the last node ne , including
all the nodes explored by the algorithm (line 14). The algorithms in figure 2
left and right only generate a set of independent paths.

ATGviaCBMC is a function that takes as input a set of paths and it will
return (if exists) a test for the program under test representing each path.
T is the set of tests generated, initially empty (line 15). Then for each


                                     8
path the program D is modified, i.e. opportunely instrumented,(line 17)
and a new program, D0 is created. D0 is then passed to the tool CBMC(line
18) that returns as assignment to the input variables exploring the path
in input. D0 has to be carefully instrumented in order to generate a test.
In [7] an assert(0) is added when a branch needs to be covered. However
CBMC chooses which path has to follow in order to generate a test to
cover the assert. We can still use CBMC but we need something to force
CBMC to reach an assertion through a path. CBMC has a construct, namely
                        assume (expression = value)
that inserted at a given point in the code enforces the expression to assume
exactly that value at that point. Using the assume construct we can enforce
a path at the time in the code, obtaining from CBMC a test exploring the
path. In order to obtain the 100% of the branch coverage for each indepen-
dent path we produce an instrumented code using the assume construct to
enforce the path and we put an assert(0) at the end of the code. Then for
each instrumented code we run CBMC that returns a test for that path.

    An example of the algorithm behavior is presented below. Let’s assume
as a function under test the function presented in figure 4 which counts
negatives in an array. The algorithm presented above takes as input C, i.e.
the control flow graph representation of the function in figure 4, left already
unwinded. The set of paths, line 0, is initially empty and the Au is updated
(line 1) with all the branches of the Control Flow graph, Au = {b1 , b2 , b3 , b4 }.
For each node n it is also updated Au (n). Notice that the function genAp-
ath will work on the control flow graph unwinded, which is shown in figure
5, right, assuming an unwind value of 3. Each function has a minimum un-
wind value k, necessary to reach full coverage, which depends on the cycles
structure. Best way to set k would be to know such minimum a priori but
that’s not feasible in an automatic way so, a good alternative solution is to
start testing with low k and increase it until coverage is reached. Lower k
are faster to test but have less chances to reach full coverage while bigger k
requires more time and effort for both CBMC and our algorithm and also
increase the chances that CBMC will fail to find an answer in the given time.
At the first node there is only one branch (without any label) and then the
recursive call to genApath(s1 ,Au ) is executed. At this point since s1 is not
the end node, its successor nodes (s2 and s13 ) would be explored. maxs = s2 ,
since |Au (s13 )| = 0 and |Au (s2 )| = 4 ({b1 , b2 , b3 , b4 }). The current coverage
status is then updated by deleting b1 , which is the incoming edge used to
reach s2 , from both Au and from each Au (n) where n is a node in C. A new
recursive call, genApath(s2 ,Au ) is executed. Then the algorithm chooses
between s3 and s4 , having |Au (s3 )| = |Au (s4 )| = 3, ({b2 , b3 , b4 }) and
being both edges b3 and b4 uncovered, the algorithm breaks the ties by lexi-
cographic order choosing s3 . Au is updated deleting b4 and it is also updated
for each node. From node s3 to node s5 there is a single path to be followed.

                                         9
On node s5 maxs = s6 with |Au (s6 )| = 2 ({b2 , b3 }) against |Au (s13 )| = 0.
From s6 , s8 will be chosen because even if |Au (s7 )| = |Au (s8 )| = 2, b3 is
not yet covered. Finally the algorithm will go from s9 to s13 , covering b2 . As
soon as the algorithm reaches the s13 node it will stop and backtrack until
the root constructing the path, i.e. p1 = {s0 , s1 , s2 , s3 , s4 , s5 , s6 , s8 , s9 , s13 }.
This path can cover all the branches, so no more calls will be necessary. The
pathGenerator function will return a set of paths, P = {p1 } and then
the function ATGviaCBMC is called. The function instrument the code
in order to force CBMC to follow the path. In figure 4, right, the modified
code (C 0 ) for the generated path is presented. Notice that:

    • the instrumented code is already unwinded,

    • unnecessary code is removed in order to help CBMC to find a solution.
      Since we are exploring a path only the code explored by the path is
      necessary.

CBMC, taking as input the modified code, will generate a test, i.e. with the
following inputs, size = 2, a[0] = −1 and a[1] = 0.

2.3     Using CBMC for Unfeasible Path
In case of unfeasible paths the algorithm presented above will generate a set
of tests that does not cover the 100% of the branches. In order to overcome
the problem whenever a path is generated, a checker is used to analyze the
path and return if the path is feasible or not. In case of unfeasible path
a new path has to be generated. As feasibility checker we use CBMC: if
a test is returned from CBMC then the path is feasible otherwise is not.
The new algorithm is composed by a function CreateTestSet that uses
the function generatePath, that it is similar to the one presented above: this
function also takes as input a set of branches that have not to be covered from
the path returned. In case that this set is empty the function is equivalent
to the one presented above.

CreateTestSet is a function presented in figure 6 that given a set of
branches(Au ) and a program P returns a set of tests covering the 100% of
the branches in Au . Given a finite set of paths of a program P (P k ), gen-
erate a path covering as much branches in Au as possible CreateTestSet
generates a path covering as much branches as possible of Au (line 2) start-
ing from the basic path (bp ) and avoiding the Forbidden branches (F). If
such path exists, then CBMC is invoked (line 4) and if the path is feasible
CBMC returns a vector of values for the input variables; then the test is
added to the set of Tests T (line 8) and the branches traversed by P are
eliminated from Au . If the path is unfeasible the function analisePath
returns (if any) the subset of arch which make it feasible (bp ) and the arches


                                             10
             set of Tests createTestSet(Au , P, kmax )
             0 k = 4; T = {}; bp = {ns }; F = {}
             1 while |Au | > 0 & k < kmax do
             2    p = generateP ath(Au , bp , F, P k )
             3    if |p| > 0 then
             4      t = generateT est(p, P k )
             5      if t == t0 then
             6         hbp , F i = analiseP ath(bp , p, F, P k )
             7      else if t == tstop then
             8         return T
             9      else         S
             10        T = T {t}
             11        bp = {ns }
             12        Au = update(Au ,p)
             13 else
             14     if bp 6= {ns } then
             15        F = {bp }
             16        bp = bp \ tail(bp )
             17     else
             18        k=k*2; F ={}; bp = {ns }
             19 return T


    Figure 6: Advanced algorithm taking into account unfeasible paths


making the path unfeasible are stored in F (line 6) so that the next path will
be generated starting from bp and avoiding the forbidden path. If the path
generated in line 2 is empty, i.e. does not exists any path starting from bp
and covering at least a branch in Au such that the branch is forbidden, then
a new basic path is computed, i.e. the tail of the basic path is removed and
it became a forbidden arch. If bp was the root, |Au | is not equal to zero, then
there are some branches not covered with the fixed k: at this point (line 16)
k is incremented and the search restarts again. The function returns the set
of tests so computed.
    Suppose, for example, that the function in figure 4 did not have the size
of the array as an input, but instead it is hard coded in the cycle and it is
greater than 2, i.e. 3. Also necessarily consider a greater unwind value, i.e.
4 instead of 3. In this case the previous algorithm would generate the exact
same path but that would be unfeasible because it tries to exit from the
cycle after two iterations, which cannot be done. The new algorithm will
then exclude that possibility by removing and forbidding the last step from
the path and compute another path with the same initial subpath which
will stay in the cycle one more iteration and exit afterwards. This will be
found feasible and reported as a test.

                                       11
                 module     functions        naive   manual    atg
               Module 01       26             154     127      106
               Module 02       12             149      57      43
               Module 03        1              12      17       6
               Module 04        1              83      43      41
               Module 05        2              76      18       7
               Module 06        8             123      39      27
               Module 07        8              44      27      18
               Module 08       24             280     200      148
               Module 09       16             182     144      108
               Module 10        9              98      35      38
               Module 11       22             340     312      263
               Module 12       12              35      34      32
               Module 13       11             210     181      156
               Module 14       11             196     157      132
               Module 15       29             335     322      238
               Module 16       14              96      69      29
               Module 17        7              73      36      28
               Module 18       26             170     101      82
               Module 19        8              89      62      38
               Module 20       25             161     119      110
                               272           2906     2100    1650

                   Figure 7: Modules with full coverage.


3    Experimental Analysis
Nowadays trains are equipped with up to six different navigational systems
which are extremely costly and take space on-board. A train crossing from
one European country to another must switch the operating standards as
it crosses the border. The European Rail Traffic Management System [5]
is an EU “major European industrial project” to enhance cross-border in-
teroperability and signaling procurement by creating a single Europe-wide
standard for railway signaling. ERTMS has two basic components:

    • ETCS, the European Train Control System, transmits speed informa-
      tion to the train driver and it monitors constantly the driver’s compli-
      ance with the speed information;

    • GSM-R is based on standard GSM but it uses various frequencies
      specific to rail as well as certain advanced functions. It is the radio
      system used to exchange voice and data information between the track
      and the train.



                                        12
             module     functions   unreachable path    time-out
           Module 21        11             1               0
           Module 22        9              2               0
           Module 23        13             2               0
           Module 24        19             1               0
           Module 25        18             1               0
           Module 26        3              1               0
           Module 27        23             1               0
           Module 28        21             2               3
           Module 29        7              0               2
           Module 30        9              2               2
           Module 31        10             0               4
                           143             13              11

                 Figure 8: Modules with partial coverage.


Ansaldo STS as part of the European Project produces the European Vital
Computer (EVC) software, a fail-safe system which supervises and controls
the speed profiles using the information received from the in-track balises
transmitted to the train. Following the CENELEC standards Ansaldo STS
needs to provide a certificate of the integrity level required, i.e. it has
to provide a set of tests covering the 100% of the branches. In order to
simplify the readability, the Ansaldo STS implementation of the EVC is
developed into different modules of fixed size. In our experimental analysis
we took a subset of the interconnected modules of the EVC and we applied
the automatic test generation strategy seen in the section above. We get
more than 130 different modules, containing more than 100.000 lines of code
distributed in more than 1700 functions. As a comparison we only took 31
different modules, the modules on which we get informations about the tests
manually generated by Ansaldo STS. As unwind, after a brief analysis, we
start from k = 5 till k = 100, with step 2. We also set a 20 minutes timeout
for both our algorithm and CBMC.
    In figure 7 are reported the results on the module on which we reach
the 100% of branch coverage. The columns from left to right represent:
the name of the module (for copyright reasons they are coded), the number
of functions contained in the module, the number of tests needed to reach
the 100% of branch coverage using the naive method presented in [7], the
number of tests using manual generation, provided by Ansaldo STS, the
number of tests generated by our technique presented above. Our algorithm
on 20 modules always reach the 100% of the branch coverage. Firstly notice
that the number of tests generated by the naive method is always greater
than the number of tests generated manually: In 5 cases the number of tests
manually generated by the naive algorithm is more than a factor two with


                                    13
respect the number of tests automatically generated, while there is only one
case where manual generation creates more tests than the naive method,
i.e. for the module03 . This is mainly due to the manual generation method.
For each function Ansaldo STS maintains a set of functionality tests say Tf :
if Tf already guarantee branch coverage then no more test are generated,
otherwise a new test set Tc is generated to cover remaining branches. The
final test set for coverage will be: T = Tf ∪ Tc , which frequently contains
redundant test. In this case, for the module03 , the number of tests for
functionality covers the 100% of the branches and from a coverage point of
view, there are some test that are redundant. Moreover, looking at figure 7,
we always automatically generate a test set having size smaller that the one
generated by Ansaldo STS. Only module10 does not follow this rule mainly
due to the unwind strategy used: starting from greater k, it is possible to
generate smaller test set covering the 100% of the branch [6]. As a matter
of fact, for all the other modules, the number of tests is always less than the
number of tests manually generated. The time used to generate the tests is
very low and it is not comparable with the time spent by Ansaldo STS to
manually generate them, as already described in [7].
     In figure 8 are also reported the modules on which we were not able to
obtain full coverage: the first column reports the name, the second column
the number of functions in the module, third the number of functions not
covered due to unreachable code and the last column reports the number of
functions not fully covered due to CBMC time-out. The functions that are
not reported in the last two columns, have been fully covered, with less test
than manual generation. As shown in the table, few functions have been
reported as uncoverable, in particular the functions presenting unreachable
codes have been manually checked. Also Ansaldo STS has reported that
these functions can not be fully covered. The results, as shown in the tables,
are that only 4 modules out of 31 can not be completely covered (13% of
the modules) and the number of functions that can not be covered are 11
(resp. 13) for timeout (resp. unreachable code) out of 415.


4    Conclusion
In this paper we have presented a methodology for the automatic genera-
tion of test set for Coverage Analysis, containing only non redundant test.
The generation goes through the construction of a set of feasible (if exist)
independent paths. We have experimented our methodology on a subset
of modules of the ERTMS/ETCS source code, an industrial system for the
control of the traffic railway, provided by Ansaldo STS. With our methodol-
ogy we were able to verify completely 20 out of 31 different modules of the
ERTMS. On the remaining we were able to conclude that 7 out of 31 could
not be covered due to unreachable code while for the last 4 out of 31 has


                                      14
been impossible to reach complete coverage since CBMC could not finish.
This paper has shown that the use of our methodology for test generation
led to a dramatic increase in the productivity of the entire Software Devel-
opment process by substantially reducing the number of tests generated and
thus the costs of the testing phase.


References
  [1] E. Clarke, D. Kroening, F. Lerda. : A Tool for Checking ANSI-C
      Program. Tools and Algorithms for the Construction and Analysis of
      Systems, 2004, pp. 168–176.

  [2] R. Cytron, J. Ferrante, B. K. Rosen, M. N. Wegman, F. K. Zadeck. :
      Efficiently Computing Static Single Assignment Form and the Control
      Dependence Graph. In ACM Transactions on Programming Languages
      and Systems, 1991, vol. 13, number 4, pp. 451–490.

  [3] European Committee for Electrotechnical Standardization. : Rail-
      way Applications - Communication, signalling and processing sys-
      tems - Software for railway control and protection systems.
      http:://www.cenelec.eu

  [4] N. Een, N. Sorensson. : An Extensible SAT-solver. In Satisfiability
      Workshop, 2003, pp. 502-518.

  [5] ERTMS: The official Website. http://www.ertms.com/, 2008.

  [6] A. Arcuri. : Longer is Better: On the Role of Test Sequence Length
      in Software Testing. 2009

  [7] D. Angeletti, E. Giunchiglia, M. Narizzano, A. Puddu, S. Sabina. :
      Automatic Test Generation for Coverage Analysis of ERTMS soft-
      ware. International Conference on Software Testing, Verification, and
      Validation (ICST). Denver (CO) 2009.

  [8] B. Beizer. : Software testing techniques. New York: Van Nostrand
      Reinhold Co. 1990.

  [9] H. Chockler, O. Kupferman, R.P. Kurshan, M.Y. Vardi. : A Practical
      Approach to Coverage in Model Checking. n Proceedings of the 13th
      international Conference on Computer Aided Verification, 2001, vol.
      2102. pp. 66-78.

 [10] J. de Halleux and N. Tillmann. : Parameterized unit testing with pex.

 [11] Luke Gregory: Path Testing. 2006.



                                    15