+ // The directory should not become watched because of an error.
+ ASSERT_EQ(1u, log.size());
+ EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND, log[0]);
+ EXPECT_EQ(0u, notification_log.size());
+
+ Watchers* const watchers = provided_file_system_->GetWatchers();
+ EXPECT_EQ(0u, watchers->size());
+
+ // The observer should not be called.
+ EXPECT_EQ(0, observer.list_changed_counter());
+ EXPECT_EQ(0, observer.tag_updated_counter());
+
+ provided_file_system_->RemoveObserver(&observer);
+}
+
+TEST_F(FileSystemProviderProvidedFileSystemTest, AddWatcher) {
+ Log log;
+ Observer observer;
+
+ provided_file_system_->AddObserver(&observer);
+
+ provided_file_system_->AddWatcher(
+ GURL(kOrigin),
+ base::FilePath::FromUTF8Unsafe(kDirectoryPath),
+ false /* recursive */,
+ true /* persistent */,
+ base::Bind(&LogStatus, base::Unretained(&log)),
+ storage::WatcherManager::NotificationCallback());
+ base::RunLoop().RunUntilIdle();
+
+ ASSERT_EQ(1u, log.size());
+ EXPECT_EQ(base::File::FILE_OK, log[0]);
+ EXPECT_EQ(1, observer.list_changed_counter());
+ EXPECT_EQ(0, observer.tag_updated_counter());
+
+ Watchers* const watchers = provided_file_system_->GetWatchers();
+ ASSERT_EQ(1u, watchers->size());
+ const Watcher& watcher = watchers->begin()->second;
+ EXPECT_EQ(FILE_PATH_LITERAL(kDirectoryPath), watcher.entry_path.value());
+ EXPECT_FALSE(watcher.recursive);
+ EXPECT_EQ("", watcher.last_tag);
+
+ provided_file_system_->RemoveObserver(&observer);
+}
+
+TEST_F(FileSystemProviderProvidedFileSystemTest, AddWatcher_PersistentIllegal) {
+ {
+ // Adding a persistent watcher with a notification callback is not allowed,
+ // as it's basically impossible to restore the callback after a shutdown.
+ Log log;
+ NotificationLog notification_log;
+
+ Observer observer;
+ provided_file_system_->AddObserver(&observer);
+
+ provided_file_system_->AddWatcher(
+ GURL(kOrigin),
+ base::FilePath::FromUTF8Unsafe(kDirectoryPath),
+ false /* recursive */,
+ true /* persistent */,
+ base::Bind(&LogStatus, base::Unretained(&log)),
+ base::Bind(&LogNotification, base::Unretained(¬ification_log)));
+ base::RunLoop().RunUntilIdle();
+
+ ASSERT_EQ(1u, log.size());
+ EXPECT_EQ(base::File::FILE_ERROR_INVALID_OPERATION, log[0]);
+ EXPECT_EQ(0, observer.list_changed_counter());
+ EXPECT_EQ(0, observer.tag_updated_counter());
+
+ provided_file_system_->RemoveObserver(&observer);
+ }
+
+ {
+ // Adding a persistent watcher is not allowed if the file system doesn't
+ // support the notify tag. It's because the notify tag is essential to be
+ // able to recreate notification during shutdown.
+ Log log;
+ Observer observer;
+
+ // Create a provided file system interface, which does not support a notify
+ // tag, though.
+ const base::FilePath mount_path =
+ util::GetMountPath(profile_.get(), kExtensionId, kFileSystemId);
+ MountOptions mount_options;
+ mount_options.file_system_id = kFileSystemId;
+ mount_options.display_name = kDisplayName;
+ mount_options.supports_notify_tag = false;
+ ProvidedFileSystemInfo file_system_info(
+ kExtensionId, mount_options, mount_path);
+ ProvidedFileSystem simple_provided_file_system(profile_.get(),
+ file_system_info);
+ simple_provided_file_system.SetEventRouterForTesting(event_router_.get());
+ simple_provided_file_system.SetNotificationManagerForTesting(
+ make_scoped_ptr(new StubNotificationManager));
+
+ simple_provided_file_system.AddObserver(&observer);
+
+ simple_provided_file_system.AddWatcher(
+ GURL(kOrigin),
+ base::FilePath::FromUTF8Unsafe(kDirectoryPath),
+ false /* recursive */,
+ true /* persistent */,
+ base::Bind(&LogStatus, base::Unretained(&log)),
+ storage::WatcherManager::NotificationCallback());
+ base::RunLoop().RunUntilIdle();
+
+ ASSERT_EQ(1u, log.size());
+ EXPECT_EQ(base::File::FILE_ERROR_INVALID_OPERATION, log[0]);
+ EXPECT_EQ(0, observer.list_changed_counter());
+ EXPECT_EQ(0, observer.tag_updated_counter());
+
+ simple_provided_file_system.RemoveObserver(&observer);
+ }
+}
+
+TEST_F(FileSystemProviderProvidedFileSystemTest, AddWatcher_Exists) {
+ Observer observer;
+ provided_file_system_->AddObserver(&observer);
+
+ {
+ // First watch a directory not recursively.
+ Log log;
+ provided_file_system_->AddWatcher(
+ GURL(kOrigin),
+ base::FilePath::FromUTF8Unsafe(kDirectoryPath),
+ false /* recursive */,
+ true /* persistent */,
+ base::Bind(&LogStatus, base::Unretained(&log)),
+ storage::WatcherManager::NotificationCallback());
+ base::RunLoop().RunUntilIdle();
+
+ ASSERT_EQ(1u, log.size());
+ EXPECT_EQ(base::File::FILE_OK, log[0]);
+ EXPECT_EQ(1, observer.list_changed_counter());
+ EXPECT_EQ(0, observer.tag_updated_counter());
+
+ Watchers* const watchers = provided_file_system_->GetWatchers();
+ ASSERT_TRUE(watchers);
+ ASSERT_EQ(1u, watchers->size());
+ const auto& watcher_it = watchers->find(
+ WatcherKey(base::FilePath(FILE_PATH_LITERAL(kDirectoryPath)),
+ false /* recursive */));
+ ASSERT_NE(watchers->end(), watcher_it);
+
+ EXPECT_EQ(1u, watcher_it->second.subscribers.size());
+ const auto& subscriber_it =
+ watcher_it->second.subscribers.find(GURL(kOrigin));
+ ASSERT_NE(watcher_it->second.subscribers.end(), subscriber_it);
+ EXPECT_EQ(kOrigin, subscriber_it->second.origin.spec());
+ EXPECT_TRUE(subscriber_it->second.persistent);
+ }
+
+ {
+ // Create another non-recursive observer. That should fail.
+ Log log;
+ provided_file_system_->AddWatcher(
+ GURL(kOrigin),
+ base::FilePath::FromUTF8Unsafe(kDirectoryPath),
+ false /* recursive */,
+ true /* persistent */,
+ base::Bind(&LogStatus, base::Unretained(&log)),
+ storage::WatcherManager::NotificationCallback());
+ base::RunLoop().RunUntilIdle();
+
+ ASSERT_EQ(1u, log.size());
+ EXPECT_EQ(base::File::FILE_ERROR_EXISTS, log[0]);
+ EXPECT_EQ(1, observer.list_changed_counter()); // No changes on the list.
+ EXPECT_EQ(0, observer.tag_updated_counter());
+ }
+
+ {
+ // Lastly, create another recursive observer. That should succeed.
+ Log log;
+ provided_file_system_->AddWatcher(
+ GURL(kOrigin),
+ base::FilePath::FromUTF8Unsafe(kDirectoryPath),
+ true /* recursive */,
+ true /* persistent */,
+ base::Bind(&LogStatus, base::Unretained(&log)),
+ storage::WatcherManager::NotificationCallback());
+ base::RunLoop().RunUntilIdle();
+
+ ASSERT_EQ(1u, log.size());
+ EXPECT_EQ(base::File::FILE_OK, log[0]);
+ EXPECT_EQ(2, observer.list_changed_counter());
+ EXPECT_EQ(0, observer.tag_updated_counter());
+ }
+
+ provided_file_system_->RemoveObserver(&observer);
+}
+
+TEST_F(FileSystemProviderProvidedFileSystemTest, AddWatcher_MultipleOrigins) {
+ Observer observer;
+ provided_file_system_->AddObserver(&observer);
+
+ {
+ // First watch a directory not recursively.
+ Log log;
+ NotificationLog notification_log;
+
+ provided_file_system_->AddWatcher(
+ GURL(kOrigin),
+ base::FilePath::FromUTF8Unsafe(kDirectoryPath),
+ false /* recursive */,
+ false /* persistent */,
+ base::Bind(&LogStatus, base::Unretained(&log)),
+ base::Bind(&LogNotification, base::Unretained(¬ification_log)));
+ base::RunLoop().RunUntilIdle();
+
+ ASSERT_EQ(1u, log.size());
+ EXPECT_EQ(base::File::FILE_OK, log[0]);
+ EXPECT_EQ(1, observer.list_changed_counter());
+ EXPECT_EQ(0, observer.tag_updated_counter());
+ EXPECT_EQ(0u, notification_log.size());
+
+ Watchers* const watchers = provided_file_system_->GetWatchers();
+ ASSERT_TRUE(watchers);
+ ASSERT_EQ(1u, watchers->size());
+ const auto& watcher_it = watchers->find(
+ WatcherKey(base::FilePath(FILE_PATH_LITERAL(kDirectoryPath)),
+ false /* recursive */));
+ ASSERT_NE(watchers->end(), watcher_it);
+
+ EXPECT_EQ(1u, watcher_it->second.subscribers.size());
+ const auto& subscriber_it =
+ watcher_it->second.subscribers.find(GURL(kOrigin));
+ ASSERT_NE(watcher_it->second.subscribers.end(), subscriber_it);
+ EXPECT_EQ(kOrigin, subscriber_it->first.spec());
+ EXPECT_EQ(kOrigin, subscriber_it->second.origin.spec());
+ EXPECT_FALSE(subscriber_it->second.persistent);
+ }
+
+ {
+ // Create another watcher, but recursive and with a different origin.
+ Log log;
+ NotificationLog notification_log;
+
+ provided_file_system_->AddWatcher(
+ GURL(kAnotherOrigin),
+ base::FilePath::FromUTF8Unsafe(kDirectoryPath),
+ true /* recursive */,
+ false /* persistent */,
+ base::Bind(&LogStatus, base::Unretained(&log)),
+ base::Bind(&LogNotification, base::Unretained(¬ification_log)));
+ base::RunLoop().RunUntilIdle();
+
+ ASSERT_EQ(1u, log.size());
+ EXPECT_EQ(base::File::FILE_OK, log[0]);
+ EXPECT_EQ(2, observer.list_changed_counter());
+ EXPECT_EQ(0, observer.tag_updated_counter());
+ EXPECT_EQ(0u, notification_log.size());
+
+ Watchers* const watchers = provided_file_system_->GetWatchers();
+ ASSERT_TRUE(watchers);
+ ASSERT_EQ(2u, watchers->size());
+ const auto& watcher_it = watchers->find(
+ WatcherKey(base::FilePath(FILE_PATH_LITERAL(kDirectoryPath)),
+ false /* recursive */));
+ ASSERT_NE(watchers->end(), watcher_it);
+
+ EXPECT_EQ(1u, watcher_it->second.subscribers.size());
+ const auto& subscriber_it =
+ watcher_it->second.subscribers.find(GURL(kOrigin));
+ ASSERT_NE(watcher_it->second.subscribers.end(), subscriber_it);
+ EXPECT_EQ(kOrigin, subscriber_it->first.spec());
+ EXPECT_EQ(kOrigin, subscriber_it->second.origin.spec());
+ EXPECT_FALSE(subscriber_it->second.persistent);
+ }
+
+ {
+ // Remove the second watcher gracefully.
+ Log log;
+ provided_file_system_->RemoveWatcher(
+ GURL(kAnotherOrigin),
+ base::FilePath::FromUTF8Unsafe(kDirectoryPath),
+ true /* recursive */,
+ base::Bind(&LogStatus, base::Unretained(&log)));
+ base::RunLoop().RunUntilIdle();
+
+ ASSERT_EQ(1u, log.size());
+ EXPECT_EQ(base::File::FILE_OK, log[0]);
+ EXPECT_EQ(3, observer.list_changed_counter());
+ EXPECT_EQ(0, observer.tag_updated_counter());
+
+ Watchers* const watchers = provided_file_system_->GetWatchers();
+ ASSERT_TRUE(watchers);
+ EXPECT_EQ(1u, watchers->size());
+ const auto& watcher_it = watchers->find(
+ WatcherKey(base::FilePath(FILE_PATH_LITERAL(kDirectoryPath)),
+ false /* recursive */));
+ ASSERT_NE(watchers->end(), watcher_it);
+
+ EXPECT_EQ(1u, watcher_it->second.subscribers.size());
+ const auto& subscriber_it =
+ watcher_it->second.subscribers.find(GURL(kOrigin));
+ ASSERT_NE(watcher_it->second.subscribers.end(), subscriber_it);
+ EXPECT_EQ(kOrigin, subscriber_it->first.spec());
+ EXPECT_EQ(kOrigin, subscriber_it->second.origin.spec());
+ EXPECT_FALSE(subscriber_it->second.persistent);
+ }
+
+ provided_file_system_->RemoveObserver(&observer);
+}
+
+TEST_F(FileSystemProviderProvidedFileSystemTest, RemoveWatcher) {
+ Observer observer;
+ provided_file_system_->AddObserver(&observer);
+
+ {
+ // First, confirm that removing a watcher which does not exist results in an
+ // error.
+ Log log;
+ provided_file_system_->RemoveWatcher(
+ GURL(kOrigin),
+ base::FilePath::FromUTF8Unsafe(kDirectoryPath),
+ false /* recursive */,
+ base::Bind(&LogStatus, base::Unretained(&log)));
+ base::RunLoop().RunUntilIdle();
+
+ ASSERT_EQ(1u, log.size());
+ EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND, log[0]);
+ EXPECT_EQ(0, observer.list_changed_counter());
+ EXPECT_EQ(0, observer.tag_updated_counter());
+ }
+
+ {
+ // Watch a directory not recursively.
+ Log log;
+ NotificationLog notification_log;
+
+ provided_file_system_->AddWatcher(
+ GURL(kOrigin),
+ base::FilePath::FromUTF8Unsafe(kDirectoryPath),
+ false /* recursive */,
+ false /* persistent */,
+ base::Bind(&LogStatus, base::Unretained(&log)),
+ base::Bind(&LogNotification, base::Unretained(¬ification_log)));
+ base::RunLoop().RunUntilIdle();
+
+ ASSERT_EQ(1u, log.size());
+ EXPECT_EQ(base::File::FILE_OK, log[0]);
+ EXPECT_EQ(1, observer.list_changed_counter());
+ EXPECT_EQ(0, observer.tag_updated_counter());
+ EXPECT_EQ(0u, notification_log.size());
+
+ Watchers* const watchers = provided_file_system_->GetWatchers();
+ EXPECT_EQ(1u, watchers->size());
+ }
+
+ {
+ // Remove a watcher gracefully.
+ Log log;
+ provided_file_system_->RemoveWatcher(
+ GURL(kOrigin),
+ base::FilePath::FromUTF8Unsafe(kDirectoryPath),
+ false /* recursive */,
+ base::Bind(&LogStatus, base::Unretained(&log)));
+ base::RunLoop().RunUntilIdle();
+
+ ASSERT_EQ(1u, log.size());
+ EXPECT_EQ(base::File::FILE_OK, log[0]);
+ EXPECT_EQ(2, observer.list_changed_counter());
+ EXPECT_EQ(0, observer.tag_updated_counter());
+
+ Watchers* const watchers = provided_file_system_->GetWatchers();
+ EXPECT_EQ(0u, watchers->size());
+ }
+
+ {
+ // Confirm that it's possible to watch it again.
+ Log log;
+ NotificationLog notification_log;
+
+ provided_file_system_->AddWatcher(
+ GURL(kOrigin),
+ base::FilePath::FromUTF8Unsafe(kDirectoryPath),
+ false /* recursive */,
+ false /* persistent */,
+ base::Bind(&LogStatus, base::Unretained(&log)),
+ base::Bind(&LogNotification, base::Unretained(¬ification_log)));
+ base::RunLoop().RunUntilIdle();
+
+ ASSERT_EQ(1u, log.size());
+ EXPECT_EQ(base::File::FILE_OK, log[0]);
+ EXPECT_EQ(3, observer.list_changed_counter());
+ EXPECT_EQ(0, observer.tag_updated_counter());
+ EXPECT_EQ(0u, notification_log.size());
+
+ Watchers* const watchers = provided_file_system_->GetWatchers();
+ EXPECT_EQ(1u, watchers->size());
+ }
+
+ {
+ // Finally, remove it, but with an error from extension. That should result
+ // in a removed watcher, anyway. The error code should not be passed.
+ event_router_->set_reply_result(base::File::FILE_ERROR_FAILED);
+
+ Log log;
+ provided_file_system_->RemoveWatcher(
+ GURL(kOrigin),
+ base::FilePath::FromUTF8Unsafe(kDirectoryPath),
+ false /* recursive */,
+ base::Bind(&LogStatus, base::Unretained(&log)));
+ base::RunLoop().RunUntilIdle();
+
+ ASSERT_EQ(1u, log.size());
+ EXPECT_EQ(base::File::FILE_OK, log[0]);
+ EXPECT_EQ(4, observer.list_changed_counter());
+ EXPECT_EQ(0, observer.tag_updated_counter());
+
+ Watchers* const watchers = provided_file_system_->GetWatchers();
+ EXPECT_EQ(0u, watchers->size());
+ }
+
+ provided_file_system_->RemoveObserver(&observer);
+}
+
+TEST_F(FileSystemProviderProvidedFileSystemTest, Notify) {
+ Observer observer;
+ provided_file_system_->AddObserver(&observer);
+ NotificationLog notification_log;
+
+ {
+ // Watch a directory.
+ Log log;
+
+ provided_file_system_->AddWatcher(
+ GURL(kOrigin),
+ base::FilePath::FromUTF8Unsafe(kDirectoryPath),
+ false /* recursive */,
+ false /* persistent */,
+ base::Bind(&LogStatus, base::Unretained(&log)),
+ base::Bind(&LogNotification, base::Unretained(¬ification_log)));
+ base::RunLoop().RunUntilIdle();
+
+ ASSERT_EQ(1u, log.size());
+ EXPECT_EQ(base::File::FILE_OK, log[0]);
+ EXPECT_EQ(1, observer.list_changed_counter());
+ EXPECT_EQ(0, observer.tag_updated_counter());
+ EXPECT_EQ(0u, notification_log.size());
+
+ Watchers* const watchers = provided_file_system_->GetWatchers();
+ EXPECT_EQ(1u, watchers->size());
+ provided_file_system_->GetWatchers();
+ EXPECT_EQ("", watchers->begin()->second.last_tag);
+ }
+
+ {
+ // Notify about a change.
+ const storage::WatcherManager::ChangeType change_type =
+ storage::WatcherManager::CHANGED;
+ const std::string tag = "hello-world";
+ EXPECT_TRUE(provided_file_system_->Notify(
+ base::FilePath::FromUTF8Unsafe(kDirectoryPath),
+ false /* recursive */,
+ change_type,
+ make_scoped_ptr(new ProvidedFileSystemObserver::Changes),
+ tag));
+
+ // Confirm that the notification callback was called.
+ ASSERT_EQ(1u, notification_log.size());
+ EXPECT_EQ(change_type, notification_log[0]);
+
+ // Verify the observer event.
+ ASSERT_EQ(1u, observer.change_events().size());
+ const Observer::ChangeEvent* const change_event =
+ observer.change_events()[0];
+ EXPECT_EQ(change_type, change_event->change_type());
+ EXPECT_EQ(0u, change_event->changes().size());
+
+ // The tag should not be updated in advance, before all observers handle
+ // the notification.
+ Watchers* const watchers = provided_file_system_->GetWatchers();
+ EXPECT_EQ(1u, watchers->size());
+ provided_file_system_->GetWatchers();
+ EXPECT_EQ("", watchers->begin()->second.last_tag);
+
+ // Wait until all observers finish handling the notification.
+ base::RunLoop().RunUntilIdle();
+
+ // Confirm, that the watcher still exists, and that the tag is updated.
+ ASSERT_EQ(1u, watchers->size());
+ EXPECT_EQ(tag, watchers->begin()->second.last_tag);
+ EXPECT_EQ(1, observer.list_changed_counter());
+ EXPECT_EQ(1, observer.tag_updated_counter());
+ }
+
+ {
+ // Notify about deleting of the watched entry.
+ const storage::WatcherManager::ChangeType change_type =
+ storage::WatcherManager::DELETED;
+ const ProvidedFileSystemObserver::Changes changes;
+ const std::string tag = "chocolate-disco";
+ EXPECT_TRUE(provided_file_system_->Notify(
+ base::FilePath::FromUTF8Unsafe(kDirectoryPath),
+ false /* recursive */,
+ change_type,
+ make_scoped_ptr(new ProvidedFileSystemObserver::Changes),
+ tag));
+ base::RunLoop().RunUntilIdle();
+
+ // Confirm that the notification callback was called.
+ ASSERT_EQ(2u, notification_log.size());
+ EXPECT_EQ(change_type, notification_log[1]);
+
+ // Verify the observer event.
+ ASSERT_EQ(2u, observer.change_events().size());
+ const Observer::ChangeEvent* const change_event =
+ observer.change_events()[1];
+ EXPECT_EQ(change_type, change_event->change_type());
+ EXPECT_EQ(0u, change_event->changes().size());
+ }
+
+ // Confirm, that the watcher is removed.
+ {
+ Watchers* const watchers = provided_file_system_->GetWatchers();
+ EXPECT_EQ(0u, watchers->size());
+ EXPECT_EQ(2, observer.list_changed_counter());
+ EXPECT_EQ(2, observer.tag_updated_counter());
+ }
+
+ provided_file_system_->RemoveObserver(&observer);