========================================================================== Section 3 Notes ========================================================================== - 1. Exploring the ECE 2400 Build System - 2. Using Criterion for Unit Testing - 3. Using lcov for Code Coverage Analysis - 4. Using gdb for Debugging * As in the tutorial, log into ecelinux from your laptop % ssh -X cb535@ecelinux-01.ece.cornell.edu % ssh -X cb535@ecelinux-02.ece.cornell.edu - don't forget to source the setup script! % source setup-ece2400.sh * Clone the tutorial repo % mkdir -p $HOME/ece2400 % cd $HOME/ece2400 % git clone git@github.com:cornell-ece2400/ece2400-tut3-c sec3 % cd sec3/tut3-gcd % TUTDIR=$PWD - if you have not uploaded your SSH key onto GitHub, then you may need to clone the repo like this: % mkdir -p $HOME/ece2400 % cd $HOME/ece2400 % git clone https://github.com/cornell-ece2400/ece2400-tut3-c sec3 % cd sec3/tut3-gcd % TUTDIR=$PWD -------------------------------------------------------------------------- 1. Exploring the ECE 2400 Build System -------------------------------------------------------------------------- - last week we learned about using Makefiles to automate compilation - a "build system" is a well-structured set of scripts and policies that facilitate compiling, testing, and evaluating software projects - the ECE 2400 build system includes a configure script and a Makefile.in template - the configure script instantiates the Makefile.in in a separate build directory so we can keep our source separate from generated files % cd $TUTDIR % tree - look at the src/gcd.h and src/gcd.c source files - look at the Makefile.in and configure script + @name@ will be filled in by the configure script % cd $TUTDIR % mkdir build % cd build % ../configure - look at the Makefile, notice that the top_dir, cflags, etc Makefile variabls are now filled in correctly - look at the rule to build gcd.o, notice use of src_dir, cflags - let's use the Makefile to build gcd.o % cd $TUTDIR/build % make gcd.o - look at gcd-main.c - add a new rule to make gcd-main gcd-main : $(src_dir)/gcd-main.c $(src_dir)/gcd.h gcd.o gcc $(cflags) -o gcd-main $(src_dir)/gcd-main.c gcd.o - let's build and run gcd-main % cd $TUTDIR/build % ../configure % make gcd-main % ./gcd-main - build system will include several pre-defined make targets + default : build all evaluation programs + check : build and run all unit tests + coverage : build and run all unit tests, perform code coverage + eval : build and run all eval programs, run evaluation + clean : remove all generated content -------------------------------------------------------------------------- 2. Using Criterion for Unit Testing -------------------------------------------------------------------------- - look at src/gcd-adhoc-test.c - build and run the adhoc test % cd $TUTDIR/build % make gcd-adhoc-test % ./gcd-adhoc-test - add a new adhoc-test if ( gcd( 15, 5 ) != 5 ) { failed += 1; printf( " - [ FAILED ] gcd( 15, 5 ) != 5 \n" ); } else { passed += 1; printf( " - [ passed ] gcd( 15, 5 ) == 5 \n" ); } - "real" testing requires well-structured, automated unit tests - want very low overhead to writing tests - we will be using the Criterion unit testing framework - standardizes writing tests, running tests, and reporting results - look at src/gcd-test.c for an example of using Criterion - build and run the unit test % cd $TUTDIR/build % make gcd-test % ./gcd-test - list the available tests, be more verbose, run a single test % ./gcd-test --list % ./gcd-test --verbose % ./gcd-test --verbose --filter gcd/basic - let's break our code - modify src/gcd.c - change return x to return x+1 % make gcd-test && ./gcd-test - see which tests fail, focus on simplest failing test first % make gcd-test && ./gcd-test --verbose --filter gcd/basic - use line number to identify which assertion is failing - potentially add more debug information with printf - let's fix the test and rerun % make gcd-test && ./gcd-test --verbose --filter gcd/basic % make gcd-test && ./gcd-test - this idea of zooming in on a test and then zooming out is important - add more basic and zeros tests Test( gcd, basic ) { cr_assert_eq( gcd( 5, 15 ), 5 ); cr_assert_eq( gcd( 15, 15 ), 15 ); cr_assert_eq( gcd( 15, 5 ), 5 ); } Test( gcd, zeros ) { cr_assert_eq( gcd( 0, 15 ), 15 ); cr_assert_eq( gcd( 15, 0 ), 15 ); cr_assert_eq( gcd( 0, 0 ), 0 ); } - build and rerun the tests - take a closer look at the random testing - you can run all of the tests in a subproject using make % cd $TUTDIR/build % make check -------------------------------------------------------------------------- 3. Using lcov for Code Coverage Analysis -------------------------------------------------------------------------- - lcov is a tool that will do code coverage analysis - code coverage analysis tells you how many times each line in your code has been executed and which lines have never been executed - if a line has never been executed then it has not been tested! - you need to compile your program specially to enable code coverage - by default the build system sets all of the correct compiler flags - usually good to do code coverage analysis from a clean build % cd $TUTDIR/build % make clean % make coverage % firefox coverage-html/index.html - browse to see the coverage analsysi for src/gcd.c - the return -1 statement is never executed! - add a new test case for negative inputs Test( gcd, negative ) { cr_assert_eq( gcd( -5, 15 ), -1 ); cr_assert_eq( gcd( 5, -15 ), -1 ); cr_assert_eq( gcd( 5, -15 ), -1 ); } - rerun the tests and verify everything passes % cd $TUTDIR/build % make check - rerun code coverage analysis % cd $TUTDIR/build % make clean % make coverage % firefox coverage-html/index.html - now we have 100% coverage! -------------------------------------------------------------------------- 4. Using gdb for Debugging -------------------------------------------------------------------------- - gdb enables stepping through your code one statement at a time - gdb essentially enables executing your code just like we do by hand - let's start gdb and run the adhoc test within gdb % cd $TUTDIR/build % make gcd-adhoc-test % gdb ./gcd-adhoc-test (gdb) run - let's set a "breakpoint" at the main function with the "break" command - a breakpoint tell's gdb to stop when it reaches that point in the code (gdb) break main - we can use the "step" to step through each statement one at a time (gdb) step (gdb) step (gdb) ... - gdb can print out variables, just like with our "stack frame" diagrams (gdb) print x (gdb) print y - we can continue to the next breakpoint or end of the program (gdb) contine - let's set a breakpoint on the statemen if ( x < y ) in gcd.c (line 21) (gdb) break gcd.c:21 - let's run until we reach the breakpoint at main, continue into the gcd function, print x and y, continue, and repeat; you should write down x and y everytime through the loop (gdb) run (gdb) continue (gdb) print x (gdb) print y (gdb) continue (gdb) ... - if you compare what you wrote down with the table we made in the notes, we are essentially doing the exact same thing