|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* vim set: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 #include "storage_test_harness.h" |
|
8 |
|
9 #include "mozilla/ReentrantMonitor.h" |
|
10 #include "nsThreadUtils.h" |
|
11 #include "mozIStorageStatement.h" |
|
12 |
|
13 /** |
|
14 * This file tests that our implementation around sqlite3_unlock_notify works |
|
15 * as expected. |
|
16 */ |
|
17 |
|
18 //////////////////////////////////////////////////////////////////////////////// |
|
19 //// Helpers |
|
20 |
|
21 enum State { |
|
22 STARTING, |
|
23 WRITE_LOCK, |
|
24 READ_LOCK, |
|
25 TEST_DONE |
|
26 }; |
|
27 |
|
28 class DatabaseLocker : public nsRunnable |
|
29 { |
|
30 public: |
|
31 DatabaseLocker(const char* aSQL) |
|
32 : monitor("DatabaseLocker::monitor") |
|
33 , mSQL(aSQL) |
|
34 , mState(STARTING) |
|
35 { |
|
36 } |
|
37 |
|
38 void RunInBackground() |
|
39 { |
|
40 (void)NS_NewThread(getter_AddRefs(mThread)); |
|
41 do_check_true(mThread); |
|
42 |
|
43 do_check_success(mThread->Dispatch(this, NS_DISPATCH_NORMAL)); |
|
44 } |
|
45 |
|
46 NS_IMETHOD Run() |
|
47 { |
|
48 mozilla::ReentrantMonitorAutoEnter lock(monitor); |
|
49 |
|
50 nsCOMPtr<mozIStorageConnection> db(getDatabase()); |
|
51 |
|
52 nsCString sql(mSQL); |
|
53 nsCOMPtr<mozIStorageStatement> stmt; |
|
54 do_check_success(db->CreateStatement(sql, getter_AddRefs(stmt))); |
|
55 |
|
56 bool hasResult; |
|
57 do_check_success(stmt->ExecuteStep(&hasResult)); |
|
58 |
|
59 Notify(WRITE_LOCK); |
|
60 WaitFor(TEST_DONE); |
|
61 |
|
62 return NS_OK; |
|
63 } |
|
64 |
|
65 void WaitFor(State aState) |
|
66 { |
|
67 monitor.AssertCurrentThreadIn(); |
|
68 while (mState != aState) { |
|
69 do_check_success(monitor.Wait()); |
|
70 } |
|
71 } |
|
72 |
|
73 void Notify(State aState) |
|
74 { |
|
75 monitor.AssertCurrentThreadIn(); |
|
76 mState = aState; |
|
77 do_check_success(monitor.Notify()); |
|
78 } |
|
79 |
|
80 mozilla::ReentrantMonitor monitor; |
|
81 |
|
82 protected: |
|
83 nsCOMPtr<nsIThread> mThread; |
|
84 const char *const mSQL; |
|
85 State mState; |
|
86 }; |
|
87 |
|
88 class DatabaseTester : public DatabaseLocker |
|
89 { |
|
90 public: |
|
91 DatabaseTester(mozIStorageConnection *aConnection, |
|
92 const char* aSQL) |
|
93 : DatabaseLocker(aSQL) |
|
94 , mConnection(aConnection) |
|
95 { |
|
96 } |
|
97 |
|
98 NS_IMETHOD Run() |
|
99 { |
|
100 mozilla::ReentrantMonitorAutoEnter lock(monitor); |
|
101 WaitFor(READ_LOCK); |
|
102 |
|
103 nsCString sql(mSQL); |
|
104 nsCOMPtr<mozIStorageStatement> stmt; |
|
105 do_check_success(mConnection->CreateStatement(sql, getter_AddRefs(stmt))); |
|
106 |
|
107 bool hasResult; |
|
108 nsresult rv = stmt->ExecuteStep(&hasResult); |
|
109 do_check_eq(rv, NS_ERROR_FILE_IS_LOCKED); |
|
110 |
|
111 // Finalize our statement and null out our connection before notifying to |
|
112 // ensure that we close on the proper thread. |
|
113 rv = stmt->Finalize(); |
|
114 do_check_eq(rv, NS_ERROR_FILE_IS_LOCKED); |
|
115 mConnection = nullptr; |
|
116 |
|
117 Notify(TEST_DONE); |
|
118 |
|
119 return NS_OK; |
|
120 } |
|
121 |
|
122 private: |
|
123 nsCOMPtr<mozIStorageConnection> mConnection; |
|
124 }; |
|
125 |
|
126 //////////////////////////////////////////////////////////////////////////////// |
|
127 //// Test Functions |
|
128 |
|
129 void |
|
130 setup() |
|
131 { |
|
132 nsCOMPtr<mozIStorageConnection> db(getDatabase()); |
|
133 |
|
134 // Create and populate a dummy table. |
|
135 nsresult rv = db->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
|
136 "CREATE TABLE test (id INTEGER PRIMARY KEY, data STRING)" |
|
137 )); |
|
138 do_check_success(rv); |
|
139 rv = db->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
|
140 "INSERT INTO test (data) VALUES ('foo')" |
|
141 )); |
|
142 do_check_success(rv); |
|
143 rv = db->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
|
144 "INSERT INTO test (data) VALUES ('bar')" |
|
145 )); |
|
146 do_check_success(rv); |
|
147 rv = db->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
|
148 "CREATE UNIQUE INDEX unique_data ON test (data)" |
|
149 )); |
|
150 do_check_success(rv); |
|
151 } |
|
152 |
|
153 void |
|
154 test_step_locked_does_not_block_main_thread() |
|
155 { |
|
156 nsCOMPtr<mozIStorageConnection> db(getDatabase()); |
|
157 |
|
158 // Need to prepare our statement ahead of time so we make sure to only test |
|
159 // step and not prepare. |
|
160 nsCOMPtr<mozIStorageStatement> stmt; |
|
161 nsresult rv = db->CreateStatement(NS_LITERAL_CSTRING( |
|
162 "INSERT INTO test (data) VALUES ('test1')" |
|
163 ), getter_AddRefs(stmt)); |
|
164 do_check_success(rv); |
|
165 |
|
166 nsRefPtr<DatabaseLocker> locker(new DatabaseLocker("SELECT * FROM test")); |
|
167 do_check_true(locker); |
|
168 mozilla::ReentrantMonitorAutoEnter lock(locker->monitor); |
|
169 locker->RunInBackground(); |
|
170 |
|
171 // Wait for the locker to notify us that it has locked the database properly. |
|
172 locker->WaitFor(WRITE_LOCK); |
|
173 |
|
174 bool hasResult; |
|
175 rv = stmt->ExecuteStep(&hasResult); |
|
176 do_check_eq(rv, NS_ERROR_FILE_IS_LOCKED); |
|
177 |
|
178 locker->Notify(TEST_DONE); |
|
179 } |
|
180 |
|
181 void |
|
182 test_drop_index_does_not_loop() |
|
183 { |
|
184 nsCOMPtr<mozIStorageConnection> db(getDatabase()); |
|
185 |
|
186 // Need to prepare our statement ahead of time so we make sure to only test |
|
187 // step and not prepare. |
|
188 nsCOMPtr<mozIStorageStatement> stmt; |
|
189 nsresult rv = db->CreateStatement(NS_LITERAL_CSTRING( |
|
190 "SELECT * FROM test" |
|
191 ), getter_AddRefs(stmt)); |
|
192 do_check_success(rv); |
|
193 |
|
194 nsRefPtr<DatabaseTester> tester = |
|
195 new DatabaseTester(db, "DROP INDEX unique_data"); |
|
196 do_check_true(tester); |
|
197 mozilla::ReentrantMonitorAutoEnter lock(tester->monitor); |
|
198 tester->RunInBackground(); |
|
199 |
|
200 // Hold a read lock on the database, and then let the tester try to execute. |
|
201 bool hasResult; |
|
202 rv = stmt->ExecuteStep(&hasResult); |
|
203 do_check_success(rv); |
|
204 do_check_true(hasResult); |
|
205 tester->Notify(READ_LOCK); |
|
206 |
|
207 // Make sure the tester finishes its test before we move on. |
|
208 tester->WaitFor(TEST_DONE); |
|
209 } |
|
210 |
|
211 void |
|
212 test_drop_table_does_not_loop() |
|
213 { |
|
214 nsCOMPtr<mozIStorageConnection> db(getDatabase()); |
|
215 |
|
216 // Need to prepare our statement ahead of time so we make sure to only test |
|
217 // step and not prepare. |
|
218 nsCOMPtr<mozIStorageStatement> stmt; |
|
219 nsresult rv = db->CreateStatement(NS_LITERAL_CSTRING( |
|
220 "SELECT * FROM test" |
|
221 ), getter_AddRefs(stmt)); |
|
222 do_check_success(rv); |
|
223 |
|
224 nsRefPtr<DatabaseTester> tester(new DatabaseTester(db, "DROP TABLE test")); |
|
225 do_check_true(tester); |
|
226 mozilla::ReentrantMonitorAutoEnter lock(tester->monitor); |
|
227 tester->RunInBackground(); |
|
228 |
|
229 // Hold a read lock on the database, and then let the tester try to execute. |
|
230 bool hasResult; |
|
231 rv = stmt->ExecuteStep(&hasResult); |
|
232 do_check_success(rv); |
|
233 do_check_true(hasResult); |
|
234 tester->Notify(READ_LOCK); |
|
235 |
|
236 // Make sure the tester finishes its test before we move on. |
|
237 tester->WaitFor(TEST_DONE); |
|
238 } |
|
239 |
|
240 void (*gTests[])(void) = { |
|
241 setup, |
|
242 test_step_locked_does_not_block_main_thread, |
|
243 test_drop_index_does_not_loop, |
|
244 test_drop_table_does_not_loop, |
|
245 }; |
|
246 |
|
247 const char *file = __FILE__; |
|
248 #define TEST_NAME "sqlite3_unlock_notify" |
|
249 #define TEST_FILE file |
|
250 #include "storage_test_harness_tail.h" |