Bug 444600 - (cookiemonster2) Cookies go missing after a few days. r=sdwilsh, sr=mconnor

This commit is contained in:
Dan Witte 2009-03-15 21:38:56 -07:00
Родитель 4f10163f72
Коммит b3267037da
5 изменённых файлов: 201 добавлений и 117 удалений

Просмотреть файл

@ -71,6 +71,7 @@ _TEST_FILES = \
test_same_base_domain_5.html \
test_same_base_domain_6.html \
file_loopback_inner.html \
test_eviction.html \
$(NULL)
# XXX see bug 454857

Просмотреть файл

@ -52,18 +52,23 @@ interface nsIChannel;
* list is changed, or a cookie is rejected:
*
* topic : "cookie-changed"
* broadcast whenever the cookie list changes in some way. there
* are four possible data strings for this notification; one
* notification will be broadcast for each change, and will involve
* a single cookie.
* subject: an nsICookie2 interface pointer representing the cookie object
* that changed.
* broadcast whenever the cookie list changes in some way. see
* explanation of data strings below.
* subject: see below.
* data : "deleted"
* a cookie was deleted. the subject is the deleted cookie.
* a cookie was deleted. the subject is an nsICookie2 representing
* the deleted cookie.
* "added"
* a cookie was added. the subject is the added cookie.
* a cookie was added. the subject is an nsICookie2 representing
* the added cookie.
* "changed"
* a cookie was changed. the subject is the new cookie.
* a cookie was changed. the subject is an nsICookie2 representing
* the new cookie. (note that host, path, and name are invariant
* for a given cookie; other parameters may change.)
* "batch-deleted"
* a batch of cookies was deleted (for instance, as part of a purging
* operation). the subject is an nsIArray of nsICookie2's representing
* the deleted cookies.
* "cleared"
* the entire cookie list was cleared. the subject is null.
* "reload"

Просмотреть файл

