michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- michael@0: * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "TestHarness.h" michael@0: #include "nsMemory.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsDocShellCID.h" michael@0: michael@0: #include "nsToolkitCompsCID.h" michael@0: #include "nsINavHistoryService.h" michael@0: #include "nsIObserverService.h" michael@0: #include "mozilla/IHistory.h" michael@0: #include "mozIStorageConnection.h" michael@0: #include "mozIStorageStatement.h" michael@0: #include "mozIStorageAsyncStatement.h" michael@0: #include "mozIStorageStatementCallback.h" michael@0: #include "mozIStoragePendingStatement.h" michael@0: #include "nsPIPlacesDatabase.h" michael@0: #include "nsIObserver.h" michael@0: #include "prinrval.h" michael@0: #include "mozilla/Attributes.h" michael@0: michael@0: #define WAITFORTOPIC_TIMEOUT_SECONDS 5 michael@0: michael@0: michael@0: static size_t gTotalTests = 0; michael@0: static size_t gPassedTests = 0; michael@0: michael@0: #define do_check_true(aCondition) \ michael@0: PR_BEGIN_MACRO \ michael@0: gTotalTests++; \ michael@0: if (aCondition) { \ michael@0: gPassedTests++; \ michael@0: } else { \ michael@0: fail("%s | Expected true, got false at line %d", __FILE__, __LINE__); \ michael@0: } \ michael@0: PR_END_MACRO michael@0: michael@0: #define do_check_false(aCondition) \ michael@0: PR_BEGIN_MACRO \ michael@0: gTotalTests++; \ michael@0: if (!aCondition) { \ michael@0: gPassedTests++; \ michael@0: } else { \ michael@0: fail("%s | Expected false, got true at line %d", __FILE__, __LINE__); \ michael@0: } \ michael@0: PR_END_MACRO michael@0: michael@0: #define do_check_success(aResult) \ michael@0: do_check_true(NS_SUCCEEDED(aResult)) michael@0: michael@0: #ifdef LINUX michael@0: // XXX Linux opt builds on tinderbox are orange due to linking with stdlib. michael@0: // This is sad and annoying, but it's a workaround that works. michael@0: #define do_check_eq(aExpected, aActual) \ michael@0: do_check_true(aExpected == aActual) michael@0: #else michael@0: #include michael@0: michael@0: #define do_check_eq(aActual, aExpected) \ michael@0: PR_BEGIN_MACRO \ michael@0: gTotalTests++; \ michael@0: if (aExpected == aActual) { \ michael@0: gPassedTests++; \ michael@0: } else { \ michael@0: std::ostringstream temp; \ michael@0: temp << __FILE__ << " | Expected '" << aExpected << "', got '"; \ michael@0: temp << aActual <<"' at line " << __LINE__; \ michael@0: fail(temp.str().c_str()); \ michael@0: } \ michael@0: PR_END_MACRO michael@0: #endif michael@0: michael@0: struct Test michael@0: { michael@0: void (*func)(void); michael@0: const char* const name; michael@0: }; michael@0: #define TEST(aName) \ michael@0: {aName, #aName} michael@0: michael@0: /** michael@0: * Runs the next text. michael@0: */ michael@0: void run_next_test(); michael@0: michael@0: /** michael@0: * To be used around asynchronous work. michael@0: */ michael@0: void do_test_pending(); michael@0: void do_test_finished(); michael@0: michael@0: /** michael@0: * Spins current thread until a topic is received. michael@0: */ michael@0: class WaitForTopicSpinner MOZ_FINAL : public nsIObserver michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: michael@0: WaitForTopicSpinner(const char* const aTopic) michael@0: : mTopicReceived(false) michael@0: , mStartTime(PR_IntervalNow()) michael@0: { michael@0: nsCOMPtr observerService = michael@0: do_GetService(NS_OBSERVERSERVICE_CONTRACTID); michael@0: do_check_true(observerService); michael@0: (void)observerService->AddObserver(this, aTopic, false); michael@0: } michael@0: michael@0: void Spin() { michael@0: while (!mTopicReceived) { michael@0: if ((PR_IntervalNow() - mStartTime) > (WAITFORTOPIC_TIMEOUT_SECONDS * PR_USEC_PER_SEC)) { michael@0: // Timed out waiting for the topic. michael@0: do_check_true(false); michael@0: break; michael@0: } michael@0: (void)NS_ProcessNextEvent(); michael@0: } michael@0: } michael@0: michael@0: NS_IMETHOD Observe(nsISupports* aSubject, michael@0: const char* aTopic, michael@0: const char16_t* aData) michael@0: { michael@0: mTopicReceived = true; michael@0: nsCOMPtr observerService = michael@0: do_GetService(NS_OBSERVERSERVICE_CONTRACTID); michael@0: do_check_true(observerService); michael@0: (void)observerService->RemoveObserver(this, aTopic); michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: bool mTopicReceived; michael@0: PRIntervalTime mStartTime; michael@0: }; michael@0: NS_IMPL_ISUPPORTS( michael@0: WaitForTopicSpinner, michael@0: nsIObserver michael@0: ) michael@0: michael@0: /** michael@0: * Spins current thread until an async statement is executed. michael@0: */ michael@0: class AsyncStatementSpinner MOZ_FINAL : public mozIStorageStatementCallback michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: NS_DECL_MOZISTORAGESTATEMENTCALLBACK michael@0: michael@0: AsyncStatementSpinner(); michael@0: void SpinUntilCompleted(); michael@0: uint16_t completionReason; michael@0: michael@0: protected: michael@0: volatile bool mCompleted; michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(AsyncStatementSpinner, michael@0: mozIStorageStatementCallback) michael@0: michael@0: AsyncStatementSpinner::AsyncStatementSpinner() michael@0: : completionReason(0) michael@0: , mCompleted(false) michael@0: { michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: AsyncStatementSpinner::HandleResult(mozIStorageResultSet *aResultSet) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: AsyncStatementSpinner::HandleError(mozIStorageError *aError) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: AsyncStatementSpinner::HandleCompletion(uint16_t aReason) michael@0: { michael@0: completionReason = aReason; michael@0: mCompleted = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: void AsyncStatementSpinner::SpinUntilCompleted() michael@0: { michael@0: nsCOMPtr thread(::do_GetCurrentThread()); michael@0: nsresult rv = NS_OK; michael@0: bool processed = true; michael@0: while (!mCompleted && NS_SUCCEEDED(rv)) { michael@0: rv = thread->ProcessNextEvent(true, &processed); michael@0: } michael@0: } michael@0: michael@0: struct PlaceRecord michael@0: { michael@0: int64_t id; michael@0: int32_t hidden; michael@0: int32_t typed; michael@0: int32_t visitCount; michael@0: nsCString guid; michael@0: }; michael@0: michael@0: struct VisitRecord michael@0: { michael@0: int64_t id; michael@0: int64_t lastVisitId; michael@0: int32_t transitionType; michael@0: }; michael@0: michael@0: already_AddRefed michael@0: do_get_IHistory() michael@0: { michael@0: nsCOMPtr history = do_GetService(NS_IHISTORY_CONTRACTID); michael@0: do_check_true(history); michael@0: return history.forget(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: do_get_NavHistory() michael@0: { michael@0: nsCOMPtr serv = michael@0: do_GetService(NS_NAVHISTORYSERVICE_CONTRACTID); michael@0: do_check_true(serv); michael@0: return serv.forget(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: do_get_db() michael@0: { michael@0: nsCOMPtr history = do_get_NavHistory(); michael@0: nsCOMPtr database = do_QueryInterface(history); michael@0: do_check_true(database); michael@0: michael@0: nsCOMPtr dbConn; michael@0: nsresult rv = database->GetDBConnection(getter_AddRefs(dbConn)); michael@0: do_check_success(rv); michael@0: return dbConn.forget(); michael@0: } michael@0: michael@0: /** michael@0: * Get the place record from the database. michael@0: * michael@0: * @param aURI The unique URI of the place we are looking up michael@0: * @param result Out parameter where the result is stored michael@0: */ michael@0: void michael@0: do_get_place(nsIURI* aURI, PlaceRecord& result) michael@0: { michael@0: nsCOMPtr dbConn = do_get_db(); michael@0: nsCOMPtr stmt; michael@0: michael@0: nsCString spec; michael@0: nsresult rv = aURI->GetSpec(spec); michael@0: do_check_success(rv); michael@0: michael@0: rv = dbConn->CreateStatement(NS_LITERAL_CSTRING( michael@0: "SELECT id, hidden, typed, visit_count, guid FROM moz_places " michael@0: "WHERE url=?1 " michael@0: ), getter_AddRefs(stmt)); michael@0: do_check_success(rv); michael@0: michael@0: rv = stmt->BindUTF8StringByIndex(0, spec); michael@0: do_check_success(rv); michael@0: michael@0: bool hasResults; michael@0: rv = stmt->ExecuteStep(&hasResults); michael@0: do_check_success(rv); michael@0: if (!hasResults) { michael@0: result.id = 0; michael@0: return; michael@0: } michael@0: michael@0: rv = stmt->GetInt64(0, &result.id); michael@0: do_check_success(rv); michael@0: rv = stmt->GetInt32(1, &result.hidden); michael@0: do_check_success(rv); michael@0: rv = stmt->GetInt32(2, &result.typed); michael@0: do_check_success(rv); michael@0: rv = stmt->GetInt32(3, &result.visitCount); michael@0: do_check_success(rv); michael@0: rv = stmt->GetUTF8String(4, result.guid); michael@0: do_check_success(rv); michael@0: } michael@0: michael@0: /** michael@0: * Gets the most recent visit to a place. michael@0: * michael@0: * @param placeID ID from the moz_places table michael@0: * @param result Out parameter where visit is stored michael@0: */ michael@0: void michael@0: do_get_lastVisit(int64_t placeId, VisitRecord& result) michael@0: { michael@0: nsCOMPtr dbConn = do_get_db(); michael@0: nsCOMPtr stmt; michael@0: michael@0: nsresult rv = dbConn->CreateStatement(NS_LITERAL_CSTRING( michael@0: "SELECT id, from_visit, visit_type FROM moz_historyvisits " michael@0: "WHERE place_id=?1 " michael@0: "LIMIT 1" michael@0: ), getter_AddRefs(stmt)); michael@0: do_check_success(rv); michael@0: michael@0: rv = stmt->BindInt64ByIndex(0, placeId); michael@0: do_check_success(rv); michael@0: michael@0: bool hasResults; michael@0: rv = stmt->ExecuteStep(&hasResults); michael@0: do_check_success(rv); michael@0: michael@0: if (!hasResults) { michael@0: result.id = 0; michael@0: return; michael@0: } michael@0: michael@0: rv = stmt->GetInt64(0, &result.id); michael@0: do_check_success(rv); michael@0: rv = stmt->GetInt64(1, &result.lastVisitId); michael@0: do_check_success(rv); michael@0: rv = stmt->GetInt32(2, &result.transitionType); michael@0: do_check_success(rv); michael@0: } michael@0: michael@0: void michael@0: do_wait_async_updates() { michael@0: nsCOMPtr db = do_get_db(); michael@0: nsCOMPtr stmt; michael@0: michael@0: db->CreateAsyncStatement(NS_LITERAL_CSTRING("BEGIN EXCLUSIVE"), michael@0: getter_AddRefs(stmt)); michael@0: nsCOMPtr pending; michael@0: (void)stmt->ExecuteAsync(nullptr, getter_AddRefs(pending)); michael@0: michael@0: db->CreateAsyncStatement(NS_LITERAL_CSTRING("COMMIT"), michael@0: getter_AddRefs(stmt)); michael@0: nsRefPtr spinner = new AsyncStatementSpinner(); michael@0: (void)stmt->ExecuteAsync(spinner, getter_AddRefs(pending)); michael@0: michael@0: spinner->SpinUntilCompleted(); michael@0: } michael@0: michael@0: /** michael@0: * Adds a URI to the database. michael@0: * michael@0: * @param aURI michael@0: * The URI to add to the database. michael@0: */ michael@0: void michael@0: addURI(nsIURI* aURI) michael@0: { michael@0: nsCOMPtr history = do_GetService(NS_IHISTORY_CONTRACTID); michael@0: do_check_true(history); michael@0: nsresult rv = history->VisitURI(aURI, nullptr, mozilla::IHistory::TOP_LEVEL); michael@0: do_check_success(rv); michael@0: michael@0: do_wait_async_updates(); michael@0: } michael@0: michael@0: static const char TOPIC_PROFILE_CHANGE[] = "profile-before-change"; michael@0: static const char TOPIC_PLACES_CONNECTION_CLOSED[] = "places-connection-closed"; michael@0: michael@0: class WaitForConnectionClosed MOZ_FINAL : public nsIObserver michael@0: { michael@0: nsRefPtr mSpinner; michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: michael@0: WaitForConnectionClosed() michael@0: { michael@0: nsCOMPtr os = michael@0: do_GetService(NS_OBSERVERSERVICE_CONTRACTID); michael@0: MOZ_ASSERT(os); michael@0: if (os) { michael@0: MOZ_ALWAYS_TRUE(NS_SUCCEEDED(os->AddObserver(this, TOPIC_PROFILE_CHANGE, false))); michael@0: } michael@0: mSpinner = new WaitForTopicSpinner(TOPIC_PLACES_CONNECTION_CLOSED); michael@0: } michael@0: michael@0: NS_IMETHOD Observe(nsISupports* aSubject, michael@0: const char* aTopic, michael@0: const char16_t* aData) michael@0: { michael@0: nsCOMPtr os = michael@0: do_GetService(NS_OBSERVERSERVICE_CONTRACTID); michael@0: MOZ_ASSERT(os); michael@0: if (os) { michael@0: MOZ_ALWAYS_TRUE(NS_SUCCEEDED(os->RemoveObserver(this, aTopic))); michael@0: } michael@0: michael@0: mSpinner->Spin(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(WaitForConnectionClosed, nsIObserver)