Bumping manifests a=b2g-bump
[gecko.git] / storage / test / storage_test_harness.h
blob4497c2a1f92b2b2347597341ed44f30d438f7ff6
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "TestHarness.h"
9 #include "nsMemory.h"
10 #include "prthread.h"
11 #include "nsThreadUtils.h"
12 #include "nsDirectoryServiceDefs.h"
13 #include "mozilla/ReentrantMonitor.h"
15 #include "mozIStorageService.h"
16 #include "mozIStorageConnection.h"
17 #include "mozIStorageStatementCallback.h"
18 #include "mozIStorageCompletionCallback.h"
19 #include "mozIStorageBindingParamsArray.h"
20 #include "mozIStorageBindingParams.h"
21 #include "mozIStorageAsyncStatement.h"
22 #include "mozIStorageStatement.h"
23 #include "mozIStoragePendingStatement.h"
24 #include "mozIStorageError.h"
25 #include "nsIInterfaceRequestorUtils.h"
26 #include "nsIEventTarget.h"
28 #include "sqlite3.h"
30 static int gTotalTests = 0;
31 static int gPassedTests = 0;
33 #define do_check_true(aCondition) \
34 PR_BEGIN_MACRO \
35 gTotalTests++; \
36 if (aCondition) { \
37 gPassedTests++; \
38 } else { \
39 fail("%s | Expected true, got false at line %d", __FILE__, __LINE__); \
40 } \
41 PR_END_MACRO
43 #define do_check_false(aCondition) \
44 PR_BEGIN_MACRO \
45 gTotalTests++; \
46 if (!aCondition) { \
47 gPassedTests++; \
48 } else { \
49 fail("%s | Expected false, got true at line %d", __FILE__, __LINE__); \
50 } \
51 PR_END_MACRO
53 #define do_check_success(aResult) \
54 do_check_true(NS_SUCCEEDED(aResult))
56 #ifdef LINUX
57 // XXX Linux opt builds on tinderbox are orange due to linking with stdlib.
58 // This is sad and annoying, but it's a workaround that works.
59 #define do_check_eq(aExpected, aActual) \
60 do_check_true(aExpected == aActual)
61 #else
62 #include <sstream>
63 // Print nsresult as uint32_t
64 std::ostream& operator<<(std::ostream& aStream, const nsresult aInput)
66 return aStream << static_cast<uint32_t>(aInput);
68 #define do_check_eq(aExpected, aActual) \
69 PR_BEGIN_MACRO \
70 gTotalTests++; \
71 if (aExpected == aActual) { \
72 gPassedTests++; \
73 } else { \
74 std::ostringstream temp; \
75 temp << __FILE__ << " | Expected '" << aExpected << "', got '"; \
76 temp << aActual <<"' at line " << __LINE__; \
77 fail(temp.str().c_str()); \
78 } \
79 PR_END_MACRO
80 #endif
82 #define do_check_ok(aInvoc) do_check_true((aInvoc) == SQLITE_OK)
84 already_AddRefed<mozIStorageService>
85 getService()
87 nsCOMPtr<mozIStorageService> ss =
88 do_GetService("@mozilla.org/storage/service;1");
89 do_check_true(ss);
90 return ss.forget();
93 already_AddRefed<mozIStorageConnection>
94 getMemoryDatabase()
96 nsCOMPtr<mozIStorageService> ss = getService();
97 nsCOMPtr<mozIStorageConnection> conn;
98 nsresult rv = ss->OpenSpecialDatabase("memory", getter_AddRefs(conn));
99 do_check_success(rv);
100 return conn.forget();
103 already_AddRefed<mozIStorageConnection>
104 getDatabase()
106 nsCOMPtr<nsIFile> dbFile;
107 (void)NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
108 getter_AddRefs(dbFile));
109 NS_ASSERTION(dbFile, "The directory doesn't exists?!");
111 nsresult rv = dbFile->Append(NS_LITERAL_STRING("storage_test_db.sqlite"));
112 do_check_success(rv);
114 nsCOMPtr<mozIStorageService> ss = getService();
115 nsCOMPtr<mozIStorageConnection> conn;
116 rv = ss->OpenDatabase(dbFile, getter_AddRefs(conn));
117 do_check_success(rv);
118 return conn.forget();
122 class AsyncStatementSpinner : public mozIStorageStatementCallback
123 , public mozIStorageCompletionCallback
125 public:
126 NS_DECL_ISUPPORTS
127 NS_DECL_MOZISTORAGESTATEMENTCALLBACK
128 NS_DECL_MOZISTORAGECOMPLETIONCALLBACK
130 AsyncStatementSpinner();
132 void SpinUntilCompleted();
134 uint16_t completionReason;
136 protected:
137 virtual ~AsyncStatementSpinner() {}
138 volatile bool mCompleted;
141 NS_IMPL_ISUPPORTS(AsyncStatementSpinner,
142 mozIStorageStatementCallback,
143 mozIStorageCompletionCallback)
145 AsyncStatementSpinner::AsyncStatementSpinner()
146 : completionReason(0)
147 , mCompleted(false)
151 NS_IMETHODIMP
152 AsyncStatementSpinner::HandleResult(mozIStorageResultSet *aResultSet)
154 return NS_OK;
157 NS_IMETHODIMP
158 AsyncStatementSpinner::HandleError(mozIStorageError *aError)
160 int32_t result;
161 nsresult rv = aError->GetResult(&result);
162 NS_ENSURE_SUCCESS(rv, rv);
163 nsAutoCString message;
164 rv = aError->GetMessage(message);
165 NS_ENSURE_SUCCESS(rv, rv);
167 nsAutoCString warnMsg;
168 warnMsg.AppendLiteral("An error occurred while executing an async statement: ");
169 warnMsg.AppendInt(result);
170 warnMsg.Append(' ');
171 warnMsg.Append(message);
172 NS_WARNING(warnMsg.get());
174 return NS_OK;
177 NS_IMETHODIMP
178 AsyncStatementSpinner::HandleCompletion(uint16_t aReason)
180 completionReason = aReason;
181 mCompleted = true;
182 return NS_OK;
185 NS_IMETHODIMP
186 AsyncStatementSpinner::Complete(nsresult, nsISupports*)
188 mCompleted = true;
189 return NS_OK;
192 void AsyncStatementSpinner::SpinUntilCompleted()
194 nsCOMPtr<nsIThread> thread(::do_GetCurrentThread());
195 nsresult rv = NS_OK;
196 bool processed = true;
197 while (!mCompleted && NS_SUCCEEDED(rv)) {
198 rv = thread->ProcessNextEvent(true, &processed);
202 #define NS_DECL_ASYNCSTATEMENTSPINNER \
203 NS_IMETHOD HandleResult(mozIStorageResultSet *aResultSet);
205 ////////////////////////////////////////////////////////////////////////////////
206 //// Async Helpers
209 * Execute an async statement, blocking the main thread until we get the
210 * callback completion notification.
212 void
213 blocking_async_execute(mozIStorageBaseStatement *stmt)
215 nsRefPtr<AsyncStatementSpinner> spinner(new AsyncStatementSpinner());
217 nsCOMPtr<mozIStoragePendingStatement> pendy;
218 (void)stmt->ExecuteAsync(spinner, getter_AddRefs(pendy));
219 spinner->SpinUntilCompleted();
223 * Invoke AsyncClose on the given connection, blocking the main thread until we
224 * get the completion notification.
226 void
227 blocking_async_close(mozIStorageConnection *db)
229 nsRefPtr<AsyncStatementSpinner> spinner(new AsyncStatementSpinner());
231 db->AsyncClose(spinner);
232 spinner->SpinUntilCompleted();
235 ////////////////////////////////////////////////////////////////////////////////
236 //// Mutex Watching
239 * Verify that mozIStorageAsyncStatement's life-cycle never triggers a mutex on
240 * the caller (generally main) thread. We do this by decorating the sqlite
241 * mutex logic with our own code that checks what thread it is being invoked on
242 * and sets a flag if it is invoked on the main thread. We are able to easily
243 * decorate the SQLite mutex logic because SQLite allows us to retrieve the
244 * current function pointers being used and then provide a new set.
247 sqlite3_mutex_methods orig_mutex_methods;
248 sqlite3_mutex_methods wrapped_mutex_methods;
250 bool mutex_used_on_watched_thread = false;
251 PRThread *watched_thread = nullptr;
253 * Ugly hack to let us figure out what a connection's async thread is. If we
254 * were MOZILLA_INTERNAL_API and linked as such we could just include
255 * mozStorageConnection.h and just ask Connection directly. But that turns out
256 * poorly.
258 * When the thread a mutex is invoked on isn't watched_thread we save it to this
259 * variable.
261 PRThread *last_non_watched_thread = nullptr;
264 * Set a flag if the mutex is used on the thread we are watching, but always
265 * call the real mutex function.
267 extern "C" void wrapped_MutexEnter(sqlite3_mutex *mutex)
269 PRThread *curThread = ::PR_GetCurrentThread();
270 if (curThread == watched_thread)
271 mutex_used_on_watched_thread = true;
272 else
273 last_non_watched_thread = curThread;
274 orig_mutex_methods.xMutexEnter(mutex);
277 extern "C" int wrapped_MutexTry(sqlite3_mutex *mutex)
279 if (::PR_GetCurrentThread() == watched_thread)
280 mutex_used_on_watched_thread = true;
281 return orig_mutex_methods.xMutexTry(mutex);
284 void hook_sqlite_mutex()
286 // We need to initialize and teardown SQLite to get it to set up the
287 // default mutex handlers for us so we can steal them and wrap them.
288 do_check_ok(sqlite3_initialize());
289 do_check_ok(sqlite3_shutdown());
290 do_check_ok(::sqlite3_config(SQLITE_CONFIG_GETMUTEX, &orig_mutex_methods));
291 do_check_ok(::sqlite3_config(SQLITE_CONFIG_GETMUTEX, &wrapped_mutex_methods));
292 wrapped_mutex_methods.xMutexEnter = wrapped_MutexEnter;
293 wrapped_mutex_methods.xMutexTry = wrapped_MutexTry;
294 do_check_ok(::sqlite3_config(SQLITE_CONFIG_MUTEX, &wrapped_mutex_methods));
298 * Call to clear the watch state and to set the watching against this thread.
300 * Check |mutex_used_on_watched_thread| to see if the mutex has fired since
301 * this method was last called. Since we're talking about the current thread,
302 * there are no race issues to be concerned about
304 void watch_for_mutex_use_on_this_thread()
306 watched_thread = ::PR_GetCurrentThread();
307 mutex_used_on_watched_thread = false;
311 ////////////////////////////////////////////////////////////////////////////////
312 //// Thread Wedgers
315 * A runnable that blocks until code on another thread invokes its unwedge
316 * method. By dispatching this to a thread you can ensure that no subsequent
317 * runnables dispatched to the thread will execute until you invoke unwedge.
319 * The wedger is self-dispatching, just construct it with its target.
321 class ThreadWedger : public nsRunnable
323 public:
324 explicit ThreadWedger(nsIEventTarget *aTarget)
325 : mReentrantMonitor("thread wedger")
326 , unwedged(false)
328 aTarget->Dispatch(this, aTarget->NS_DISPATCH_NORMAL);
331 NS_IMETHOD Run()
333 mozilla::ReentrantMonitorAutoEnter automon(mReentrantMonitor);
335 if (!unwedged)
336 automon.Wait();
338 return NS_OK;
341 void unwedge()
343 mozilla::ReentrantMonitorAutoEnter automon(mReentrantMonitor);
344 unwedged = true;
345 automon.Notify();
348 private:
349 mozilla::ReentrantMonitor mReentrantMonitor;
350 bool unwedged;
353 ////////////////////////////////////////////////////////////////////////////////
354 //// Async Helpers
357 * A horrible hack to figure out what the connection's async thread is. By
358 * creating a statement and async dispatching we can tell from the mutex who
359 * is the async thread, PRThread style. Then we map that to an nsIThread.
361 already_AddRefed<nsIThread>
362 get_conn_async_thread(mozIStorageConnection *db)
364 // Make sure we are tracking the current thread as the watched thread
365 watch_for_mutex_use_on_this_thread();
367 // - statement with nothing to bind
368 nsCOMPtr<mozIStorageAsyncStatement> stmt;
369 db->CreateAsyncStatement(
370 NS_LITERAL_CSTRING("SELECT 1"),
371 getter_AddRefs(stmt));
372 blocking_async_execute(stmt);
373 stmt->Finalize();
375 nsCOMPtr<nsIThreadManager> threadMan =
376 do_GetService("@mozilla.org/thread-manager;1");
377 nsCOMPtr<nsIThread> asyncThread;
378 threadMan->GetThreadFromPRThread(last_non_watched_thread,
379 getter_AddRefs(asyncThread));
381 // Additionally, check that the thread we get as the background thread is the
382 // same one as the one we report from getInterface.
383 nsCOMPtr<nsIEventTarget> target = do_GetInterface(db);
384 nsCOMPtr<nsIThread> allegedAsyncThread = do_QueryInterface(target);
385 PRThread *allegedPRThread;
386 (void)allegedAsyncThread->GetPRThread(&allegedPRThread);
387 do_check_eq(allegedPRThread, last_non_watched_thread);
388 return asyncThread.forget();