@ -55,7 +55,9 @@
#include "nsILineInputStream.h"
#include "nsIEffectiveTLDService.h"
#include "nsTArray.h"
#include "nsCOMArray.h"
#include "nsIMutableArray.h"
#include "nsArrayEnumerator.h"
#include "nsAutoPtr.h"
#include "nsReadableUtils.h"
@ -86,6 +88,7 @@ static const char kCookieFileName[] = "cookies.sqlite";
#define COOKIES_SCHEMA_VERSION 2
static const PRInt64 kCookieStaleThreshold = 60 * PR_USEC_PER_SEC; // 1 minute in microseconds
static const PRInt64 kCookiePurgeAge = 30 * 24 * 60 * 60 * PR_USEC_PER_SEC; // 30 days in microseconds
static const char kOldCookieFileName[] = "cookies.txt";
@ -117,6 +120,7 @@ static const PRUint32 BEHAVIOR_REJECT = 2;
static const char kPrefCookiesPermissions[] = "network.cookie.cookieBehavior";
static const char kPrefMaxNumberOfCookies[] = "network.cookie.maxNumber";
static const char kPrefMaxCookiesPerHost[] = "network.cookie.maxPerHost";
static const char kPrefCookiePurgeAge[] = "network.cookie.purgeAge";
// struct for temporarily storing cookie attributes during header parsing
struct nsCookieAttributes
@ -206,7 +210,13 @@ static PRLogModuleInfo *sCookieLog = PR_NewLogModule("cookie");
#define COOKIE_LOGFAILURE(a, b, c, d) LogFailure(a, b, c, d)
#define COOKIE_LOGSUCCESS(a, b, c, d, e) LogSuccess(a, b, c, d, e)
#define COOKIE_LOGEVICTED(a) LogEvicted(a)
#define COOKIE_LOGEVICTED(a) \
PR_BEGIN_MACRO \
if (PR_LOG_TEST(sCookieLog, PR_LOG_DEBUG)) \
LogEvicted(a); \
PR_END_MACRO
#define COOKIE_LOGSTRING(lvl, fmt) \
PR_BEGIN_MACRO \
PR_LOG(sCookieLog, lvl, fmt); \
@ -299,11 +309,6 @@ LogSuccess(PRBool aSetCookie, nsIURI *aHostURI, const char *aCookieString, nsCoo
static void
LogEvicted(nsCookie *aCookie)
{
// if logging isn't enabled, return now to save cycles
if (!PR_LOG_TEST(sCookieLog, PR_LOG_DEBUG)) {
return;
}
PR_LOG(sCookieLog, PR_LOG_DEBUG,("===== COOKIE EVICTED =====\n"));
LogCookie(aCookie);
@ -377,10 +382,12 @@ NS_IMPL_ISUPPORTS5(nsCookieService,
nsCookieService::nsCookieService()
: mHostTable(&mDefaultHostTable)
, mCookieOldestTime(LL_MAXINT)
, mCookieCount(0)
, mCookiesPermissions(BEHAVIOR_ACCEPT)
, mMaxNumberOfCookies(kMaxNumberOfCookies)
, mMaxCookiesPerHost(kMaxCookiesPerHost)
, mCookiePurgeAge(kCookiePurgeAge)
{
}
@ -401,6 +408,7 @@ nsCookieService::Init()
prefBranch->AddObserver(kPrefCookiesPermissions, this, PR_TRUE);
prefBranch->AddObserver(kPrefMaxNumberOfCookies, this, PR_TRUE);
prefBranch->AddObserver(kPrefMaxCookiesPerHost, this, PR_TRUE);
prefBranch->AddObserver(kPrefCookiePurgeAge, this, PR_TRUE);
PrefChanged(prefBranch);
}
@ -774,18 +782,19 @@ nsCookieService::NotifyRejected(nsIURI *aHostURI)
mObserverService->NotifyObservers(aHostURI, "cookie-rejected", nsnull);
}
// notify observers that the cookie list changed. there are four possible
// notify observers that the cookie list changed. there are five possible
// values for aData:
// "deleted" means a cookie was deleted. aCookie is the deleted cookie.
// "added" means a cookie was added. aCookie is the added cookie.
// "changed" means a cookie was altered. aCookie is the new cookie.
// "cleared" means the entire cookie list was cleared. aCookie is null.
// "deleted" means a cookie was deleted. aSubject is the deleted cookie.
// "added" means a cookie was added. aSubject is the added cookie.
// "changed" means a cookie was altered. aSubject is the new cookie.
// "cleared" means the entire cookie list was cleared. aSubject is null.
// "batch-deleted" means multiple cookies were deleted. aSubject is the list of cookies.
void
nsCookieService::NotifyChanged(nsICookie2 *aCookie,
nsCookieService::NotifyChanged(nsISupports *aSubject,
const PRUnichar *aData)
{
if (mObserverService)
mObserverService->NotifyObservers(aCookie, "cookie-changed", aData);
mObserverService->NotifyObservers(aSubject, "cookie-changed", aData);
}
/******************************************************************************
@ -805,6 +814,9 @@ nsCookieService::PrefChanged(nsIPrefBranch *aPrefBranch)
if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefMaxCookiesPerHost, &val)))
mMaxCookiesPerHost = (PRUint16) LIMIT(val, 0, 0xFFFF, 0xFFFF);
if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefCookiePurgeAge, &val)))
mCookiePurgeAge = val * PR_USEC_PER_SEC; // convert seconds to microseconds
}
/******************************************************************************
@ -895,7 +907,7 @@ nsCookieService::Add(const nsACString &aDomain,
return NS_ERROR_OUT_OF_MEMORY;
}
AddInternal(cookie, currentTimeInUsec / PR_USEC_PER_SEC, nsnull, nsnull, PR_TRUE);
AddInternal(cookie, currentTimeInUsec, nsnull, nsnull, PR_TRUE);
return NS_OK;
}
@ -1135,7 +1147,7 @@ nsCookieService::ImportCookies(nsIFile *aCookieFile)
if (originalCookieCount == 0)
AddCookieToList(newCookie);
else
AddInternal(newCookie, currentTime, nsnull, nsnull, PR_TRUE);
AddInternal(newCookie, currentTimeInUsec, nsnull, nsnull, PR_TRUE);
}
COOKIE_LOGSTRING(PR_LOG_DEBUG, ("ImportCookies(): %ld cookies imported", mCookieCount));
@ -1430,7 +1442,7 @@ nsCookieService::SetCookieInternal(nsIURI *aHostURI,
// add the cookie to the list. AddInternal() takes care of logging.
// we get the current time again here, since it may have changed during prompting
AddInternal(cookie, PR_Now() / PR_USEC_PER_SEC, aHostURI, savedCookieHeader.get(), aFromHttp);
AddInternal(cookie, PR_Now(), aHostURI, savedCookieHeader.get(), aFromHttp);
return newCookie;
}
@ -1441,11 +1453,13 @@ nsCookieService::SetCookieInternal(nsIURI *aHostURI,
// reached). also performs list maintenance by removing expired cookies.
void
nsCookieService::AddInternal(nsCookie *aCookie,
PRInt64 aCurrentTime,
PRInt64 aCurrentTimeInUsec,
nsIURI *aHostURI,
const char *aCookieHeader,
PRBool aFromHttp)
{
PRInt64 currentTime = aCurrentTimeInUsec / PR_USEC_PER_SEC;
// if the new cookie is httponly, make sure we're not coming from script
if (!aFromHttp && aCookie->IsHttpOnly()) {
COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader, "cookie is httponly; coming from script");
@ -1459,7 +1473,7 @@ nsCookieService::AddInternal(nsCookie *aCookie,
nsListIter matchIter;
PRBool foundCookie = FindCookie(aCookie->Host(), aCookie->Name(), aCookie->Path(),
matchIter, aCurrentTime);
matchIter, currentTime);
nsRefPtr<nsCookie> oldCookie;
if (foundCookie) {
@ -1474,7 +1488,7 @@ nsCookieService::AddInternal(nsCookie *aCookie,
RemoveCookieFromList(matchIter);
// check if the cookie has expired
if (aCookie->Expiry() <= aCurrentTime) {
if (aCookie->Expiry() <= currentTime) {
COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader, "previously stored cookie was deleted");
NotifyChanged(oldCookie, NS_LITERAL_STRING("deleted").get());
return;
@ -1486,36 +1500,30 @@ nsCookieService::AddInternal(nsCookie *aCookie,
} else {
// check if cookie has already expired
if (aCookie->Expiry() <= aCurrentTime) {
if (aCookie->Expiry() <= currentTime) {
COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader, "cookie has already expired");
return;
}
// check if we have to delete an old cookie.
nsEnumerationData data(aCurrentTime, LL_MAXINT);
nsEnumerationData data(currentTime, LL_MAXINT);
if (CountCookiesFromHostInternal(aCookie->RawHost(), data) >= mMaxCookiesPerHost) {
// remove the oldest cookie from host
oldCookie = data.iter.current;
COOKIE_LOGEVICTED(oldCookie);
RemoveCookieFromList(data.iter);
} else if (mCookieCount >= mMaxNumberOfCookies) {
// try to make room, by removing expired cookies
RemoveExpiredCookies(aCurrentTime);
// check if we still have to get rid of something
if (mCookieCount >= mMaxNumberOfCookies) {
// find the position of the oldest cookie, and remove it
data.oldestTime = LL_MAXINT;
FindOldestCookie(data);
oldCookie = data.iter.current;
RemoveCookieFromList(data.iter);
}
}
// if we deleted an old cookie, notify consumers
if (oldCookie) {
COOKIE_LOGEVICTED(oldCookie);
NotifyChanged(oldCookie, NS_LITERAL_STRING("deleted").get());
} else if (mCookieCount >= 1.1 * mMaxNumberOfCookies &&
aCurrentTimeInUsec - mCookieOldestTime >= 1.1 * mCookiePurgeAge) {
// we're over both size and age limits by 10%; time to purge the table!
// do this by:
// 1) removing expired cookies;
// 2) evicting the balance of old cookies, until we reach the size limit.
// note that the mCookieOldestTime indicator can be pessimistic - if it's
// older than the actual oldest cookie, we'll just purge more eagerly.
PurgeCookies(aCurrentTimeInUsec);
}
}
@ -2069,32 +2077,151 @@ nsCookieService::RemoveAllFromMemory()
// which releases all their respective children.
mHostTable->Clear();
mCookieCount = 0;
mCookieOldestTime = LL_MAXINT;
}
PLDHashOperator
removeExpiredCallback(nsCookieEntry *aEntry,
void *aArg)
// stores temporary data for enumerating over the hash entries,
// since enumeration is done using callback functions
struct nsPurgeData
{
const PRInt64 &currentTime = *static_cast<PRInt64*>(aArg);
nsPurgeData(PRInt64 aCurrentTime,
PRInt64 aPurgeTime,
nsTArray<nsListIter> &aPurgeList,
nsIMutableArray *aRemovedList)
: currentTime(aCurrentTime)
, purgeTime(aPurgeTime)
, oldestTime(LL_MAXINT)
, purgeList(aPurgeList)
, removedList(aRemovedList) {}
// the current time, in seconds
PRInt64 currentTime;
// lastAccessed time older than which cookies are eligible for purge
PRInt64 purgeTime;
// lastAccessed time of the oldest cookie found during purge, to update our indicator
PRInt64 oldestTime;
// list of cookies over the age limit, for purging
nsTArray<nsListIter> &purgeList;
// list of all cookies we've removed, for notification
nsIMutableArray *removedList;
};
// comparator class for lastaccessed times of cookies.
class CompareCookiesByAge {
public:
// returns true if (a == b); false otherwise.
PRBool Equals(const nsListIter &a, const nsListIter &b) const
{
return a.current->LastAccessed() == b.current->LastAccessed();
}
// returns true if (a < b); false otherwise.
PRBool LessThan(const nsListIter &a, const nsListIter &b) const
{
return a.current->LastAccessed() < b.current->LastAccessed();
}
};
PLDHashOperator
purgeCookiesCallback(nsCookieEntry *aEntry,
void *aArg)
{
nsPurgeData &data = *static_cast<nsPurgeData*>(aArg);
for (nsListIter iter(aEntry, nsnull, aEntry->Head()); iter.current; ) {
if (iter.current->Expiry() <= currentTime)
// check if the cookie has expired
if (iter.current->Expiry() <= data.currentTime) {
nsCookie *cookie = iter.current;
data.removedList->AppendElement(cookie, PR_FALSE);
COOKIE_LOGEVICTED(cookie);
// remove from list. this takes care of updating the iterator for us
nsCookieService::gCookieService->RemoveCookieFromList(iter);
else
} else {
// check if the cookie is over the age limit
if (iter.current->LastAccessed() <= data.purgeTime) {
data.purgeList.AppendElement(iter);
} else if (iter.current->LastAccessed() < data.oldestTime) {
// reset our indicator
data.oldestTime = iter.current->LastAccessed();
}
++iter;
}
}
return PL_DHASH_NEXT;
}
// removes any expired cookies from memory
// purges expired and old cookies in a batch operation.
void
nsCookieService::RemoveExpiredCookies(PRInt64 aCurrentTime)
nsCookieService::PurgeCookies(PRInt64 aCurrentTimeInUsec)
{
#ifdef PR_LOGGING
PRUint32 initialCookieCount = mCookieCount;
COOKIE_LOGSTRING(PR_LOG_DEBUG, ("PurgeCookies(): beginning purge with %ld cookies and %lld age",
mCookieCount, aCurrentTimeInUsec - mCookieOldestTime));
#endif
mHostTable->EnumerateEntries(removeExpiredCallback, &aCurrentTime);
COOKIE_LOGSTRING(PR_LOG_DEBUG, ("RemoveExpiredCookies(): %ld purged; %ld remain", initialCookieCount - mCookieCount, mCookieCount));
nsAutoTArray<nsListIter, kMaxNumberOfCookies> purgeList;
nsCOMPtr<nsIMutableArray> removedList = do_CreateInstance(NS_ARRAY_CONTRACTID);
if (!removedList)
return;
nsPurgeData data(aCurrentTimeInUsec / PR_USEC_PER_SEC, aCurrentTimeInUsec - mCookiePurgeAge, purgeList, removedList);
mHostTable->EnumerateEntries(purgeCookiesCallback, &data);
#ifdef PR_LOGGING
PRUint32 postExpiryCookieCount = mCookieCount;
#endif
// now we have a list of iterators for cookies over the age limit.
// sort them by age, and then we'll see how many to remove...
purgeList.Sort(CompareCookiesByAge());
// only remove old cookies until we reach the max cookie limit, no more.
PRUint32 excess = mCookieCount - mMaxNumberOfCookies;
//printf("count %u, maxnum %u, listlen %u, excess %d\n", mCookieCount, mMaxNumberOfCookies, list.Length(), excess);
if (purgeList.Length() > excess) {
// we're not purging everything in the list, so update our indicator
data.oldestTime = purgeList[excess].current->LastAccessed();
purgeList.SetLength(excess);
}
// traverse the list and remove cookies. the iterators we've stored
// in the list aren't stable under list mutation, so we need to do a
// fresh linked list traversal from the hash entryclass for each cookie.
for (PRUint32 i = 0; i < purgeList.Length(); ++i) {
for (nsListIter iter(purgeList[i].entry, nsnull, purgeList[i].entry->Head()); iter.current; ++iter) {
if (iter.current == purgeList[i].current) {
// remove from list. this takes care of updating the iterator for us
nsCookie *cookie = iter.current;
removedList->AppendElement(cookie, PR_FALSE);
COOKIE_LOGEVICTED(cookie);
RemoveCookieFromList(iter);
break;
}
}
}
// take all the cookies in the removed list, and notify about them in one batch
NotifyChanged(removedList, NS_LITERAL_STRING("batch-deleted").get());
// reset the oldest time indicator
mCookieOldestTime = data.oldestTime;
COOKIE_LOGSTRING(PR_LOG_DEBUG, ("PurgeCookies(): %ld expired; %ld purged; %ld remain; %lld oldest age",
initialCookieCount - postExpiryCookieCount,
mCookieCount - postExpiryCookieCount,
mCookieCount,
aCurrentTimeInUsec - mCookieOldestTime));
}
// find whether a given cookie has been previously set. this is provided by the
@ -2282,6 +2409,10 @@ nsCookieService::AddCookieToList(nsCookie *aCookie, PRBool aWriteToDB)
entry->Head() = aCookie;
++mCookieCount;
// keep track of the oldest cookie, for when it comes time to purge
if (aCookie->LastAccessed() < mCookieOldestTime)
mCookieOldestTime = aCookie->LastAccessed();
// if it's a non-session cookie and hasn't just been read from the db, write it out.
if (aWriteToDB && !aCookie->IsSession() && mStmtInsert) {
// use our cached sqlite "insert" statement
@ -2329,23 +2460,3 @@ nsCookieService::UpdateCookieInList(nsCookie *aCookie, PRInt64 aLastAccessed)
}
}
static PLDHashOperator
findOldestCallback(nsCookieEntry *aEntry,
void *aArg)
{
nsEnumerationData *data = static_cast<nsEnumerationData*>(aArg);
for (nsListIter iter(aEntry, nsnull, aEntry->Head()); iter.current; ++iter) {
// check if we've found the oldest cookie so far
if (data->oldestTime > iter.current->LastAccessed()) {
data->oldestTime = iter.current->LastAccessed();
data->iter = iter;
}
}
return PL_DHASH_NEXT;
}
void
nsCookieService::FindOldestCookie(nsEnumerationData &aData)
{
mHostTable->EnumerateEntries(findOldestCallback, &aData);
}

Просмотреть файл

@ -171,7 +171,7 @@ class nsCookieService : public nsICookieService
void GetCookieInternal(nsIURI *aHostURI, nsIChannel *aChannel, PRBool aHttpBound, char **aCookie);
nsresult SetCookieStringInternal(nsIURI *aHostURI, nsIPrompt *aPrompt, const char *aCookieHeader, const char *aServerTime, nsIChannel *aChannel, PRBool aFromHttp);
PRBool SetCookieInternal(nsIURI *aHostURI, nsIChannel *aChannel, nsDependentCString &aCookieHeader, PRInt64 aServerTime, PRBool aFromHttp);
void AddInternal(nsCookie *aCookie, PRInt64 aCurrentTime, nsIURI *aHostURI, const char *aCookieHeader, PRBool aFromHttp);
void AddInternal(nsCookie *aCookie, PRInt64 aCurrentTimeInUsec, nsIURI *aHostURI, const char *aCookieHeader, PRBool aFromHttp);
void RemoveCookieFromList(nsListIter &aIter);
PRBool AddCookieToList(nsCookie *aCookie, PRBool aWriteToDB = PR_TRUE);
void UpdateCookieInList(nsCookie *aCookie, PRInt64 aLastAccessed);
@ -183,12 +183,11 @@ class nsCookieService : public nsICookieService
static PRBool CheckPath(nsCookieAttributes &aCookie, nsIURI *aHostURI);
static PRBool GetExpiry(nsCookieAttributes &aCookie, PRInt64 aServerTime, PRInt64 aCurrentTime);
void RemoveAllFromMemory();
void RemoveExpiredCookies(PRInt64 aCurrentTime);
void PurgeCookies(PRInt64 aCurrentTimeInUsec);
PRBool FindCookie(const nsAFlatCString &aHost, const nsAFlatCString &aName, const nsAFlatCString &aPath, nsListIter &aIter, PRInt64 aCurrentTime);
void FindOldestCookie(nsEnumerationData &aData);
PRUint32 CountCookiesFromHostInternal(const nsACString &aHost, nsEnumerationData &aData);
void NotifyRejected(nsIURI *aHostURI);
void NotifyChanged(nsICookie2 *aCookie, const PRUnichar *aData);
void NotifyChanged(nsISupports *aSubject, const PRUnichar *aData);
protected:
// cached members
@ -204,19 +203,21 @@ class nsCookieService : public nsICookieService
nsTHashtable<nsCookieEntry> *mHostTable;
nsTHashtable<nsCookieEntry> mDefaultHostTable;
nsTHashtable<nsCookieEntry> mPrivateHostTable;
PRInt64 mCookieOldestTime;
PRUint32 mCookieCount;
// cached prefs
PRUint8 mCookiesPermissions; // BEHAVIOR_{ACCEPT, REJECTFOREIGN, REJECT}
PRUint16 mMaxNumberOfCookies;
PRUint16 mMaxCookiesPerHost;
PRUint64 mCookiePurgeAge;
// private static member, used to cache a ptr to nsCookieService,
// so we can make nsCookieService a singleton xpcom object.
static nsCookieService *gCookieService;
// this callback needs access to member functions
friend PLDHashOperator removeExpiredCallback(nsCookieEntry *aEntry, void *aArg);
friend PLDHashOperator purgeCookiesCallback(nsCookieEntry *aEntry, void *aArg);
};
#endif // nsCookieService_h__

Просмотреть файл

@ -761,41 +761,7 @@ main(PRInt32 argc, char *argv[])
GetACookie(cookieService, "http://creation.ordering.tests/", nsnull, getter_Copies(cookie));
rv[0] = CheckResult(cookie.get(), MUST_EQUAL, expected.get());
// test that cookies are evicted by order of lastAccessed time, if the limit on total cookies
// (3000) is reached
nsCAutoString host;
for (PRInt32 i = 0; i < 3010; ++i) {
host = NS_LITERAL_CSTRING("http://eviction.");
host.AppendInt(i);
host += NS_LITERAL_CSTRING(".tests/");
SetACookie(cookieService, host.get(), nsnull, "test=eviction", nsnull);
if (i == 9) {
// sleep a couple of seconds, to make sure the first 10 cookies are older than
// subsequent ones (timer resolution varies on different platforms).
PR_Sleep(2 * PR_TicksPerSecond());
}
}
rv[1] = NS_SUCCEEDED(cookieMgr->GetEnumerator(getter_AddRefs(enumerator)));
i = 0;
rv[2] = PR_FALSE; // init to failure in case we break from the while loop
while (NS_SUCCEEDED(enumerator->HasMoreElements(&more)) && more) {
nsCOMPtr<nsISupports> cookie;
if (NS_FAILED(enumerator->GetNext(getter_AddRefs(cookie)))) break;
++i;
// keep tabs on the third cookie, so we can check it later
nsCOMPtr<nsICookie2> cookie2(do_QueryInterface(cookie));
if (!cookie2) break;
nsCAutoString domain;
cookie2->GetRawHost(domain);
PRInt32 hostNumber;
PRInt32 numInts = PR_sscanf(domain.get(), "eviction.%ld.tests", &hostNumber);
if (numInts != 1 || hostNumber < 10) break;
}
rv[2] = i == 3000;
allTestsPassed = PrintResult(rv, 3) && allTestsPassed;
allTestsPassed = PrintResult(rv, 1) && allTestsPassed;
// XXX the following are placeholders: add these tests please!