1 /* Any copyright is dedicated to the Public Domain.
2    http://creativecommons.org/publicdomain/zero/1.0/ */
3 
4 #include "storage_test_harness.h"
5 #include "prthread.h"
6 #include "nsIEventTarget.h"
7 #include "nsIInterfaceRequestorUtils.h"
8 #include "mozilla/Attributes.h"
9 
10 #include "sqlite3.h"
11 
12 ////////////////////////////////////////////////////////////////////////////////
13 //// Async Helpers
14 
15 /**
16  * Spins the events loop for current thread until aCondition is true.
17  */
18 void
spin_events_loop_until_true(const bool * const aCondition)19 spin_events_loop_until_true(const bool* const aCondition)
20 {
21   nsCOMPtr<nsIThread> thread(::do_GetCurrentThread());
22   nsresult rv = NS_OK;
23   bool processed = true;
24   while (!(*aCondition) && NS_SUCCEEDED(rv)) {
25     rv = thread->ProcessNextEvent(true, &processed);
26   }
27 }
28 
29 ////////////////////////////////////////////////////////////////////////////////
30 //// mozIStorageStatementCallback implementation
31 
32 class UnownedCallback final : public mozIStorageStatementCallback
33 {
34 public:
35   NS_DECL_ISUPPORTS
36 
37   // Whether the object has been destroyed.
38   static bool sAlive;
39   // Whether the first result was received.
40   static bool sResult;
41   // Whether an error was received.
42   static bool sError;
43 
UnownedCallback(mozIStorageConnection * aDBConn)44   explicit UnownedCallback(mozIStorageConnection* aDBConn)
45   : mDBConn(aDBConn)
46   , mCompleted(false)
47   {
48     sAlive = true;
49     sResult = false;
50     sError = false;
51   }
52 
53 private:
~UnownedCallback()54   ~UnownedCallback()
55   {
56     sAlive = false;
57     blocking_async_close(mDBConn);
58   }
59 
60 public:
HandleResult(mozIStorageResultSet * aResultSet)61   NS_IMETHOD HandleResult(mozIStorageResultSet* aResultSet) override
62   {
63     sResult = true;
64     spin_events_loop_until_true(&mCompleted);
65     if (!sAlive) {
66       NS_RUNTIMEABORT("The statement callback was destroyed prematurely.");
67     }
68     return NS_OK;
69   }
70 
HandleError(mozIStorageError * aError)71   NS_IMETHOD HandleError(mozIStorageError* aError) override
72   {
73     sError = true;
74     spin_events_loop_until_true(&mCompleted);
75     if (!sAlive) {
76       NS_RUNTIMEABORT("The statement callback was destroyed prematurely.");
77     }
78     return NS_OK;
79   }
80 
HandleCompletion(uint16_t aReason)81   NS_IMETHOD HandleCompletion(uint16_t aReason) override
82   {
83     mCompleted = true;
84     return NS_OK;
85   }
86 
87 protected:
88   nsCOMPtr<mozIStorageConnection> mDBConn;
89   bool mCompleted;
90 };
91 
92 NS_IMPL_ISUPPORTS(UnownedCallback, mozIStorageStatementCallback)
93 
94 bool UnownedCallback::sAlive = false;
95 bool UnownedCallback::sResult = false;
96 bool UnownedCallback::sError = false;
97 
98 ////////////////////////////////////////////////////////////////////////////////
99 //// Tests
100 
101 void
test_SpinEventsLoopInHandleResult()102 test_SpinEventsLoopInHandleResult()
103 {
104   nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
105 
106   // Create a test table and populate it.
107   nsCOMPtr<mozIStorageStatement> stmt;
108   db->CreateStatement(NS_LITERAL_CSTRING(
109     "CREATE TABLE test (id INTEGER PRIMARY KEY)"
110   ), getter_AddRefs(stmt));
111   stmt->Execute();
112   stmt->Finalize();
113 
114   db->CreateStatement(NS_LITERAL_CSTRING(
115     "INSERT INTO test (id) VALUES (?)"
116   ), getter_AddRefs(stmt));
117   for (int32_t i = 0; i < 30; ++i) {
118     stmt->BindInt32ByIndex(0, i);
119     stmt->Execute();
120     stmt->Reset();
121   }
122   stmt->Finalize();
123 
124   db->CreateStatement(NS_LITERAL_CSTRING(
125     "SELECT * FROM test"
126   ), getter_AddRefs(stmt));
127   nsCOMPtr<mozIStoragePendingStatement> ps;
128   do_check_success(stmt->ExecuteAsync(new UnownedCallback(db),
129                                       getter_AddRefs(ps)));
130   stmt->Finalize();
131 
132   spin_events_loop_until_true(&UnownedCallback::sResult);
133 }
134 
135 void
test_SpinEventsLoopInHandleError()136 test_SpinEventsLoopInHandleError()
137 {
138   nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
139 
140   // Create a test table and populate it.
141   nsCOMPtr<mozIStorageStatement> stmt;
142   db->CreateStatement(NS_LITERAL_CSTRING(
143     "CREATE TABLE test (id INTEGER PRIMARY KEY)"
144   ), getter_AddRefs(stmt));
145   stmt->Execute();
146   stmt->Finalize();
147 
148   db->CreateStatement(NS_LITERAL_CSTRING(
149     "INSERT INTO test (id) VALUES (1)"
150   ), getter_AddRefs(stmt));
151   stmt->Execute();
152   stmt->Finalize();
153 
154   // This will cause a constraint error.
155   db->CreateStatement(NS_LITERAL_CSTRING(
156     "INSERT INTO test (id) VALUES (1)"
157   ), getter_AddRefs(stmt));
158   nsCOMPtr<mozIStoragePendingStatement> ps;
159   do_check_success(stmt->ExecuteAsync(new UnownedCallback(db),
160                                       getter_AddRefs(ps)));
161   stmt->Finalize();
162 
163   spin_events_loop_until_true(&UnownedCallback::sError);
164 }
165 
166 void (*gTests[])(void) = {
167   test_SpinEventsLoopInHandleResult,
168   test_SpinEventsLoopInHandleError,
169 };
170 
171 const char *file = __FILE__;
172 #define TEST_NAME "test async callbacks with spun event loops"
173 #define TEST_FILE file
174 #include "storage_test_harness_tail.h"
175