OLD | NEW |
| (Empty) |
1 // Copyright (c) 2006-2009 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 #ifdef CHROME_PERSONALIZATION | |
5 | |
6 #include "base/thread.h" | |
7 #include "chrome/browser/sync/engine/syncapi.h" | |
8 #include "chrome/browser/sync/glue/bookmark_model_worker.h" | |
9 #include "testing/gtest/include/gtest/gtest.h" | |
10 | |
11 using browser_sync::BookmarkModelWorker; | |
12 using namespace sync_api; | |
13 | |
14 // Various boilerplate, primarily for the StopWithPendingWork test. | |
15 | |
16 class BookmarkModelWorkerVisitor : public ModelSafeWorkerInterface::Visitor { | |
17 public: | |
18 BookmarkModelWorkerVisitor(MessageLoop* faux_ui_loop, | |
19 base::WaitableEvent* was_run, | |
20 bool quit_loop) | |
21 : faux_ui_loop_(faux_ui_loop), quit_loop_when_run_(quit_loop), | |
22 was_run_(was_run) { } | |
23 virtual ~BookmarkModelWorkerVisitor() { } | |
24 | |
25 virtual void DoWork() { | |
26 EXPECT_EQ(MessageLoop::current(), faux_ui_loop_); | |
27 was_run_->Signal(); | |
28 if (quit_loop_when_run_) | |
29 MessageLoop::current()->Quit(); | |
30 } | |
31 | |
32 private: | |
33 MessageLoop* faux_ui_loop_; | |
34 bool quit_loop_when_run_; | |
35 base::WaitableEvent* was_run_; | |
36 DISALLOW_COPY_AND_ASSIGN(BookmarkModelWorkerVisitor); | |
37 }; | |
38 | |
39 // A faux-syncer that only interacts with its model safe worker. | |
40 class Syncer { | |
41 public: | |
42 explicit Syncer(BookmarkModelWorker* worker) : worker_(worker){ } | |
43 ~Syncer() { } | |
44 | |
45 void SyncShare(BookmarkModelWorkerVisitor* visitor) { | |
46 worker_->CallDoWorkFromModelSafeThreadAndWait(visitor); | |
47 } | |
48 private: | |
49 BookmarkModelWorker* worker_; | |
50 DISALLOW_COPY_AND_ASSIGN(Syncer); | |
51 }; | |
52 | |
53 // A task run from the SyncerThread to "sync share", ie tell the Syncer to | |
54 // ask it's ModelSafeWorker to do something. | |
55 class FakeSyncShareTask : public Task { | |
56 public: | |
57 FakeSyncShareTask(Syncer* syncer, BookmarkModelWorkerVisitor* visitor) | |
58 : syncer_(syncer), visitor_(visitor) { | |
59 } | |
60 virtual void Run() { | |
61 syncer_->SyncShare(visitor_); | |
62 } | |
63 private: | |
64 Syncer* syncer_; | |
65 BookmarkModelWorkerVisitor* visitor_; | |
66 DISALLOW_COPY_AND_ASSIGN(FakeSyncShareTask); | |
67 }; | |
68 | |
69 // A task run from the CoreThread to simulate terminating syncapi. | |
70 class FakeSyncapiShutdownTask : public Task { | |
71 public: | |
72 FakeSyncapiShutdownTask(base::Thread* syncer_thread, | |
73 BookmarkModelWorker* worker, | |
74 base::WaitableEvent** jobs, | |
75 size_t job_count) | |
76 : syncer_thread_(syncer_thread), worker_(worker), jobs_(jobs), | |
77 job_count_(job_count), all_jobs_done_(false, false) { } | |
78 virtual void Run() { | |
79 // In real life, we would try and close a sync directory, which would | |
80 // result in the syncer calling it's own destructor, which results in | |
81 // the SyncerThread::HaltSyncer being called, which sets the | |
82 // syncer in RequestEarlyExit mode and waits until the Syncer finishes | |
83 // SyncShare to remove the syncer from it's watch. Here we just manually | |
84 // wait until all outstanding jobs are done to simulate what happens in | |
85 // SyncerThread::HaltSyncer. | |
86 all_jobs_done_.WaitMany(jobs_, job_count_); | |
87 | |
88 // These two calls are made from SyncBackendHost::Core::DoShutdown. | |
89 syncer_thread_->Stop(); | |
90 worker_->OnSyncerShutdownComplete(); | |
91 } | |
92 private: | |
93 base::Thread* syncer_thread_; | |
94 BookmarkModelWorker* worker_; | |
95 base::WaitableEvent** jobs_; | |
96 size_t job_count_; | |
97 base::WaitableEvent all_jobs_done_; | |
98 DISALLOW_COPY_AND_ASSIGN(FakeSyncapiShutdownTask); | |
99 }; | |
100 | |
101 class BookmarkModelWorkerTest : public testing::Test { | |
102 public: | |
103 BookmarkModelWorkerTest() : faux_syncer_thread_("FauxSyncerThread"), | |
104 faux_core_thread_("FauxCoreThread") { } | |
105 | |
106 virtual void SetUp() { | |
107 faux_syncer_thread_.Start(); | |
108 bmw_.reset(new BookmarkModelWorker(&faux_ui_loop_)); | |
109 syncer_.reset(new Syncer(bmw_.get())); | |
110 } | |
111 | |
112 Syncer* syncer() { return syncer_.get(); } | |
113 BookmarkModelWorker* bmw() { return bmw_.get(); } | |
114 base::Thread* core_thread() { return &faux_core_thread_; } | |
115 base::Thread* syncer_thread() { return &faux_syncer_thread_; } | |
116 MessageLoop* ui_loop() { return &faux_ui_loop_; } | |
117 private: | |
118 MessageLoop faux_ui_loop_; | |
119 base::Thread faux_syncer_thread_; | |
120 base::Thread faux_core_thread_; | |
121 scoped_ptr<BookmarkModelWorker> bmw_; | |
122 scoped_ptr<Syncer> syncer_; | |
123 }; | |
124 | |
125 TEST_F(BookmarkModelWorkerTest, ScheduledWorkRunsOnUILoop) { | |
126 base::WaitableEvent v_was_run(false, false); | |
127 scoped_ptr<BookmarkModelWorkerVisitor> v( | |
128 new BookmarkModelWorkerVisitor(ui_loop(), &v_was_run, true)); | |
129 | |
130 syncer_thread()->message_loop()->PostTask(FROM_HERE, | |
131 new FakeSyncShareTask(syncer(), v.get())); | |
132 | |
133 // We are on the UI thread, so run our loop to process the | |
134 // (hopefully) scheduled task from a SyncShare invocation. | |
135 MessageLoop::current()->Run(); | |
136 | |
137 bmw()->OnSyncerShutdownComplete(); | |
138 bmw()->Stop(); | |
139 syncer_thread()->Stop(); | |
140 } | |
141 | |
142 TEST_F(BookmarkModelWorkerTest, StopWithPendingWork) { | |
143 // What we want to set up is the following: | |
144 // ("ui_thread" is the thread we are currently executing on) | |
145 // 1 - simulate the user shutting down the browser, and the ui thread needing | |
146 // to terminate the core thread. | |
147 // 2 - the core thread is where the syncapi is accessed from, and so it needs | |
148 // to shut down the SyncerThread. | |
149 // 3 - the syncer is waiting on the BookmarkModelWorker to | |
150 // perform a task for it. | |
151 // The BookmarkModelWorker's manual shutdown pump will save the day, as the | |
152 // UI thread is not actually trying to join() the core thread, it is merely | |
153 // waiting for the SyncerThread to give it work or to finish. After that, it | |
154 // will join the core thread which should succeed as the SyncerThread has left | |
155 // the building. Unfortunately this test as written is not provably decidable, | |
156 // as it will always halt on success, but it may not on failure (namely if | |
157 // the task scheduled by the Syncer is _never_ run). | |
158 core_thread()->Start(); | |
159 base::WaitableEvent v_ran(false, false); | |
160 scoped_ptr<BookmarkModelWorkerVisitor> v(new BookmarkModelWorkerVisitor( | |
161 ui_loop(), &v_ran, false)); | |
162 base::WaitableEvent* jobs[] = { &v_ran }; | |
163 | |
164 // The current message loop is not running, so queue a task to cause | |
165 // BookmarkModelWorker::Stop() to play a crucial role. See comment below. | |
166 syncer_thread()->message_loop()->PostTask(FROM_HERE, | |
167 new FakeSyncShareTask(syncer(), v.get())); | |
168 | |
169 // This is what gets the core_thread blocked on the syncer_thread. | |
170 core_thread()->message_loop()->PostTask(FROM_HERE, | |
171 new FakeSyncapiShutdownTask(syncer_thread(), bmw(), jobs, 1)); | |
172 | |
173 // This is what gets the UI thread blocked until NotifyExitRequested, | |
174 // which is called when FakeSyncapiShutdownTask runs and deletes the syncer. | |
175 bmw()->Stop(); | |
176 | |
177 EXPECT_FALSE(syncer_thread()->IsRunning()); | |
178 core_thread()->Stop(); | |
179 } | |
180 | |
181 TEST_F(BookmarkModelWorkerTest, HypotheticalManualPumpFlooding) { | |
182 // This situation should not happen in real life because the Syncer should | |
183 // never send more than one CallDoWork notification after early_exit_requested | |
184 // has been set, but our BookmarkModelWorker is built to handle this case | |
185 // nonetheless. It may be needed in the future, and since we support it and | |
186 // it is not actually exercised in the wild this test is essential. | |
187 // It is identical to above except we schedule more than one visitor. | |
188 core_thread()->Start(); | |
189 | |
190 // Our ammunition. | |
191 base::WaitableEvent fox1_ran(false, false); | |
192 scoped_ptr<BookmarkModelWorkerVisitor> fox1(new BookmarkModelWorkerVisitor( | |
193 ui_loop(), &fox1_ran, false)); | |
194 base::WaitableEvent fox2_ran(false, false); | |
195 scoped_ptr<BookmarkModelWorkerVisitor> fox2(new BookmarkModelWorkerVisitor( | |
196 ui_loop(), &fox2_ran, false)); | |
197 base::WaitableEvent fox3_ran(false, false); | |
198 scoped_ptr<BookmarkModelWorkerVisitor> fox3(new BookmarkModelWorkerVisitor( | |
199 ui_loop(), &fox3_ran, false)); | |
200 base::WaitableEvent* jobs[] = { &fox1_ran, &fox2_ran, &fox3_ran }; | |
201 | |
202 // The current message loop is not running, so queue a task to cause | |
203 // BookmarkModelWorker::Stop() to play a crucial role. See comment below. | |
204 syncer_thread()->message_loop()->PostTask(FROM_HERE, | |
205 new FakeSyncShareTask(syncer(), fox1.get())); | |
206 syncer_thread()->message_loop()->PostTask(FROM_HERE, | |
207 new FakeSyncShareTask(syncer(), fox2.get())); | |
208 | |
209 // This is what gets the core_thread blocked on the syncer_thread. | |
210 core_thread()->message_loop()->PostTask(FROM_HERE, | |
211 new FakeSyncapiShutdownTask(syncer_thread(), bmw(), jobs, 3)); | |
212 syncer_thread()->message_loop()->PostTask(FROM_HERE, | |
213 new FakeSyncShareTask(syncer(), fox3.get())); | |
214 | |
215 // This is what gets the UI thread blocked until NotifyExitRequested, | |
216 // which is called when FakeSyncapiShutdownTask runs and deletes the syncer. | |
217 bmw()->Stop(); | |
218 | |
219 // Was the thread killed? | |
220 EXPECT_FALSE(syncer_thread()->IsRunning()); | |
221 core_thread()->Stop(); | |
222 } | |
223 | |
224 #endif // CHROME_PERSONALIZATION | |
OLD | NEW |