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 : /* ***** BEGIN LICENSE BLOCK *****
4 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 : *
6 : * The contents of this file are subject to the Mozilla Public License Version
7 : * 1.1 (the "License"); you may not use this file except in compliance with
8 : * the License. You may obtain a copy of the License at
9 : * http://www.mozilla.org/MPL/
10 : *
11 : * Software distributed under the License is distributed on an "AS IS" basis,
12 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 : * for the specific language governing rights and limitations under the
14 : * License.
15 : *
16 : * The Original Code is storage test code.
17 : *
18 : * The Initial Developer of the Original Code is
19 : * The Mozilla Foundation.
20 : * Portions created by the Initial Developer are Copyright (C) 2010
21 : * the Initial Developer. All Rights Reserved.
22 : *
23 : * Contributor(s):
24 : * Shawn Wilsher <me@shawnwilsher.com> (Original Author)
25 : *
26 : * Alternatively, the contents of this file may be used under the terms of
27 : * either the GNU General Public License Version 2 or later (the "GPL"), or
28 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 : * in which case the provisions of the GPL or the LGPL are applicable instead
30 : * of those above. If you wish to allow use of your version of this file only
31 : * under the terms of either the GPL or the LGPL, and not to allow others to
32 : * use your version of this file under the terms of the MPL, indicate your
33 : * decision by deleting the provisions above and replace them with the notice
34 : * and other provisions required by the GPL or the LGPL. If you do not delete
35 : * the provisions above, a recipient may use your version of this file under
36 : * the terms of any one of the MPL, the GPL or the LGPL.
37 : *
38 : * ***** END LICENSE BLOCK ***** */
39 :
40 : #include "storage_test_harness.h"
41 :
42 : #include "mozilla/ReentrantMonitor.h"
43 : #include "nsThreadUtils.h"
44 : #include "mozIStorageStatement.h"
45 :
46 : /**
47 : * This file tests that our implementation around sqlite3_unlock_notify works
48 : * as expected.
49 : */
50 :
51 : ////////////////////////////////////////////////////////////////////////////////
52 : //// Helpers
53 :
54 : enum State {
55 : STARTING,
56 : WRITE_LOCK,
57 : READ_LOCK,
58 : TEST_DONE
59 : };
60 :
61 : class DatabaseLocker : public nsRunnable
62 8 : {
63 : public:
64 3 : DatabaseLocker(const char* aSQL)
65 : : monitor("DatabaseLocker::monitor")
66 : , mSQL(aSQL)
67 3 : , mState(STARTING)
68 : {
69 3 : }
70 :
71 3 : void RunInBackground()
72 : {
73 3 : (void)NS_NewThread(getter_AddRefs(mThread));
74 3 : do_check_true(mThread);
75 :
76 3 : do_check_success(mThread->Dispatch(this, NS_DISPATCH_NORMAL));
77 3 : }
78 :
79 1 : NS_IMETHOD Run()
80 : {
81 2 : mozilla::ReentrantMonitorAutoEnter lock(monitor);
82 :
83 2 : nsCOMPtr<mozIStorageConnection> db(getDatabase());
84 :
85 2 : nsCString sql(mSQL);
86 2 : nsCOMPtr<mozIStorageStatement> stmt;
87 1 : do_check_success(db->CreateStatement(sql, getter_AddRefs(stmt)));
88 :
89 : bool hasResult;
90 1 : do_check_success(stmt->ExecuteStep(&hasResult));
91 :
92 1 : Notify(WRITE_LOCK);
93 1 : WaitFor(TEST_DONE);
94 :
95 1 : return NS_OK;
96 : }
97 :
98 6 : void WaitFor(State aState)
99 : {
100 6 : monitor.AssertCurrentThreadIn();
101 16 : while (mState != aState) {
102 4 : do_check_success(monitor.Wait());
103 : }
104 6 : }
105 :
106 6 : void Notify(State aState)
107 : {
108 6 : monitor.AssertCurrentThreadIn();
109 6 : mState = aState;
110 6 : do_check_success(monitor.Notify());
111 6 : }
112 :
113 : mozilla::ReentrantMonitor monitor;
114 :
115 : protected:
116 : nsCOMPtr<nsIThread> mThread;
117 : const char *const mSQL;
118 : State mState;
119 : };
120 :
121 : class DatabaseTester : public DatabaseLocker
122 8 : {
123 : public:
124 2 : DatabaseTester(mozIStorageConnection *aConnection,
125 : const char* aSQL)
126 : : DatabaseLocker(aSQL)
127 2 : , mConnection(aConnection)
128 : {
129 2 : }
130 :
131 2 : NS_IMETHOD Run()
132 : {
133 4 : mozilla::ReentrantMonitorAutoEnter lock(monitor);
134 2 : WaitFor(READ_LOCK);
135 :
136 4 : nsCString sql(mSQL);
137 4 : nsCOMPtr<mozIStorageStatement> stmt;
138 2 : do_check_success(mConnection->CreateStatement(sql, getter_AddRefs(stmt)));
139 :
140 : bool hasResult;
141 2 : nsresult rv = stmt->ExecuteStep(&hasResult);
142 2 : do_check_eq(rv, NS_ERROR_FILE_IS_LOCKED);
143 :
144 : // Finalize our statement and null out our connection before notifying to
145 : // ensure that we close on the proper thread.
146 2 : rv = stmt->Finalize();
147 2 : do_check_eq(rv, NS_ERROR_FILE_IS_LOCKED);
148 2 : mConnection = nsnull;
149 :
150 2 : Notify(TEST_DONE);
151 :
152 2 : return NS_OK;
153 : }
154 :
155 : private:
156 : nsCOMPtr<mozIStorageConnection> mConnection;
157 : State mState;
158 : };
159 :
160 : ////////////////////////////////////////////////////////////////////////////////
161 : //// Test Functions
162 :
163 : void
164 1 : setup()
165 : {
166 2 : nsCOMPtr<mozIStorageConnection> db(getDatabase());
167 :
168 : // Create and populate a dummy table.
169 2 : nsresult rv = db->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
170 : "CREATE TABLE test (id INTEGER PRIMARY KEY, data STRING)"
171 1 : ));
172 1 : do_check_success(rv);
173 2 : rv = db->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
174 : "INSERT INTO test (data) VALUES ('foo')"
175 1 : ));
176 1 : do_check_success(rv);
177 2 : rv = db->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
178 : "INSERT INTO test (data) VALUES ('bar')"
179 1 : ));
180 1 : do_check_success(rv);
181 2 : rv = db->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
182 : "CREATE UNIQUE INDEX unique_data ON test (data)"
183 1 : ));
184 1 : do_check_success(rv);
185 1 : }
186 :
187 : void
188 1 : test_step_locked_does_not_block_main_thread()
189 : {
190 2 : nsCOMPtr<mozIStorageConnection> db(getDatabase());
191 :
192 : // Need to prepare our statement ahead of time so we make sure to only test
193 : // step and not prepare.
194 2 : nsCOMPtr<mozIStorageStatement> stmt;
195 2 : nsresult rv = db->CreateStatement(NS_LITERAL_CSTRING(
196 : "INSERT INTO test (data) VALUES ('test1')"
197 2 : ), getter_AddRefs(stmt));
198 1 : do_check_success(rv);
199 :
200 2 : nsRefPtr<DatabaseLocker> locker(new DatabaseLocker("SELECT * FROM test"));
201 1 : do_check_true(locker);
202 2 : mozilla::ReentrantMonitorAutoEnter lock(locker->monitor);
203 1 : locker->RunInBackground();
204 :
205 : // Wait for the locker to notify us that it has locked the database properly.
206 1 : locker->WaitFor(WRITE_LOCK);
207 :
208 : bool hasResult;
209 1 : rv = stmt->ExecuteStep(&hasResult);
210 1 : do_check_eq(rv, NS_ERROR_FILE_IS_LOCKED);
211 :
212 1 : locker->Notify(TEST_DONE);
213 1 : }
214 :
215 : void
216 1 : test_drop_index_does_not_loop()
217 : {
218 2 : nsCOMPtr<mozIStorageConnection> db(getDatabase());
219 :
220 : // Need to prepare our statement ahead of time so we make sure to only test
221 : // step and not prepare.
222 2 : nsCOMPtr<mozIStorageStatement> stmt;
223 2 : nsresult rv = db->CreateStatement(NS_LITERAL_CSTRING(
224 : "SELECT * FROM test"
225 2 : ), getter_AddRefs(stmt));
226 1 : do_check_success(rv);
227 :
228 : nsRefPtr<DatabaseTester> tester =
229 3 : new DatabaseTester(db, "DROP INDEX unique_data");
230 1 : do_check_true(tester);
231 2 : mozilla::ReentrantMonitorAutoEnter lock(tester->monitor);
232 1 : tester->RunInBackground();
233 :
234 : // Hold a read lock on the database, and then let the tester try to execute.
235 : bool hasResult;
236 1 : rv = stmt->ExecuteStep(&hasResult);
237 1 : do_check_success(rv);
238 1 : do_check_true(hasResult);
239 1 : tester->Notify(READ_LOCK);
240 :
241 : // Make sure the tester finishes its test before we move on.
242 1 : tester->WaitFor(TEST_DONE);
243 1 : }
244 :
245 : void
246 1 : test_drop_table_does_not_loop()
247 : {
248 2 : nsCOMPtr<mozIStorageConnection> db(getDatabase());
249 :
250 : // Need to prepare our statement ahead of time so we make sure to only test
251 : // step and not prepare.
252 2 : nsCOMPtr<mozIStorageStatement> stmt;
253 2 : nsresult rv = db->CreateStatement(NS_LITERAL_CSTRING(
254 : "SELECT * FROM test"
255 2 : ), getter_AddRefs(stmt));
256 1 : do_check_success(rv);
257 :
258 3 : nsRefPtr<DatabaseTester> tester(new DatabaseTester(db, "DROP TABLE test"));
259 1 : do_check_true(tester);
260 2 : mozilla::ReentrantMonitorAutoEnter lock(tester->monitor);
261 1 : tester->RunInBackground();
262 :
263 : // Hold a read lock on the database, and then let the tester try to execute.
264 : bool hasResult;
265 1 : rv = stmt->ExecuteStep(&hasResult);
266 1 : do_check_success(rv);
267 1 : do_check_true(hasResult);
268 1 : tester->Notify(READ_LOCK);
269 :
270 : // Make sure the tester finishes its test before we move on.
271 1 : tester->WaitFor(TEST_DONE);
272 1 : }
273 :
274 : void (*gTests[])(void) = {
275 : setup,
276 : test_step_locked_does_not_block_main_thread,
277 : test_drop_index_does_not_loop,
278 : test_drop_table_does_not_loop,
279 : };
280 :
281 : const char *file = __FILE__;
282 : #define TEST_NAME "sqlite3_unlock_notify"
283 : #define TEST_FILE file
284 : #include "storage_test_harness_tail.h"
|