// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-// TODO(mtomasz): Move these test cases to operations/unmount_unittest.cc.
-
#include "chrome/browser/chromeos/file_system_provider/provided_file_system.h"
#include <string>
#include <vector>
#include "base/files/file.h"
+#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
#include "base/run_loop.h"
+#include "base/thread_task_runner_handle.h"
#include "base/values.h"
#include "chrome/browser/chromeos/file_system_provider/mount_path_util.h"
+#include "chrome/browser/chromeos/file_system_provider/notification_manager.h"
#include "chrome/browser/chromeos/file_system_provider/provided_file_system_info.h"
#include "chrome/browser/chromeos/file_system_provider/provided_file_system_interface.h"
+#include "chrome/browser/chromeos/file_system_provider/provided_file_system_observer.h"
#include "chrome/browser/chromeos/file_system_provider/request_manager.h"
+#include "chrome/browser/chromeos/file_system_provider/watcher.h"
#include "chrome/common/extensions/api/file_system_provider.h"
+#include "chrome/common/extensions/api/file_system_provider_internal.h"
#include "chrome/test/base/testing_profile.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "extensions/browser/event_router.h"
+#include "storage/browser/fileapi/watcher_manager.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace chromeos {
namespace file_system_provider {
-
namespace {
+const char kOrigin[] =
+ "chrome-extension://abcabcabcabcabcabcabcabcabcabcabcabca/";
+const char kAnotherOrigin[] =
+ "chrome-extension://efgefgefgefgefgefgefgefgefgefgefgefge/";
const char kExtensionId[] = "mbflcebpggnecokmikipoihdbecnjfoj";
-const int kExpectedRequestId = 1;
const char kFileSystemId[] = "camera-pictures";
const char kDisplayName[] = "Camera Pictures";
+const base::FilePath::CharType kDirectoryPath[] = "/hello/world";
+// Fake implementation of the event router, mocking out a real extension.
+// Handles requests and replies with fake answers back to the file system via
+// the request manager.
class FakeEventRouter : public extensions::EventRouter {
public:
- explicit FakeEventRouter(Profile* profile) : EventRouter(profile, NULL) {}
+ FakeEventRouter(Profile* profile, ProvidedFileSystemInterface* file_system)
+ : EventRouter(profile, NULL),
+ file_system_(file_system),
+ reply_result_(base::File::FILE_OK) {}
virtual ~FakeEventRouter() {}
- virtual void DispatchEventToExtension(const std::string& extension_id,
- scoped_ptr<extensions::Event> event)
- OVERRIDE {
- extension_id_ = extension_id;
- event_ = event.Pass();
+ // Handles an event which would normally be routed to an extension. Instead
+ // replies with a hard coded response.
+ virtual void DispatchEventToExtension(
+ const std::string& extension_id,
+ scoped_ptr<extensions::Event> event) override {
+ ASSERT_TRUE(file_system_);
+ std::string file_system_id;
+ const base::DictionaryValue* dictionary_value = NULL;
+ ASSERT_TRUE(event->event_args->GetDictionary(0, &dictionary_value));
+ EXPECT_TRUE(dictionary_value->GetString("fileSystemId", &file_system_id));
+ EXPECT_EQ(kFileSystemId, file_system_id);
+ int request_id = -1;
+ EXPECT_TRUE(dictionary_value->GetInteger("requestId", &request_id));
+ EXPECT_TRUE(event->event_name == extensions::api::file_system_provider::
+ OnAddWatcherRequested::kEventName ||
+ event->event_name == extensions::api::file_system_provider::
+ OnRemoveWatcherRequested::kEventName);
+
+ if (reply_result_ == base::File::FILE_OK) {
+ base::ListValue value_as_list;
+ value_as_list.Set(0, new base::StringValue(kFileSystemId));
+ value_as_list.Set(1, new base::FundamentalValue(request_id));
+ value_as_list.Set(2, new base::FundamentalValue(0) /* execution_time */);
+
+ using extensions::api::file_system_provider_internal::
+ OperationRequestedSuccess::Params;
+ scoped_ptr<Params> params(Params::Create(value_as_list));
+ ASSERT_TRUE(params.get());
+ file_system_->GetRequestManager()->FulfillRequest(
+ request_id,
+ RequestValue::CreateForOperationSuccess(params.Pass()),
+ false /* has_more */);
+ } else {
+ file_system_->GetRequestManager()->RejectRequest(
+ request_id, make_scoped_ptr(new RequestValue()), reply_result_);
+ }
}
- const std::string& extension_id() const { return extension_id_; }
-
- const extensions::Event* event() const { return event_.get(); }
+ void set_reply_result(base::File::Error result) { reply_result_ = result; }
private:
- std::string extension_id_;
- scoped_ptr<extensions::Event> event_;
-
+ ProvidedFileSystemInterface* const file_system_; // Not owned.
+ base::File::Error reply_result_;
DISALLOW_COPY_AND_ASSIGN(FakeEventRouter);
};
-class EventLogger {
+// Observes the tested file system.
+class Observer : public ProvidedFileSystemObserver {
public:
- EventLogger() {}
- virtual ~EventLogger() {}
+ class ChangeEvent {
+ public:
+ ChangeEvent(storage::WatcherManager::ChangeType change_type,
+ const ProvidedFileSystemObserver::Changes& changes)
+ : change_type_(change_type), changes_(changes) {}
+ virtual ~ChangeEvent() {}
+
+ storage::WatcherManager::ChangeType change_type() const {
+ return change_type_;
+ }
+ const ProvidedFileSystemObserver::Changes& changes() const {
+ return changes_;
+ }
+
+ private:
+ const storage::WatcherManager::ChangeType change_type_;
+ const ProvidedFileSystemObserver::Changes changes_;
+
+ DISALLOW_COPY_AND_ASSIGN(ChangeEvent);
+ };
+
+ Observer() : list_changed_counter_(0), tag_updated_counter_(0) {}
+
+ // ProvidedFileSystemInterfaceObserver overrides.
+ virtual void OnWatcherChanged(
+ const ProvidedFileSystemInfo& file_system_info,
+ const Watcher& watcher,
+ storage::WatcherManager::ChangeType change_type,
+ const ProvidedFileSystemObserver::Changes& changes,
+ const base::Closure& callback) override {
+ EXPECT_EQ(kFileSystemId, file_system_info.file_system_id());
+ change_events_.push_back(new ChangeEvent(change_type, changes));
+ base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, callback);
+ }
+
+ virtual void OnWatcherTagUpdated(
+ const ProvidedFileSystemInfo& file_system_info,
+ const Watcher& watcher) override {
+ EXPECT_EQ(kFileSystemId, file_system_info.file_system_id());
+ ++tag_updated_counter_;
+ }
- void OnStatusCallback(base::File::Error error) {
- error_.reset(new base::File::Error(error));
+ virtual void OnWatcherListChanged(
+ const ProvidedFileSystemInfo& file_system_info,
+ const Watchers& watchers) override {
+ EXPECT_EQ(kFileSystemId, file_system_info.file_system_id());
+ ++list_changed_counter_;
}
- base::File::Error* error() { return error_.get(); }
+ int list_changed_counter() const { return list_changed_counter_; }
+ const ScopedVector<ChangeEvent>& change_events() const {
+ return change_events_;
+ }
+ int tag_updated_counter() const { return tag_updated_counter_; }
private:
- scoped_ptr<base::File::Error> error_;
- DISALLOW_COPY_AND_ASSIGN(EventLogger);
+ ScopedVector<ChangeEvent> change_events_;
+ int list_changed_counter_;
+ int tag_updated_counter_;
+
+ DISALLOW_COPY_AND_ASSIGN(Observer);
};
+// Stub notification manager, which works in unit tests.
+class StubNotificationManager : public NotificationManagerInterface {
+ public:
+ StubNotificationManager() {}
+ virtual ~StubNotificationManager() {}
+
+ // NotificationManagerInterface overrides.
+ virtual void ShowUnresponsiveNotification(
+ int id,
+ const NotificationCallback& callback) override {}
+ virtual void HideUnresponsiveNotification(int id) override {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(StubNotificationManager);
+};
+
+typedef std::vector<base::File::Error> Log;
+typedef std::vector<storage::WatcherManager::ChangeType> NotificationLog;
+
+// Writes a |result| to the |log| vector.
+void LogStatus(Log* log, base::File::Error result) {
+ log->push_back(result);
+}
+
+// Writes an |change_type| to the |notification_log| vector.
+void LogNotification(NotificationLog* notification_log,
+ storage::WatcherManager::ChangeType change_type) {
+ notification_log->push_back(change_type);
+}
+
} // namespace
class FileSystemProviderProvidedFileSystemTest : public testing::Test {
FileSystemProviderProvidedFileSystemTest() {}
virtual ~FileSystemProviderProvidedFileSystemTest() {}
- virtual void SetUp() OVERRIDE {
+ virtual void SetUp() override {
profile_.reset(new TestingProfile);
- event_router_.reset(new FakeEventRouter(profile_.get()));
- event_router_->AddEventListener(
- extensions::api::file_system_provider::OnUnmountRequested::kEventName,
- NULL,
- kExtensionId);
-
const base::FilePath mount_path =
util::GetMountPath(profile_.get(), kExtensionId, kFileSystemId);
- file_system_info_.reset(new ProvidedFileSystemInfo(
- kExtensionId, kFileSystemId, kDisplayName, mount_path));
+ MountOptions mount_options;
+ mount_options.file_system_id = kFileSystemId;
+ mount_options.display_name = kDisplayName;
+ mount_options.supports_notify_tag = true;
+ file_system_info_.reset(
+ new ProvidedFileSystemInfo(kExtensionId, mount_options, mount_path));
provided_file_system_.reset(
- new ProvidedFileSystem(event_router_.get(), *file_system_info_.get()));
+ new ProvidedFileSystem(profile_.get(), *file_system_info_.get()));
+ event_router_.reset(
+ new FakeEventRouter(profile_.get(), provided_file_system_.get()));
+ event_router_->AddEventListener(extensions::api::file_system_provider::
+ OnAddWatcherRequested::kEventName,
+ NULL,
+ kExtensionId);
+ event_router_->AddEventListener(extensions::api::file_system_provider::
+ OnRemoveWatcherRequested::kEventName,
+ NULL,
+ kExtensionId);
+ provided_file_system_->SetEventRouterForTesting(event_router_.get());
+ provided_file_system_->SetNotificationManagerForTesting(
+ make_scoped_ptr(new StubNotificationManager));
}
content::TestBrowserThreadBundle thread_bundle_;
scoped_ptr<TestingProfile> profile_;
scoped_ptr<FakeEventRouter> event_router_;
scoped_ptr<ProvidedFileSystemInfo> file_system_info_;
- scoped_ptr<ProvidedFileSystemInterface> provided_file_system_;
+ scoped_ptr<ProvidedFileSystem> provided_file_system_;
};
-TEST_F(FileSystemProviderProvidedFileSystemTest, RequestUnmount_Success) {
- EventLogger logger;
+TEST_F(FileSystemProviderProvidedFileSystemTest, AutoUpdater) {
+ Log log;
+ base::Closure firstCallback;
+ base::Closure secondCallback;
+
+ {
+ // Auto updater is ref counted, and bound to all callbacks.
+ scoped_refptr<AutoUpdater> auto_updater(new AutoUpdater(
+ base::Bind(&LogStatus, base::Unretained(&log), base::File::FILE_OK)));
+
+ firstCallback = auto_updater->CreateCallback();
+ secondCallback = auto_updater->CreateCallback();
+ }
+
+ // Getting out of scope, should not invoke updating if there are pending
+ // callbacks.
+ EXPECT_EQ(0u, log.size());
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, firstCallback);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(0u, log.size());
- provided_file_system_->RequestUnmount(
- base::Bind(&EventLogger::OnStatusCallback, base::Unretained(logger)));
+ base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, secondCallback);
base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(1u, log.size());
+}
+
+TEST_F(FileSystemProviderProvidedFileSystemTest, AutoUpdater_NoCallbacks) {
+ Log log;
+ {
+ scoped_refptr<AutoUpdater> auto_updater(new AutoUpdater(
+ base::Bind(&LogStatus, base::Unretained(&log), base::File::FILE_OK)));
+ }
+ EXPECT_EQ(1u, log.size());
+}
- // Verify that the event has been sent to the providing extension.
- EXPECT_EQ(kExtensionId, event_router_->extension_id());
- const extensions::Event* event = event_router_->event();
- ASSERT_TRUE(event);
- ASSERT_TRUE(event->event_args);
- base::ListValue* event_args = event->event_args.get();
- EXPECT_EQ(2u, event_args->GetSize());
- std::string file_system_id;
- EXPECT_TRUE(event_args->GetString(0, &file_system_id));
- EXPECT_EQ(kFileSystemId, file_system_id);
-
- // Remember the request id, and verify it is valid.
- int request_id = 0;
- EXPECT_TRUE(event_args->GetInteger(1, &request_id));
- EXPECT_EQ(kExpectedRequestId, request_id);
-
- // Callback should not be called, yet.
- EXPECT_FALSE(logger.error());
-
- // Simulate sending a success response from the providing extension.
- RequestManager* request_manager = provided_file_system_->GetRequestManager();
- ASSERT_TRUE(request_manager);
- scoped_ptr<RequestValue> response;
- bool reply_result = request_manager->FulfillRequest(
- request_id, response.Pass(), false /* has_more */);
- EXPECT_TRUE(reply_result);
-
- // Callback should be called. Verify the error code.
- ASSERT_TRUE(logger.error());
- EXPECT_EQ(base::File::FILE_OK, *logger.error());
+TEST_F(FileSystemProviderProvidedFileSystemTest, AutoUpdater_CallbackIgnored) {
+ Log log;
+ {
+ scoped_refptr<AutoUpdater> auto_updater(new AutoUpdater(
+ base::Bind(&LogStatus, base::Unretained(&log), base::File::FILE_OK)));
+ base::Closure callback = auto_updater->CreateCallback();
+ // The callback gets out of scope, so the ref counted auto updater instance
+ // gets deleted. Still, updating shouldn't be invoked, since the callback
+ // wasn't executed.
+ }
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(0u, log.size());
}
-TEST_F(FileSystemProviderProvidedFileSystemTest, RequestUnmount_Error) {
- EventLogger logger;
+TEST_F(FileSystemProviderProvidedFileSystemTest, AddWatcher_NotFound) {
+ Log log;
+ NotificationLog notification_log;
+ Observer observer;
+
+ provided_file_system_->AddObserver(&observer);
+
+ // First, set the extension response to an error.
+ event_router_->set_reply_result(base::File::FILE_ERROR_NOT_FOUND);
- provided_file_system_->RequestUnmount(
- base::Bind(&EventLogger::OnStatusCallback, base::Unretained(logger)));
+ 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();
- // Verify that the event has been sent to the providing extension.
- EXPECT_EQ(kExtensionId, event_router_->extension_id());
- const extensions::Event* event = event_router_->event();
- ASSERT_TRUE(event);
- ASSERT_TRUE(event->event_args);
- base::ListValue* event_args = event->event_args.get();
- EXPECT_EQ(2u, event_args->GetSize());
- std::string file_system_id;
- EXPECT_TRUE(event_args->GetString(0, &file_system_id));
- EXPECT_EQ(kFileSystemId, file_system_id);
-
- // Remember the request id, and verify it is valid.
- int request_id = 0;
- EXPECT_TRUE(event_args->GetInteger(1, &request_id));
- EXPECT_EQ(kExpectedRequestId, request_id);
-
- // Simulate sending an error response from the providing extension.
- RequestManager* request_manager = provided_file_system_->GetRequestManager();
- ASSERT_TRUE(request_manager);
- bool reply_result = request_manager->RejectRequest(
- request_id, base::File::FILE_ERROR_NOT_FOUND);
- EXPECT_TRUE(reply_result);
-
- // Callback should be called. Verify the error code.
- ASSERT_TRUE(logger.error());
- EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND, *logger.error());
+ // 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);
}
} // namespace file_system_provider