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 "nsIInterfaceRequestorUtils.h"
7 #include "mozilla/Attributes.h"
8 
9 #include "sqlite3.h"
10 
11 ////////////////////////////////////////////////////////////////////////////////
12 //// Async Helpers
13 
14 /**
15  * Spins the events loop for current thread until aCondition is true.
16  */
spin_events_loop_until_true(const bool * const aCondition)17 void spin_events_loop_until_true(const bool* const aCondition) {
18   nsCOMPtr<nsIThread> thread(::do_GetCurrentThread());
19   nsresult rv = NS_OK;
20   bool processed = true;
21   while (!(*aCondition) && NS_SUCCEEDED(rv)) {
22     rv = thread->ProcessNextEvent(true, &processed);
23   }
24 }
25 
26 ////////////////////////////////////////////////////////////////////////////////
27 //// mozIStorageStatementCallback implementation
28 
29 class UnownedCallback final : public mozIStorageStatementCallback {
30  public:
31   NS_DECL_ISUPPORTS
32 
33   // Whether the object has been destroyed.
34   static bool sAlive;
35   // Whether the first result was received.
36   static bool sResult;
37   // Whether an error was received.
38   static bool sError;
39 
UnownedCallback(mozIStorageConnection * aDBConn)40   explicit UnownedCallback(mozIStorageConnection* aDBConn)
41       : mDBConn(aDBConn), mCompleted(false) {
42     sAlive = true;
43     sResult = false;
44     sError = false;
45   }
46 
47  private:
~UnownedCallback()48   ~UnownedCallback() {
49     sAlive = false;
50     blocking_async_close(mDBConn);
51   }
52 
53  public:
HandleResult(mozIStorageResultSet * aResultSet)54   NS_IMETHOD HandleResult(mozIStorageResultSet* aResultSet) override {
55     sResult = true;
56     spin_events_loop_until_true(&mCompleted);
57     if (!sAlive) {
58       MOZ_CRASH("The statement callback was destroyed prematurely.");
59     }
60     return NS_OK;
61   }
62 
HandleError(mozIStorageError * aError)63   NS_IMETHOD HandleError(mozIStorageError* aError) override {
64     sError = true;
65     spin_events_loop_until_true(&mCompleted);
66     if (!sAlive) {
67       MOZ_CRASH("The statement callback was destroyed prematurely.");
68     }
69     return NS_OK;
70   }
71 
HandleCompletion(uint16_t aReason)72   NS_IMETHOD HandleCompletion(uint16_t aReason) override {
73     mCompleted = true;
74     return NS_OK;
75   }
76 
77  protected:
78   nsCOMPtr<mozIStorageConnection> mDBConn;
79   bool mCompleted;
80 };
81 
82 NS_IMPL_ISUPPORTS(UnownedCallback, mozIStorageStatementCallback)
83 
84 bool UnownedCallback::sAlive = false;
85 bool UnownedCallback::sResult = false;
86 bool UnownedCallback::sError = false;
87 
88 ////////////////////////////////////////////////////////////////////////////////
89 //// Tests
90 
TEST(storage_async_callbacks_with_spun_event_loops,SpinEventsLoopInHandleResult)91 TEST(storage_async_callbacks_with_spun_event_loops,
92      SpinEventsLoopInHandleResult)
93 {
94   nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
95 
96   // Create a test table and populate it.
97   nsCOMPtr<mozIStorageStatement> stmt;
98   db->CreateStatement("CREATE TABLE test (id INTEGER PRIMARY KEY)"_ns,
99                       getter_AddRefs(stmt));
100   stmt->Execute();
101   stmt->Finalize();
102 
103   db->CreateStatement("INSERT INTO test (id) VALUES (?)"_ns,
104                       getter_AddRefs(stmt));
105   for (int32_t i = 0; i < 30; ++i) {
106     stmt->BindInt32ByIndex(0, i);
107     stmt->Execute();
108     stmt->Reset();
109   }
110   stmt->Finalize();
111 
112   db->CreateStatement("SELECT * FROM test"_ns, getter_AddRefs(stmt));
113   nsCOMPtr<mozIStoragePendingStatement> ps;
114   do_check_success(
115       stmt->ExecuteAsync(new UnownedCallback(db), getter_AddRefs(ps)));
116   stmt->Finalize();
117 
118   spin_events_loop_until_true(&UnownedCallback::sResult);
119 }
120 
TEST(storage_async_callbacks_with_spun_event_loops,SpinEventsLoopInHandleError)121 TEST(storage_async_callbacks_with_spun_event_loops, SpinEventsLoopInHandleError)
122 {
123   nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
124 
125   // Create a test table and populate it.
126   nsCOMPtr<mozIStorageStatement> stmt;
127   db->CreateStatement("CREATE TABLE test (id INTEGER PRIMARY KEY)"_ns,
128                       getter_AddRefs(stmt));
129   stmt->Execute();
130   stmt->Finalize();
131 
132   db->CreateStatement("INSERT INTO test (id) VALUES (1)"_ns,
133                       getter_AddRefs(stmt));
134   stmt->Execute();
135   stmt->Finalize();
136 
137   // This will cause a constraint error.
138   db->CreateStatement("INSERT INTO test (id) VALUES (1)"_ns,
139                       getter_AddRefs(stmt));
140   nsCOMPtr<mozIStoragePendingStatement> ps;
141   do_check_success(
142       stmt->ExecuteAsync(new UnownedCallback(db), getter_AddRefs(ps)));
143   stmt->Finalize();
144 
145   spin_events_loop_until_true(&UnownedCallback::sError);
146 }
147