1 // Copyright 2013 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_file_system/drive_backend/remote_to_local_syncer.h"
6
7 #include <map>
8 #include <utility>
9
10 #include "base/bind.h"
11 #include "base/callback.h"
12 #include "base/files/scoped_temp_dir.h"
13 #include "base/macros.h"
14 #include "base/run_loop.h"
15 #include "base/threading/thread_task_runner_handle.h"
16 #include "chrome/browser/sync_file_system/drive_backend/drive_backend_constants.h"
17 #include "chrome/browser/sync_file_system/drive_backend/drive_backend_test_util.h"
18 #include "chrome/browser/sync_file_system/drive_backend/fake_drive_service_helper.h"
19 #include "chrome/browser/sync_file_system/drive_backend/list_changes_task.h"
20 #include "chrome/browser/sync_file_system/drive_backend/metadata_database.h"
21 #include "chrome/browser/sync_file_system/drive_backend/sync_engine_context.h"
22 #include "chrome/browser/sync_file_system/drive_backend/sync_engine_initializer.h"
23 #include "chrome/browser/sync_file_system/drive_backend/sync_task_manager.h"
24 #include "chrome/browser/sync_file_system/drive_backend/sync_task_token.h"
25 #include "chrome/browser/sync_file_system/fake_remote_change_processor.h"
26 #include "chrome/browser/sync_file_system/sync_file_system_test_util.h"
27 #include "chrome/browser/sync_file_system/syncable_file_system_util.h"
28 #include "components/drive/drive_uploader.h"
29 #include "components/drive/service/fake_drive_service.h"
30 #include "content/public/test/browser_task_environment.h"
31 #include "google_apis/drive/drive_api_error_codes.h"
32 #include "mojo/public/cpp/bindings/pending_remote.h"
33 #include "testing/gtest/include/gtest/gtest.h"
34 #include "third_party/leveldatabase/leveldb_chrome.h"
35
36 namespace sync_file_system {
37 namespace drive_backend {
38
39 namespace {
40
URL(const GURL & origin,const std::string & path)41 storage::FileSystemURL URL(const GURL& origin, const std::string& path) {
42 return CreateSyncableFileSystemURL(
43 origin, base::FilePath::FromUTF8Unsafe(path));
44 }
45
46 } // namespace
47
48 class RemoteToLocalSyncerTest : public testing::Test {
49 public:
50 typedef FakeRemoteChangeProcessor::URLToFileChangesMap URLToFileChangesMap;
51
RemoteToLocalSyncerTest()52 RemoteToLocalSyncerTest()
53 : task_environment_(content::BrowserTaskEnvironment::IO_MAINLOOP) {}
~RemoteToLocalSyncerTest()54 ~RemoteToLocalSyncerTest() override {}
55
SetUp()56 void SetUp() override {
57 ASSERT_TRUE(database_dir_.CreateUniqueTempDir());
58 in_memory_env_ = leveldb_chrome::NewMemEnv("RemoteToLocalSyncerTest");
59
60 std::unique_ptr<drive::FakeDriveService> fake_drive_service(
61 new drive::FakeDriveService);
62
63 std::unique_ptr<drive::DriveUploaderInterface> drive_uploader(
64 new drive::DriveUploader(fake_drive_service.get(),
65 base::ThreadTaskRunnerHandle::Get().get(),
66 mojo::NullRemote()));
67 fake_drive_helper_.reset(
68 new FakeDriveServiceHelper(fake_drive_service.get(),
69 drive_uploader.get(),
70 kSyncRootFolderTitle));
71 remote_change_processor_.reset(new FakeRemoteChangeProcessor);
72
73 context_.reset(new SyncEngineContext(
74 std::move(fake_drive_service), std::move(drive_uploader),
75 nullptr /* task_logger */, base::ThreadTaskRunnerHandle::Get(),
76 base::ThreadTaskRunnerHandle::Get()));
77 context_->SetRemoteChangeProcessor(remote_change_processor_.get());
78
79 RegisterSyncableFileSystem();
80
81 sync_task_manager_.reset(new SyncTaskManager(
82 base::WeakPtr<SyncTaskManager::Client>(), 10 /* max_parallel_task */,
83 base::ThreadTaskRunnerHandle::Get()));
84 sync_task_manager_->Initialize(SYNC_STATUS_OK);
85 }
86
TearDown()87 void TearDown() override {
88 sync_task_manager_.reset();
89 RevokeSyncableFileSystem();
90 fake_drive_helper_.reset();
91 context_.reset();
92 base::RunLoop().RunUntilIdle();
93 }
94
InitializeMetadataDatabase()95 void InitializeMetadataDatabase() {
96 SyncEngineInitializer* initializer = new SyncEngineInitializer(
97 context_.get(), database_dir_.GetPath(), in_memory_env_.get());
98 SyncStatusCode status = SYNC_STATUS_UNKNOWN;
99 sync_task_manager_->ScheduleSyncTask(
100 FROM_HERE, std::unique_ptr<SyncTask>(initializer),
101 SyncTaskManager::PRIORITY_MED,
102 base::Bind(&RemoteToLocalSyncerTest::DidInitializeMetadataDatabase,
103 base::Unretained(this), initializer, &status));
104
105 base::RunLoop().RunUntilIdle();
106 EXPECT_EQ(SYNC_STATUS_OK, status);
107 }
108
DidInitializeMetadataDatabase(SyncEngineInitializer * initializer,SyncStatusCode * status_out,SyncStatusCode status)109 void DidInitializeMetadataDatabase(SyncEngineInitializer* initializer,
110 SyncStatusCode* status_out,
111 SyncStatusCode status) {
112 *status_out = status;
113 context_->SetMetadataDatabase(initializer->PassMetadataDatabase());
114 }
115
116
RegisterApp(const std::string & app_id,const std::string & app_root_folder_id)117 void RegisterApp(const std::string& app_id,
118 const std::string& app_root_folder_id) {
119 SyncStatusCode status = context_->GetMetadataDatabase()->RegisterApp(
120 app_id, app_root_folder_id);
121 EXPECT_EQ(SYNC_STATUS_OK, status);
122 }
123
GetMetadataDatabase()124 MetadataDatabase* GetMetadataDatabase() {
125 return context_->GetMetadataDatabase();
126 }
127
128 protected:
CreateSyncRoot()129 std::string CreateSyncRoot() {
130 std::string sync_root_folder_id;
131 EXPECT_EQ(google_apis::HTTP_CREATED,
132 fake_drive_helper_->AddOrphanedFolder(
133 kSyncRootFolderTitle, &sync_root_folder_id));
134 return sync_root_folder_id;
135 }
136
CreateRemoteFolder(const std::string & parent_folder_id,const std::string & title)137 std::string CreateRemoteFolder(const std::string& parent_folder_id,
138 const std::string& title) {
139 std::string folder_id;
140 EXPECT_EQ(google_apis::HTTP_CREATED,
141 fake_drive_helper_->AddFolder(
142 parent_folder_id, title, &folder_id));
143 return folder_id;
144 }
145
CreateRemoteFile(const std::string & parent_folder_id,const std::string & title,const std::string & content)146 std::string CreateRemoteFile(const std::string& parent_folder_id,
147 const std::string& title,
148 const std::string& content) {
149 std::string file_id;
150 EXPECT_EQ(google_apis::HTTP_SUCCESS,
151 fake_drive_helper_->AddFile(
152 parent_folder_id, title, content, &file_id));
153 return file_id;
154 }
155
DeleteRemoteFile(const std::string & file_id)156 void DeleteRemoteFile(const std::string& file_id) {
157 EXPECT_EQ(google_apis::HTTP_NO_CONTENT,
158 fake_drive_helper_->DeleteResource(file_id));
159 }
160
CreateLocalFolder(const storage::FileSystemURL & url)161 void CreateLocalFolder(const storage::FileSystemURL& url) {
162 remote_change_processor_->UpdateLocalFileMetadata(
163 url, FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
164 SYNC_FILE_TYPE_DIRECTORY));
165 }
166
CreateLocalFile(const storage::FileSystemURL & url)167 void CreateLocalFile(const storage::FileSystemURL& url) {
168 remote_change_processor_->UpdateLocalFileMetadata(
169 url, FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
170 SYNC_FILE_TYPE_FILE));
171 }
172
RunSyncer()173 SyncStatusCode RunSyncer() {
174 SyncStatusCode status = SYNC_STATUS_UNKNOWN;
175 std::unique_ptr<RemoteToLocalSyncer> syncer(
176 new RemoteToLocalSyncer(context_.get()));
177 syncer->RunPreflight(SyncTaskToken::CreateForTesting(
178 CreateResultReceiver(&status)));
179 base::RunLoop().RunUntilIdle();
180 return status;
181 }
182
RunSyncerUntilIdle()183 SyncStatusCode RunSyncerUntilIdle() {
184 const int kRetryLimit = 100;
185 SyncStatusCode status = SYNC_STATUS_UNKNOWN;
186 int count = 0;
187 do {
188 if (count++ > kRetryLimit)
189 return status;
190 status = RunSyncer();
191 } while (status == SYNC_STATUS_OK ||
192 status == SYNC_STATUS_RETRY);
193 return status;
194 }
195
RunSyncerAndPromoteUntilIdle()196 SyncStatusCode RunSyncerAndPromoteUntilIdle() {
197 const int kRetryLimit = 100;
198 SyncStatusCode status = SYNC_STATUS_UNKNOWN;
199 MetadataDatabase* metadata_database = context_->GetMetadataDatabase();
200 int count = 0;
201 do {
202 if (count++ > kRetryLimit)
203 return status;
204 status = RunSyncer();
205 } while (status == SYNC_STATUS_OK ||
206 status == SYNC_STATUS_RETRY ||
207 metadata_database->PromoteDemotedTrackers());
208 return status;
209 }
210
ListChanges()211 SyncStatusCode ListChanges() {
212 SyncStatusCode status = SYNC_STATUS_UNKNOWN;
213 sync_task_manager_->ScheduleSyncTask(
214 FROM_HERE,
215 std::unique_ptr<SyncTask>(new ListChangesTask(context_.get())),
216 SyncTaskManager::PRIORITY_MED, CreateResultReceiver(&status));
217 base::RunLoop().RunUntilIdle();
218 return status;
219 }
220
AppendExpectedChange(const storage::FileSystemURL & url,FileChange::ChangeType change_type,SyncFileType file_type)221 void AppendExpectedChange(const storage::FileSystemURL& url,
222 FileChange::ChangeType change_type,
223 SyncFileType file_type) {
224 expected_changes_[url].push_back(FileChange(change_type, file_type));
225 }
226
VerifyConsistency()227 void VerifyConsistency() {
228 remote_change_processor_->VerifyConsistency(expected_changes_);
229 }
230
231 private:
232 content::BrowserTaskEnvironment task_environment_;
233 base::ScopedTempDir database_dir_;
234 std::unique_ptr<leveldb::Env> in_memory_env_;
235
236 std::unique_ptr<SyncEngineContext> context_;
237 std::unique_ptr<FakeDriveServiceHelper> fake_drive_helper_;
238 std::unique_ptr<FakeRemoteChangeProcessor> remote_change_processor_;
239
240 std::unique_ptr<SyncTaskManager> sync_task_manager_;
241
242 URLToFileChangesMap expected_changes_;
243
244 DISALLOW_COPY_AND_ASSIGN(RemoteToLocalSyncerTest);
245 };
246
TEST_F(RemoteToLocalSyncerTest,AddNewFile)247 TEST_F(RemoteToLocalSyncerTest, AddNewFile) {
248 const GURL kOrigin("chrome-extension://example");
249 const std::string sync_root = CreateSyncRoot();
250 const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host());
251 InitializeMetadataDatabase();
252 RegisterApp(kOrigin.host(), app_root);
253
254 const std::string folder1 = CreateRemoteFolder(app_root, "folder1");
255 const std::string file1 = CreateRemoteFile(app_root, "file1", "data1");
256 const std::string folder2 = CreateRemoteFolder(folder1, "folder2");
257 const std::string file2 = CreateRemoteFile(folder1, "file2", "data2");
258
259 EXPECT_EQ(SYNC_STATUS_NO_CHANGE_TO_SYNC, RunSyncerAndPromoteUntilIdle());
260
261 // Create expected changes.
262 // TODO(nhiroki): Clean up creating URL part.
263 AppendExpectedChange(URL(kOrigin, "folder1"),
264 FileChange::FILE_CHANGE_ADD_OR_UPDATE,
265 SYNC_FILE_TYPE_DIRECTORY);
266 AppendExpectedChange(URL(kOrigin, "file1"),
267 FileChange::FILE_CHANGE_ADD_OR_UPDATE,
268 SYNC_FILE_TYPE_FILE);
269 AppendExpectedChange(URL(kOrigin, "folder1/folder2"),
270 FileChange::FILE_CHANGE_ADD_OR_UPDATE,
271 SYNC_FILE_TYPE_DIRECTORY);
272 AppendExpectedChange(URL(kOrigin, "folder1/file2"),
273 FileChange::FILE_CHANGE_ADD_OR_UPDATE,
274 SYNC_FILE_TYPE_FILE);
275
276 VerifyConsistency();
277
278 EXPECT_FALSE(GetMetadataDatabase()->HasDirtyTracker());
279 }
280
TEST_F(RemoteToLocalSyncerTest,DeleteFile)281 TEST_F(RemoteToLocalSyncerTest, DeleteFile) {
282 const GURL kOrigin("chrome-extension://example");
283 const std::string sync_root = CreateSyncRoot();
284 const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host());
285 InitializeMetadataDatabase();
286 RegisterApp(kOrigin.host(), app_root);
287
288 const std::string folder = CreateRemoteFolder(app_root, "folder");
289 const std::string file = CreateRemoteFile(app_root, "file", "data");
290
291 AppendExpectedChange(URL(kOrigin, "folder"),
292 FileChange::FILE_CHANGE_ADD_OR_UPDATE,
293 SYNC_FILE_TYPE_DIRECTORY);
294 AppendExpectedChange(URL(kOrigin, "file"),
295 FileChange::FILE_CHANGE_ADD_OR_UPDATE,
296 SYNC_FILE_TYPE_FILE);
297
298 EXPECT_EQ(SYNC_STATUS_NO_CHANGE_TO_SYNC, RunSyncerAndPromoteUntilIdle());
299 VerifyConsistency();
300
301 DeleteRemoteFile(folder);
302 DeleteRemoteFile(file);
303
304 AppendExpectedChange(URL(kOrigin, "folder"),
305 FileChange::FILE_CHANGE_DELETE,
306 SYNC_FILE_TYPE_UNKNOWN);
307 AppendExpectedChange(URL(kOrigin, "file"),
308 FileChange::FILE_CHANGE_DELETE,
309 SYNC_FILE_TYPE_UNKNOWN);
310
311 EXPECT_EQ(SYNC_STATUS_OK, ListChanges());
312 EXPECT_EQ(SYNC_STATUS_NO_CHANGE_TO_SYNC, RunSyncerUntilIdle());
313 VerifyConsistency();
314
315 EXPECT_FALSE(GetMetadataDatabase()->HasDirtyTracker());
316 }
317
TEST_F(RemoteToLocalSyncerTest,DeleteNestedFiles)318 TEST_F(RemoteToLocalSyncerTest, DeleteNestedFiles) {
319 const GURL kOrigin("chrome-extension://example");
320 const std::string sync_root = CreateSyncRoot();
321 const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host());
322 InitializeMetadataDatabase();
323 RegisterApp(kOrigin.host(), app_root);
324
325 const std::string folder1 = CreateRemoteFolder(app_root, "folder1");
326 const std::string file1 = CreateRemoteFile(app_root, "file1", "data1");
327 const std::string folder2 = CreateRemoteFolder(folder1, "folder2");
328 const std::string file2 = CreateRemoteFile(folder1, "file2", "data2");
329
330 AppendExpectedChange(URL(kOrigin, "folder1"),
331 FileChange::FILE_CHANGE_ADD_OR_UPDATE,
332 SYNC_FILE_TYPE_DIRECTORY);
333 AppendExpectedChange(URL(kOrigin, "file1"),
334 FileChange::FILE_CHANGE_ADD_OR_UPDATE,
335 SYNC_FILE_TYPE_FILE);
336 AppendExpectedChange(URL(kOrigin, "folder1/folder2"),
337 FileChange::FILE_CHANGE_ADD_OR_UPDATE,
338 SYNC_FILE_TYPE_DIRECTORY);
339 AppendExpectedChange(URL(kOrigin, "folder1/file2"),
340 FileChange::FILE_CHANGE_ADD_OR_UPDATE,
341 SYNC_FILE_TYPE_FILE);
342
343 EXPECT_EQ(SYNC_STATUS_NO_CHANGE_TO_SYNC, RunSyncerAndPromoteUntilIdle());
344 VerifyConsistency();
345
346 DeleteRemoteFile(folder1);
347
348 AppendExpectedChange(URL(kOrigin, "folder1"),
349 FileChange::FILE_CHANGE_DELETE,
350 SYNC_FILE_TYPE_UNKNOWN);
351 // Changes for descendant files ("folder2" and "file2") should be ignored.
352
353 EXPECT_EQ(SYNC_STATUS_OK, ListChanges());
354 EXPECT_EQ(SYNC_STATUS_NO_CHANGE_TO_SYNC, RunSyncerUntilIdle());
355 VerifyConsistency();
356
357 EXPECT_FALSE(GetMetadataDatabase()->HasDirtyTracker());
358 }
359
TEST_F(RemoteToLocalSyncerTest,Conflict_CreateFileOnFolder)360 TEST_F(RemoteToLocalSyncerTest, Conflict_CreateFileOnFolder) {
361 const GURL kOrigin("chrome-extension://example");
362 const std::string sync_root = CreateSyncRoot();
363 const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host());
364 InitializeMetadataDatabase();
365 RegisterApp(kOrigin.host(), app_root);
366
367 CreateLocalFolder(URL(kOrigin, "folder"));
368 CreateRemoteFile(app_root, "folder", "data");
369
370 // Folder-File conflict happens. File creation should be ignored.
371
372 EXPECT_EQ(SYNC_STATUS_OK, ListChanges());
373 EXPECT_EQ(SYNC_STATUS_NO_CHANGE_TO_SYNC, RunSyncerUntilIdle());
374 VerifyConsistency();
375
376 // Tracker for the remote file should has low priority.
377 EXPECT_FALSE(GetMetadataDatabase()->GetDirtyTracker(nullptr));
378 EXPECT_TRUE(GetMetadataDatabase()->HasDemotedDirtyTracker());
379 }
380
TEST_F(RemoteToLocalSyncerTest,Conflict_CreateFolderOnFile)381 TEST_F(RemoteToLocalSyncerTest, Conflict_CreateFolderOnFile) {
382 const GURL kOrigin("chrome-extension://example");
383 const std::string sync_root = CreateSyncRoot();
384 const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host());
385 InitializeMetadataDatabase();
386 RegisterApp(kOrigin.host(), app_root);
387
388 EXPECT_EQ(SYNC_STATUS_NO_CHANGE_TO_SYNC, RunSyncerUntilIdle());
389 VerifyConsistency();
390
391 CreateLocalFile(URL(kOrigin, "file"));
392 CreateRemoteFolder(app_root, "file");
393
394 // File-Folder conflict happens. Folder should override the existing file.
395 AppendExpectedChange(URL(kOrigin, "file"),
396 FileChange::FILE_CHANGE_ADD_OR_UPDATE,
397 SYNC_FILE_TYPE_DIRECTORY);
398
399 EXPECT_EQ(SYNC_STATUS_OK, ListChanges());
400 EXPECT_EQ(SYNC_STATUS_NO_CHANGE_TO_SYNC, RunSyncerUntilIdle());
401 VerifyConsistency();
402
403 EXPECT_FALSE(GetMetadataDatabase()->HasDirtyTracker());
404 }
405
TEST_F(RemoteToLocalSyncerTest,Conflict_CreateFolderOnFolder)406 TEST_F(RemoteToLocalSyncerTest, Conflict_CreateFolderOnFolder) {
407 const GURL kOrigin("chrome-extension://example");
408 const std::string sync_root = CreateSyncRoot();
409 const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host());
410 InitializeMetadataDatabase();
411 RegisterApp(kOrigin.host(), app_root);
412
413 CreateLocalFolder(URL(kOrigin, "folder"));
414 CreateRemoteFolder(app_root, "folder");
415
416 // Folder-Folder conflict happens. Folder creation should be ignored.
417
418 EXPECT_EQ(SYNC_STATUS_OK, ListChanges());
419 EXPECT_EQ(SYNC_STATUS_NO_CHANGE_TO_SYNC, RunSyncerUntilIdle());
420 VerifyConsistency();
421
422 EXPECT_FALSE(GetMetadataDatabase()->HasDirtyTracker());
423 }
424
TEST_F(RemoteToLocalSyncerTest,Conflict_CreateFileOnFile)425 TEST_F(RemoteToLocalSyncerTest, Conflict_CreateFileOnFile) {
426 const GURL kOrigin("chrome-extension://example");
427 const std::string sync_root = CreateSyncRoot();
428 const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host());
429 InitializeMetadataDatabase();
430 RegisterApp(kOrigin.host(), app_root);
431
432 CreateLocalFile(URL(kOrigin, "file"));
433 CreateRemoteFile(app_root, "file", "data");
434
435 // File-File conflict happens. File creation should be ignored.
436
437 EXPECT_EQ(SYNC_STATUS_OK, ListChanges());
438 EXPECT_EQ(SYNC_STATUS_NO_CHANGE_TO_SYNC, RunSyncerUntilIdle());
439 VerifyConsistency();
440
441 // Tracker for the remote file should be lowered.
442 EXPECT_FALSE(GetMetadataDatabase()->GetDirtyTracker(nullptr));
443 EXPECT_TRUE(GetMetadataDatabase()->HasDemotedDirtyTracker());
444 }
445
TEST_F(RemoteToLocalSyncerTest,Conflict_CreateNestedFolderOnFile)446 TEST_F(RemoteToLocalSyncerTest, Conflict_CreateNestedFolderOnFile) {
447 const GURL kOrigin("chrome-extension://example");
448 const std::string sync_root = CreateSyncRoot();
449 const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host());
450 InitializeMetadataDatabase();
451 RegisterApp(kOrigin.host(), app_root);
452
453 EXPECT_EQ(SYNC_STATUS_NO_CHANGE_TO_SYNC, RunSyncerUntilIdle());
454 VerifyConsistency();
455
456 const std::string folder = CreateRemoteFolder(app_root, "folder");
457 CreateLocalFile(URL(kOrigin, "/folder"));
458 CreateRemoteFile(folder, "file", "data");
459
460 // File-Folder conflict happens. Folder should override the existing file.
461 AppendExpectedChange(URL(kOrigin, "/folder"),
462 FileChange::FILE_CHANGE_ADD_OR_UPDATE,
463 SYNC_FILE_TYPE_DIRECTORY);
464
465 EXPECT_EQ(SYNC_STATUS_OK, ListChanges());
466 EXPECT_EQ(SYNC_STATUS_NO_CHANGE_TO_SYNC, RunSyncerUntilIdle());
467 VerifyConsistency();
468 }
469
TEST_F(RemoteToLocalSyncerTest,AppRootDeletion)470 TEST_F(RemoteToLocalSyncerTest, AppRootDeletion) {
471 const GURL kOrigin("chrome-extension://example");
472 const std::string sync_root = CreateSyncRoot();
473 const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host());
474 InitializeMetadataDatabase();
475 RegisterApp(kOrigin.host(), app_root);
476
477 EXPECT_EQ(SYNC_STATUS_NO_CHANGE_TO_SYNC, RunSyncerUntilIdle());
478 VerifyConsistency();
479
480 DeleteRemoteFile(app_root);
481
482 AppendExpectedChange(URL(kOrigin, "/"),
483 FileChange::FILE_CHANGE_DELETE,
484 SYNC_FILE_TYPE_UNKNOWN);
485
486 EXPECT_EQ(SYNC_STATUS_OK, ListChanges());
487 EXPECT_EQ(SYNC_STATUS_NO_CHANGE_TO_SYNC, RunSyncerUntilIdle());
488 VerifyConsistency();
489
490 // SyncEngine will re-register the app and resurrect the app root later.
491 }
492
493 } // namespace drive_backend
494 } // namespace sync_file_system
495