michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim set: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 "storage_test_harness.h" michael@0: michael@0: #include "mozilla/ReentrantMonitor.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "mozIStorageStatement.h" michael@0: michael@0: /** michael@0: * This file tests that our implementation around sqlite3_unlock_notify works michael@0: * as expected. michael@0: */ michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// Helpers michael@0: michael@0: enum State { michael@0: STARTING, michael@0: WRITE_LOCK, michael@0: READ_LOCK, michael@0: TEST_DONE michael@0: }; michael@0: michael@0: class DatabaseLocker : public nsRunnable michael@0: { michael@0: public: michael@0: DatabaseLocker(const char* aSQL) michael@0: : monitor("DatabaseLocker::monitor") michael@0: , mSQL(aSQL) michael@0: , mState(STARTING) michael@0: { michael@0: } michael@0: michael@0: void RunInBackground() michael@0: { michael@0: (void)NS_NewThread(getter_AddRefs(mThread)); michael@0: do_check_true(mThread); michael@0: michael@0: do_check_success(mThread->Dispatch(this, NS_DISPATCH_NORMAL)); michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: mozilla::ReentrantMonitorAutoEnter lock(monitor); michael@0: michael@0: nsCOMPtr db(getDatabase()); michael@0: michael@0: nsCString sql(mSQL); michael@0: nsCOMPtr stmt; michael@0: do_check_success(db->CreateStatement(sql, getter_AddRefs(stmt))); michael@0: michael@0: bool hasResult; michael@0: do_check_success(stmt->ExecuteStep(&hasResult)); michael@0: michael@0: Notify(WRITE_LOCK); michael@0: WaitFor(TEST_DONE); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void WaitFor(State aState) michael@0: { michael@0: monitor.AssertCurrentThreadIn(); michael@0: while (mState != aState) { michael@0: do_check_success(monitor.Wait()); michael@0: } michael@0: } michael@0: michael@0: void Notify(State aState) michael@0: { michael@0: monitor.AssertCurrentThreadIn(); michael@0: mState = aState; michael@0: do_check_success(monitor.Notify()); michael@0: } michael@0: michael@0: mozilla::ReentrantMonitor monitor; michael@0: michael@0: protected: michael@0: nsCOMPtr mThread; michael@0: const char *const mSQL; michael@0: State mState; michael@0: }; michael@0: michael@0: class DatabaseTester : public DatabaseLocker michael@0: { michael@0: public: michael@0: DatabaseTester(mozIStorageConnection *aConnection, michael@0: const char* aSQL) michael@0: : DatabaseLocker(aSQL) michael@0: , mConnection(aConnection) michael@0: { michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: mozilla::ReentrantMonitorAutoEnter lock(monitor); michael@0: WaitFor(READ_LOCK); michael@0: michael@0: nsCString sql(mSQL); michael@0: nsCOMPtr stmt; michael@0: do_check_success(mConnection->CreateStatement(sql, getter_AddRefs(stmt))); michael@0: michael@0: bool hasResult; michael@0: nsresult rv = stmt->ExecuteStep(&hasResult); michael@0: do_check_eq(rv, NS_ERROR_FILE_IS_LOCKED); michael@0: michael@0: // Finalize our statement and null out our connection before notifying to michael@0: // ensure that we close on the proper thread. michael@0: rv = stmt->Finalize(); michael@0: do_check_eq(rv, NS_ERROR_FILE_IS_LOCKED); michael@0: mConnection = nullptr; michael@0: michael@0: Notify(TEST_DONE); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: nsCOMPtr mConnection; michael@0: }; michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// Test Functions michael@0: michael@0: void michael@0: setup() michael@0: { michael@0: nsCOMPtr db(getDatabase()); michael@0: michael@0: // Create and populate a dummy table. michael@0: nsresult rv = db->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "CREATE TABLE test (id INTEGER PRIMARY KEY, data STRING)" michael@0: )); michael@0: do_check_success(rv); michael@0: rv = db->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "INSERT INTO test (data) VALUES ('foo')" michael@0: )); michael@0: do_check_success(rv); michael@0: rv = db->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "INSERT INTO test (data) VALUES ('bar')" michael@0: )); michael@0: do_check_success(rv); michael@0: rv = db->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "CREATE UNIQUE INDEX unique_data ON test (data)" michael@0: )); michael@0: do_check_success(rv); michael@0: } michael@0: michael@0: void michael@0: test_step_locked_does_not_block_main_thread() michael@0: { michael@0: nsCOMPtr db(getDatabase()); michael@0: michael@0: // Need to prepare our statement ahead of time so we make sure to only test michael@0: // step and not prepare. michael@0: nsCOMPtr stmt; michael@0: nsresult rv = db->CreateStatement(NS_LITERAL_CSTRING( michael@0: "INSERT INTO test (data) VALUES ('test1')" michael@0: ), getter_AddRefs(stmt)); michael@0: do_check_success(rv); michael@0: michael@0: nsRefPtr locker(new DatabaseLocker("SELECT * FROM test")); michael@0: do_check_true(locker); michael@0: mozilla::ReentrantMonitorAutoEnter lock(locker->monitor); michael@0: locker->RunInBackground(); michael@0: michael@0: // Wait for the locker to notify us that it has locked the database properly. michael@0: locker->WaitFor(WRITE_LOCK); michael@0: michael@0: bool hasResult; michael@0: rv = stmt->ExecuteStep(&hasResult); michael@0: do_check_eq(rv, NS_ERROR_FILE_IS_LOCKED); michael@0: michael@0: locker->Notify(TEST_DONE); michael@0: } michael@0: michael@0: void michael@0: test_drop_index_does_not_loop() michael@0: { michael@0: nsCOMPtr db(getDatabase()); michael@0: michael@0: // Need to prepare our statement ahead of time so we make sure to only test michael@0: // step and not prepare. michael@0: nsCOMPtr stmt; michael@0: nsresult rv = db->CreateStatement(NS_LITERAL_CSTRING( michael@0: "SELECT * FROM test" michael@0: ), getter_AddRefs(stmt)); michael@0: do_check_success(rv); michael@0: michael@0: nsRefPtr tester = michael@0: new DatabaseTester(db, "DROP INDEX unique_data"); michael@0: do_check_true(tester); michael@0: mozilla::ReentrantMonitorAutoEnter lock(tester->monitor); michael@0: tester->RunInBackground(); michael@0: michael@0: // Hold a read lock on the database, and then let the tester try to execute. michael@0: bool hasResult; michael@0: rv = stmt->ExecuteStep(&hasResult); michael@0: do_check_success(rv); michael@0: do_check_true(hasResult); michael@0: tester->Notify(READ_LOCK); michael@0: michael@0: // Make sure the tester finishes its test before we move on. michael@0: tester->WaitFor(TEST_DONE); michael@0: } michael@0: michael@0: void michael@0: test_drop_table_does_not_loop() michael@0: { michael@0: nsCOMPtr db(getDatabase()); michael@0: michael@0: // Need to prepare our statement ahead of time so we make sure to only test michael@0: // step and not prepare. michael@0: nsCOMPtr stmt; michael@0: nsresult rv = db->CreateStatement(NS_LITERAL_CSTRING( michael@0: "SELECT * FROM test" michael@0: ), getter_AddRefs(stmt)); michael@0: do_check_success(rv); michael@0: michael@0: nsRefPtr tester(new DatabaseTester(db, "DROP TABLE test")); michael@0: do_check_true(tester); michael@0: mozilla::ReentrantMonitorAutoEnter lock(tester->monitor); michael@0: tester->RunInBackground(); michael@0: michael@0: // Hold a read lock on the database, and then let the tester try to execute. michael@0: bool hasResult; michael@0: rv = stmt->ExecuteStep(&hasResult); michael@0: do_check_success(rv); michael@0: do_check_true(hasResult); michael@0: tester->Notify(READ_LOCK); michael@0: michael@0: // Make sure the tester finishes its test before we move on. michael@0: tester->WaitFor(TEST_DONE); michael@0: } michael@0: michael@0: void (*gTests[])(void) = { michael@0: setup, michael@0: test_step_locked_does_not_block_main_thread, michael@0: test_drop_index_does_not_loop, michael@0: test_drop_table_does_not_loop, michael@0: }; michael@0: michael@0: const char *file = __FILE__; michael@0: #define TEST_NAME "sqlite3_unlock_notify" michael@0: #define TEST_FILE file michael@0: #include "storage_test_harness_tail.h"