blob: 64be6fcc965c30af221fd4009e72950bbb1582ff [file] [log] [blame]
// Copyright 2014 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.
#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_capabilities/file_system_provider_capabilities_handler.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 char kFileSystemId[] = "camera-pictures";
const char kDisplayName[] = "Camera Pictures";
const base::FilePath::CharType kDirectoryPath[] =
FILE_PATH_LITERAL("/hello/world");
const base::FilePath::CharType kFilePath[] =
FILE_PATH_LITERAL("/welcome/to/my/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:
FakeEventRouter(Profile* profile, ProvidedFileSystemInterface* file_system)
: EventRouter(profile, NULL),
file_system_(file_system),
reply_result_(base::File::FILE_OK) {}
~FakeEventRouter() override {}
// Handles an event which would normally be routed to an extension. Instead
// replies with a hard coded response.
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 ||
event->event_name == extensions::api::file_system_provider::
OnOpenFileRequested::kEventName ||
event->event_name == extensions::api::file_system_provider::
OnCloseFileRequested::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_);
}
}
void set_reply_result(base::File::Error result) { reply_result_ = result; }
private:
ProvidedFileSystemInterface* const file_system_; // Not owned.
base::File::Error reply_result_;
DISALLOW_COPY_AND_ASSIGN(FakeEventRouter);
};
// Observes the tested file system.
class Observer : public ProvidedFileSystemObserver {
public:
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.
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));
complete_callback_ = callback;
}
void OnWatcherTagUpdated(const ProvidedFileSystemInfo& file_system_info,
const Watcher& watcher) override {
EXPECT_EQ(kFileSystemId, file_system_info.file_system_id());
++tag_updated_counter_;
}
void OnWatcherListChanged(const ProvidedFileSystemInfo& file_system_info,
const Watchers& watchers) override {
EXPECT_EQ(kFileSystemId, file_system_info.file_system_id());
++list_changed_counter_;
}
// Completes handling the OnWatcherChanged event.
void CompleteOnWatcherChanged() {
DCHECK(!complete_callback_.is_null());
base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
complete_callback_);
complete_callback_ = base::Closure();
}
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:
ScopedVector<ChangeEvent> change_events_;
int list_changed_counter_;
int tag_updated_counter_;
base::Closure complete_callback_;
DISALLOW_COPY_AND_ASSIGN(Observer);
};
// Stub notification manager, which works in unit tests.
class StubNotificationManager : public NotificationManagerInterface {
public:
StubNotificationManager() {}
~StubNotificationManager() override {}
// NotificationManagerInterface overrides.
void ShowUnresponsiveNotification(
int id,
const NotificationCallback& callback) override {}
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;
typedef std::vector<std::pair<int, base::File::Error>> OpenFileLog;
// Writes a |result| to the |log| vector.
void LogStatus(Log* log, base::File::Error result) {
log->push_back(result);
}
// Writes a |change_type| to the |notification_log| vector.
void LogNotification(NotificationLog* notification_log,
storage::WatcherManager::ChangeType change_type) {
notification_log->push_back(change_type);
}
// Writes a |file_handle| and |result| to the |open_file_log| vector.
void LogOpenFile(OpenFileLog* open_file_log,
int file_handle,
base::File::Error result) {
open_file_log->push_back(std::make_pair(file_handle, result));
}
} // namespace
class FileSystemProviderProvidedFileSystemTest : public testing::Test {
protected:
FileSystemProviderProvidedFileSystemTest() {}
~FileSystemProviderProvidedFileSystemTest() override {}
void SetUp() override {
profile_.reset(new TestingProfile);
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 = true;
mount_options.writable = true;
file_system_info_.reset(new ProvidedFileSystemInfo(
kExtensionId, mount_options, mount_path, false /* configurable */,
true /* watchable */, extensions::SOURCE_FILE));
provided_file_system_.reset(
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);
event_router_->AddEventListener(
extensions::api::file_system_provider::OnOpenFileRequested::kEventName,
NULL, kExtensionId);
event_router_->AddEventListener(
extensions::api::file_system_provider::OnCloseFileRequested::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<ProvidedFileSystem> provided_file_system_;
};
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());
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());
}
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, 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_->AddWatcher(
GURL(kOrigin), base::FilePath(kDirectoryPath), false /* recursive */,
false /* persistent */, base::Bind(&LogStatus, base::Unretained(&log)),
base::Bind(&LogNotification, base::Unretained(&notification_log)));
base::RunLoop().RunUntilIdle();
// 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(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(kDirectoryPath), false /* recursive */,
true /* persistent */, base::Bind(&LogStatus, base::Unretained(&log)),
base::Bind(&LogNotification, base::Unretained(&notification_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, false /* configurable */,
true /* watchable */, extensions::SOURCE_FILE);
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(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(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(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(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(kDirectoryPath), false /* recursive */,
false /* persistent */, base::Bind(&LogStatus, base::Unretained(&log)),
base::Bind(&LogNotification, base::Unretained(&notification_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(kDirectoryPath),
true /* recursive */, false /* persistent */,
base::Bind(&LogStatus, base::Unretained(&log)),
base::Bind(&LogNotification, base::Unretained(&notification_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(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(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(kDirectoryPath), false /* recursive */,
false /* persistent */, base::Bind(&LogStatus, base::Unretained(&log)),
base::Bind(&LogNotification, base::Unretained(&notification_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(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(kDirectoryPath), false /* recursive */,
false /* persistent */, base::Bind(&LogStatus, base::Unretained(&log)),
base::Bind(&LogNotification, base::Unretained(&notification_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(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(kDirectoryPath), false /* recursive */,
false /* persistent */, base::Bind(&LogStatus, base::Unretained(&log)),
base::Bind(&LogNotification, base::Unretained(&notification_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";
Log log;
provided_file_system_->Notify(
base::FilePath(kDirectoryPath), false /* recursive */, change_type,
make_scoped_ptr(new ProvidedFileSystemObserver::Changes), tag,
base::Bind(&LogStatus, base::Unretained(&log)));
base::RunLoop().RunUntilIdle();
// 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.
observer.CompleteOnWatcherChanged();
base::RunLoop().RunUntilIdle();
ASSERT_EQ(1u, log.size());
EXPECT_EQ(base::File::FILE_OK, log[0]);
// 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";
Log log;
provided_file_system_->Notify(
base::FilePath(kDirectoryPath), false /* recursive */, change_type,
make_scoped_ptr(new ProvidedFileSystemObserver::Changes), tag,
base::Bind(&LogStatus, base::Unretained(&log)));
base::RunLoop().RunUntilIdle();
// Complete all change events.
observer.CompleteOnWatcherChanged();
base::RunLoop().RunUntilIdle();
ASSERT_EQ(1u, log.size());
EXPECT_EQ(base::File::FILE_OK, log[0]);
// 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);
}
TEST_F(FileSystemProviderProvidedFileSystemTest, OpenedFiles) {
Observer observer;
provided_file_system_->AddObserver(&observer);
OpenFileLog log;
provided_file_system_->OpenFile(
base::FilePath(kFilePath), OPEN_FILE_MODE_WRITE,
base::Bind(LogOpenFile, base::Unretained(&log)));
base::RunLoop().RunUntilIdle();
ASSERT_EQ(1u, log.size());
EXPECT_EQ(base::File::FILE_OK, log[0].second);
const int file_handle = log[0].first;
const OpenedFiles& opened_files = provided_file_system_->GetOpenedFiles();
const auto opened_file_it = opened_files.find(file_handle);
ASSERT_NE(opened_files.end(), opened_file_it);
EXPECT_EQ(kFilePath, opened_file_it->second.file_path.AsUTF8Unsafe());
EXPECT_EQ(OPEN_FILE_MODE_WRITE, opened_file_it->second.mode);
Log close_log;
provided_file_system_->CloseFile(
file_handle, base::Bind(LogStatus, base::Unretained(&close_log)));
base::RunLoop().RunUntilIdle();
ASSERT_EQ(1u, close_log.size());
EXPECT_EQ(base::File::FILE_OK, close_log[0]);
EXPECT_EQ(0u, opened_files.size());
provided_file_system_->RemoveObserver(&observer);
}
TEST_F(FileSystemProviderProvidedFileSystemTest, OpenedFiles_OpeningFailure) {
Observer observer;
provided_file_system_->AddObserver(&observer);
event_router_->set_reply_result(base::File::FILE_ERROR_NOT_FOUND);
OpenFileLog log;
provided_file_system_->OpenFile(
base::FilePath(kFilePath), OPEN_FILE_MODE_WRITE,
base::Bind(LogOpenFile, base::Unretained(&log)));
base::RunLoop().RunUntilIdle();
ASSERT_EQ(1u, log.size());
EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND, log[0].second);
const OpenedFiles& opened_files = provided_file_system_->GetOpenedFiles();
EXPECT_EQ(0u, opened_files.size());
provided_file_system_->RemoveObserver(&observer);
}
TEST_F(FileSystemProviderProvidedFileSystemTest, OpenedFile_ClosingFailure) {
Observer observer;
provided_file_system_->AddObserver(&observer);
OpenFileLog log;
provided_file_system_->OpenFile(
base::FilePath(kFilePath), OPEN_FILE_MODE_WRITE,
base::Bind(LogOpenFile, base::Unretained(&log)));
base::RunLoop().RunUntilIdle();
ASSERT_EQ(1u, log.size());
EXPECT_EQ(base::File::FILE_OK, log[0].second);
const int file_handle = log[0].first;
const OpenedFiles& opened_files = provided_file_system_->GetOpenedFiles();
const auto opened_file_it = opened_files.find(file_handle);
ASSERT_NE(opened_files.end(), opened_file_it);
EXPECT_EQ(kFilePath, opened_file_it->second.file_path.AsUTF8Unsafe());
EXPECT_EQ(OPEN_FILE_MODE_WRITE, opened_file_it->second.mode);
// Simulate an error for closing a file. Still, the file should be closed
// in the C++ layer, anyway.
event_router_->set_reply_result(base::File::FILE_ERROR_NOT_FOUND);
Log close_log;
provided_file_system_->CloseFile(
file_handle, base::Bind(LogStatus, base::Unretained(&close_log)));
base::RunLoop().RunUntilIdle();
ASSERT_EQ(1u, close_log.size());
EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND, close_log[0]);
EXPECT_EQ(0u, opened_files.size());
provided_file_system_->RemoveObserver(&observer);
}
} // namespace file_system_provider
} // namespace chromeos