|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- |
|
2 * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : |
|
3 * This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 #include "TestHarness.h" |
|
8 #include "nsMemory.h" |
|
9 #include "nsThreadUtils.h" |
|
10 #include "nsNetUtil.h" |
|
11 #include "nsDocShellCID.h" |
|
12 |
|
13 #include "nsToolkitCompsCID.h" |
|
14 #include "nsINavHistoryService.h" |
|
15 #include "nsIObserverService.h" |
|
16 #include "mozilla/IHistory.h" |
|
17 #include "mozIStorageConnection.h" |
|
18 #include "mozIStorageStatement.h" |
|
19 #include "mozIStorageAsyncStatement.h" |
|
20 #include "mozIStorageStatementCallback.h" |
|
21 #include "mozIStoragePendingStatement.h" |
|
22 #include "nsPIPlacesDatabase.h" |
|
23 #include "nsIObserver.h" |
|
24 #include "prinrval.h" |
|
25 #include "mozilla/Attributes.h" |
|
26 |
|
27 #define WAITFORTOPIC_TIMEOUT_SECONDS 5 |
|
28 |
|
29 |
|
30 static size_t gTotalTests = 0; |
|
31 static size_t gPassedTests = 0; |
|
32 |
|
33 #define do_check_true(aCondition) \ |
|
34 PR_BEGIN_MACRO \ |
|
35 gTotalTests++; \ |
|
36 if (aCondition) { \ |
|
37 gPassedTests++; \ |
|
38 } else { \ |
|
39 fail("%s | Expected true, got false at line %d", __FILE__, __LINE__); \ |
|
40 } \ |
|
41 PR_END_MACRO |
|
42 |
|
43 #define do_check_false(aCondition) \ |
|
44 PR_BEGIN_MACRO \ |
|
45 gTotalTests++; \ |
|
46 if (!aCondition) { \ |
|
47 gPassedTests++; \ |
|
48 } else { \ |
|
49 fail("%s | Expected false, got true at line %d", __FILE__, __LINE__); \ |
|
50 } \ |
|
51 PR_END_MACRO |
|
52 |
|
53 #define do_check_success(aResult) \ |
|
54 do_check_true(NS_SUCCEEDED(aResult)) |
|
55 |
|
56 #ifdef LINUX |
|
57 // XXX Linux opt builds on tinderbox are orange due to linking with stdlib. |
|
58 // This is sad and annoying, but it's a workaround that works. |
|
59 #define do_check_eq(aExpected, aActual) \ |
|
60 do_check_true(aExpected == aActual) |
|
61 #else |
|
62 #include <sstream> |
|
63 |
|
64 #define do_check_eq(aActual, aExpected) \ |
|
65 PR_BEGIN_MACRO \ |
|
66 gTotalTests++; \ |
|
67 if (aExpected == aActual) { \ |
|
68 gPassedTests++; \ |
|
69 } else { \ |
|
70 std::ostringstream temp; \ |
|
71 temp << __FILE__ << " | Expected '" << aExpected << "', got '"; \ |
|
72 temp << aActual <<"' at line " << __LINE__; \ |
|
73 fail(temp.str().c_str()); \ |
|
74 } \ |
|
75 PR_END_MACRO |
|
76 #endif |
|
77 |
|
78 struct Test |
|
79 { |
|
80 void (*func)(void); |
|
81 const char* const name; |
|
82 }; |
|
83 #define TEST(aName) \ |
|
84 {aName, #aName} |
|
85 |
|
86 /** |
|
87 * Runs the next text. |
|
88 */ |
|
89 void run_next_test(); |
|
90 |
|
91 /** |
|
92 * To be used around asynchronous work. |
|
93 */ |
|
94 void do_test_pending(); |
|
95 void do_test_finished(); |
|
96 |
|
97 /** |
|
98 * Spins current thread until a topic is received. |
|
99 */ |
|
100 class WaitForTopicSpinner MOZ_FINAL : public nsIObserver |
|
101 { |
|
102 public: |
|
103 NS_DECL_ISUPPORTS |
|
104 |
|
105 WaitForTopicSpinner(const char* const aTopic) |
|
106 : mTopicReceived(false) |
|
107 , mStartTime(PR_IntervalNow()) |
|
108 { |
|
109 nsCOMPtr<nsIObserverService> observerService = |
|
110 do_GetService(NS_OBSERVERSERVICE_CONTRACTID); |
|
111 do_check_true(observerService); |
|
112 (void)observerService->AddObserver(this, aTopic, false); |
|
113 } |
|
114 |
|
115 void Spin() { |
|
116 while (!mTopicReceived) { |
|
117 if ((PR_IntervalNow() - mStartTime) > (WAITFORTOPIC_TIMEOUT_SECONDS * PR_USEC_PER_SEC)) { |
|
118 // Timed out waiting for the topic. |
|
119 do_check_true(false); |
|
120 break; |
|
121 } |
|
122 (void)NS_ProcessNextEvent(); |
|
123 } |
|
124 } |
|
125 |
|
126 NS_IMETHOD Observe(nsISupports* aSubject, |
|
127 const char* aTopic, |
|
128 const char16_t* aData) |
|
129 { |
|
130 mTopicReceived = true; |
|
131 nsCOMPtr<nsIObserverService> observerService = |
|
132 do_GetService(NS_OBSERVERSERVICE_CONTRACTID); |
|
133 do_check_true(observerService); |
|
134 (void)observerService->RemoveObserver(this, aTopic); |
|
135 return NS_OK; |
|
136 } |
|
137 |
|
138 private: |
|
139 bool mTopicReceived; |
|
140 PRIntervalTime mStartTime; |
|
141 }; |
|
142 NS_IMPL_ISUPPORTS( |
|
143 WaitForTopicSpinner, |
|
144 nsIObserver |
|
145 ) |
|
146 |
|
147 /** |
|
148 * Spins current thread until an async statement is executed. |
|
149 */ |
|
150 class AsyncStatementSpinner MOZ_FINAL : public mozIStorageStatementCallback |
|
151 { |
|
152 public: |
|
153 NS_DECL_ISUPPORTS |
|
154 NS_DECL_MOZISTORAGESTATEMENTCALLBACK |
|
155 |
|
156 AsyncStatementSpinner(); |
|
157 void SpinUntilCompleted(); |
|
158 uint16_t completionReason; |
|
159 |
|
160 protected: |
|
161 volatile bool mCompleted; |
|
162 }; |
|
163 |
|
164 NS_IMPL_ISUPPORTS(AsyncStatementSpinner, |
|
165 mozIStorageStatementCallback) |
|
166 |
|
167 AsyncStatementSpinner::AsyncStatementSpinner() |
|
168 : completionReason(0) |
|
169 , mCompleted(false) |
|
170 { |
|
171 } |
|
172 |
|
173 NS_IMETHODIMP |
|
174 AsyncStatementSpinner::HandleResult(mozIStorageResultSet *aResultSet) |
|
175 { |
|
176 return NS_OK; |
|
177 } |
|
178 |
|
179 NS_IMETHODIMP |
|
180 AsyncStatementSpinner::HandleError(mozIStorageError *aError) |
|
181 { |
|
182 return NS_OK; |
|
183 } |
|
184 |
|
185 NS_IMETHODIMP |
|
186 AsyncStatementSpinner::HandleCompletion(uint16_t aReason) |
|
187 { |
|
188 completionReason = aReason; |
|
189 mCompleted = true; |
|
190 return NS_OK; |
|
191 } |
|
192 |
|
193 void AsyncStatementSpinner::SpinUntilCompleted() |
|
194 { |
|
195 nsCOMPtr<nsIThread> thread(::do_GetCurrentThread()); |
|
196 nsresult rv = NS_OK; |
|
197 bool processed = true; |
|
198 while (!mCompleted && NS_SUCCEEDED(rv)) { |
|
199 rv = thread->ProcessNextEvent(true, &processed); |
|
200 } |
|
201 } |
|
202 |
|
203 struct PlaceRecord |
|
204 { |
|
205 int64_t id; |
|
206 int32_t hidden; |
|
207 int32_t typed; |
|
208 int32_t visitCount; |
|
209 nsCString guid; |
|
210 }; |
|
211 |
|
212 struct VisitRecord |
|
213 { |
|
214 int64_t id; |
|
215 int64_t lastVisitId; |
|
216 int32_t transitionType; |
|
217 }; |
|
218 |
|
219 already_AddRefed<mozilla::IHistory> |
|
220 do_get_IHistory() |
|
221 { |
|
222 nsCOMPtr<mozilla::IHistory> history = do_GetService(NS_IHISTORY_CONTRACTID); |
|
223 do_check_true(history); |
|
224 return history.forget(); |
|
225 } |
|
226 |
|
227 already_AddRefed<nsINavHistoryService> |
|
228 do_get_NavHistory() |
|
229 { |
|
230 nsCOMPtr<nsINavHistoryService> serv = |
|
231 do_GetService(NS_NAVHISTORYSERVICE_CONTRACTID); |
|
232 do_check_true(serv); |
|
233 return serv.forget(); |
|
234 } |
|
235 |
|
236 already_AddRefed<mozIStorageConnection> |
|
237 do_get_db() |
|
238 { |
|
239 nsCOMPtr<nsINavHistoryService> history = do_get_NavHistory(); |
|
240 nsCOMPtr<nsPIPlacesDatabase> database = do_QueryInterface(history); |
|
241 do_check_true(database); |
|
242 |
|
243 nsCOMPtr<mozIStorageConnection> dbConn; |
|
244 nsresult rv = database->GetDBConnection(getter_AddRefs(dbConn)); |
|
245 do_check_success(rv); |
|
246 return dbConn.forget(); |
|
247 } |
|
248 |
|
249 /** |
|
250 * Get the place record from the database. |
|
251 * |
|
252 * @param aURI The unique URI of the place we are looking up |
|
253 * @param result Out parameter where the result is stored |
|
254 */ |
|
255 void |
|
256 do_get_place(nsIURI* aURI, PlaceRecord& result) |
|
257 { |
|
258 nsCOMPtr<mozIStorageConnection> dbConn = do_get_db(); |
|
259 nsCOMPtr<mozIStorageStatement> stmt; |
|
260 |
|
261 nsCString spec; |
|
262 nsresult rv = aURI->GetSpec(spec); |
|
263 do_check_success(rv); |
|
264 |
|
265 rv = dbConn->CreateStatement(NS_LITERAL_CSTRING( |
|
266 "SELECT id, hidden, typed, visit_count, guid FROM moz_places " |
|
267 "WHERE url=?1 " |
|
268 ), getter_AddRefs(stmt)); |
|
269 do_check_success(rv); |
|
270 |
|
271 rv = stmt->BindUTF8StringByIndex(0, spec); |
|
272 do_check_success(rv); |
|
273 |
|
274 bool hasResults; |
|
275 rv = stmt->ExecuteStep(&hasResults); |
|
276 do_check_success(rv); |
|
277 if (!hasResults) { |
|
278 result.id = 0; |
|
279 return; |
|
280 } |
|
281 |
|
282 rv = stmt->GetInt64(0, &result.id); |
|
283 do_check_success(rv); |
|
284 rv = stmt->GetInt32(1, &result.hidden); |
|
285 do_check_success(rv); |
|
286 rv = stmt->GetInt32(2, &result.typed); |
|
287 do_check_success(rv); |
|
288 rv = stmt->GetInt32(3, &result.visitCount); |
|
289 do_check_success(rv); |
|
290 rv = stmt->GetUTF8String(4, result.guid); |
|
291 do_check_success(rv); |
|
292 } |
|
293 |
|
294 /** |
|
295 * Gets the most recent visit to a place. |
|
296 * |
|
297 * @param placeID ID from the moz_places table |
|
298 * @param result Out parameter where visit is stored |
|
299 */ |
|
300 void |
|
301 do_get_lastVisit(int64_t placeId, VisitRecord& result) |
|
302 { |
|
303 nsCOMPtr<mozIStorageConnection> dbConn = do_get_db(); |
|
304 nsCOMPtr<mozIStorageStatement> stmt; |
|
305 |
|
306 nsresult rv = dbConn->CreateStatement(NS_LITERAL_CSTRING( |
|
307 "SELECT id, from_visit, visit_type FROM moz_historyvisits " |
|
308 "WHERE place_id=?1 " |
|
309 "LIMIT 1" |
|
310 ), getter_AddRefs(stmt)); |
|
311 do_check_success(rv); |
|
312 |
|
313 rv = stmt->BindInt64ByIndex(0, placeId); |
|
314 do_check_success(rv); |
|
315 |
|
316 bool hasResults; |
|
317 rv = stmt->ExecuteStep(&hasResults); |
|
318 do_check_success(rv); |
|
319 |
|
320 if (!hasResults) { |
|
321 result.id = 0; |
|
322 return; |
|
323 } |
|
324 |
|
325 rv = stmt->GetInt64(0, &result.id); |
|
326 do_check_success(rv); |
|
327 rv = stmt->GetInt64(1, &result.lastVisitId); |
|
328 do_check_success(rv); |
|
329 rv = stmt->GetInt32(2, &result.transitionType); |
|
330 do_check_success(rv); |
|
331 } |
|
332 |
|
333 void |
|
334 do_wait_async_updates() { |
|
335 nsCOMPtr<mozIStorageConnection> db = do_get_db(); |
|
336 nsCOMPtr<mozIStorageAsyncStatement> stmt; |
|
337 |
|
338 db->CreateAsyncStatement(NS_LITERAL_CSTRING("BEGIN EXCLUSIVE"), |
|
339 getter_AddRefs(stmt)); |
|
340 nsCOMPtr<mozIStoragePendingStatement> pending; |
|
341 (void)stmt->ExecuteAsync(nullptr, getter_AddRefs(pending)); |
|
342 |
|
343 db->CreateAsyncStatement(NS_LITERAL_CSTRING("COMMIT"), |
|
344 getter_AddRefs(stmt)); |
|
345 nsRefPtr<AsyncStatementSpinner> spinner = new AsyncStatementSpinner(); |
|
346 (void)stmt->ExecuteAsync(spinner, getter_AddRefs(pending)); |
|
347 |
|
348 spinner->SpinUntilCompleted(); |
|
349 } |
|
350 |
|
351 /** |
|
352 * Adds a URI to the database. |
|
353 * |
|
354 * @param aURI |
|
355 * The URI to add to the database. |
|
356 */ |
|
357 void |
|
358 addURI(nsIURI* aURI) |
|
359 { |
|
360 nsCOMPtr<mozilla::IHistory> history = do_GetService(NS_IHISTORY_CONTRACTID); |
|
361 do_check_true(history); |
|
362 nsresult rv = history->VisitURI(aURI, nullptr, mozilla::IHistory::TOP_LEVEL); |
|
363 do_check_success(rv); |
|
364 |
|
365 do_wait_async_updates(); |
|
366 } |
|
367 |
|
368 static const char TOPIC_PROFILE_CHANGE[] = "profile-before-change"; |
|
369 static const char TOPIC_PLACES_CONNECTION_CLOSED[] = "places-connection-closed"; |
|
370 |
|
371 class WaitForConnectionClosed MOZ_FINAL : public nsIObserver |
|
372 { |
|
373 nsRefPtr<WaitForTopicSpinner> mSpinner; |
|
374 public: |
|
375 NS_DECL_ISUPPORTS |
|
376 |
|
377 WaitForConnectionClosed() |
|
378 { |
|
379 nsCOMPtr<nsIObserverService> os = |
|
380 do_GetService(NS_OBSERVERSERVICE_CONTRACTID); |
|
381 MOZ_ASSERT(os); |
|
382 if (os) { |
|
383 MOZ_ALWAYS_TRUE(NS_SUCCEEDED(os->AddObserver(this, TOPIC_PROFILE_CHANGE, false))); |
|
384 } |
|
385 mSpinner = new WaitForTopicSpinner(TOPIC_PLACES_CONNECTION_CLOSED); |
|
386 } |
|
387 |
|
388 NS_IMETHOD Observe(nsISupports* aSubject, |
|
389 const char* aTopic, |
|
390 const char16_t* aData) |
|
391 { |
|
392 nsCOMPtr<nsIObserverService> os = |
|
393 do_GetService(NS_OBSERVERSERVICE_CONTRACTID); |
|
394 MOZ_ASSERT(os); |
|
395 if (os) { |
|
396 MOZ_ALWAYS_TRUE(NS_SUCCEEDED(os->RemoveObserver(this, aTopic))); |
|
397 } |
|
398 |
|
399 mSpinner->Spin(); |
|
400 |
|
401 return NS_OK; |
|
402 } |
|
403 }; |
|
404 |
|
405 NS_IMPL_ISUPPORTS(WaitForConnectionClosed, nsIObserver) |