1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/sync/test/integration/typed_urls_helper.h"
6
7#include "base/compiler_specific.h"
8#include "base/strings/utf_string_conversions.h"
9#include "base/synchronization/waitable_event.h"
10#include "base/task/cancelable_task_tracker.h"
11#include "base/time/time.h"
12#include "chrome/browser/history/history_backend.h"
13#include "chrome/browser/history/history_db_task.h"
14#include "chrome/browser/history/history_service.h"
15#include "chrome/browser/history/history_service_factory.h"
16#include "chrome/browser/profiles/profile.h"
17#include "chrome/browser/sync/test/integration/multi_client_status_change_checker.h"
18#include "chrome/browser/sync/test/integration/sync_datatype_helper.h"
19#include "chrome/browser/sync/test/integration/sync_test.h"
20#include "components/history/core/browser/history_types.h"
21
22using sync_datatype_helper::test;
23
24namespace {
25
26class FlushHistoryDBQueueTask : public history::HistoryDBTask {
27 public:
28  explicit FlushHistoryDBQueueTask(base::WaitableEvent* event)
29      : wait_event_(event) {}
30  virtual bool RunOnDBThread(history::HistoryBackend* backend,
31                             history::HistoryDatabase* db) OVERRIDE {
32    wait_event_->Signal();
33    return true;
34  }
35
36  virtual void DoneRunOnMainThread() OVERRIDE {}
37
38 private:
39  virtual ~FlushHistoryDBQueueTask() {}
40
41  base::WaitableEvent* wait_event_;
42};
43
44class GetTypedUrlsTask : public history::HistoryDBTask {
45 public:
46  GetTypedUrlsTask(history::URLRows* rows, base::WaitableEvent* event)
47      : rows_(rows), wait_event_(event) {}
48
49  virtual bool RunOnDBThread(history::HistoryBackend* backend,
50                             history::HistoryDatabase* db) OVERRIDE {
51    // Fetch the typed URLs.
52    backend->GetAllTypedURLs(rows_);
53    wait_event_->Signal();
54    return true;
55  }
56
57  virtual void DoneRunOnMainThread() OVERRIDE {}
58
59 private:
60  virtual ~GetTypedUrlsTask() {}
61
62  history::URLRows* rows_;
63  base::WaitableEvent* wait_event_;
64};
65
66class GetUrlTask : public history::HistoryDBTask {
67 public:
68  GetUrlTask(const GURL& url,
69             history::URLRow* row,
70             bool* found,
71             base::WaitableEvent* event)
72      : url_(url), row_(row), wait_event_(event), found_(found) {}
73
74  virtual bool RunOnDBThread(history::HistoryBackend* backend,
75                             history::HistoryDatabase* db) OVERRIDE {
76    // Fetch the typed URLs.
77    *found_ = backend->GetURL(url_, row_);
78    wait_event_->Signal();
79    return true;
80  }
81
82  virtual void DoneRunOnMainThread() OVERRIDE {}
83
84 private:
85  virtual ~GetUrlTask() {}
86
87  GURL url_;
88  history::URLRow* row_;
89  base::WaitableEvent* wait_event_;
90  bool* found_;
91};
92
93class GetVisitsTask : public history::HistoryDBTask {
94 public:
95  GetVisitsTask(history::URLID id,
96                history::VisitVector* visits,
97                base::WaitableEvent* event)
98      : id_(id), visits_(visits), wait_event_(event) {}
99
100  virtual bool RunOnDBThread(history::HistoryBackend* backend,
101                             history::HistoryDatabase* db) OVERRIDE {
102    // Fetch the visits.
103    backend->GetVisitsForURL(id_, visits_);
104    wait_event_->Signal();
105    return true;
106  }
107
108  virtual void DoneRunOnMainThread() OVERRIDE {}
109
110 private:
111  virtual ~GetVisitsTask() {}
112
113  history::URLID id_;
114  history::VisitVector* visits_;
115  base::WaitableEvent* wait_event_;
116};
117
118class RemoveVisitsTask : public history::HistoryDBTask {
119 public:
120  RemoveVisitsTask(const history::VisitVector& visits,
121                   base::WaitableEvent* event)
122      : visits_(visits), wait_event_(event) {}
123
124  virtual bool RunOnDBThread(history::HistoryBackend* backend,
125                             history::HistoryDatabase* db) OVERRIDE {
126    // Fetch the visits.
127    backend->RemoveVisits(visits_);
128    wait_event_->Signal();
129    return true;
130  }
131
132  virtual void DoneRunOnMainThread() OVERRIDE {}
133
134 private:
135  virtual ~RemoveVisitsTask() {}
136
137  const history::VisitVector& visits_;
138  base::WaitableEvent* wait_event_;
139};
140
141// Waits for the history DB thread to finish executing its current set of
142// tasks.
143void WaitForHistoryDBThread(int index) {
144  base::CancelableTaskTracker tracker;
145  HistoryService* service = HistoryServiceFactory::GetForProfileWithoutCreating(
146      test()->GetProfile(index));
147  base::WaitableEvent wait_event(true, false);
148  service->ScheduleDBTask(
149      scoped_ptr<history::HistoryDBTask>(
150          new FlushHistoryDBQueueTask(&wait_event)),
151      &tracker);
152  wait_event.Wait();
153}
154
155// Creates a URLRow in the specified HistoryService with the passed transition
156// type.
157void AddToHistory(HistoryService* service,
158                  const GURL& url,
159                  ui::PageTransition transition,
160                  history::VisitSource source,
161                  const base::Time& timestamp) {
162  service->AddPage(url,
163                   timestamp,
164                   NULL, // scope
165                   1234, // page_id
166                   GURL(),  // referrer
167                   history::RedirectList(),
168                   transition,
169                   source,
170                   false);
171  service->SetPageTitle(url, base::ASCIIToUTF16(url.spec() + " - title"));
172}
173
174history::URLRows GetTypedUrlsFromHistoryService(HistoryService* service) {
175  base::CancelableTaskTracker tracker;
176  history::URLRows rows;
177  base::WaitableEvent wait_event(true, false);
178  service->ScheduleDBTask(
179      scoped_ptr<history::HistoryDBTask>(
180          new GetTypedUrlsTask(&rows, &wait_event)),
181      &tracker);
182  wait_event.Wait();
183  return rows;
184}
185
186bool GetUrlFromHistoryService(HistoryService* service,
187                              const GURL& url, history::URLRow* row) {
188  base::CancelableTaskTracker tracker;
189  base::WaitableEvent wait_event(true, false);
190  bool found = false;
191  service->ScheduleDBTask(
192      scoped_ptr<history::HistoryDBTask>(
193          new GetUrlTask(url, row, &found, &wait_event)),
194      &tracker);
195  wait_event.Wait();
196  return found;
197}
198
199history::VisitVector GetVisitsFromHistoryService(HistoryService* service,
200                                                 history::URLID id) {
201  base::CancelableTaskTracker tracker;
202  base::WaitableEvent wait_event(true, false);
203  history::VisitVector visits;
204  service->ScheduleDBTask(
205      scoped_ptr<history::HistoryDBTask>(
206          new GetVisitsTask(id, &visits, &wait_event)),
207      &tracker);
208  wait_event.Wait();
209  return visits;
210}
211
212void RemoveVisitsFromHistoryService(HistoryService* service,
213                                    const history::VisitVector& visits) {
214  base::CancelableTaskTracker tracker;
215  base::WaitableEvent wait_event(true, false);
216  service->ScheduleDBTask(
217      scoped_ptr<history::HistoryDBTask>(
218          new RemoveVisitsTask(visits, &wait_event)),
219      &tracker);
220  wait_event.Wait();
221}
222
223static base::Time* timestamp = NULL;
224
225}  // namespace
226
227namespace typed_urls_helper {
228
229history::URLRows GetTypedUrlsFromClient(int index) {
230  HistoryService* service = HistoryServiceFactory::GetForProfileWithoutCreating(
231      test()->GetProfile(index));
232  return GetTypedUrlsFromHistoryService(service);
233}
234
235bool GetUrlFromClient(int index, const GURL& url, history::URLRow* row) {
236  HistoryService* service = HistoryServiceFactory::GetForProfileWithoutCreating(
237      test()->GetProfile(index));
238  return GetUrlFromHistoryService(service, url, row);
239}
240
241history::VisitVector GetVisitsFromClient(int index, history::URLID id) {
242  HistoryService* service = HistoryServiceFactory::GetForProfileWithoutCreating(
243      test()->GetProfile(index));
244  return GetVisitsFromHistoryService(service, id);
245}
246
247void RemoveVisitsFromClient(int index, const history::VisitVector& visits) {
248  HistoryService* service = HistoryServiceFactory::GetForProfileWithoutCreating(
249      test()->GetProfile(index));
250  RemoveVisitsFromHistoryService(service, visits);
251}
252
253base::Time GetTimestamp() {
254  // The history subsystem doesn't like identical timestamps for page visits,
255  // and it will massage the visit timestamps if we try to use identical
256  // values, which can lead to spurious errors. So make sure all timestamps
257  // are unique.
258  if (!::timestamp)
259    ::timestamp = new base::Time(base::Time::Now());
260  base::Time original = *::timestamp;
261  *::timestamp += base::TimeDelta::FromMilliseconds(1);
262  return original;
263}
264
265void AddUrlToHistory(int index, const GURL& url) {
266  AddUrlToHistoryWithTransition(index, url, ui::PAGE_TRANSITION_TYPED,
267                                history::SOURCE_BROWSED);
268}
269void AddUrlToHistoryWithTransition(int index,
270                                   const GURL& url,
271                                   ui::PageTransition transition,
272                                   history::VisitSource source) {
273  base::Time timestamp = GetTimestamp();
274  AddUrlToHistoryWithTimestamp(index, url, transition, source, timestamp);
275}
276void AddUrlToHistoryWithTimestamp(int index,
277                                  const GURL& url,
278                                  ui::PageTransition transition,
279                                  history::VisitSource source,
280                                  const base::Time& timestamp) {
281  AddToHistory(HistoryServiceFactory::GetForProfileWithoutCreating(
282                   test()->GetProfile(index)),
283               url,
284               transition,
285               source,
286               timestamp);
287  if (test()->use_verifier())
288    AddToHistory(
289        HistoryServiceFactory::GetForProfile(test()->verifier(),
290                                             Profile::IMPLICIT_ACCESS),
291        url,
292        transition,
293        source,
294        timestamp);
295
296  // Wait until the AddPage() request has completed so we know the change has
297  // filtered down to the sync observers (don't need to wait for the
298  // verifier profile since it doesn't sync).
299  WaitForHistoryDBThread(index);
300}
301
302void DeleteUrlFromHistory(int index, const GURL& url) {
303  HistoryServiceFactory::GetForProfileWithoutCreating(
304      test()->GetProfile(index))->DeleteURL(url);
305  if (test()->use_verifier())
306    HistoryServiceFactory::GetForProfile(test()->verifier(),
307                                         Profile::IMPLICIT_ACCESS)->
308        DeleteURL(url);
309  WaitForHistoryDBThread(index);
310}
311
312void DeleteUrlsFromHistory(int index, const std::vector<GURL>& urls) {
313  HistoryServiceFactory::GetForProfileWithoutCreating(
314      test()->GetProfile(index))->DeleteURLsForTest(urls);
315  if (test()->use_verifier())
316    HistoryServiceFactory::GetForProfile(test()->verifier(),
317                                         Profile::IMPLICIT_ACCESS)->
318        DeleteURLsForTest(urls);
319  WaitForHistoryDBThread(index);
320}
321
322bool CheckURLRowVectorsAreEqual(const history::URLRows& left,
323                                const history::URLRows& right) {
324  if (left.size() != right.size())
325    return false;
326  for (size_t i = 0; i < left.size(); ++i) {
327    // URLs could be out-of-order, so look for a matching URL in the second
328    // array.
329    bool found = false;
330    for (size_t j = 0; j < right.size(); ++j) {
331      if (left[i].url() == right[j].url()) {
332        if (CheckURLRowsAreEqual(left[i], right[j])) {
333          found = true;
334          break;
335        }
336      }
337    }
338    if (!found)
339      return false;
340  }
341  return true;
342}
343
344bool AreVisitsEqual(const history::VisitVector& visit1,
345                    const history::VisitVector& visit2) {
346  if (visit1.size() != visit2.size())
347    return false;
348  for (size_t i = 0; i < visit1.size(); ++i) {
349    if (visit1[i].transition != visit2[i].transition)
350      return false;
351    if (visit1[i].visit_time != visit2[i].visit_time)
352      return false;
353  }
354  return true;
355}
356
357bool AreVisitsUnique(const history::VisitVector& visits) {
358  base::Time t = base::Time::FromInternalValue(0);
359  for (size_t i = 0; i < visits.size(); ++i) {
360    if (t == visits[i].visit_time)
361      return false;
362    t = visits[i].visit_time;
363  }
364  return true;
365}
366
367bool CheckURLRowsAreEqual(
368    const history::URLRow& left, const history::URLRow& right) {
369  return (left.url() == right.url()) &&
370      (left.title() == right.title()) &&
371      (left.visit_count() == right.visit_count()) &&
372      (left.typed_count() == right.typed_count()) &&
373      (left.last_visit() == right.last_visit()) &&
374      (left.hidden() == right.hidden());
375}
376
377bool CheckAllProfilesHaveSameURLsAsVerifier() {
378  HistoryService* verifier_service =
379      HistoryServiceFactory::GetForProfile(test()->verifier(),
380                                           Profile::IMPLICIT_ACCESS);
381  history::URLRows verifier_urls =
382      GetTypedUrlsFromHistoryService(verifier_service);
383  for (int i = 0; i < test()->num_clients(); ++i) {
384    history::URLRows urls = GetTypedUrlsFromClient(i);
385    if (!CheckURLRowVectorsAreEqual(verifier_urls, urls))
386      return false;
387  }
388  return true;
389}
390
391namespace {
392
393// Helper class used in the implementation of
394// AwaitCheckAllProfilesHaveSameURLsAsVerifier.
395class ProfilesHaveSameURLsChecker : public MultiClientStatusChangeChecker {
396 public:
397  ProfilesHaveSameURLsChecker();
398  virtual ~ProfilesHaveSameURLsChecker();
399
400  virtual bool IsExitConditionSatisfied() OVERRIDE;
401  virtual std::string GetDebugMessage() const OVERRIDE;
402};
403
404ProfilesHaveSameURLsChecker::ProfilesHaveSameURLsChecker()
405    : MultiClientStatusChangeChecker(
406        sync_datatype_helper::test()->GetSyncServices()) {}
407
408ProfilesHaveSameURLsChecker::~ProfilesHaveSameURLsChecker() {}
409
410bool ProfilesHaveSameURLsChecker::IsExitConditionSatisfied() {
411  return CheckAllProfilesHaveSameURLsAsVerifier();
412}
413
414std::string ProfilesHaveSameURLsChecker::GetDebugMessage() const {
415  return "Waiting for matching typed urls profiles";
416}
417
418}  //  namespace
419
420bool AwaitCheckAllProfilesHaveSameURLsAsVerifier() {
421  ProfilesHaveSameURLsChecker checker;
422  checker.Wait();
423  return !checker.TimedOut();
424}
425
426}  // namespace typed_urls_helper
427