Last modified: Jul 15, 2014
Testing versus Debugging
Testing is the act of executing a program with selected data to uncover bugs.
Debugging, is the process of finding the faulty code responsible for failed tests.
Rules of Debugging
You can’t debug what you don’t understand
Make it fail…
Make it fail reliably
Make it fail easily
Make it fail visibly
Track down the culprit
Don’t Guess, Hypothesize
Divide and Conquer
If you didn’t fix it, it ain’t fixed!
In this lesson we’ll focus on the first two
How can you hope to fix (or even recognize) what is wrong if you don’t know what it will look like when it’s right?
Read all available documentation before you start testing
Then read it again when you start debugging
Engineers often talk about “sanity checks” and “back-of-the-envelope” estimates. These refer to quick considerations of what is actually a reasonable result in a given situation.
So if you see something that isn’t reasonable, your first responses should be to mistrust the calculations (or, in our case, the code) that produced that “obviously” unreasonable behavior.
Where do we get the knowledge of what is and isn’t reasonable?
Technical knowledge:
what are common values in various data types?
how does this algorithm normally behave?
Domain & application knowledge:
What will correct output look like?
What are reasonable ranges for values?
Large programs are made of many components. When you see an output that is incorrect, your debugging will be much more efficient if you have an idea which of those components contributed to that particular output.
What are the pieces of the system?
How do they connect/interact with one another?
Example: if you were debugging this program after seeing an identical mistake in both reports, would you look first at the report generators or would you concentrate on the “shared” input processing and main calculation?
Typically you start debugging because your program failed
maybe during your own testing
or maybe someone else told you about it.
What’s the next thing you need to do?
Make It Fail Again
Once You’ve Made it Fail …
Make sure you can make it fail again
This is where testing and debugging meet
Why “Make It Fail Again”?
You need to observe the failure
You need to figure out what’s causing it
You need to know if you have fixed it
Try to repeat whatever you did to cause the failure
Write down your steps
Try to get to the point where you can automatically produce the failure.
For programs with textual input, if the failure is replicatable, you should be able to do this. E.g.,
program < inputData.txt
This is a good reason not to enter your tests manually in the first place.
Sometimes, replicating the failure is hard …
A failure that, given the same inputs, occurs only some of the time is called intermittent
An intermittent failure may occur 99% of the time when the buggy input is presented, or only 1% of the time, or even less.
Can Hardware Failures Be Truly Intermittent?
But hardware really is subject to “random” factors such as vibration, electronic noise, loose connections, temperature variations, timing variations
Can Software Failures Be Truly Intermittent?
Sometimes
software that controls hardware can have timing issues
So can software that runs on distributed hardware
But usually we are simply not seeing the critical factor that, when repeated, guarantees the failure
Tracking Down Intermittent Failures
Work backwards — the problem may be in an earlier input sequence
Run repeatedly (invoke the buggy code from within a loop)
Take advantage of the nature of the failure
What Causes Intermittent Software Failures?
Typically,
Uninitialized variables
Out of bounds data access
Pointer errors
Uninitialized variables
The errors resulting from this are more likely to be seen in longer test sequences
Out of bounds data access
Timing and actual symptom of failure may depend on how/when those other variables get used.
Pointer errors - dangling pointers
Dangling pointers
We saw earlier that if bc is destroyed, …
we are left with dangling pointers.
Pointer errors - Deleting a pointer twice
Question: What happens if all the collections were destroyed and they each deleted the pointer?
When the first collection is destroyed, its destructor deletes the array.
When the second and third collections are destroyed, their destructors delete their dangling pointers to the same address.
Deleting the same address multiple times will typically corrupt the data structures that the run-time system’s memory manager uses to respond to future requests for memory via new.
So we wind up with a really nasty bug. It probably will not crash the program now. Instead it will crash it at some point in the future when our program requests new memory and then uses that.
It’s so hard to predict what the nature of the failure will be or when it will occur, that this is almost certain to be one of the hardest possible problems to debug.
Debugging often requires you to run the failing test cases over and over again.
If it takes hours to demonstrate a failure, how long will it take you to debug it?
Even if it merely takes several minutes, how often are you going to run that failing code?
Debugging is primarily an activity of working backwards from the point of failure towards the cause.
Simplify, Simplify, Simplify
“The only reasonable numbers in a programming language design are zero, one and infinity.” – Bruce MacLennan (1987)
There are very few things that programs a small, finite number of times. There are things that they never do, things that they do once (e.g., initialization and termination code), and things that they do in loops.
But loops typically have the characteristic that one run through the loop body is pretty much like any other.
If you made the program fail on the 100th input or 100th time around a loop, can you find a simpler test that fails after the 10th time?
Shortening that loop gives you less to debug.
KISS
“Two is an impossible number, and can’t exist.” – I. Asimov (1972)
(Asimov was actually talking about physics, where we sometimes see zero instances of a phenomenon (things that turn out ot be impossible), and sometimes see near-infinite numbers of things (stars, atoms, sub-atomic particles), and occasionally see one unique instance of a thing (the universe), but the moment you have seen 2 instances of a thing, the smart money is on the chances of its being much more common than that.)
Later we will make a distinction between unit testing and system testing,
Unit tests are typically much simpler
which is one of the major reasons to use them
If It Takes Too Long to Fail…
By adding more and earlier output, you can move up the point of failure and see things going wrong sooner.
We’ve talked about the idea of adding debugging output in an earlier lesson.
When memory is deleted, it usually gets added to a “free list”
Example: When Do Pointer Errors Fail? (cont.)
Tracking Pointer Errors - Redefining Failure
That time delay between the problem and the actual failure can be hard to deal with
If we can reduce the delay, error will no longer appear intermittent
Possibilities:
If a tree falls in the forest and no one is there to hear, does it make a sound?
If a program fails and no one observes the error, can you debug it?
Program States
A program state is the combination of
what you’ve got (the values of all program variables)
where you are (the location to which execution has reached)
how you got here (the activation stack recording the places from which all still-active function calls were made)
Infected States
A program state is infected if one of its components is affected by a bug :
a bad value in one or more variables, or
at a wrong location (because we followed an incorrect branch at some conditional statement)
Propagating an Infection
As we move forward through the code, an infected state can
propagate - as bad values in some variables are used to compute the values of other variables, or
vanish - if the infected values are never written to output and/or are overwritten by good values
From Infection to Failure
A failure occurs when an infected state affects (propagates to) the program output.
Debugging is a process of tracing backwards from bad output towards the cause (faulty code)
Requires viewing of the infected states
Add debugging output
Minimizes interference with “real” output written to standard out (cout)
Can be separated out at the command line.
E.g.,
myProgram
writes cout and cerr to screen
myProgram > output.dat`
writes cout to file output.dat but cerr to the screen
myProgram.exe >& output.dat
(Unix only) writes both cout and cerr to file output.dat
Design Instrumentation In
Good rule of thumb for C++ ADT designers:
Always provide an output function for your ADT, showing the value of each data member.
Even if the application itself does not need it
More often than not, you will want it during debugging
Build Instrumentation In Later
Actual outputs are likely to be added during debugging
cerr << "I suspect " << x << " is wrong" << endl;
Special macros useful during debugging:
__FILE__ expands to the name of the file being compiled
__LINE__ expands to the line number in which it appears
cerr << "I suspect " << x
<< " is wrong at "
<< __FILE__ << ":" << __LINE__<< endl;
You can even make your own macros:
#define DEBUGOUT(x) cerr << #x << "@" \
<< __File__ << ":" << __LINE__ << " " << x << endl;
#include <iostream>
using namespace std;
#define DEBUGOUT(x) cerr << #x << "@" << __FILE__ << ":" << __LINE__ \
<< ": " << x << endl;
int main()
{
int k = 0;
string y = "hello";
cout << "Real output " << k << endl;
cerr << "Debugging output " << k+1 << endl;
cout << "Real output " << y << endl;
cerr << "I suspect " << y << " is incorrect at "
<< __FILE__ << ":" << __LINE__ << endl;
cout << "Real output " << k << endl;
DEBUGOUT (k);
DEBUGOUT (y);
DEBUGOUT (k + k);
DEBUGOUT (y.size());
return 0;
}
Never Throw a Debugging Tool Away
When you have finished debugging a problem, deactivate your debug code, don’t delete it!
You may need it again for the next bug.
Different ways to deactivate
Comment it out
x = foo(x,y);
cerr << "x is " << x << endl;
x = bar(x,y,z);
becomes
x = foo(x,y);
// cerr << "x is " << x << endl;
x = bar(x,y,z);
Use #ifdefs:
x = foo(x,y);
#ifdef DEBUG
cerr << "x is " << x << endl;
#endif
x = bar(x,y,z);
or
x = foo(x,y);
#ifndef NDEBUG
cerr << "x is " << x << endl;
#endif
x = bar(x,y,z);
Build deactivation in to debugging macros
#ifdef DEBUG
#define DEBUGOUT(x) cerr << #x << "@" \
<< __File__ << ":" << __LINE__ << " " << x << endl;
#else
#define DEBUGOUT(x) // ignored if not debugging
#endif
#include <iostream>
using namespace std;
#ifdef DEBUG
#define DEBUGOUT(x) cerr << #x << "@" << __FILE__ << ":" << __LINE__ \
<< ": " << x << endl;
#else
#define DEBUGOUT(x) // ignored if not debugging
#endif
int main()
{
int k = 0;
string y = "hello";
cout << "Real output " << k << endl;
#ifdef DEBUG
cerr << "Debugging output " << k+1 << endl;
#endif
cout << "Real output " << y << endl;
#ifndef NDEBUG
cerr << "I suspect " << y << " is incorrect at "
<< __FILE__ << ":" << __LINE__ << endl;
#endif
cout << "Real output " << k << endl;
DEBUGOUT (k);
DEBUGOUT (y);
DEBUGOUT (k + k);
DEBUGOUT (y.size());
return 0;
}
The Heisenberg Uncertainty Principle
Debugging instrumentation may affect the behavior of the bug
Sometimes unavoidable (but that’s pretty rare)
Be careful
As last seen: Auction ADTs
Failure Report
During an early run, the seller of a Chinese Vase complained that a bid for $27 was accepted as a winner even though a reserve price of $50 had been set.
A check of the items file:
24 10.00 12:00:00 Star Wars Collectables 1.00 23:00:00 Bonds autograph ⋮ 50.00 20:06:27 Chinese Vase ⋮ 100.00 23:30:00 Cornflake shaped like Illinois
showed that the reserve price was indeed set.
The bids file did indeed contain a bid for the indicated amount.
4000 ⋮ jjones 26.00 00:18:03 Hummel Figurine ⋮ ssmith 27.00 04:03:05 Chinese Vase ⋮
Rerunning the program showed that the failure was reliable.
If the program was run with only the $27 bid, it correctly rejects that bid and announces that the reserve price on the vase was not met.
We can easily select only the bids on the vase:
grep "Chinese Vase" items.dat > items0.dat
but the failure does not then occur. Instead, a $60 bid by jjones is selected as the winner.
Rounding Up the Suspects
A closer look at items0.dat shows that only two people, jjones and ssmith, bid on the vase.
We can collect their bids for the day
grep "jjones" bids.dat > bids0a.dat
grep "ssmith" bids.dat > bids0b.dat
cat bids0a.dat bids0b.dat > bids0.dat
and discover that they bid on only two items.
Collecting those two items:
2 50.00 20:06:27 Chinese Vase 25.00 15:30:11 Hummel Figurine
those two bidders:
2 jjones 75.00 ssmith 55.00
and the related bids:
4
jjones 25.00 05:00:00 Chinese Vase
jjones 26.00 00:18:03 Hummel Figurine
jjones 60.00 04:03:01 Chinese Vase
ssmith 27.00 04:03:05 Chinese Vase
we are able to reproduce the failure reliably:
Hummel Figurine won by jjones for 26 Chinese Vase won by ssmith for 27
We have our test case!
The most obvious place to check for this problem is in the resolveAuction function
That’s where the incorrect output is produced
Should we first check the input to see if everything is being read in correctly?
Probably not an effective way to use our time
Most of the program is probably correct (have already passed many tests)
So we could waste a lot of time checking everything that works
Exception would be if this were a divide and conquer strategy (later)
Problem appears to be in the selection of winningBidderSoFar and highestBidSoFar.
/**
* Determine the winner of the auction for an item.
* Announce the winner and remove money from winner's account.
*/
void resolveAuction (const Item& item,
BidderCollection& bidders,
BidCollection& bids)
{
double highestBidSoFar = 0.0;
string winningBidderSoFar;
bool reservePriceMet = false;
for (int bidNum = 0; bidNum < bids.getSize(); ++bidNum)
{
Bid bid = bids.get(bidNum);
if (bid.getTimePlacedAt().noLaterThan(item.getAuctionEndTime()))
{
if (bid.getItem() == item.getName()
&& bid.getAmount() > highestBidSoFar
)
{
int bidderNum = bidders.findBidder(bid.getBidder());
Bidder bidder = bidders.get(bidderNum);
// Can this bidder afford it?
if (bid.getAmount() <= bidder.getBalance())
{
highestBidSoFar = bid.getAmount();
winningBidderSoFar = bid.getBidder();
}
}
if (bid.getAmount() > item.getReservedPrice())
reservePriceMet = true;
}
}
// If highestBidSoFar is non-zero, we have a winner
if (reservePriceMet && highestBidSoFar > 0.0)
{
int bidderNum = bidders.findBidder(winningBidderSoFar);
cout << item.getName()
<< " won by " << winningBidderSoFar
<< " for " << highestBidSoFar << endl;
Bidder& bidder = bidders.get(bidderNum);
bidder.setBalance (bidder.getBalance() - highestBidSoFar);
}
else
{
cout << item.getName()
<< " reserve not met"
<< endl;
}
}
\
These are initialized outside (before) the loop and reset inside, so we might want to check that the values are reasonable when they are reset.
When a New Best Bid is Found
Adding debugging output:
void resolveAuction (const Item& item,
BidderCollection& bidders,
BidCollection& bids)
{
double highestBidSoFar = 0.0;
string winningBidderSoFar;
bool reservePriceMet = false;
for (int bidNum = 0; bidNum < bids.getSize(); ++bidNum)
{
Bid bid = bids.get(bidNum);
if (bid.getTimePlacedAt().noLaterThan(item.getAuctionEndTime()))
{
if (bid.getItem() == item.getName()
&& bid.getAmount() > highestBidSoFar
)
{
int bidderNum = bidders.findBidder(bid.getBidder());
Bidder bidder = bidders.get(bidderNum);
// Can this bidder afford it?
if (bid.getAmount() <= bidder.getBalance())
{
highestBidSoFar = bid.getAmount();
winningBidderSoFar = bid.getBidder();
cerr << "Best bid so far is "
<< highestBidSoFar << " from "
<< winningBidderSoFar << endl;
}
}
if (bid.getAmount() > item.getReservedPrice())
reservePriceMet = true;
}
}
// If highestBidSoFar is non-zero, we have a winner
if (reservePriceMet && highestBidSoFar > 0.0)
{
int bidderNum = bidders.findBidder(winningBidderSoFar);
cout << item.getName()
<< " won by " << winningBidderSoFar
<< " for " << highestBidSoFar << endl;
Bidder& bidder = bidders.get(bidderNum);
bidder.setBalance (bidder.getBalance() - highestBidSoFar);
}
else
{
cout << item.getName()
<< " reserve not met"
<< endl;
}
}
gives us the output:
Best bid so far is 26 from jjones
Hummel Figurine won by jjones for 26
Best bid so far is 27 from ssmith
Chinese Vase won by ssmith for 27
which does not really tell us much.
Adding Detail
void resolveAuction (const Item& item,
BidderCollection& bidders,
BidCollection& bids)
{
double highestBidSoFar = 0.0;
string winningBidderSoFar;
bool reservePriceMet = false;
for (int bidNum = 0; bidNum < bids.getSize(); ++bidNum)
{
Bid bid = bids.get(bidNum);
if (bid.getTimePlacedAt().noLaterThan(item.getAuctionEndTime()))
{
if (bid.getItem() == item.getName()
&& bid.getAmount() > highestBidSoFar
)
{
int bidderNum = bidders.findBidder(bid.getBidder());
Bidder bidder = bidders.get(bidderNum);
// Can this bidder afford it?
if (bid.getAmount() <= bidder.getBalance())
{
highestBidSoFar = bid.getAmount();
winningBidderSoFar = bid.getBidder();
cerr << "Best bid so far is ["
<< bid.getBidder() << " "
<< bid.getAmount() << " "
<< bid.getItem() << " ";
bid.getTimePlacedAt().print(cerr);
cerr << "] from ["
<< bidder.getName() << " "
<< bidder.getBalance() << "]"
<< endl;
}
}
if (bid.getAmount() > item.getReservedPrice())
reservePriceMet = true;
}
}
// If highestBidSoFar is non-zero, we have a winner
if (reservePriceMet && highestBidSoFar > 0.0)
{
int bidderNum = bidders.findBidder(winningBidderSoFar);
cout << item.getName()
<< " won by " << winningBidderSoFar
<< " for " << highestBidSoFar << endl;
Bidder& bidder = bidders.get(bidderNum);
bidder.setBalance (bidder.getBalance() - highestBidSoFar);
}
else
{
cout << item.getName()
<< " reserve not met"
<< endl;
}
}
gives us the output:
Best bid so far is [jjones 26 Hummel Figurine 00:18:03] from [jjones 75] Hummel Figurine won by jjones for 26 Best bid so far is [ssmith 27 Chinese Vase 04:03:05] from [ssmith 55] Chinese Vase won by ssmith for 27
all of which looks OK.
Actually I would never write debugging output as clumsy as that
Compare the effort required to print all the fields of a bid or bidder to the effort required to print all the fields of a time
That’s because, for Time, we observed the rule of thumb to actually provide an output function for our ADTs
Adding Detail the Easy Way
void resolveAuction (const Item& item,
BidderCollection& bidders,
BidCollection& bids)
{
double highestBidSoFar = 0.0;
string winningBidderSoFar;
bool reservePriceMet = false;
for (int bidNum = 0; bidNum < bids.getSize(); ++bidNum)
{
Bid bid = bids.get(bidNum);
if (bid.getTimePlacedAt().noLaterThan(item.getAuctionEndTime()))
{
if (bid.getItem() == item.getName()
&& bid.getAmount() > highestBidSoFar
)
{
int bidderNum = bidders.findBidder(bid.getBidder());
Bidder bidder = bidders.get(bidderNum);
// Can this bidder afford it?
if (bid.getAmount() <= bidder.getBalance())
{
highestBidSoFar = bid.getAmount();
winningBidderSoFar = bid.getBidder();
cerr << "Best bid so far is ";
bid.print (cerr);
cerr << " from ";
bidder.print (cerr);
cerr << endl;
}
}
if (bid.getAmount() > item.getReservedPrice())
reservePriceMet = true;
}
}
// If highestBidSoFar is non-zero, we have a winner
if (reservePriceMet && highestBidSoFar > 0.0)
{
int bidderNum = bidders.findBidder(winningBidderSoFar);
cout << item.getName()
<< " won by " << winningBidderSoFar
<< " for " << highestBidSoFar << endl;
Bidder& bidder = bidders.get(bidderNum);
bidder.setBalance (bidder.getBalance() - highestBidSoFar);
}
else
{
cout << item.getName()
<< " reserve not met"
<< endl;
}
}
For this to work, though, we need to add the print functions to bids and bidders (that we should probably have had there in the first place.)
Auction with Output Functions
#ifndef BIDS_H
#define BIDS_H
#include <iostream>
#include <string>
#include "time.h"
//
// Bids Received During Auction
//
class Bid {
std::string bidderName;
double amount;
std::string itemName;
Time bidPlacedAt;
public:
Bid (std::string bidder, double amt,
std::string item, Time placedAt);
Bid();
// Access to attribute
std::string getBidder() const {return bidderName;}
double getAmount() const {return amount;}
std::string getItem() const {return itemName;}
Time getTimePlacedAt() const {return bidPlacedAt;}
// Print a bid
void print (std::ostream& out) const;
};
#endif
and
#include <string>
#include <fstream>
using namespace std;
//
// Bids Received During Auction
//
#include "bids.h"
Bid::Bid (std::string bidder, double amt,
std::string item, Time placedAt)
: bidderName(bidder), amount(amt),
itemName(item), bidPlacedAt(placedAt)
{
}
Bid::Bid ()
: amount(0.0)
{
}
// Print a bid
void Bid::print (std::ostream& out) const
{
out << "[" << bidderName << " " << amount
<< " " << itemName << " ";
bidPlacedAt.print (out);
out << "]";
}
#ifndef BIDDERS_H
#define BIDDERS_H
#include <iostream>
#include <string>
//
// Bidders Registered for auction
//
class Bidder {
// describes someone registered to participate in an auction
std::string name;
double balance;
public:
Bidder();
Bidder (std::string theName, double theBalance);
// Access to attributes
std::string getName() const {return name;}
double getBalance() const {return balance;}
void setBalance (double newBal) {balance = newBal;}
// print a bidder
void print (std::ostream& out) const;
};
#endif
and
#include <string>
#include <fstream>
#include <iostream>
//
// Bidders Registered for auction
//
#include "bidders.h"
using namespace std;
Bidder::Bidder()
{
name = "";
balance = 0.0;
}
Bidder::Bidder (std::string theName, double theBalance)
{
name = theName;
balance = theBalance;
}
// print a bidder
void Bidder::print (std::ostream& out) const
{
out << "[" << name << " " << balance << "]";
}
#ifndef ITEMS_H
#define ITEMS_H
#include <iostream>
#include <string>
#include "time.h"
//
// Items up for auction
//
class Item {
std::string name;
double reservedPrice;
Time auctionEndsAt;
public:
Item();
Item (std::string itemName, double reserve, Time auctionEnd);
// Access to attribute
std::string getName() const {return name;}
double getReservedPrice() const {return reservedPrice;}
Time getAuctionEndTime() const {return auctionEndsAt;}
/**
* Read one item from the indicated file
*/
void read (std::istream& in);
// Print an item
void print (std::ostream& out) const;
};
#endif
and
#include <iostream>
#include <fstream>
#include "items.h"
//
// Items up for auction
//
using namespace std;
Item::Item()
: reservedPrice(0.0)
{
}
Item::Item (std::string itemName, double reserve, Time auctionEnd)
: name(itemName), reservedPrice(reserve), auctionEndsAt(auctionEnd)
{
}
/**
* Read one item from the indicated file
*/
void Item::read (istream& in)
{
in >> reservedPrice;
auctionEndsAt.read (in);
// Reading the item name.
char c;
in >> c; // Skips blanks and reads first character of the name
string line;
getline (in, line); // Read the rest of the line
name = string(1,c) + line;
}
// print a bidder
void Item::print (std::ostream& out) const
{
out << "[" << name << " " << reservedPrice << " ";
auctionEndsAt.print (out);
out << "]";
}
#ifndef TIMES_H
#define TIMES_H
#include <iostream>
/**
* Times in this program are represented by three integers: H, M, & S, representing
* the hours, minutes, and seconds, respecitvely.
*/
struct Time {
// Create time objects
Time(); // midnight
Time (int h, int m, int s);
// Access to attributes
int getHours() const;
int getMinutes() const;
int getSeconds() const;
// Calculations with time
void add (Time delta);
Time difference (Time fromTime);
/**
* Read a time (in the format HH:MM:SS) after skipping any
* prior whitepace.
*/
void read (std::istream& in);
/**
* Print a time in the format HH:MM:SS (two digits each)
*/
void print (std::ostream& out) const;
/**
* Compare two times. Return true iff time1 is earlier than or equal to
* time2
*/
bool noLaterThan(const Time& time2);
/**
* Compare two times. Return true iff time1 is equal to
* time2
*
*/
bool equalTo(const Time& time2);
// From here on is hidden
int secondsSinceMidnight;
};
#endif // TIMES_H
and
#include "time.h"
#include <iomanip>
using namespace std;
/**
* Times in this program are represented by three integers: H, M, & S, representing
* the hours, minutes, and seconds, respecitvely.
*/
// Create time objects
Time::Time() // midnight
{
secondsSinceMidnight = 0;
}
Time::Time (int h, int m, int s)
{
secondsSinceMidnight = s + 60 * m + 3600*h;
}
// Access to attributes
int Time::getHours() const
{
return secondsSinceMidnight / 3600;
}
int Time::getMinutes() const
{
return (secondsSinceMidnight % 3600) / 60;
}
int Time::getSeconds() const
{
return secondsSinceMidnight % 60;
}
// Calculations with time
void Time::add (Time delta)
{
secondsSinceMidnight += delta.secondsSinceMidnight;
}
Time Time::difference (Time fromTime)
{
Time diff;
diff.secondsSinceMidnight =
secondsSinceMidnight - fromTime.secondsSinceMidnight;
}
/**
* Read a time (in the format HH:MM:SS) after skipping any
* prior whitepace.
*/
void Time::read (std::istream& in)
{
char c;
int hours, minutes, seconds;
in >> hours >> c >> minutes >> c >> seconds;
Time t (hours, minutes, seconds);
secondsSinceMidnight = t.secondsSinceMidnight;
}
/**
* Print a time in the format HH:MM:SS (two digits each)
*/
void Time::print (std::ostream& out) const
{
out << setfill('0') << setw(2) << getHours() << ':'
<< setfill('0') << setw(2) << getMinutes() << ':'
<< setfill('0') << setw(2) << getSeconds();
}
/**
* Compare two times. Return true iff time1 is earlier than or equal to
* time2
*/
bool Time::noLaterThan(const Time& time2)
{
return secondsSinceMidnight <= time2.secondsSinceMidnight;
}
/**
* Compare two times. Return true iff time1 is equal to
* time2
*
*/
bool Time::equalTo(const Time& time2)
{
return secondsSinceMidnight == time2.secondsSinceMidnight;
}
#ifndef BIDCOLLECTION_H
#define BIDCOLLECTION_H
#include "bids.h"
#include <iostream>
class BidCollection {
int MaxSize;
int size;
Bid* elements; // array of bids
public:
/**
* Create a collection capable of holding the indicated number of bids
*/
BidCollection (int MaxBids = 1000);
~BidCollection ();
// Access to attributes
int getMaxSize() const {return MaxSize;}
int getSize() const {return size;}
// Access to individual elements
const Bid& get(int index) const {return elements[index];}
// Collection operations
void addInTimeOrder (const Bid& value);
// Adds this bid into a position such that
// all bids are ordered by the time the bid was placed
//Pre: getSize() < getMaxSize()
void remove (int index);
// Remove the bid at the indicated position
//Pre: 0 <= index < getSize()
/**
* Read all bids from the indicated file
*/
void readBids (std::string fileName);
// Print the collection
void print (std::ostream& out) const;
};
#endif
and
#include "bidcollection.h"
#include "arrayUtils.h"
#include <fstream>
using namespace std;
/**
* Create a collection capable of holding the indicated number of bids
*/
BidCollection::BidCollection (int MaxBids)
: MaxSize(MaxBids), size(0)
{
elements = new Bid [MaxSize];
}
BidCollection::~BidCollection ()
{
delete [] elements;
}
// Collection operations
void BidCollection::addInTimeOrder (const Bid& value)
// Adds this bid into a position such that
// all bids are ordered by the time the bid was placed
//Pre: getSize() < getMaxSize()
{
// Make room for the insertion
int toBeMoved = size - 1;
while (toBeMoved >= 0 &&
value.getTimePlacedAt().noLaterThan(elements[toBeMoved].getTimePlacedAt())) {
elements[toBeMoved+1] = elements[toBeMoved];
--toBeMoved;
}
// Insert the new value
elements[toBeMoved+1] = value;
++size;
}
void BidCollection::remove (int index)
// Remove the bid at the indicated position
//Pre: 0 <= index < getSize()
{
removeElement (elements, size, index);
}
/**
* Read all bids from the indicated file
*/
void BidCollection::readBids (std::string fileName)
{
size = 0;
ifstream in (fileName.c_str());
int nBids;
in >> nBids;
for (int i = 0; i < nBids; ++i)
{
char c;
string bidderName;
double amount;
in >> bidderName >> amount;
Time bidPlacedAt;
bidPlacedAt.read (in);
string word, line;
in >> word; // First word of itemName
getline (in, line); // rest of item name
string itemName = word + line;
addInTimeOrder (Bid (bidderName, amount, itemName, bidPlacedAt));
}
}
// Print the collection
void BidCollection::print (std::ostream& out) const
{
out << size << "/" << MaxSize << "{";
for (int i = 0; i < size; ++i)
{
out << " ";
elements[i].print (out);
out << "\n";
}
out << "}";
}
#ifndef BIDDERCOLLECTION_H
#define BIDDERCOLLECTION_H
#include "bidders.h"
#include <iostream>
//
// Bidders Registered for auction
//
class BidderCollection {
int MaxSize;
int size;
Bidder* elements; // array of items
public:
/**
* Create a collection capable of holding the indicated number of items
*/
BidderCollection (int MaxBidders = 1000);
~BidderCollection ();
// Access to attributes
int getMaxSize() const {return MaxSize;}
int getSize() const {return size;}
// Access to individual elements
Bidder& get(int index) {return elements[index];}
// Collection operations
int add (const Bidder& value);
// Adds this bidder to the collection at an unspecified position.
// Returns the position where added.
//Pre: getSize() < getMaxSize()
void remove (int index);
// Remove the item at the indicated position
//Pre: 0 <= index < getSize()
int findBidder (std::string name) const;
// Returns the position where a bidde mathcing the given
// name can be found, or getSize() if no bidder with that name exists.
/**
* Read all items from the indicated file
*/
void readBidders (std::string fileName);
// Print the collection
void print (std::ostream& out) const;
};
#endif
and
#include <iostream>
#include "arrayUtils.h"
#include <fstream>
#include "biddercollection.h"
using namespace std;
/**
* Create a collection capable of holding the indicated number of items
*/
BidderCollection::BidderCollection (int MaxBidders)
: MaxSize(MaxBidders), size(0)
{
elements = new Bidder [MaxSize];
}
BidderCollection::~BidderCollection ()
{
delete [] elements;
}
// Collection operations
int BidderCollection::add (const Bidder& value)
// Adds this bidder
//Pre: getSize() < getMaxSize()
{
addToEnd (elements, size, value);
return size - 1;
}
void BidderCollection::remove (int index)
// Remove the bidder at the indicated position
//Pre: 0 <= index < getSize()
{
removeElement (elements, size, index);
}
/**
* Read all bidders from the indicated file
*/
void BidderCollection::readBidders (std::string fileName)
{
size = 0;
ifstream in (fileName.c_str());
int nBidders;
in >> nBidders;
for (int i = 0; i < nBidders && i < MaxSize; ++i)
{
string nme;
double bal;
in >> nme >> bal;
Bidder bidder (nme, bal);;
add (bidder);
}
}
/**
* Find the index of the bidder with the given name. If no such bidder exists,
* return nBidders.
*/
int BidderCollection::findBidder (std::string name) const
{
int found = size;
for (int i = 0; i < size && found == size; ++i)
{
if (name == elements[i].getName())
found = i;
}
return found;
}
// Print the collection
void BidderCollection::print (std::ostream& out) const
{
out << size << "/" << MaxSize << "{";
for (int i = 0; i < size; ++i)
{
out << " ";
elements[i].print (out);
out << "\n";
}
out << "}";
}
#ifndef ITEMCOLLECTION_H
#define ITEMCOLLECTION_H
#include "items.h"
#include <iostream>
class ItemCollection {
int MaxSize;
int size;
Item* elements; // array of items
public:
/**
* Create a collection capable of holding the indicated number of items
*/
ItemCollection (int MaxItems = 1000);
~ItemCollection ();
// Access to attributes
int getMaxSize() const {return MaxSize;}
int getSize() const {return size;}
// Access to individual elements
const Item& get(int index) const {return elements[index];}
// Collection operations
void addInTimeOrder (const Item& value);
// Adds this item into a position such that
// all items are ordered by the time at which the auction for the
// item ends.
//Pre: getSize() < getMaxSize()
void remove (int index);
// Remove the item at the indicated position
//Pre: 0 <= index < getSize()
/**
* Read all items from the indicated file
*/
void readItems (std::string fileName);
// Print the collection
void print (std::ostream& out) const;
};
#endif
and
#include <iostream>
#include "arrayUtils.h"
#include <fstream>
#include "itemcollection.h"
//
// Items up for auction
//
using namespace std;
/**
* Create a collection capable of holding the indicated number of items
*/
ItemCollection::ItemCollection (int MaxItems)
: MaxSize(MaxItems), size(0)
{
elements = new Item [MaxSize];
}
ItemCollection::~ItemCollection ()
{
delete [] elements;
}
// Collection operations
void ItemCollection::addInTimeOrder (const Item& value)
// Adds this item into a position such that
// all items are ordered by the time the item' auction ends
//Pre: getSize() < getMaxSize()
{
// Make room for the insertion
int toBeMoved = size - 1;
while (toBeMoved >= 0 &&
value.getAuctionEndTime().noLaterThan(elements[toBeMoved].getAuctionEndTime())) {
elements[toBeMoved+1] = elements[toBeMoved];
--toBeMoved;
}
// Insert the new value
elements[toBeMoved+1] = value;
++size;
}
void ItemCollection::remove (int index)
// Remove the item at the indicated position
//Pre: 0 <= index < getSize()
{
removeElement (elements, size, index);
}
/**
* Read all items from the indicated file
*/
void ItemCollection::readItems (std::string fileName)
{
size = 0;
ifstream in (fileName.c_str());
int nItems;
in >> nItems;
for (int i = 0; i < nItems; ++i)
{
Item item;
item.read (in);
addInTimeOrder (item);
}
}
// Print the collection
void ItemCollection::print (std::ostream& out) const
{
out << size << "/" << MaxSize << "{";
for (int i = 0; i < size; ++i)
{
out << " ";
elements[i].print (out);
out << "\n";
}
out << "}";
}
Looking at All Bids
Since the determination of the best bid is not obviously wrong, we might wonder if all the non-best bids are being handled correctly. In particular, we recall that there were more than two bids for the two items.
void resolveAuction (const Item& item,
BidderCollection& bidders,
BidCollection& bids)
{
cerr << "resolving item ";
item.print(cerr);
cerr << endl;
double highestBidSoFar = 0.0;
string winningBidderSoFar;
bool reservePriceMet = false;
for (int bidNum = 0; bidNum < bids.getSize(); ++bidNum)
{
Bid bid = bids.get(bidNum);
cerr << "Looking at bid ";
bid.print (cerr);
cerr << endl;
if (bid.getTimePlacedAt().noLaterThan(item.getAuctionEndTime()))
{
if (bid.getItem() == item.getName()
&& bid.getAmount() > highestBidSoFar
)
{
int bidderNum = bidders.findBidder(bid.getBidder());
Bidder bidder = bidders.get(bidderNum);
cerr << "Bidder is ";
bidder.print (cerr);
cerr << endl;
// Can this bidder afford it?
if (bid.getAmount() <= bidder.getBalance())
{
highestBidSoFar = bid.getAmount();
winningBidderSoFar = bid.getBidder();
cerr << "Best bid so far is ";
bid.print (cerr);
cerr << " from ";
bidder.print (cerr);
cerr << endl;
}
}
if (bid.getAmount() > item.getReservedPrice())
reservePriceMet = true;
}
}
// If highestBidSoFar is non-zero, we have a winner
if (reservePriceMet && highestBidSoFar > 0.0)
{
int bidderNum = bidders.findBidder(winningBidderSoFar);
cout << item.getName()
<< " won by " << winningBidderSoFar
<< " for " << highestBidSoFar << endl;
Bidder& bidder = bidders.get(bidderNum);
bidder.setBalance (bidder.getBalance() - highestBidSoFar);
}
else
{
cout << item.getName()
<< " reserve not met"
<< endl;
}
}
gives output
resolving item [Hummel Figurine 25 15:30:11]
Looking at bid [jjones 26 Hummel Figurine 00:18:03]
Bidder is [jjones 75]
Best bid so far is [jjones 26 Hummel Figurine 00:18:03] from [jjones 75]
Looking at bid [jjones 60 Chinese Vase 04:03:01]
Looking at bid [ssmith 27 Chinese Vase 04:03:05]
Looking at bid [jjones 25 Chinese Vase 05:00:00]
Hummel Figurine won by jjones for 26
resolving item [Chinese Vase 50 20:06:27]
Looking at bid [jjones 26 Hummel Figurine 00:18:03]
Looking at bid [jjones 60 Chinese Vase 04:03:01]
Bidder is [jjones 49]
Looking at bid [ssmith 27 Chinese Vase 04:03:05]
Bidder is [ssmith 55]
Best bid so far is [ssmith 27 Chinese Vase 04:03:05] from [ssmith 55]
Looking at bid [jjones 25 Chinese Vase 05:00:00]
Chinese Vase won by ssmith for 27
It’s still not clear why the low bid by smith is being accepted, but we can see that all bids are in fact being processed in some fashion.
Since we are not getting enough info about the reserve, let’s also monitor when the reservePriceMet flag changes.
When Does reservePriceMet change?
void resolveAuction (const Item& item,
BidderCollection& bidders,
BidCollection& bids)
{
cerr << "resolving item ";
item.print(cerr);
cerr << endl;
double highestBidSoFar = 0.0;
string winningBidderSoFar;
bool reservePriceMet = false;
for (int bidNum = 0; bidNum < bids.getSize(); ++bidNum)
{
Bid bid = bids.get(bidNum);
cerr << "Looking at bid ";
bid.print (cerr);
cerr << endl;
if (bid.getTimePlacedAt().noLaterThan(item.getAuctionEndTime()))
{
if (bid.getItem() == item.getName()
&& bid.getAmount() > highestBidSoFar
)
{
int bidderNum = bidders.findBidder(bid.getBidder());
Bidder bidder = bidders.get(bidderNum);
cerr << "Bidder is ";
bidder.print (cerr);
cerr << endl;
// Can this bidder afford it?
if (bid.getAmount() <= bidder.getBalance())
{
highestBidSoFar = bid.getAmount();
winningBidderSoFar = bid.getBidder();
cerr << "Best bid so far is ";
bid.print (cerr);
cerr << " from ";
bidder.print (cerr);
cerr << endl;
}
}
if (bid.getAmount() > item.getReservedPrice())
{
reservePriceMet = true;
cerr << "reserve price met by bid ";
bid.print (cerr);
cerr << endl;
}
}
}
// If highestBidSoFar is non-zero, we have a winner
if (reservePriceMet && highestBidSoFar > 0.0)
{
int bidderNum = bidders.findBidder(winningBidderSoFar);
cout << item.getName()
<< " won by " << winningBidderSoFar
<< " for " << highestBidSoFar << endl;
Bidder& bidder = bidders.get(bidderNum);
bidder.setBalance (bidder.getBalance() - highestBidSoFar);
}
else
{
cout << item.getName()
<< " reserve not met"
<< endl;
}
}
which has output
resolving item [Hummel Figurine 25 15:30:11]
Looking at bid [jjones 26 Hummel Figurine 00:18:03]
Bidder is [jjones 75]
Best bid so far is [jjones 26 Hummel Figurine 00:18:03] from [jjones 75]
reserve price met by bid [jjones 26 Hummel Figurine 00:18:03]
Looking at bid [jjones 60 Chinese Vase 04:03:01]
reserve price met by bid [jjones 60 Chinese Vase 04:03:01]
Looking at bid [ssmith 27 Chinese Vase 04:03:05]
reserve price met by bid [ssmith 27 Chinese Vase 04:03:05]
Looking at bid [jjones 25 Chinese Vase 05:00:00]
Hummel Figurine won by jjones for 26
resolving item [Chinese Vase 50 20:06:27]
Looking at bid [jjones 26 Hummel Figurine 00:18:03]
Looking at bid [jjones 60 Chinese Vase 04:03:01]
Bidder is [jjones 49]
reserve price met by bid [jjones 60 Chinese Vase 04:03:01]
Looking at bid [ssmith 27 Chinese Vase 04:03:05]
Bidder is [ssmith 55]
Best bid so far is [ssmith 27 Chinese Vase 04:03:05] from [ssmith 55]
Looking at bid [jjones 25 Chinese Vase 05:00:00]
Chinese Vase won by ssmith for 27
Diagnosis
Looking at that output:
resolving item [Hummel Figurine 25 15:30:11]
Looking at bid [jjones 26 Hummel Figurine 00:18:03]
Bidder is [jjones 75]
Best bid so far is [jjones 26 Hummel Figurine 00:18:03] from [jjones 75]
reserve price met by bid [jjones 26 Hummel Figurine 00:18:03]
Looking at bid [jjones 60 Chinese Vase 04:03:01]
reserve price met by bid [jjones 60 Chinese Vase 04:03:01]
Looking at bid [ssmith 27 Chinese Vase 04:03:05]
reserve price met by bid [ssmith 27 Chinese Vase 04:03:05] ➊
Looking at bid [jjones 25 Chinese Vase 05:00:00]
Hummel Figurine won by jjones for 26
resolving item [Chinese Vase 50 20:06:27]
Looking at bid [jjones 26 Hummel Figurine 00:18:03]
Looking at bid [jjones 60 Chinese Vase 04:03:01]
Bidder is [jjones 49] ➌
reserve price met by bid [jjones 60 Chinese Vase 04:03:01] ➋
Looking at bid [ssmith 27 Chinese Vase 04:03:05]
Bidder is [ssmith 55]
Best bid so far is [ssmith 27 Chinese Vase 04:03:05] from [ssmith 55]
Looking at bid [jjones 25 Chinese Vase 05:00:00]
Chinese Vase won by ssmith for 27
Now we can see, not just one, but two problems.
➊ The Hummel reserve price is being met by a bid on the Chinese vase
➋ The Chinese vase reserve price is being met by a bid from someone who does not have enough money (\co3) to pay that bid.
So the condition
if (bid.getAmount() > item.getReservedPrice())
{
reservePriceMet = true;
is far too lax.
Diagnosis Precedes the Cure
At this point, we have diagnosed the problem.
That’s not the same thing as actually fixing it, but it’s close.
In fact, we mainly need to move that test inside the if statements above it.
#include <iostream>
#include <string>
using namespace std;
#include "items.h"
#include "itemcollection.h"
#include "bidders.h"
#include "biddercollection.h"
#include "bids.h"
#include "bidcollection.h"
#include "time.h"
const int MaxBidders = 100;
const int MaxBids = 5000;
const int MaxItems = 100;
/**
* Determine the winner of the auction for item #i.
* Announce the winner and remove money from winner's account.
*/
void resolveAuction (const Item& item,
BidderCollection& bidders,
BidCollection& bids);
int main (int argc, char** argv)
{
if (argc != 4)
{
cerr << "Usage: " << argv[0] << " itemsFile biddersFile bidsFile" << endl;
return -1;
}
ItemCollection items (MaxItems);
items.readItems (argv[1]);
BidderCollection bidders (MaxBidders);
bidders.readBidders (argv[2]);
BidCollection bids (MaxBids);
bids.readBids (argv[3]);
for (int i = 0; i < items.getSize(); ++i)
{
resolveAuction(items.get(i), bidders, bids);
}
return 0;
}
/**
* Determine the winner of the auction for an item.
* Announce the winner and remove money from winner's account.
*/
void resolveAuction (const Item& item,
BidderCollection& bidders,
BidCollection& bids)
{
#ifdef DEBUG
cerr << "resolving item ";
item.print(cerr);
cerr << endl;
#endif
double highestBidSoFar = 0.0;
string winningBidderSoFar;
bool reservePriceMet = false;
for (int bidNum = 0; bidNum < bids.getSize(); ++bidNum)
{
Bid bid = bids.get(bidNum);
#ifdef DEBUG
cerr << "Looking at bid ";
bid.print (cerr);
cerr << endl;
#endif
if (bid.getTimePlacedAt().noLaterThan(item.getAuctionEndTime()))
{
if (bid.getItem() == item.getName()
&& bid.getAmount() > highestBidSoFar
)
{
int bidderNum = bidders.findBidder(bid.getBidder());
Bidder bidder = bidders.get(bidderNum);
#ifdef DEBUG
cerr << "Bidder is ";
bidder.print (cerr);
cerr << endl;
#endif
// Can this bidder afford it?
if (bid.getAmount() <= bidder.getBalance())
{
highestBidSoFar = bid.getAmount();
winningBidderSoFar = bid.getBidder();
#ifdef DEBUG
cerr << "Best bid so far is ";
bid.print (cerr);
cerr << " from ";
bidder.print (cerr);
cerr << endl;
#endif
if (bid.getAmount() > item.getReservedPrice())
{
reservePriceMet = true;
#ifdef DEBUG
cerr << "reserve price met by bid ";
bid.print (cerr);
cerr << endl;
#endif
}
}
}
}
}
// If highestBidSoFar is non-zero, we have a winner
if (reservePriceMet && highestBidSoFar > 0.0)
{
int bidderNum = bidders.findBidder(winningBidderSoFar);
cout << item.getName()
<< " won by " << winningBidderSoFar
<< " for " << highestBidSoFar << endl;
Bidder& bidder = bidders.get(bidderNum);
bidder.setBalance (bidder.getBalance() - highestBidSoFar);
}
else
{
cout << item.getName()
<< " reserve not met"
<< endl;
}
}
Simplify the failing test cases
Add debugging output to cerr
Plan for debugging by adding output functions to ADTs
Work backwards from the point of failure
Deactivate debugging output; don’t throw it away