ECE 453 – CS 447 – SE 465 Software Testing & Quality Assurance Instructor Kostas Kontogiannis
Overview Structural Testing Introduction – General Concepts Flow Graph Testing Data Flow Testing Definitions Some Basic Data Flow Analysis Algorithms Define/use Testing Slice Based Testing Guidelines and Observations Hybrid Methods Retrospective on Structural Testing
Slicing The notion of a program slice is useful in software testing, program debugging, automatic parallelization, and program integration A slice of a program is taken with respect to a program point P and a variable x. The slice consists of all program statements that may affect the value x at program point P. The tuple <P, x> is called a slicing criterion A Forward Slice of a program with respect to a program point P and variable x consists of all statements of the program that may be affected by the value of x at point P One way to compute a slice is by constructing a Program Dependence Graph (PDG) and then appropriately traverse this graph [Ref: S. Horowitz, T. Reps, and D. Binkley. Interprocedural slicing using depen- dence graphs. In Proceedings of the SIGPLAN '88 Conference on Programming Language Design and Implementation, Atlanta, GA, June 1988].
Program Dependency Graphs (1) Directed graphs. Three types of vertices: Entry vertex Initial-state(x) for every variable x such that, there exists a path in the CFG on which x is used before it is defined. It represents an assignment to x from the initial state Final-Use(x) for every variable x named in P’s end statement. It represents an access to the final value of x computed by P
Program Dependence Graphs (2) Three types of edges: Control Dependence edges labeled as True or False. The source is either a predicate or the entry vertex. A control dependence means that during program execution, if the label of the edge matches the execution result of the source vertex predicate then, the target vertex will be eventually executed Data Dependence between two nodes iff one is defining a variable and the other is using the variable. Two types of data dependence edges: Flow dependence Loop carried Loop independent Def-order dependence
Control Dependence Edges A Program Dependence Graph of a program P contains a Control Dependence edge from vertex v1 to vertex v2 (v1 c v2 iff one of the following holds: Vertex v1 is the entry vertex and v2 represents a component of program P that is not nested within a loop or conditional; these edges are labeled True Vertex v1 represents a control dependence and v2 represents a component of P immediately nested within a loop or a conditional statement whose predicate is represented by v1. If v1 is a test predicate of a loop structure the vertex is labeled True. If it is a test predicate in a conditional statement the edge is labeled according to the boolean result of the predicate.
Data Dependence Edges A Program Dependence Graph of a program P contains a Data Dependence edge from vertex v1 to vertex v2 (v1 f v2 iff all of the following holds: Vertex v1 is a vertex that defines variable x Vertex v2 is a vertex that uses variable y Variables x and y are either the same variable or aliases Control can reach v2 after v1, via an execution path along which there is no other intervening definition of x or y. That is there is a path in the program’s CFG by which definition of x at v1 reaches the use of x at v2. Data flow dependence edges can be further classified as loop carried and loop independent
Loop Carried Data Dependence Loop carried dependencies between vertex v1 and vertex v2 denoted as v1 lc(L) v2 occur when in addition to the other criteria for data dependence we have: There is an execution path that includes a back edge to the predicate of the loop L Both vertex v1 and vertex v2 are enclosed in the loop L If there is no back edge to the predicate the data dependence is called loop independent
Def-Order Dependence A PDG contains a def-order dependence edge from vertex v1 to vertex v2 with witness vertex v3 denoted as v1 do(v3) v2 iff all of the following hold: Vertex v1 and v2 define variables x1 and x2 respectively Variables x1 and x2 are either aliases or the same variable Vertex v1 and v2 and in the same branch of any conditional statement that encloses both of them There exists a program component v3 that v1 f v2 and v2 f v3 Vertex v1 occurs to the left of v2 in the program’s AST (i.e .the statement related to v1 executes before the statement related to vertex v2)
Example Legend: program Sum sum = 0; i=1; while i < 10) do i = i+1; enddo end Control dependence Loop independent data dependence Loop carried data dependence Def-order dependence ENTRY T T T T T FinalUse(i); FinalUse(sum); while i < 10; sum = 0 i = 1; T T sum=sum+1; i = i +1;
Computing a Slice Using a PDG (1) For vertex S of the Program Dependence Graphs G of a program P, the slice of G with respect to S is a graph (denoted as G/S) containing all vertices of which S has a transitive flow or control dependence (i.e. all vertices that can reach S via flow or control edges) The edges in the slice graph G/S are data dependence (loop independent), control dependence, and ded-order edges of the original graph G that have source and targets in vertices in G/S
Computing a Slice Using a PDG (2) Therefore the vertices of a slice G/S are: V(G/S) = {w | w in V(G) and w * S} We can extend the definition to a slice with respect to a set of vertices S = Ui Si V(G/S) = V(G/ Ui Si) = Ui (V(G/Si)) The edges in a slice are: E(G/S) = {u f w | u w in E(G) and u, w in V(G/S)} U {u c w | u w in E(G) and u, w in V(G/S)} U {u do w | u w in E(G) and u, w in V(G/S)} c,f
Algorithm for Intra-Procedural Slice Using a PDG Program ComputeSlice(G, S) G: a program dependence graph S: a set of vertices in G WorkList: a set of vertices in G v, wc: verices in G Begin WorkList = S; while Worklist ≠ 0 do begin Select and remove vertex v from WorkList; Mark v For each unmarked vertex w such that w f v or w c v or w do v do begin Insert w in WorkList end End
Example (1) Slice based of the criterion FinalUse(i) ENTRY T T T T T FinalUse(sum); sum = 0 while i < 10; FinalUse(i); i = 1; T T sum=sum+1; i = i +1; Slice based of the criterion FinalUse(i)
Example (2) Slice on FinalUse(i) program Sum sum = 0; i=1; while i < 10) do sum = sum +i; i = i+1; enddo end program SumSlice i=1; while i < 10) do i = i+1; enddo end Slice on FinalUse(i) ENTRY T T T i = 1; while i < 10; FinalUse(i); T i = i +1;
Slicing using Control Flow Graphs Given a program P, and a program graph G(P) in which statements and statement fragments are numbered, and a set V of variables in P, the slice of the set of variables V at statement fragment n, written as S(V, n), is the set node numbers of all statement fragments W in P prior to n that contribute to the values of variables in V at point n Contribute means that a statement in S(V, n) will affect the c-use or a p-use of a variable v in V at statement n. Actually, we refine here the concepts of c-use and p-use. More specifically, P-use: used in a predicate C-use: used in a computation O-use: used for output L-use: used for location (pointers, array indices) I-use: iteration (loop counters, internal counters) Also we can distinguish two definition nodes: I-def: defined by input A-def: defined by assignment
Slices We can consider that in a slice S(V. n), V has only one element say variable v. If n is a defining node for v then is included the slice. If n is a usage node for v is not included in the slice p-uses, and c-uses of other variables not in V are included as long their execution affects the value of variable v at point n We chose to exclude O-uses, L-uses, and I-uses from the slice
Slices – Points of Interest Do not make slices with a criterion (V, n) where variables of V do not appear in n Consider making slices with respect of one variable not a set of variables Consider making slices for all A-def nodes (focus is on the variables in the right hand side of an assignment) Consider making slices for p-use nodes Consider making slices compilable
Uses of Slices in Testing If a slice is compilable one could use the tetsing techniques we have examine so far On the other hand, in general slices do not map nicely to test cases. We can use slices: to identify and eliminate unwanted dependencies between variables to identify problems in mutual exclusive parts of the code by considering set complements or localizing potential problems in segments