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       MOZ_CRASH("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       MOZ_CRASH("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 
TEST(storage_async_callbacks_with_spun_event_loops,SpinEventsLoopInHandleResult)101 TEST(storage_async_callbacks_with_spun_event_loops, SpinEventsLoopInHandleResult)
102 {
103   nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
104 
105   // Create a test table and populate it.
106   nsCOMPtr<mozIStorageStatement> stmt;
107   db->CreateStatement(NS_LITERAL_CSTRING(
108     "CREATE TABLE test (id INTEGER PRIMARY KEY)"
109   ), getter_AddRefs(stmt));
110   stmt->Execute();
111   stmt->Finalize();
112 
113   db->CreateStatement(NS_LITERAL_CSTRING(
114     "INSERT INTO test (id) VALUES (?)"
115   ), getter_AddRefs(stmt));
116   for (int32_t i = 0; i < 30; ++i) {
117     stmt->BindInt32ByIndex(0, i);
118     stmt->Execute();
119     stmt->Reset();
120   }
121   stmt->Finalize();
122 
123   db->CreateStatement(NS_LITERAL_CSTRING(
124     "SELECT * FROM test"
125   ), getter_AddRefs(stmt));
126   nsCOMPtr<mozIStoragePendingStatement> ps;
127   do_check_success(stmt->ExecuteAsync(new UnownedCallback(db),
128                                       getter_AddRefs(ps)));
129   stmt->Finalize();
130 
131   spin_events_loop_until_true(&UnownedCallback::sResult);
132 }
133 
TEST(storage_async_callbacks_with_spun_event_loops,SpinEventsLoopInHandleError)134 TEST(storage_async_callbacks_with_spun_event_loops, SpinEventsLoopInHandleError)
135 {
136   nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
137 
138   // Create a test table and populate it.
139   nsCOMPtr<mozIStorageStatement> stmt;
140   db->CreateStatement(NS_LITERAL_CSTRING(
141     "CREATE TABLE test (id INTEGER PRIMARY KEY)"
142   ), getter_AddRefs(stmt));
143   stmt->Execute();
144   stmt->Finalize();
145 
146   db->CreateStatement(NS_LITERAL_CSTRING(
147     "INSERT INTO test (id) VALUES (1)"
148   ), getter_AddRefs(stmt));
149   stmt->Execute();
150   stmt->Finalize();
151 
152   // This will cause a constraint error.
153   db->CreateStatement(NS_LITERAL_CSTRING(
154     "INSERT INTO test (id) VALUES (1)"
155   ), getter_AddRefs(stmt));
156   nsCOMPtr<mozIStoragePendingStatement> ps;
157   do_check_success(stmt->ExecuteAsync(new UnownedCallback(db),
158                                       getter_AddRefs(ps)));
159   stmt->Finalize();
160 
161   spin_events_loop_until_true(&UnownedCallback::sError);
162 }
163