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 : * ***** BEGIN LICENSE BLOCK *****
4 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 : *
6 : * The contents of this file are subject to the Mozilla Public License Version
7 : * 1.1 (the "License"); you may not use this file except in compliance with
8 : * the License. You may obtain a copy of the License at
9 : * http://www.mozilla.org/MPL/
10 : *
11 : * Software distributed under the License is distributed on an "AS IS" basis,
12 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 : * for the specific language governing rights and limitations under the
14 : * License.
15 : *
16 : * The Original Code is places test code.
17 : *
18 : * The Initial Developer of the Original Code is
19 : * the Mozilla Foundation.
20 : * Portions created by the Initial Developer are Copyright (C) 2009
21 : * the Initial Developer. All Rights Reserved.
22 : *
23 : * Contributor(s):
24 : * Shawn Wilsher <me@shawnwilsher.com> (Original Author)
25 : *
26 : * Alternatively, the contents of this file may be used under the terms of
27 : * either the GNU General Public License Version 2 or later (the "GPL"), or
28 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 : * in which case the provisions of the GPL or the LGPL are applicable instead
30 : * of those above. If you wish to allow use of your version of this file only
31 : * under the terms of either the GPL or the LGPL, and not to allow others to
32 : * use your version of this file under the terms of the MPL, indicate your
33 : * decision by deleting the provisions above and replace them with the notice
34 : * and other provisions required by the GPL or the LGPL. If you do not delete
35 : * the provisions above, a recipient may use your version of this file under
36 : * the terms of any one of the MPL, the GPL or the LGPL.
37 : *
38 : * ***** END LICENSE BLOCK ***** */
39 :
40 : #include "TestHarness.h"
41 : #include "nsMemory.h"
42 : #include "nsThreadUtils.h"
43 : #include "nsNetUtil.h"
44 : #include "nsDocShellCID.h"
45 :
46 : #include "nsToolkitCompsCID.h"
47 : #include "nsINavHistoryService.h"
48 : #include "nsIObserverService.h"
49 : #include "mozilla/IHistory.h"
50 : #include "mozIStorageConnection.h"
51 : #include "mozIStorageStatement.h"
52 : #include "nsPIPlacesDatabase.h"
53 : #include "nsIObserver.h"
54 : #include "prinrval.h"
55 :
56 : #define TOPIC_FRECENCY_UPDATED "places-frecency-updated"
57 : #define WAITFORTOPIC_TIMEOUT_SECONDS 5
58 :
59 : using namespace mozilla;
60 :
61 : static size_t gTotalTests = 0;
62 : static size_t gPassedTests = 0;
63 :
64 : #define do_check_true(aCondition) \
65 : PR_BEGIN_MACRO \
66 : gTotalTests++; \
67 : if (aCondition) { \
68 : gPassedTests++; \
69 : } else { \
70 : fail("%s | Expected true, got false at line %d", __FILE__, __LINE__); \
71 : } \
72 : PR_END_MACRO
73 :
74 : #define do_check_false(aCondition) \
75 : PR_BEGIN_MACRO \
76 : gTotalTests++; \
77 : if (!aCondition) { \
78 : gPassedTests++; \
79 : } else { \
80 : fail("%s | Expected false, got true at line %d", __FILE__, __LINE__); \
81 : } \
82 : PR_END_MACRO
83 :
84 : #define do_check_success(aResult) \
85 : do_check_true(NS_SUCCEEDED(aResult))
86 :
87 : #ifdef LINUX
88 : // XXX Linux opt builds on tinderbox are orange due to linking with stdlib.
89 : // This is sad and annoying, but it's a workaround that works.
90 : #define do_check_eq(aExpected, aActual) \
91 : do_check_true(aExpected == aActual)
92 : #else
93 : #include <sstream>
94 :
95 : #define do_check_eq(aActual, aExpected) \
96 : PR_BEGIN_MACRO \
97 : gTotalTests++; \
98 : if (aExpected == aActual) { \
99 : gPassedTests++; \
100 : } else { \
101 : std::ostringstream temp; \
102 : temp << __FILE__ << " | Expected '" << aExpected << "', got '"; \
103 : temp << aActual <<"' at line " << __LINE__; \
104 : fail(temp.str().c_str()); \
105 : } \
106 : PR_END_MACRO
107 : #endif
108 :
109 : struct Test
110 : {
111 : void (*func)(void);
112 : const char* const name;
113 : };
114 : #define TEST(aName) \
115 : {aName, #aName}
116 :
117 : /**
118 : * Runs the next text.
119 : */
120 : void run_next_test();
121 :
122 : /**
123 : * To be used around asynchronous work.
124 : */
125 : void do_test_pending();
126 : void do_test_finished();
127 :
128 : /**
129 : * Spins current thread until a topic is received.
130 : */
131 : class WaitForTopicSpinner : public nsIObserver
132 : {
133 : public:
134 : NS_DECL_ISUPPORTS
135 :
136 7 : WaitForTopicSpinner(const char* const aTopic)
137 : : mTopicReceived(false)
138 7 : , mStartTime(PR_IntervalNow())
139 : {
140 : nsCOMPtr<nsIObserverService> observerService =
141 14 : do_GetService(NS_OBSERVERSERVICE_CONTRACTID);
142 7 : do_check_true(observerService);
143 7 : (void)observerService->AddObserver(this, aTopic, false);
144 7 : }
145 :
146 7 : void Spin() {
147 28 : while (!mTopicReceived) {
148 14 : if ((PR_IntervalNow() - mStartTime) > (WAITFORTOPIC_TIMEOUT_SECONDS * PR_USEC_PER_SEC)) {
149 : // Timed out waiting for the topic.
150 0 : do_check_true(false);
151 0 : break;
152 : }
153 14 : (void)NS_ProcessNextEvent();
154 : }
155 7 : }
156 :
157 7 : NS_IMETHOD Observe(nsISupports* aSubject,
158 : const char* aTopic,
159 : const PRUnichar* aData)
160 : {
161 7 : mTopicReceived = true;
162 : nsCOMPtr<nsIObserverService> observerService =
163 14 : do_GetService(NS_OBSERVERSERVICE_CONTRACTID);
164 7 : do_check_true(observerService);
165 7 : (void)observerService->RemoveObserver(this, aTopic);
166 7 : return NS_OK;
167 : }
168 :
169 : private:
170 : bool mTopicReceived;
171 : PRIntervalTime mStartTime;
172 : };
173 98 : NS_IMPL_ISUPPORTS1(
174 : WaitForTopicSpinner,
175 : nsIObserver
176 : )
177 :
178 : /**
179 : * Adds a URI to the database.
180 : *
181 : * @param aURI
182 : * The URI to add to the database.
183 : */
184 : void
185 6 : addURI(nsIURI* aURI)
186 : {
187 : nsRefPtr<WaitForTopicSpinner> spinner =
188 12 : new WaitForTopicSpinner(TOPIC_FRECENCY_UPDATED);
189 :
190 : nsCOMPtr<nsINavHistoryService> hist =
191 12 : do_GetService(NS_NAVHISTORYSERVICE_CONTRACTID);
192 : PRInt64 id;
193 6 : nsresult rv = hist->AddVisit(aURI, PR_Now(), nsnull,
194 : nsINavHistoryService::TRANSITION_LINK, false,
195 6 : 0, &id);
196 6 : do_check_success(rv);
197 :
198 : // Wait for frecency update.
199 6 : spinner->Spin();
200 6 : }
201 :
202 : struct PlaceRecord
203 14 : {
204 : PRInt64 id;
205 : PRInt32 hidden;
206 : PRInt32 typed;
207 : PRInt32 visitCount;
208 : nsCString guid;
209 : };
210 :
211 : struct VisitRecord
212 : {
213 : PRInt64 id;
214 : PRInt64 lastVisitId;
215 : PRInt32 transitionType;
216 : };
217 :
218 : already_AddRefed<IHistory>
219 16 : do_get_IHistory()
220 : {
221 32 : nsCOMPtr<IHistory> history = do_GetService(NS_IHISTORY_CONTRACTID);
222 16 : do_check_true(history);
223 16 : return history.forget();
224 : }
225 :
226 : already_AddRefed<nsINavHistoryService>
227 12 : do_get_NavHistory()
228 : {
229 : nsCOMPtr<nsINavHistoryService> serv =
230 24 : do_GetService(NS_NAVHISTORYSERVICE_CONTRACTID);
231 12 : do_check_true(serv);
232 12 : return serv.forget();
233 : }
234 :
235 : already_AddRefed<mozIStorageConnection>
236 10 : do_get_db()
237 : {
238 20 : nsCOMPtr<nsINavHistoryService> history = do_get_NavHistory();
239 20 : nsCOMPtr<nsPIPlacesDatabase> database = do_QueryInterface(history);
240 10 : do_check_true(database);
241 :
242 : mozIStorageConnection* dbConn;
243 10 : nsresult rv = database->GetDBConnection(&dbConn);
244 10 : do_check_success(rv);
245 10 : return dbConn;
246 : }
247 :
248 : /**
249 : * Get the place record from the database.
250 : *
251 : * @param aURI The unique URI of the place we are looking up
252 : * @param result Out parameter where the result is stored
253 : */
254 : void
255 7 : do_get_place(nsIURI* aURI, PlaceRecord& result)
256 : {
257 14 : nsCOMPtr<mozIStorageConnection> dbConn = do_get_db();
258 14 : nsCOMPtr<mozIStorageStatement> stmt;
259 :
260 14 : nsCString spec;
261 7 : nsresult rv = aURI->GetSpec(spec);
262 7 : do_check_success(rv);
263 :
264 14 : rv = dbConn->CreateStatement(NS_LITERAL_CSTRING(
265 : "SELECT id, hidden, typed, visit_count, guid FROM moz_places "
266 : "WHERE url=?1 "
267 14 : ), getter_AddRefs(stmt));
268 7 : do_check_success(rv);
269 :
270 7 : rv = stmt->BindUTF8StringByIndex(0, spec);
271 7 : do_check_success(rv);
272 :
273 : bool hasResults;
274 7 : rv = stmt->ExecuteStep(&hasResults);
275 7 : do_check_success(rv);
276 7 : if (!hasResults) {
277 1 : result.id = 0;
278 : return;
279 : }
280 :
281 6 : rv = stmt->GetInt64(0, &result.id);
282 6 : do_check_success(rv);
283 6 : rv = stmt->GetInt32(1, &result.hidden);
284 6 : do_check_success(rv);
285 6 : rv = stmt->GetInt32(2, &result.typed);
286 6 : do_check_success(rv);
287 6 : rv = stmt->GetInt32(3, &result.visitCount);
288 6 : do_check_success(rv);
289 6 : rv = stmt->GetUTF8String(4, result.guid);
290 6 : do_check_success(rv);
291 : }
292 :
293 : /**
294 : * Gets the most recent visit to a place.
295 : *
296 : * @param placeID ID from the moz_places table
297 : * @param result Out parameter where visit is stored
298 : */
299 : void
300 3 : do_get_lastVisit(PRInt64 placeId, VisitRecord& result)
301 : {
302 6 : nsCOMPtr<mozIStorageConnection> dbConn = do_get_db();
303 6 : nsCOMPtr<mozIStorageStatement> stmt;
304 :
305 6 : nsresult rv = dbConn->CreateStatement(NS_LITERAL_CSTRING(
306 : "SELECT id, from_visit, visit_type FROM moz_historyvisits "
307 : "WHERE place_id=?1 "
308 : "LIMIT 1"
309 6 : ), getter_AddRefs(stmt));
310 3 : do_check_success(rv);
311 :
312 3 : rv = stmt->BindInt64ByIndex(0, placeId);
313 3 : do_check_success(rv);
314 :
315 : bool hasResults;
316 3 : rv = stmt->ExecuteStep(&hasResults);
317 3 : do_check_success(rv);
318 :
319 3 : if (!hasResults) {
320 1 : result.id = 0;
321 : return;
322 : }
323 :
324 2 : rv = stmt->GetInt64(0, &result.id);
325 2 : do_check_success(rv);
326 2 : rv = stmt->GetInt64(1, &result.lastVisitId);
327 2 : do_check_success(rv);
328 2 : rv = stmt->GetInt32(2, &result.transitionType);
329 2 : do_check_success(rv);
330 : }
331 :
332 : static const char TOPIC_PROFILE_CHANGE[] = "profile-before-change";
333 : static const char TOPIC_PLACES_CONNECTION_CLOSED[] = "places-connection-closed";
334 :
335 : class WaitForConnectionClosed : public nsIObserver
336 1 : {
337 : nsRefPtr<WaitForTopicSpinner> mSpinner;
338 : public:
339 : NS_DECL_ISUPPORTS
340 :
341 1 : WaitForConnectionClosed()
342 1 : {
343 : nsCOMPtr<nsIObserverService> os =
344 2 : do_GetService(NS_OBSERVERSERVICE_CONTRACTID);
345 1 : MOZ_ASSERT(os);
346 1 : if (os) {
347 1 : MOZ_ALWAYS_TRUE(NS_SUCCEEDED(os->AddObserver(this, TOPIC_PROFILE_CHANGE, false)));
348 : }
349 1 : mSpinner = new WaitForTopicSpinner(TOPIC_PLACES_CONNECTION_CLOSED);
350 1 : }
351 :
352 1 : NS_IMETHOD Observe(nsISupports* aSubject,
353 : const char* aTopic,
354 : const PRUnichar* aData)
355 : {
356 : nsCOMPtr<nsIObserverService> os =
357 2 : do_GetService(NS_OBSERVERSERVICE_CONTRACTID);
358 1 : MOZ_ASSERT(os);
359 1 : if (os) {
360 1 : MOZ_ALWAYS_TRUE(NS_SUCCEEDED(os->RemoveObserver(this, aTopic)));
361 : }
362 :
363 1 : mSpinner->Spin();
364 :
365 1 : return NS_OK;
366 : }
367 : };
368 :
369 14 : NS_IMPL_ISUPPORTS1(WaitForConnectionClosed, nsIObserver)
|