// Copyright (c) 2012 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 "base/bind.h"
#include "base/message_loop/message_loop.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/fake_cros_disks_client.h"
#include "chromeos/disks/disk_mount_manager.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using chromeos::disks::DiskMountManager;
using chromeos::CrosDisksClient;
using chromeos::DBusThreadManager;
using chromeos::FakeCrosDisksClient;
using testing::_;
using testing::Field;
using testing::InSequence;
namespace {
// Holds information needed to create a DiskMountManager::Disk instance.
struct TestDiskInfo {
const char* source_path;
const char* mount_path;
const char* system_path;
const char* file_path;
const char* device_label;
const char* drive_label;
const char* vendor_id;
const char* vendor_name;
const char* product_id;
const char* product_name;
const char* fs_uuid;
const char* system_path_prefix;
chromeos::DeviceType device_type;
uint64 size_in_bytes;
bool is_parent;
bool is_read_only;
bool has_media;
bool on_boot_device;
bool on_removable_device;
bool is_hidden;
};
// Holds information to create a DiskMOuntManager::MountPointInfo instance.
struct TestMountPointInfo {
const char* source_path;
const char* mount_path;
chromeos::MountType mount_type;
chromeos::disks::MountCondition mount_condition;
};
// List of disks held in DiskMountManager at the begining of the test.
const TestDiskInfo kTestDisks[] = {
{
"/device/source_path",
"/device/mount_path",
"/device/prefix/system_path",
"/device/file_path",
"/device/device_label",
"/device/drive_label",
"/device/vendor_id",
"/device/vendor_name",
"/device/product_id",
"/device/product_name",
"/device/fs_uuid",
"/device/prefix",
chromeos::DEVICE_TYPE_USB,
1073741824, // size in bytes
false, // is parent
false, // is read only
true, // has media
false, // is on boot device
true, // is on removable device
false // is hidden
},
};
// List of mount points held in DiskMountManager at the begining of the test.
const TestMountPointInfo kTestMountPoints[] = {
{
"/archive/source_path",
"/archive/mount_path",
chromeos::MOUNT_TYPE_ARCHIVE,
chromeos::disks::MOUNT_CONDITION_NONE
},
{
"/device/source_path",
"/device/mount_path",
chromeos::MOUNT_TYPE_DEVICE,
chromeos::disks::MOUNT_CONDITION_NONE
},
};
// Mocks DiskMountManager observer.
class MockDiskMountManagerObserver : public DiskMountManager::Observer {
public:
virtual ~MockDiskMountManagerObserver() {}
MOCK_METHOD2(OnDiskEvent, void(DiskMountManager::DiskEvent event,
const DiskMountManager::Disk* disk));
MOCK_METHOD2(OnDeviceEvent, void(DiskMountManager::DeviceEvent event,
const std::string& device_path));
MOCK_METHOD3(OnMountEvent,
void(DiskMountManager::MountEvent event,
chromeos::MountError error_code,
const DiskMountManager::MountPointInfo& mount_point));
MOCK_METHOD3(OnFormatEvent,
void(DiskMountManager::FormatEvent event,
chromeos::FormatError error_code,
const std::string& device_path));
};
class DiskMountManagerTest : public testing::Test {
public:
DiskMountManagerTest() {}
virtual ~DiskMountManagerTest() {}
// Sets up test dbus tread manager and disks mount manager.
// Initializes disk mount manager disks and mount points.
// Adds a test observer to the disk mount manager.
virtual void SetUp() {
fake_cros_disks_client_ = new FakeCrosDisksClient;
DBusThreadManager::GetSetterForTesting()->SetCrosDisksClient(
scoped_ptr<CrosDisksClient>(fake_cros_disks_client_));
DiskMountManager::Initialize();
InitDisksAndMountPoints();
DiskMountManager::GetInstance()->AddObserver(&observer_);
}
// Shuts down dbus thread manager and disk moutn manager used in the test.
virtual void TearDown() {
DiskMountManager::GetInstance()->RemoveObserver(&observer_);
DiskMountManager::Shutdown();
DBusThreadManager::Shutdown();
}
protected:
// Checks if disk mount manager contains a mount point with specified moutn
// path.
bool HasMountPoint(const std::string& mount_path) {
const DiskMountManager::MountPointMap& mount_points =
DiskMountManager::GetInstance()->mount_points();
return mount_points.find(mount_path) != mount_points.end();
}
private:
// Adds a new disk to the disk mount manager.
void AddTestDisk(const TestDiskInfo& disk) {
EXPECT_TRUE(DiskMountManager::GetInstance()->AddDiskForTest(
new DiskMountManager::Disk(disk.source_path,
disk.mount_path,
disk.system_path,
disk.file_path,
disk.device_label,
disk.drive_label,
disk.vendor_id,
disk.vendor_name,
disk.product_id,
disk.product_name,
disk.fs_uuid,
disk.system_path_prefix,
disk.device_type,
disk.size_in_bytes,
disk.is_parent,
disk.is_read_only,
disk.has_media,
disk.on_boot_device,
disk.on_removable_device,
disk.is_hidden)));
}
// Adds a new mount point to the disk mount manager.
// If the moutn point is a device mount point, disk with its source path
// should already be added to the disk mount manager.
void AddTestMountPoint(const TestMountPointInfo& mount_point) {
EXPECT_TRUE(DiskMountManager::GetInstance()->AddMountPointForTest(
DiskMountManager::MountPointInfo(mount_point.source_path,
mount_point.mount_path,
mount_point.mount_type,
mount_point.mount_condition)));
}
// Adds disks and mount points to disk mount manager.
void InitDisksAndMountPoints() {
// Disks should be added first (when adding device mount points it is
// expected that the corresponding disk is already added).
for (size_t i = 0; i < arraysize(kTestDisks); i++)
AddTestDisk(kTestDisks[i]);
for (size_t i = 0; i < arraysize(kTestMountPoints); i++)
AddTestMountPoint(kTestMountPoints[i]);
}
protected:
chromeos::FakeCrosDisksClient* fake_cros_disks_client_;
MockDiskMountManagerObserver observer_;
base::MessageLoopForUI message_loop_;
};
// Tests that the observer gets notified on attempt to format non existent mount
// point.
TEST_F(DiskMountManagerTest, Format_NotMounted) {
EXPECT_CALL(observer_, OnFormatEvent(DiskMountManager::FORMAT_COMPLETED,
chromeos::FORMAT_ERROR_UNKNOWN,
"/mount/non_existent"))
.Times(1);
DiskMountManager::GetInstance()->FormatMountedDevice("/mount/non_existent");
}
// Tests that it is not possible to format archive mount point.
TEST_F(DiskMountManagerTest, Format_Archive) {
EXPECT_CALL(observer_, OnFormatEvent(DiskMountManager::FORMAT_COMPLETED,
chromeos::FORMAT_ERROR_UNKNOWN,
"/archive/source_path"))
.Times(1);
DiskMountManager::GetInstance()->FormatMountedDevice("/archive/mount_path");
}
// Tests that format fails if the device cannot be unmounted.
TEST_F(DiskMountManagerTest, Format_FailToUnmount) {
// Before formatting mounted device, the device should be unmounted.
// In this test unmount will fail, and there should be no attempt to
// format the device.
// Set up expectations for observer mock.
// Observer should be notified that unmount attempt fails and format task
// failed to start.
{
InSequence s;
EXPECT_CALL(observer_,
OnMountEvent(DiskMountManager::UNMOUNTING,
chromeos::MOUNT_ERROR_INTERNAL,
Field(&DiskMountManager::MountPointInfo::mount_path,
"/device/mount_path")))
.Times(1);
EXPECT_CALL(observer_, OnFormatEvent(DiskMountManager::FORMAT_COMPLETED,
chromeos::FORMAT_ERROR_UNKNOWN,
"/device/source_path"))
.Times(1);
}
fake_cros_disks_client_->MakeUnmountFail();
// Start test.
DiskMountManager::GetInstance()->FormatMountedDevice("/device/mount_path");
// Cros disks will respond asynchronoulsy, so let's drain the message loop.
message_loop_.RunUntilIdle();
EXPECT_EQ(1, fake_cros_disks_client_->unmount_call_count());
EXPECT_EQ("/device/mount_path",
fake_cros_disks_client_->last_unmount_device_path());
EXPECT_EQ(chromeos::UNMOUNT_OPTIONS_NONE,
fake_cros_disks_client_->last_unmount_options());
EXPECT_EQ(0, fake_cros_disks_client_->format_call_count());
// The device mount should still be here.
EXPECT_TRUE(HasMountPoint("/device/mount_path"));
}
// Tests that observer is notified when cros disks fails to start format
// process.
TEST_F(DiskMountManagerTest, Format_FormatFailsToStart) {
// Before formatting mounted device, the device should be unmounted.
// In this test, unmount will succeed, but call to Format method will
// fail.
// Set up expectations for observer mock.
// Observer should be notified that the device was unmounted and format task
// failed to start.
{
InSequence s;
EXPECT_CALL(observer_,
OnMountEvent(DiskMountManager::UNMOUNTING,
chromeos::MOUNT_ERROR_NONE,
Field(&DiskMountManager::MountPointInfo::mount_path,
"/device/mount_path")))
.Times(1);
EXPECT_CALL(observer_, OnFormatEvent(DiskMountManager::FORMAT_COMPLETED,
chromeos::FORMAT_ERROR_UNKNOWN,
"/device/source_path"))
.Times(1);
}
fake_cros_disks_client_->MakeFormatFail();
// Start the test.
DiskMountManager::GetInstance()->FormatMountedDevice("/device/mount_path");
// Cros disks will respond asynchronoulsy, so let's drain the message loop.
message_loop_.RunUntilIdle();
EXPECT_EQ(1, fake_cros_disks_client_->unmount_call_count());
EXPECT_EQ("/device/mount_path",
fake_cros_disks_client_->last_unmount_device_path());
EXPECT_EQ(chromeos::UNMOUNT_OPTIONS_NONE,
fake_cros_disks_client_->last_unmount_options());
EXPECT_EQ(1, fake_cros_disks_client_->format_call_count());
EXPECT_EQ("/device/source_path",
fake_cros_disks_client_->last_format_device_path());
EXPECT_EQ("vfat", fake_cros_disks_client_->last_format_filesystem());
// The device mount should be gone.
EXPECT_FALSE(HasMountPoint("/device/mount_path"));
}
// Tests the case where there are two format requests for the same device.
TEST_F(DiskMountManagerTest, Format_ConcurrentFormatCalls) {
// Only the first format request should be processed (the second unmount
// request fails because the device is already unmounted at that point).
// CrosDisksClient will report that the format process for the first request
// is successfully started.
// Set up expectations for observer mock.
// The observer should get a FORMAT_STARTED event for one format request and a
// FORMAT_COMPLETED with an error code for the other format request. The
// formatting will be started only for the first request.
// There should be only one UNMOUNTING event. The result of the second one
// should not be reported as the mount point will go away after the first
// request.
//
// Note that in this test the format completion signal will not be simulated,
// so the observer should not get FORMAT_COMPLETED signal.
{
InSequence s;
EXPECT_CALL(observer_,
OnMountEvent(DiskMountManager::UNMOUNTING,
chromeos::MOUNT_ERROR_NONE,
Field(&DiskMountManager::MountPointInfo::mount_path,
"/device/mount_path")))
.Times(1);
EXPECT_CALL(observer_, OnFormatEvent(DiskMountManager::FORMAT_COMPLETED,
chromeos::FORMAT_ERROR_UNKNOWN,
"/device/source_path"))
.Times(1);
EXPECT_CALL(observer_, OnFormatEvent(DiskMountManager::FORMAT_STARTED,
chromeos::FORMAT_ERROR_NONE,
"/device/source_path"))
.Times(1);
}
fake_cros_disks_client_->set_unmount_listener(
base::Bind(&FakeCrosDisksClient::MakeUnmountFail,
base::Unretained(fake_cros_disks_client_)));
// Start the test.
DiskMountManager::GetInstance()->FormatMountedDevice("/device/mount_path");
DiskMountManager::GetInstance()->FormatMountedDevice("/device/mount_path");
// Cros disks will respond asynchronoulsy, so let's drain the message loop.
message_loop_.RunUntilIdle();
EXPECT_EQ(2, fake_cros_disks_client_->unmount_call_count());
EXPECT_EQ("/device/mount_path",
fake_cros_disks_client_->last_unmount_device_path());
EXPECT_EQ(chromeos::UNMOUNT_OPTIONS_NONE,
fake_cros_disks_client_->last_unmount_options());
EXPECT_EQ(1, fake_cros_disks_client_->format_call_count());
EXPECT_EQ("/device/source_path",
fake_cros_disks_client_->last_format_device_path());
EXPECT_EQ("vfat",
fake_cros_disks_client_->last_format_filesystem());
// The device mount should be gone.
EXPECT_FALSE(HasMountPoint("/device/mount_path"));
}
// Tests the case when the format process actually starts and fails.
TEST_F(DiskMountManagerTest, Format_FormatFails) {
// Both unmount and format device cals are successful in this test.
// Set up expectations for observer mock.
// The observer should get notified that the device was unmounted and that
// formatting has started.
// After the formatting starts, the test will simulate failing
// FORMAT_COMPLETED signal, so the observer should also be notified the
// formatting has failed (FORMAT_COMPLETED event).
{
InSequence s;
EXPECT_CALL(observer_,
OnMountEvent(DiskMountManager::UNMOUNTING,
chromeos::MOUNT_ERROR_NONE,
Field(&DiskMountManager::MountPointInfo::mount_path,
"/device/mount_path")))
.Times(1);
EXPECT_CALL(observer_, OnFormatEvent(DiskMountManager::FORMAT_STARTED,
chromeos::FORMAT_ERROR_NONE,
"/device/source_path"))
.Times(1);
EXPECT_CALL(observer_, OnFormatEvent(DiskMountManager::FORMAT_COMPLETED,
chromeos::FORMAT_ERROR_UNKNOWN,
"/device/source_path"))
.Times(1);
}
// Start the test.
DiskMountManager::GetInstance()->FormatMountedDevice("/device/mount_path");
// Wait for Unmount and Format calls to end.
message_loop_.RunUntilIdle();
EXPECT_EQ(1, fake_cros_disks_client_->unmount_call_count());
EXPECT_EQ("/device/mount_path",
fake_cros_disks_client_->last_unmount_device_path());
EXPECT_EQ(chromeos::UNMOUNT_OPTIONS_NONE,
fake_cros_disks_client_->last_unmount_options());
EXPECT_EQ(1, fake_cros_disks_client_->format_call_count());
EXPECT_EQ("/device/source_path",
fake_cros_disks_client_->last_format_device_path());
EXPECT_EQ("vfat", fake_cros_disks_client_->last_format_filesystem());
// The device should be unmounted by now.
EXPECT_FALSE(HasMountPoint("/device/mount_path"));
// Send failing FORMAT_COMPLETED signal.
// The failure is marked by ! in fromt of the path (but this should change
// soon).
fake_cros_disks_client_->SendFormatCompletedEvent(
chromeos::FORMAT_ERROR_UNKNOWN, "/device/source_path");
}
// Tests the case when formatting completes successfully.
TEST_F(DiskMountManagerTest, Format_FormatSuccess) {
// Set up cros disks client mocks.
// Both unmount and format device cals are successful in this test.
// Set up expectations for observer mock.
// The observer should receive UNMOUNTING, FORMAT_STARTED and FORMAT_COMPLETED
// events (all of them without an error set).
{
InSequence s;
EXPECT_CALL(observer_,
OnMountEvent(DiskMountManager::UNMOUNTING,
chromeos::MOUNT_ERROR_NONE,
Field(&DiskMountManager::MountPointInfo::mount_path,
"/device/mount_path")))
.Times(1);
EXPECT_CALL(observer_, OnFormatEvent(DiskMountManager::FORMAT_STARTED,
chromeos::FORMAT_ERROR_NONE,
"/device/source_path"))
.Times(1);
EXPECT_CALL(observer_, OnFormatEvent(DiskMountManager::FORMAT_COMPLETED,
chromeos::FORMAT_ERROR_NONE,
"/device/source_path"))
.Times(1);
}
// Start the test.
DiskMountManager::GetInstance()->FormatMountedDevice("/device/mount_path");
// Wait for Unmount and Format calls to end.
message_loop_.RunUntilIdle();
EXPECT_EQ(1, fake_cros_disks_client_->unmount_call_count());
EXPECT_EQ("/device/mount_path",
fake_cros_disks_client_->last_unmount_device_path());
EXPECT_EQ(chromeos::UNMOUNT_OPTIONS_NONE,
fake_cros_disks_client_->last_unmount_options());
EXPECT_EQ(1, fake_cros_disks_client_->format_call_count());
EXPECT_EQ("/device/source_path",
fake_cros_disks_client_->last_format_device_path());
EXPECT_EQ("vfat", fake_cros_disks_client_->last_format_filesystem());
// The device should be unmounted by now.
EXPECT_FALSE(HasMountPoint("/device/mount_path"));
// Simulate cros_disks reporting success.
fake_cros_disks_client_->SendFormatCompletedEvent(
chromeos::FORMAT_ERROR_NONE, "/device/source_path");
}
// Tests that it's possible to format the device twice in a row (this may not be
// true if the list of pending formats is not properly cleared).
TEST_F(DiskMountManagerTest, Format_ConsecutiveFormatCalls) {
// All unmount and format device cals are successful in this test.
// Each of the should be made twice (once for each formatting task).
// Set up expectations for observer mock.
// The observer should receive UNMOUNTING, FORMAT_STARTED and FORMAT_COMPLETED
// events (all of them without an error set) twice (once for each formatting
// task).
// Also, there should be a MOUNTING event when the device remounting is
// simulated.
EXPECT_CALL(observer_, OnFormatEvent(DiskMountManager::FORMAT_COMPLETED,
chromeos::FORMAT_ERROR_NONE,
"/device/source_path"))
.Times(2);
EXPECT_CALL(observer_, OnFormatEvent(DiskMountManager::FORMAT_STARTED,
chromeos::FORMAT_ERROR_NONE,
"/device/source_path"))
.Times(2);
EXPECT_CALL(observer_,
OnMountEvent(DiskMountManager::UNMOUNTING,
chromeos::MOUNT_ERROR_NONE,
Field(&DiskMountManager::MountPointInfo::mount_path,
"/device/mount_path")))
.Times(2);
EXPECT_CALL(observer_,
OnMountEvent(DiskMountManager::MOUNTING,
chromeos::MOUNT_ERROR_NONE,
Field(&DiskMountManager::MountPointInfo::mount_path,
"/device/mount_path")))
.Times(1);
// Start the test.
DiskMountManager::GetInstance()->FormatMountedDevice("/device/mount_path");
// Wait for Unmount and Format calls to end.
message_loop_.RunUntilIdle();
EXPECT_EQ(1, fake_cros_disks_client_->unmount_call_count());
EXPECT_EQ("/device/mount_path",
fake_cros_disks_client_->last_unmount_device_path());
EXPECT_EQ(chromeos::UNMOUNT_OPTIONS_NONE,
fake_cros_disks_client_->last_unmount_options());
EXPECT_EQ(1, fake_cros_disks_client_->format_call_count());
EXPECT_EQ("/device/source_path",
fake_cros_disks_client_->last_format_device_path());
EXPECT_EQ("vfat", fake_cros_disks_client_->last_format_filesystem());
// The device should be unmounted by now.
EXPECT_FALSE(HasMountPoint("/device/mount_path"));
// Simulate cros_disks reporting success.
fake_cros_disks_client_->SendFormatCompletedEvent(
chromeos::FORMAT_ERROR_NONE, "/device/source_path");
// Simulate the device remounting.
fake_cros_disks_client_->SendMountCompletedEvent(
chromeos::MOUNT_ERROR_NONE,
"/device/source_path",
chromeos::MOUNT_TYPE_DEVICE,
"/device/mount_path");
EXPECT_TRUE(HasMountPoint("/device/mount_path"));
// Try formatting again.
DiskMountManager::GetInstance()->FormatMountedDevice("/device/mount_path");
// Wait for Unmount and Format calls to end.
message_loop_.RunUntilIdle();
EXPECT_EQ(2, fake_cros_disks_client_->unmount_call_count());
EXPECT_EQ("/device/mount_path",
fake_cros_disks_client_->last_unmount_device_path());
EXPECT_EQ(chromeos::UNMOUNT_OPTIONS_NONE,
fake_cros_disks_client_->last_unmount_options());
EXPECT_EQ(2, fake_cros_disks_client_->format_call_count());
EXPECT_EQ("/device/source_path",
fake_cros_disks_client_->last_format_device_path());
EXPECT_EQ("vfat", fake_cros_disks_client_->last_format_filesystem());
// Simulate cros_disks reporting success.
fake_cros_disks_client_->SendFormatCompletedEvent(
chromeos::FORMAT_ERROR_NONE, "/device/source_path");
}
} // namespace