510 likes | 636 Views
DART D irected A utomated R andom T esting Patrice Godefroid, Nils Klarlund, and Koushik Sen. Syed Nabeel. Motivation. Software Testing emerges as one of the most important aspects in Software Engineering Unit Testing Limitations in Unit Testing
E N D
DARTDirected Automated Random TestingPatrice Godefroid, Nils Klarlund,and Koushik Sen Syed Nabeel
Motivation • Software Testing emerges as one of the most important aspects in Software Engineering • Unit Testing • Limitations in Unit Testing • DART a novel approach to counter limitations in Unit Testing
Unit Testing Testing small portions of programs, such as methods, groups of methods, or classes. • a sequence of calls to the units (for e.g. methods) • BEFORE the calls some code to generate parameters for the methods • AFTER the calls some code checking whether the method has performed correctly.
Goals of Unit Testing • Detect Errors in the components logic • Check ALL corner cases • Provide 100% code coverage
Question What are some limitations of Unit Testing?
Limitations of Unit Testing • Requires test driver and harness code which is representative of ALL external environment • Expensive and hard to perform manually (rarely done properly)
DART To The Rescue Automates unit testing removing need for writing test drivers • Automated extraction of the program interface by static code parsing • Automatic generation of test driver for this interface that performs random testing • Dynamic Analysis of the program behavior to generate specific test inputs for the direction of program execution through alternate program paths
Execution Model For DART • Concrete Execution: based on a memory model M that maps addresses to values • Symbolic Execution: based on symbolic memory S that maps addresses to expressions
Directed Testing by Constraint Building • Progressively constraints are built as branches are encountered • Linear constraints are solved to explore various execution paths • In case of getting stuck or non linear constraints symbolic execution falls back to concrete execution
Consider An Example …. int double(int x) { return 2 * x; } void test_me(int x, int y) { int z = double(x); if(z==y){ if(x != y+10){ printf(“I am fine here”); } else { printf(“I should not reach here”); abort(); } }
Random Testing int double(int x) { return 2 * x; } void test_me(int x, int y) { int z = double(x); if(z==y){ if(x != y+10){ printf(“I am fine here”); } else { printf(“I should not reach here”); abort(); } } main(){ int tmp1 = randomInt(); int tmp2 = randomInt(); test_me(tmp1,tmp2); }
Random Testing int double(int x) { return 2 * x; } void test_me(int x, int y) { int z = double(x); if(z==y){ if(x != y+10){ printf(“I am fine here”); } else { printf(“I should not reach here”); abort(); } } main(){ int tmp1 = randomInt(); int tmp2 = randomInt(); test_me(tmp1,tmp2); } Evident Problem: Probability of Reaching Abort is very low
DART Approach main(){ int t1 = randomInt(); int t2 = randomInt(); test_me(t1,t2); } int double(int x) { return 2 * x; } void test_me(int x, int y) { int z = double(x); if(z==y){ if(x != y+10) { printf(“I am fine here”); } else { printf(“I should not reach here”); abort(); } } • Full example taken from Presentation by Koushik • at http://osl.cs.uiuc.edu/~ksen/slides/dart-fm.ppt
concrete state symbolic state constraints t1=36 t1=m DART Approach Concrete Execution Symbolic Execution main(){ int t1 = randomInt(); int t2 = randomInt(); test_me(t1,t2); } int double(int x) { return 2 * x; } void test_me(int x, int y) { int z = double(x); if(z==y){ if(x != y+10) { printf(“I am fine here”); } else { printf(“I should not reach here”); abort(); } }
concrete state symbolic state constraints t1=36, t2=-7 t1=m, t2=n DART Approach Concrete Execution Symbolic Execution main(){ int t1 = randomInt(); int t2 = randomInt(); test_me(t1,t2); } int double(int x) { return 2 * x; } void test_me(int x, int y) { int z = double(x); if(z==y){ if(x != y+10) { printf(“I am fine here”); } else { printf(“I should not reach here”); abort(); } }
concrete state symbolic state constraints t1=36, t2=-7 t1=m, t2=n DART Approach Concrete Execution Symbolic Execution main(){ int t1 = randomInt(); int t2 = randomInt(); test_me(t1,t2); } int double(int x) { return 2 * x; } void test_me(int x, int y) { int z = double(x); if(z==y){ if(x != y+10) { printf(“I am fine here”); } else { printf(“I should not reach here”); abort(); } }
concrete state symbolic state constraints x=36, y=-7 x=m, y=n DART Approach Concrete Execution Symbolic Execution main(){ int t1 = randomInt(); int t2 = randomInt(); test_me(t1,t2); } int double(int x) { return 2 * x; } void test_me(int x, int y) { int z = double(x); if(z==y){ if(x != y+10) { printf(“I am fine here”); } else { printf(“I should not reach here”); abort(); } }
concrete state symbolic state constraints x=36, y=-7, z=72 x=m, y=n, z=2m DART Approach Concrete Execution Symbolic Execution main(){ int t1 = randomInt(); int t2 = randomInt(); test_me(t1,t2); } int double(int x) { return 2 * x; } void test_me(int x, int y) { int z = double(x); if(z==y){ if(x != y+10) { printf(“I am fine here”); } else { printf(“I should not reach here”); abort(); } }
concrete state symbolic state constraints x=36, y=-7, z=72 x=m, y=n, z=2m DART Approach Concrete Execution Symbolic Execution main(){ int t1 = randomInt(); int t2 = randomInt(); test_me(t1,t2); } int double(int x) { return 2 * x; } void test_me(int x, int y) { int z = double(x); if(z==y){ if(x != y+10) { printf(“I am fine here”); } else { printf(“I should not reach here”); abort(); } } 2m != n
concrete state symbolic state constraints x=36, y=-7, z=72 x=m, y=n, z=2m DART Approach Concrete Execution Symbolic Execution main(){ int t1 = randomInt(); int t2 = randomInt(); test_me(t1,t2); } int double(int x) { return 2 * x; } void test_me(int x, int y) { int z = double(x); if(z==y){ if(x != y+10) { printf(“I am fine here”); } else { printf(“I should not reach here”); abort(); } } 2m != n
concrete state symbolic state constraints x=36, y=-7, z=72 x=m, y=n, z=2m DART Approach Concrete Execution Symbolic Execution main(){ int t1 = randomInt(); int t2 = randomInt(); test_me(t1,t2); } int double(int x) { return 2 * x; } void test_me(int x, int y) { int z = double(x); if(z==y){ if(x != y+10) { printf(“I am fine here”); } else { printf(“I should not reach here”); abort(); } } solve: 2m = n m=1, n=2 solve: 2m = n m=1, n=2 2m != n
Question Which portion of code identifies a new constraint ?
The Example Again main(){ int t1 = randomInt(); int t2 = randomInt(); test_me(t1,t2); } int double(int x) { return 2 * x; } void test_me(int x, int y) { int z = double(x); if(z==y){ if(x != y+10) { printf(“I am fine here”); } else { printf(“I should not reach here”); abort(); } }
The Example Again main(){ int t1 = randomInt(); int t2 = randomInt(); test_me(t1,t2); } int double(int x) { return 2 * x; } void test_me(int x, int y) { int z = double(x); if(z==y){ if(x != y+10) { printf(“I am fine here”); } else { printf(“I should not reach here”); abort(); } }
concrete state symbolic state constraints t1=1 t1=m DART Approach Concrete Execution Symbolic Execution main(){ int t1 = randomInt(); int t2 = randomInt(); test_me(t1,t2); } int double(int x) { return 2 * x; } void test_me(int x, int y) { int z = double(x); if(z==y){ if(x != y+10) { printf(“I am fine here”); } else { printf(“I should not reach here”); abort(); } }
concrete state symbolic state constraints t1=1, t2=2 t1=m, t2=n DART Approach Concrete Execution Symbolic Execution main(){ int t1 = randomInt(); int t2 = randomInt(); test_me(t1,t2); } int double(int x) { return 2 * x; } void test_me(int x, int y) { int z = double(x); if(z==y){ if(x != y+10) { printf(“I am fine here”); } else { printf(“I should not reach here”); abort(); } }
concrete state symbolic state constraints t1=1, t2=2 t1=m, t2=n DART Approach Concrete Execution Symbolic Execution main(){ int t1 = randomInt(); int t2 = randomInt(); test_me(t1,t2); } int double(int x) { return 2 * x; } void test_me(int x, int y) { int z = double(x); if(z==y){ if(x != y+10) { printf(“I am fine here”); } else { printf(“I should not reach here”); abort(); } }
concrete state symbolic state constraints x=1, y=2 x=m, y=n DART Approach Concrete Execution Symbolic Execution main(){ int t1 = randomInt(); int t2 = randomInt(); test_me(t1,t2); } int double(int x) { return 2 * x; } void test_me(int x, int y) { int z = double(x); if(z==y){ if(x != y+10) { printf(“I am fine here”); } else { printf(“I should not reach here”); abort(); } }
concrete state symbolic state constraints x=1, y=2 x=m, y=n DART Approach Concrete Execution Symbolic Execution main(){ int t1 = randomInt(); int t2 = randomInt(); test_me(t1,t2); } int double(int x) { return 2 * x; } void test_me(int x, int y) { int z = double(x); if(z==y){ if(x != y+10) { printf(“I am fine here”); } else { printf(“I should not reach here”); abort(); } }
concrete state symbolic state constraints x=1, y=2 x=m, y=n DART Approach Concrete Execution Symbolic Execution main(){ int t1 = randomInt(); int t2 = randomInt(); test_me(t1,t2); } int double(int x) { return 2 * x; } void test_me(int x, int y) { int z = double(x); if(z==y){ if(x != y+10) { printf(“I am fine here”); } else { printf(“I should not reach here”); abort(); } }
concrete state symbolic state constraints x=1, y=2 x=m, y=n DART Approach Concrete Execution Symbolic Execution main(){ int t1 = randomInt(); int t2 = randomInt(); test_me(t1,t2); } int double(int x) { return 2 * x; } void test_me(int x, int y) { int z = double(x); if(z==y){ if(x != y+10) { printf(“I am fine here”); } else { printf(“I should not reach here”); abort(); } } 2m = n m != n+10
concrete state symbolic state constraints x=1, y=2, z=2 x=m, y=n, z=2m DART Approach Concrete Execution Symbolic Execution main(){ int t1 = randomInt(); int t2 = randomInt(); test_me(t1,t2); } int double(int x) { return 2 * x; } void test_me(int x, int y) { int z = double(x); if(z==y){ if(x != y+10) { printf(“I am fine here”); } else { printf(“I should not reach here”); abort(); } } 2m = n m != n+10
concrete state symbolic state constraints x=1, y=2, z=2 x=m, y=n, z=2m DART Approach Concrete Execution Symbolic Execution main(){ int t1 = randomInt(); int t2 = randomInt(); test_me(t1,t2); } int double(int x) { return 2 * x; } void test_me(int x, int y) { int z = double(x); if(z==y){ if(x != y+10) { printf(“I am fine here”); } else { printf(“I should not reach here”); abort(); } } 2m = n m != n+10
concrete state symbolic state constraints x=36, y=-7, z=72 x=m, y=n, z=2m DART Approach Concrete Execution Symbolic Execution main(){ int t1 = randomInt(); int t2 = randomInt(); test_me(t1,t2); } int double(int x) { return 2 * x; } void test_me(int x, int y) { int z = double(x); if(z==y){ if(x != y+10) { printf(“I am fine here”); } else { printf(“I should not reach here”); abort(); } } solve: 2m = n m=1, n=2 solve: 2m = n and m=n+10 m= -10, n= -20 2m = n m != n+10
concrete state symbolic state constraints t1=-10 t1=m DART Approach Concrete Execution Symbolic Execution main(){ int t1 = randomInt(); int t2 = randomInt(); test_me(t1,t2); } int double(int x) { return 2 * x; } void test_me(int x, int y) { int z = double(x); if(z==y){ if(x != y+10) { printf(“I am fine here”); } else { printf(“I should not reach here”); abort(); } }
concrete state symbolic state constraints t1=-10, t2=-20 t1=m, t2=n DART Approach Concrete Execution Symbolic Execution main(){ int t1 = randomInt(); int t2 = randomInt(); test_me(t1,t2); } int double(int x) { return 2 * x; } void test_me(int x, int y) { int z = double(x); if(z==y){ if(x != y+10) { printf(“I am fine here”); } else { printf(“I should not reach here”); abort(); } }
concrete state symbolic state constraints t1=-10, t2=-20 t1=m, t2=n DART Approach Concrete Execution Symbolic Execution main(){ int t1 = randomInt(); int t2 = randomInt(); test_me(t1,t2); } int double(int x) { return 2 * x; } void test_me(int x, int y) { int z = double(x); if(z==y){ if(x != y+10) { printf(“I am fine here”); } else { printf(“I should not reach here”); abort(); } }
concrete state symbolic state constraints x=-10, y=-20 x=m, y=n DART Approach Concrete Execution Symbolic Execution main(){ int t1 = randomInt(); int t2 = randomInt(); test_me(t1,t2); } int double(int x) { return 2 * x; } void test_me(int x, int y) { int z = double(x); if(z==y){ if(x != y+10) { printf(“I am fine here”); } else { printf(“I should not reach here”); abort(); } }
concrete state symbolic state constraints x=-10, y=-20, z=-20 x=m, y=n, z=2m DART Approach Concrete Execution Symbolic Execution main(){ int t1 = randomInt(); int t2 = randomInt(); test_me(t1,t2); } int double(int x) { return 2 * x; } void test_me(int x, int y) { int z = double(x); if(z==y){ if(x != y+10) { printf(“I am fine here”); } else { printf(“I should not reach here”); abort(); } }
concrete state symbolic state constraints x=m, y=n, z=2m x=-10, y=-20, z=-20 DART Approach Concrete Execution Symbolic Execution main(){ int t1 = randomInt(); int t2 = randomInt(); test_me(t1,t2); } int double(int x) { return 2 * x; } void test_me(int x, int y) { int z = double(x); if(z==y){ if(x != y+10) { printf(“I am fine here”); } else { printf(“I should not reach here”); abort(); } } 2m = n
concrete state symbolic state constraints x=m, y=n, z=2m x=-10, y=-20, z=-20 DART Approach Concrete Execution Symbolic Execution main(){ int t1 = randomInt(); int t2 = randomInt(); test_me(t1,t2); } int double(int x) { return 2 * x; } void test_me(int x, int y) { int z = double(x); if(z==y){ if(x != y+10) { printf(“I am fine here”); } else { printf(“I should not reach here”); abort(); } } 2m = n m = n+10
concrete state symbolic state constraints x=m, y=n, z=2m x=-10, y=-20, z=-20 DART Approach Concrete Execution Symbolic Execution main(){ int t1 = randomInt(); int t2 = randomInt(); test_me(t1,t2); } int double(int x) { return 2 * x; } void test_me(int x, int y) { int z = double(x); if(z==y){ if(x != y+10) { printf(“I am fine here”); } else { printf(“I should not reach here”); abort(); } } Program Error 2m = n m = n+10
Properties of DART • Sound w.r.t errors found (no false positives) • Complete (in a limited sense) If DART terminates without reporting a bug, no bug exists
Advantages: • Dynamic Data Analysis • Sound Errors
Dynamic Data Analysis struct foo { int i; char c; } bar (struct foo *a) { if (a->c == 0) { *((char *)a + sizeof(int)) = 1; if (a->c != 0) abort(); } }
Dynamic Data Analysis • Static Analyzers would not be able to detect the change in the value of a->c and declare the program to be safe • DART locates the error by means of satisfying the constraint a->c==0
Sound Errors foobar(int x, int y){ if (x*x*x > 0){ if (x>0 && y==10) abort(); } else { if (x>0 && y==20) abort(); } }
Sound Errors • Static analysis using predicate abstraction state that both aborts may be reachable • Test-generation using symbolic execution get stuck at first conditional • DART detects first abort with high probability
DART For C • Interface Extraction done by light weight static parsing of source code • Test Driver Generation involves initialization of external variables with random values, stubs for external functions • Directed Search comprises of implementation of constraint satisfaction, parsing and analysis of C code