| Index: chrome/browser/sync/glue/bookmark_model_worker_unittest.cc | 
| =================================================================== | 
| --- chrome/browser/sync/glue/bookmark_model_worker_unittest.cc	(revision 0) | 
| +++ chrome/browser/sync/glue/bookmark_model_worker_unittest.cc	(revision 0) | 
| @@ -0,0 +1,224 @@ | 
| +// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. | 
| +// Use of this source code is governed by a BSD-style license that can be | 
| +// found in the LICENSE file. | 
| +#ifdef CHROME_PERSONALIZATION | 
| + | 
| +#include "base/thread.h" | 
| +#include "chrome/browser/sync/engine/syncapi.h" | 
| +#include "chrome/browser/sync/glue/bookmark_model_worker.h" | 
| +#include "testing/gtest/include/gtest/gtest.h" | 
| + | 
| +using browser_sync::BookmarkModelWorker; | 
| +using namespace sync_api; | 
| + | 
| +// Various boilerplate, primarily for the StopWithPendingWork test. | 
| + | 
| +class BookmarkModelWorkerVisitor : public ModelSafeWorkerInterface::Visitor { | 
| + public: | 
| +  BookmarkModelWorkerVisitor(MessageLoop* faux_ui_loop, | 
| +                             base::WaitableEvent* was_run, | 
| +                             bool quit_loop) | 
| +     : faux_ui_loop_(faux_ui_loop), quit_loop_when_run_(quit_loop), | 
| +       was_run_(was_run) { } | 
| +  virtual ~BookmarkModelWorkerVisitor() { } | 
| + | 
| +  virtual void DoWork() { | 
| +    EXPECT_EQ(MessageLoop::current(), faux_ui_loop_); | 
| +    was_run_->Signal(); | 
| +    if (quit_loop_when_run_) | 
| +      MessageLoop::current()->Quit(); | 
| +  } | 
| + | 
| + private: | 
| +  MessageLoop* faux_ui_loop_; | 
| +  bool quit_loop_when_run_; | 
| +  base::WaitableEvent* was_run_; | 
| +  DISALLOW_COPY_AND_ASSIGN(BookmarkModelWorkerVisitor); | 
| +}; | 
| + | 
| +// A faux-syncer that only interacts with its model safe worker. | 
| +class Syncer { | 
| + public: | 
| +  explicit Syncer(BookmarkModelWorker* worker) : worker_(worker) {} | 
| +  ~Syncer() {} | 
| + | 
| +  void SyncShare(BookmarkModelWorkerVisitor* visitor) { | 
| +    worker_->CallDoWorkFromModelSafeThreadAndWait(visitor); | 
| +  } | 
| + private: | 
| +  BookmarkModelWorker* worker_; | 
| +  DISALLOW_COPY_AND_ASSIGN(Syncer); | 
| +}; | 
| + | 
| +// A task run from the SyncerThread to "sync share", ie tell the Syncer to | 
| +// ask it's ModelSafeWorker to do something. | 
| +class FakeSyncShareTask : public Task { | 
| + public: | 
| +  FakeSyncShareTask(Syncer* syncer, BookmarkModelWorkerVisitor* visitor) | 
| +      : syncer_(syncer), visitor_(visitor) { | 
| +  } | 
| +  virtual void Run() { | 
| +    syncer_->SyncShare(visitor_); | 
| +  } | 
| + private: | 
| +  Syncer* syncer_; | 
| +  BookmarkModelWorkerVisitor* visitor_; | 
| +  DISALLOW_COPY_AND_ASSIGN(FakeSyncShareTask); | 
| +}; | 
| + | 
| +// A task run from the CoreThread to simulate terminating syncapi. | 
| +class FakeSyncapiShutdownTask : public Task { | 
| + public: | 
| +  FakeSyncapiShutdownTask(base::Thread* syncer_thread, | 
| +                          BookmarkModelWorker* worker, | 
| +                          base::WaitableEvent** jobs, | 
| +                          size_t job_count) | 
| +      : syncer_thread_(syncer_thread), worker_(worker), jobs_(jobs), | 
| +        job_count_(job_count), all_jobs_done_(false, false) { } | 
| +  virtual void Run() { | 
| +    // In real life, we would try and close a sync directory, which would | 
| +    // result in the syncer calling it's own destructor, which results in | 
| +    // the SyncerThread::HaltSyncer being called, which sets the | 
| +    // syncer in RequestEarlyExit mode and waits until the Syncer finishes | 
| +    // SyncShare to remove the syncer from it's watch. Here we just manually | 
| +    // wait until all outstanding jobs are done to simulate what happens in | 
| +    // SyncerThread::HaltSyncer. | 
| +    all_jobs_done_.WaitMany(jobs_, job_count_); | 
| + | 
| +    // These two calls are made from SyncBackendHost::Core::DoShutdown. | 
| +    syncer_thread_->Stop(); | 
| +    worker_->OnSyncerShutdownComplete(); | 
| +  } | 
| + private: | 
| +  base::Thread* syncer_thread_; | 
| +  BookmarkModelWorker* worker_; | 
| +  base::WaitableEvent** jobs_; | 
| +  size_t job_count_; | 
| +  base::WaitableEvent all_jobs_done_; | 
| +  DISALLOW_COPY_AND_ASSIGN(FakeSyncapiShutdownTask); | 
| +}; | 
| + | 
| +class BookmarkModelWorkerTest : public testing::Test { | 
| + public: | 
| +  BookmarkModelWorkerTest() : faux_syncer_thread_("FauxSyncerThread"), | 
| +                              faux_core_thread_("FauxCoreThread") { } | 
| + | 
| +  virtual void SetUp() { | 
| +    faux_syncer_thread_.Start(); | 
| +    bmw_.reset(new BookmarkModelWorker(&faux_ui_loop_)); | 
| +    syncer_.reset(new Syncer(bmw_.get())); | 
| +  } | 
| + | 
| +  Syncer* syncer() { return syncer_.get(); } | 
| +  BookmarkModelWorker* bmw() { return bmw_.get(); } | 
| +  base::Thread* core_thread() { return &faux_core_thread_; } | 
| +  base::Thread* syncer_thread() { return &faux_syncer_thread_; } | 
| +  MessageLoop* ui_loop() { return &faux_ui_loop_; } | 
| + private: | 
| +  MessageLoop faux_ui_loop_; | 
| +  base::Thread faux_syncer_thread_; | 
| +  base::Thread faux_core_thread_; | 
| +  scoped_ptr<BookmarkModelWorker> bmw_; | 
| +  scoped_ptr<Syncer> syncer_; | 
| +}; | 
| + | 
| +TEST_F(BookmarkModelWorkerTest, ScheduledWorkRunsOnUILoop) { | 
| +  base::WaitableEvent v_was_run(false, false); | 
| +  scoped_ptr<BookmarkModelWorkerVisitor> v( | 
| +      new BookmarkModelWorkerVisitor(ui_loop(), &v_was_run, true)); | 
| + | 
| +  syncer_thread()->message_loop()->PostTask(FROM_HERE, | 
| +      new FakeSyncShareTask(syncer(), v.get())); | 
| + | 
| +  // We are on the UI thread, so run our loop to process the | 
| +  // (hopefully) scheduled task from a SyncShare invocation. | 
| +  MessageLoop::current()->Run(); | 
| + | 
| +  bmw()->OnSyncerShutdownComplete(); | 
| +  bmw()->Stop(); | 
| +  syncer_thread()->Stop(); | 
| +} | 
| + | 
| +TEST_F(BookmarkModelWorkerTest, StopWithPendingWork) { | 
| +  // What we want to set up is the following: | 
| +  // ("ui_thread" is the thread we are currently executing on) | 
| +  // 1 - simulate the user shutting down the browser, and the ui thread needing | 
| +  //     to terminate the core thread. | 
| +  // 2 - the core thread is where the syncapi is accessed from, and so it needs | 
| +  //     to shut down the SyncerThread. | 
| +  // 3 - the syncer is waiting on the BookmarkModelWorker to | 
| +  //     perform a task for it. | 
| +  // The BookmarkModelWorker's manual shutdown pump will save the day, as the | 
| +  // UI thread is not actually trying to join() the core thread, it is merely | 
| +  // waiting for the SyncerThread to give it work or to finish. After that, it | 
| +  // will join the core thread which should succeed as the SyncerThread has left | 
| +  // the building. Unfortunately this test as written is not provably decidable, | 
| +  // as it will always halt on success, but it may not on failure (namely if | 
| +  // the task scheduled by the Syncer is _never_ run). | 
| +  core_thread()->Start(); | 
| +  base::WaitableEvent v_ran(false, false); | 
| +  scoped_ptr<BookmarkModelWorkerVisitor> v(new BookmarkModelWorkerVisitor( | 
| +       ui_loop(), &v_ran, false)); | 
| +  base::WaitableEvent* jobs[] = { &v_ran }; | 
| + | 
| +  // The current message loop is not running, so queue a task to cause | 
| +  // BookmarkModelWorker::Stop() to play a crucial role. See comment below. | 
| +  syncer_thread()->message_loop()->PostTask(FROM_HERE, | 
| +      new FakeSyncShareTask(syncer(), v.get())); | 
| + | 
| +  // This is what gets the core_thread blocked on the syncer_thread. | 
| +  core_thread()->message_loop()->PostTask(FROM_HERE, | 
| +      new FakeSyncapiShutdownTask(syncer_thread(), bmw(), jobs, 1)); | 
| + | 
| +  // This is what gets the UI thread blocked until NotifyExitRequested, | 
| +  // which is called when FakeSyncapiShutdownTask runs and deletes the syncer. | 
| +  bmw()->Stop(); | 
| + | 
| +  EXPECT_FALSE(syncer_thread()->IsRunning()); | 
| +  core_thread()->Stop(); | 
| +} | 
| + | 
| +TEST_F(BookmarkModelWorkerTest, HypotheticalManualPumpFlooding) { | 
| +  // This situation should not happen in real life because the Syncer should | 
| +  // never send more than one CallDoWork notification after early_exit_requested | 
| +  // has been set, but our BookmarkModelWorker is built to handle this case | 
| +  // nonetheless. It may be needed in the future, and since we support it and | 
| +  // it is not actually exercised in the wild this test is essential. | 
| +  // It is identical to above except we schedule more than one visitor. | 
| +  core_thread()->Start(); | 
| + | 
| +  // Our ammunition. | 
| +  base::WaitableEvent fox1_ran(false, false); | 
| +  scoped_ptr<BookmarkModelWorkerVisitor> fox1(new BookmarkModelWorkerVisitor( | 
| +      ui_loop(), &fox1_ran, false)); | 
| +  base::WaitableEvent fox2_ran(false, false); | 
| +  scoped_ptr<BookmarkModelWorkerVisitor> fox2(new BookmarkModelWorkerVisitor( | 
| +      ui_loop(), &fox2_ran, false)); | 
| +  base::WaitableEvent fox3_ran(false, false); | 
| +  scoped_ptr<BookmarkModelWorkerVisitor> fox3(new BookmarkModelWorkerVisitor( | 
| +      ui_loop(), &fox3_ran, false)); | 
| +  base::WaitableEvent* jobs[] = { &fox1_ran, &fox2_ran, &fox3_ran }; | 
| + | 
| +  // The current message loop is not running, so queue a task to cause | 
| +  // BookmarkModelWorker::Stop() to play a crucial role. See comment below. | 
| +  syncer_thread()->message_loop()->PostTask(FROM_HERE, | 
| +      new FakeSyncShareTask(syncer(), fox1.get())); | 
| +  syncer_thread()->message_loop()->PostTask(FROM_HERE, | 
| +      new FakeSyncShareTask(syncer(), fox2.get())); | 
| + | 
| +  // This is what gets the core_thread blocked on the syncer_thread. | 
| +  core_thread()->message_loop()->PostTask(FROM_HERE, | 
| +      new FakeSyncapiShutdownTask(syncer_thread(), bmw(), jobs, 3)); | 
| +  syncer_thread()->message_loop()->PostTask(FROM_HERE, | 
| +      new FakeSyncShareTask(syncer(), fox3.get())); | 
| + | 
| +  // This is what gets the UI thread blocked until NotifyExitRequested, | 
| +  // which is called when FakeSyncapiShutdownTask runs and deletes the syncer. | 
| +  bmw()->Stop(); | 
| + | 
| +  // Was the thread killed? | 
| +  EXPECT_FALSE(syncer_thread()->IsRunning()); | 
| +  core_thread()->Stop(); | 
| +} | 
| + | 
| +#endif  // CHROME_PERSONALIZATION | 
|  | 
| Property changes on: chrome\browser\sync\glue\bookmark_model_worker_unittest.cc | 
| ___________________________________________________________________ | 
| Added: svn:eol-style | 
| + LF | 
|  | 
|  |