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/. */
6
7 #ifndef storage_test_harness_h__
8 #define storage_test_harness_h__
9
10 #include "gtest/gtest.h"
11
12 #include "prthread.h"
13 #include "nsAppDirectoryServiceDefs.h"
14 #include "nsComponentManagerUtils.h"
15 #include "nsDirectoryServiceDefs.h"
16 #include "nsDirectoryServiceUtils.h"
17 #include "nsMemory.h"
18 #include "nsServiceManagerUtils.h"
19 #include "nsIThread.h"
20 #include "nsThreadUtils.h"
21 #include "mozilla/ReentrantMonitor.h"
22
23 #include "mozIStorageService.h"
24 #include "mozIStorageConnection.h"
25 #include "mozIStorageStatementCallback.h"
26 #include "mozIStorageCompletionCallback.h"
27 #include "mozIStorageBindingParamsArray.h"
28 #include "mozIStorageBindingParams.h"
29 #include "mozIStorageAsyncStatement.h"
30 #include "mozIStorageStatement.h"
31 #include "mozIStoragePendingStatement.h"
32 #include "mozIStorageError.h"
33 #include "nsIInterfaceRequestorUtils.h"
34 #include "nsIEventTarget.h"
35
36 #include "sqlite3.h"
37
38 #define do_check_true(aCondition) EXPECT_TRUE(aCondition)
39
40 #define do_check_false(aCondition) EXPECT_FALSE(aCondition)
41
42 #define do_check_success(aResult) do_check_true(NS_SUCCEEDED(aResult))
43
44 #define do_check_eq(aExpected, aActual) do_check_true(aExpected == aActual)
45
46 #define do_check_ok(aInvoc) do_check_true((aInvoc) == SQLITE_OK)
47
getService()48 already_AddRefed<mozIStorageService> getService() {
49 nsCOMPtr<mozIStorageService> ss =
50 do_CreateInstance("@mozilla.org/storage/service;1");
51 do_check_true(ss);
52 return ss.forget();
53 }
54
getMemoryDatabase()55 already_AddRefed<mozIStorageConnection> getMemoryDatabase() {
56 nsCOMPtr<mozIStorageService> ss = getService();
57 nsCOMPtr<mozIStorageConnection> conn;
58 nsresult rv = ss->OpenSpecialDatabase(
59 kMozStorageMemoryStorageKey, VoidCString(),
60 mozIStorageService::CONNECTION_DEFAULT, getter_AddRefs(conn));
61 do_check_success(rv);
62 return conn.forget();
63 }
64
65 already_AddRefed<mozIStorageConnection> getDatabase(
66 nsIFile* aDBFile = nullptr,
67 uint32_t aConnectionFlags = mozIStorageService::CONNECTION_DEFAULT) {
68 nsCOMPtr<nsIFile> dbFile;
69 nsresult rv;
70 if (!aDBFile) {
71 MOZ_RELEASE_ASSERT(NS_IsMainThread(), "Can't get tmp dir off mainthread.");
72 (void)NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
73 getter_AddRefs(dbFile));
74 NS_ASSERTION(dbFile, "The directory doesn't exists?!");
75
76 rv = dbFile->Append(u"storage_test_db.sqlite"_ns);
77 do_check_success(rv);
78 } else {
79 dbFile = aDBFile;
80 }
81
82 nsCOMPtr<mozIStorageService> ss = getService();
83 nsCOMPtr<mozIStorageConnection> conn;
84 rv = ss->OpenDatabase(dbFile, aConnectionFlags, getter_AddRefs(conn));
85 do_check_success(rv);
86 return conn.forget();
87 }
88
89 class AsyncStatementSpinner : public mozIStorageStatementCallback,
90 public mozIStorageCompletionCallback {
91 public:
92 NS_DECL_ISUPPORTS
93 NS_DECL_MOZISTORAGESTATEMENTCALLBACK
94 NS_DECL_MOZISTORAGECOMPLETIONCALLBACK
95
96 AsyncStatementSpinner();
97
98 void SpinUntilCompleted();
99
100 uint16_t completionReason;
101
102 protected:
~AsyncStatementSpinner()103 virtual ~AsyncStatementSpinner() {}
104 volatile bool mCompleted;
105 };
106
NS_IMPL_ISUPPORTS(AsyncStatementSpinner,mozIStorageStatementCallback,mozIStorageCompletionCallback)107 NS_IMPL_ISUPPORTS(AsyncStatementSpinner, mozIStorageStatementCallback,
108 mozIStorageCompletionCallback)
109
110 AsyncStatementSpinner::AsyncStatementSpinner()
111 : completionReason(0), mCompleted(false) {}
112
113 NS_IMETHODIMP
HandleResult(mozIStorageResultSet * aResultSet)114 AsyncStatementSpinner::HandleResult(mozIStorageResultSet* aResultSet) {
115 return NS_OK;
116 }
117
118 NS_IMETHODIMP
HandleError(mozIStorageError * aError)119 AsyncStatementSpinner::HandleError(mozIStorageError* aError) {
120 int32_t result;
121 nsresult rv = aError->GetResult(&result);
122 NS_ENSURE_SUCCESS(rv, rv);
123 nsAutoCString message;
124 rv = aError->GetMessage(message);
125 NS_ENSURE_SUCCESS(rv, rv);
126
127 nsAutoCString warnMsg;
128 warnMsg.AppendLiteral(
129 "An error occurred while executing an async statement: ");
130 warnMsg.AppendInt(result);
131 warnMsg.Append(' ');
132 warnMsg.Append(message);
133 NS_WARNING(warnMsg.get());
134
135 return NS_OK;
136 }
137
138 NS_IMETHODIMP
HandleCompletion(uint16_t aReason)139 AsyncStatementSpinner::HandleCompletion(uint16_t aReason) {
140 completionReason = aReason;
141 mCompleted = true;
142 return NS_OK;
143 }
144
145 NS_IMETHODIMP
Complete(nsresult,nsISupports *)146 AsyncStatementSpinner::Complete(nsresult, nsISupports*) {
147 mCompleted = true;
148 return NS_OK;
149 }
150
SpinUntilCompleted()151 void AsyncStatementSpinner::SpinUntilCompleted() {
152 nsCOMPtr<nsIThread> thread(::do_GetCurrentThread());
153 nsresult rv = NS_OK;
154 bool processed = true;
155 while (!mCompleted && NS_SUCCEEDED(rv)) {
156 rv = thread->ProcessNextEvent(true, &processed);
157 }
158 }
159
160 #define NS_DECL_ASYNCSTATEMENTSPINNER \
161 NS_IMETHOD HandleResult(mozIStorageResultSet* aResultSet) override;
162
163 ////////////////////////////////////////////////////////////////////////////////
164 //// Async Helpers
165
166 /**
167 * Execute an async statement, blocking the main thread until we get the
168 * callback completion notification.
169 */
blocking_async_execute(mozIStorageBaseStatement * stmt)170 void blocking_async_execute(mozIStorageBaseStatement* stmt) {
171 RefPtr<AsyncStatementSpinner> spinner(new AsyncStatementSpinner());
172
173 nsCOMPtr<mozIStoragePendingStatement> pendy;
174 (void)stmt->ExecuteAsync(spinner, getter_AddRefs(pendy));
175 spinner->SpinUntilCompleted();
176 }
177
178 /**
179 * Invoke AsyncClose on the given connection, blocking the main thread until we
180 * get the completion notification.
181 */
blocking_async_close(mozIStorageConnection * db)182 void blocking_async_close(mozIStorageConnection* db) {
183 RefPtr<AsyncStatementSpinner> spinner(new AsyncStatementSpinner());
184
185 db->AsyncClose(spinner);
186 spinner->SpinUntilCompleted();
187 }
188
189 ////////////////////////////////////////////////////////////////////////////////
190 //// Mutex Watching
191
192 /**
193 * Verify that mozIStorageAsyncStatement's life-cycle never triggers a mutex on
194 * the caller (generally main) thread. We do this by decorating the sqlite
195 * mutex logic with our own code that checks what thread it is being invoked on
196 * and sets a flag if it is invoked on the main thread. We are able to easily
197 * decorate the SQLite mutex logic because SQLite allows us to retrieve the
198 * current function pointers being used and then provide a new set.
199 */
200
201 sqlite3_mutex_methods orig_mutex_methods;
202 sqlite3_mutex_methods wrapped_mutex_methods;
203
204 bool mutex_used_on_watched_thread = false;
205 PRThread* watched_thread = nullptr;
206 /**
207 * Ugly hack to let us figure out what a connection's async thread is. If we
208 * were MOZILLA_INTERNAL_API and linked as such we could just include
209 * mozStorageConnection.h and just ask Connection directly. But that turns out
210 * poorly.
211 *
212 * When the thread a mutex is invoked on isn't watched_thread we save it to this
213 * variable.
214 */
215 nsIThread* last_non_watched_thread = nullptr;
216
217 /**
218 * Set a flag if the mutex is used on the thread we are watching, but always
219 * call the real mutex function.
220 */
wrapped_MutexEnter(sqlite3_mutex * mutex)221 extern "C" void wrapped_MutexEnter(sqlite3_mutex* mutex) {
222 if (PR_GetCurrentThread() == watched_thread)
223 mutex_used_on_watched_thread = true;
224 else
225 last_non_watched_thread = NS_GetCurrentThread();
226 orig_mutex_methods.xMutexEnter(mutex);
227 }
228
wrapped_MutexTry(sqlite3_mutex * mutex)229 extern "C" int wrapped_MutexTry(sqlite3_mutex* mutex) {
230 if (::PR_GetCurrentThread() == watched_thread)
231 mutex_used_on_watched_thread = true;
232 return orig_mutex_methods.xMutexTry(mutex);
233 }
234
235 class HookSqliteMutex {
236 public:
HookSqliteMutex()237 HookSqliteMutex() {
238 // We need to initialize and teardown SQLite to get it to set up the
239 // default mutex handlers for us so we can steal them and wrap them.
240 do_check_ok(sqlite3_initialize());
241 do_check_ok(sqlite3_shutdown());
242 do_check_ok(::sqlite3_config(SQLITE_CONFIG_GETMUTEX, &orig_mutex_methods));
243 do_check_ok(
244 ::sqlite3_config(SQLITE_CONFIG_GETMUTEX, &wrapped_mutex_methods));
245 wrapped_mutex_methods.xMutexEnter = wrapped_MutexEnter;
246 wrapped_mutex_methods.xMutexTry = wrapped_MutexTry;
247 do_check_ok(::sqlite3_config(SQLITE_CONFIG_MUTEX, &wrapped_mutex_methods));
248 }
249
~HookSqliteMutex()250 ~HookSqliteMutex() {
251 do_check_ok(sqlite3_shutdown());
252 do_check_ok(::sqlite3_config(SQLITE_CONFIG_MUTEX, &orig_mutex_methods));
253 do_check_ok(sqlite3_initialize());
254 }
255 };
256
257 /**
258 * Call to clear the watch state and to set the watching against this thread.
259 *
260 * Check |mutex_used_on_watched_thread| to see if the mutex has fired since
261 * this method was last called. Since we're talking about the current thread,
262 * there are no race issues to be concerned about
263 */
watch_for_mutex_use_on_this_thread()264 void watch_for_mutex_use_on_this_thread() {
265 watched_thread = ::PR_GetCurrentThread();
266 mutex_used_on_watched_thread = false;
267 }
268
269 ////////////////////////////////////////////////////////////////////////////////
270 //// Thread Wedgers
271
272 /**
273 * A runnable that blocks until code on another thread invokes its unwedge
274 * method. By dispatching this to a thread you can ensure that no subsequent
275 * runnables dispatched to the thread will execute until you invoke unwedge.
276 *
277 * The wedger is self-dispatching, just construct it with its target.
278 */
279 class ThreadWedger : public mozilla::Runnable {
280 public:
ThreadWedger(nsIEventTarget * aTarget)281 explicit ThreadWedger(nsIEventTarget* aTarget)
282 : mozilla::Runnable("ThreadWedger"),
283 mReentrantMonitor("thread wedger"),
284 unwedged(false) {
285 aTarget->Dispatch(this, aTarget->NS_DISPATCH_NORMAL);
286 }
287
Run()288 NS_IMETHOD Run() override {
289 mozilla::ReentrantMonitorAutoEnter automon(mReentrantMonitor);
290
291 if (!unwedged) automon.Wait();
292
293 return NS_OK;
294 }
295
unwedge()296 void unwedge() {
297 mozilla::ReentrantMonitorAutoEnter automon(mReentrantMonitor);
298 unwedged = true;
299 automon.Notify();
300 }
301
302 private:
303 mozilla::ReentrantMonitor mReentrantMonitor;
304 bool unwedged;
305 };
306
307 ////////////////////////////////////////////////////////////////////////////////
308 //// Async Helpers
309
310 /**
311 * A horrible hack to figure out what the connection's async thread is. By
312 * creating a statement and async dispatching we can tell from the mutex who
313 * is the async thread, PRThread style. Then we map that to an nsIThread.
314 */
get_conn_async_thread(mozIStorageConnection * db)315 already_AddRefed<nsIThread> get_conn_async_thread(mozIStorageConnection* db) {
316 // Make sure we are tracking the current thread as the watched thread
317 watch_for_mutex_use_on_this_thread();
318
319 // - statement with nothing to bind
320 nsCOMPtr<mozIStorageAsyncStatement> stmt;
321 db->CreateAsyncStatement("SELECT 1"_ns, getter_AddRefs(stmt));
322 blocking_async_execute(stmt);
323 stmt->Finalize();
324
325 nsCOMPtr<nsIThread> asyncThread = last_non_watched_thread;
326
327 // Additionally, check that the thread we get as the background thread is the
328 // same one as the one we report from getInterface.
329 nsCOMPtr<nsIEventTarget> target = do_GetInterface(db);
330 nsCOMPtr<nsIThread> allegedAsyncThread = do_QueryInterface(target);
331 do_check_eq(allegedAsyncThread, asyncThread);
332 return asyncThread.forget();
333 }
334
335 #endif // storage_test_harness_h__
336