Testing and Debugging
Pre- and Post-Conditions
MyroC.hheader file documentation.
- In the documentation, find at least two functions that have stated a "pre-condition" and at least two functions that have stated a "post-condition". (Note that every function should have postconditions, otherwise there would be no point in calling it.)
In anticipation of later work in this lab, review the
documentation for the function
rMotors. The documentation does not explicitly state pre-conditions for this function, but one might infer such conditions. Write a careful statement of the implied pre-condition(s) for
motors-test.csets the motor speeds of the Scribbler to the given
Initialize the variables
rightspeedto 1; compile and run the program.
Initialize the variables
rightspeedto 1 and -1 respectively. Compile and run the program.
Initialize the variables
rightspeedto 2 and -1. respectively. Compile and run the program.
- Now try using values 6 and 5 respectively.
- Try other numbers that you might need to figure out what works and what doesn't.
- How do these experimental results compare with the pre-conditions that you inferred in Step 1b?
- Initialize the variables
assert Function in C
Modify the same code,
motors-test.c, to use
assertso that it will test the precondition(s) you wrote for
Note: You can read about C's
assert function in
the accompanying reading
and/or using the command
man assert in a terminal
window. Better yet, read about
assert in both places!
Choosing Test Cases
reduce.cis supposed to reduce a fraction, with integers entered as numerator and (positive) denominator separately, to its simplest form.
Develop two test plans for this program to determine whether
the program works correctly. Apply both black-box
and white-box testing strategies by identifying test cases that
will cover a full range of situations that might be encountered
in executing the program. Recall:
- Black-box testing is when the problem is examined to determine the logical cases that might arise. Test cases are developed without reference to details of code.
- White-box testing is when the code is examined to determine each of the possible conditions that may arise, and tests are developed to exercise each part of the code.
reduce.cwith all the cases from your test plan to determine whether the program works correctly.
- Try to fix all the errors you found in the program.
- Run the program again with all the cases from your test plan to be sure that it now works correctly.
- Develop two test plans for this program to determine whether the program works correctly. Apply both black-box and white-box testing strategies by identifying test cases that will cover a full range of situations that might be encountered in executing the program. Recall:
Recall from today's reading that you may run GDB from the terminal or within Emacs. While you might settle into using GDB from within Emacs (one of its great strengths as a development environment), here you will experiment with both. There was a lot of information in the reading, so you can take a look at this cheat sheet to follow some initial steps: GDB Lab Cheatsheet
array-bug.ccontains two simple functions for doing array manipulations.
- Copy the program to your account.
Read over the program (particularly the function documentation
mainprocedure) to be sure you understand what it is supposed to do, but do not make any changes to it.
Compile and run the program to confirm its behavior. For
this exercise, you MUST use the system default compiler (so that you do get obviously wrong output):
cc -g -o array-bug array-bug.c
Use the terminal to start GDB on your program and then run your
program within GDB. (You will give one terminal command, and one
GDB command within terminal.) You may then exit GDB with
- Start GDB within Emacs and then run your program within the GDB buffer window. Leave GDB running after the program completes.
Setting break points
You should have noticed that your program does not yield the "expected" results. To isolate the bug (in case you haven't spotted it already), we will want to probe some values while the program is running. (That way we don't have to decide before we compile which values may be interesting to look at.) Thus we will practice setting break points in the code to get a better handle on the action.
Set a breakpoint at the line just after the definition of the
expected, (i.e., the line with the call to
compute_average, then run your program.
Use GDB to inspect the value of
compute_averageprocedure runs. Does it have the value intended? (You may also want to verify the value of
Use GDB to "step over" the call to
compute_average(that is, use the
Inspect the value of
expected(i.e., just before the call to the
halveprocedure). Did it change?
Inspect the value of the
meanvariable after calling
compute_average. Is it correct?
Step over the call to
halveand inspect the value of
expectedonce again. Did it change?
Corruptions are often caused by mismanaging
references. Determine the memory addresses of the four variables
killto release the current execution of the program.
- Use GDB to inspect the value of
By this point you should have identified two problematic behaviors in
the program: (1)
mean is computed incorrectly
compute_average, and (2)
somehow corrupted within
halve. We will now use
additional GDB tools to help isolate the cause(s).
Run the program again. While stopped at the first breakpoint
main) set a write watchpoint for the
continueexecuting the program. That is, don't use the
nextcommands; instead, GDB should pause the program wherever
Where does the corruption occur? What are the old and new values?
While GDB shows you the code context, issue a
backtracerequest to see the execution context as well. (This particular stackframe is very short, but when a function has many possible entry points, understanding the execution context is a critical step in the debugging process.)
Determine the values and addresses of the several local
variables available where the program is currently paused:
values, and even
values[i]. Compare these addresses to those you identified earlier.
- Construct a hypothesis about how (not why) the variable gets corrupted.
Stepping the source
Chances are very good now you've not only determined the nature of the bug, but also its source. Bear with us as we walk through one more important aspect of using the debugger, stepping through code line by line to monitor behavior.
Set a breakpoint within the
halveprocedure. (Recall you may simply give a function name as an argument to
break—a rather pleasing command in this instance.) Temporarily
disablethe first breakpoint you had set earlier and then re-run the program.
- Where does the program actually break? Is it where you expected? Why do you suppose it stops there?
Inspect the value of the relevant variables (all those listed
in Exercise 4c) in a single line using a printf statement. Here
is a starting example:
printf "i=\t%p\t=\t%d\n",&i,iAre any of these values surprising? Construct a hypothesis to explain them.
stepcommand once, note the output produced, and then re-issue (via command history) the
printfcommand to help identify the side-effect and execution context.
Repeat the previous instruction
printf, inspect) several times as you traverse the loop.
If you've been paying attention to the values and the addresses, by now you should be able to hypothesize the precise cause for the bug(s), which you may now attempt to fix. Be sure to confirm your fixes. If they do not fix the problem, restore your program to its original state and continue to use the debugger to explore the behavior while also studying the source code carefully.
Optional: Über-advanced GDB
For those with extra time, explore one or more of the following sections of the GDB Manual, identify some more advanced command(s) we have not yet discussed or used in the lab, and try them out on this or another program. There are also many different cheatsheets for GBD, this one is very concise.