text:
using base::StringPrintf;
using testing::_;
using testing::Field;

namespace ash {
namespace disks {

namespace {

const char kDevice1SourcePath[] = "/device/source_path";
const char kDevice1MountPath[] = "/device/mount_path";
const char kDevice2SourcePath[] = "/device/source_path2";
const char kReadOnlyDeviceMountPath[] = "/device/read_only_mount_path";
const char kReadOnlyDeviceSourcePath[] = "/device/read_only_source_path";
const char kFileSystemType1[] = "ntfs";
const char kFileSystemType2[] = "exfat";
const FormatFileSystemType kFormatFileSystemType1 = FormatFileSystemType::kVfat;
const FormatFileSystemType kFormatFileSystemType2 =
    FormatFileSystemType::kExfat;
const char kFormatFileSystemType1String[] = "vfat";
const char kFormatFileSystemType2String[] = "exfat";
const char kFormatLabel1[] = "UNTITLED";
const char kFormatLabel2[] = "TESTUSB";

// Holds information needed to create a Disk instance.
struct TestDiskInfo {
  const char* source_path;
  const char* mount_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* storage_device_path;
  DeviceType device_type;
  uint64_t size_in_bytes;
  bool is_read_only;
  const char* file_system_type;
  bool is_mounted;
};

// List of disks held in DiskMountManager at the beginning of the test.
const TestDiskInfo kTestDisks[] = {
    {
        kDevice1SourcePath,
        kDevice1MountPath,
        "/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",
        DeviceType::kUSB,
        1073741824,  // size in bytes
        false,       // is read only
        kFileSystemType1,
        true,  // is_mounted
    },
    {
        kDevice2SourcePath,
        "",     // not mounted initially
        "/device/file_path2",
        "/device/device_label2",
        "/device/drive_label2",
        "/device/vendor_id2",
        "/device/vendor_name2",
        "/device/product_id2",
        "/device/product_name2",
        "/device/fs_uuid2",
        "/device/prefix2",
        DeviceType::kSD,
        1073741824,  // size in bytes
        false,       // is read only
        kFileSystemType2,
        false,  // is_mounted
    },
    {
        kReadOnlyDeviceSourcePath,
        kReadOnlyDeviceMountPath,
        "/device/file_path_3",
        "/device/device_label_3",
        "/device/drive_label_3",
        "/device/vendor_id_3",
        "/device/vendor_name_3",
        "/device/product_id_3",
        "/device/product_name_3",
        "/device/fs_uuid_3",
        "/device/prefix",
        DeviceType::kUSB,
        1073741824,  // size in bytes
        true,        // is read only
        kFileSystemType2,
        true,  // is_mounted
    },
};

// Represents which function in |DiskMountManager::Observer| was invoked.
enum ObserverEventType {
  DEVICE_EVENT,               // OnDeviceEvent()
  AUTO_MOUNTABLE_DISK_EVENT,  // OnAutoMountableDiskEvent()
  BOOT_DEVICE_DISK_EVENT,     // OnBootDeviceDiskEvent()
  FORMAT_EVENT,               // OnFormatEvent()
  MOUNT_EVENT,                // OnMountEvent()
  RENAME_EVENT                // OnRenameEvent()
};

// Represents every event notified to |DiskMountManager::Observer|.
struct ObserverEvent {
 public:
  virtual ObserverEventType type() const = 0;
  virtual ~ObserverEvent() = default;
};

// Represents an invocation of |DiskMountManager::Observer::OnDeviceEvent()|.
struct DeviceEvent : public ObserverEvent {
  DiskMountManager::DeviceEvent event;
  std::string device_path;

  DeviceEvent() = default;

  DeviceEvent(DiskMountManager::DeviceEvent event,
              const std::string& device_path)
      : event(event), device_path(device_path) {}

  ObserverEventType type() const override { return DEVICE_EVENT; }

  bool operator==(const DeviceEvent& other) const {
    return event == other.event && device_path == other.device_path;
  }

  std::string DebugString() const {
    return StringPrintf("OnDeviceEvent(%d, %s)", event, device_path.c_str());
  }
};

// Represents an invocation of
// DiskMountManager::Observer::OnAutoMountableDiskEvent().
struct AutoMountableDiskEvent : public ObserverEvent {
  DiskMountManager::DiskEvent event;
  std::unique_ptr<Disk> disk;

  AutoMountableDiskEvent(DiskMountManager::DiskEvent event, const Disk& disk)
      : event(event), disk(std::make_unique<Disk>(disk)) {}

  AutoMountableDiskEvent(AutoMountableDiskEvent&& other)
      : event(other.event), disk(std::move(other.disk)) {}

  ObserverEventType type() const override { return AUTO_MOUNTABLE_DISK_EVENT; }

  bool operator==(const AutoMountableDiskEvent& other) const {
    return event == other.event && disk == other.disk;
  }

  std::string DebugString() const {
    return StringPrintf(
        "OnAutoMountableDiskEvent(event=%d, device_path=%s, mount_path=%s",
        event, disk->device_path().c_str(), disk->mount_path().c_str());
  }
};

// Represents an invocation of
// DiskMountManager::Observer::OnBootDeviceDiskEvent().
// TODO(agawronska): Add tests for disks events.
struct BootDeviceDiskEvent : public ObserverEvent {
  DiskMountManager::DiskEvent event;
  std::unique_ptr<Disk> disk;

  BootDeviceDiskEvent(DiskMountManager::DiskEvent event, const Disk& disk)
      : event(event), disk(std::make_unique<Disk>(disk)) {}

  BootDeviceDiskEvent(BootDeviceDiskEvent&& other)
      : event(other.event), disk(std::move(other.disk)) {}

  ObserverEventType type() const override { return BOOT_DEVICE_DISK_EVENT; }

  bool operator==(const BootDeviceDiskEvent& other) const {
    return event == other.event && disk == other.disk;
  }

  std::string DebugString() const {
    return StringPrintf(
        "OnBootDeviceDiskEvent(event=%d, device_path=%s, mount_path=%s", event,
        disk->device_path().c_str(), disk->mount_path().c_str());
  }
};

// Represents an invocation of |DiskMountManager::Observer::OnFormatEvent()|.
struct FormatEvent : public ObserverEvent {
  DiskMountManager::FormatEvent event;
  FormatError error_code;
  std::string device_path;
  std::string device_label;

  FormatEvent() = default;
  FormatEvent(DiskMountManager::FormatEvent event,
              FormatError error_code,
              const std::string& device_path,
              const std::string& device_label)
      : event(event),
        error_code(error_code),
        device_path(device_path),
        device_label(device_label) {}

  ObserverEventType type() const override { return FORMAT_EVENT; }

  bool operator==(const FormatEvent& other) const {
    return event == other.event && error_code == other.error_code &&
           device_path == other.device_path &&
           device_label == other.device_label;
  }

  std::string DebugString() const {
    return StringPrintf("OnFormatEvent(%d, %d, %s, %s)", event, error_code,
                        device_path.c_str(), device_label.c_str());
  }
};

// Represents an invocation of |DiskMountManager::Observer::OnRenameEvent()|.
struct RenameEvent : public ObserverEvent {
  DiskMountManager::RenameEvent event;
  RenameError error_code;
  std::string device_path;
  std::string device_label;

  RenameEvent(DiskMountManager::RenameEvent event,
              RenameError error_code,
              const std::string& device_path,
              const std::string& device_label)
      : event(event),
        error_code(error_code),
        device_path(device_path),
        device_label(device_label) {}

  ObserverEventType type() const override { return RENAME_EVENT; }

  bool operator==(const RenameEvent& other) const {
    return event == other.event && error_code == other.error_code &&
           device_path == other.device_path &&
           device_label == other.device_label;
  }

  std::string DebugString() const {
    return StringPrintf("OnRenameEvent(%d, %d, %s, %s)", event, error_code,
                        device_path.c_str(), device_label.c_str());
  }
};

// Represents an invocation of |DiskMountManager::Observer::OnMountEvent()|.
struct MountEvent : public ObserverEvent {
  DiskMountManager::MountEvent event;
  MountError error_code;
  DiskMountManager::MountPoint mount_point;

  // Not passed to callback, but read by handlers. So it's captured upon
  // callback.
  std::unique_ptr<Disk> disk;

  MountEvent(MountEvent&& other) = default;

  MountEvent(DiskMountManager::MountEvent event,
             MountError error_code,
             const DiskMountManager::MountPoint& mount_point,
             const Disk& disk)
      : event(event),
        error_code(error_code),
        mount_point(mount_point),
        disk(std::make_unique<Disk>(disk)) {}

  ObserverEventType type() const override { return MOUNT_EVENT; }

  bool operator==(const MountEvent& other) const;

  std::string DebugString() const {
    return StringPrintf("OnMountEvent(%d, %d, %s, %s, %d, %d)", event,
                        error_code, mount_point.source_path.c_str(),
                        mount_point.mount_path.c_str(), mount_point.mount_type,
                        mount_point.mount_error);
  }
};

// A mock |Observer| class which records all invocation of the methods invoked
// from DiskMountManager and all the arguments passed to them.
class MockDiskMountManagerObserver : public DiskMountManager::Observer {
 public:
  explicit MockDiskMountManagerObserver(const DiskMountManager* manager)
      : manager_(manager) {}
  ~MockDiskMountManagerObserver() override = default;

  // Mock notify methods.
  void OnDeviceEvent(DiskMountManager::DeviceEvent event,
                     const std::string& device_path) override {
    events_.push_back(std::make_unique<DeviceEvent>(event, device_path));
  }

  void OnBootDeviceDiskEvent(DiskMountManager::DiskEvent event,
                             const Disk& disk) override {
    // Take a snapshot (copy) of the Disk object at the time of invocation for
    // later verification.
    events_.push_back(std::make_unique<BootDeviceDiskEvent>(event, disk));
  }

  void OnAutoMountableDiskEvent(DiskMountManager::DiskEvent event,
                                const Disk& disk) override {
    // Take a snapshot (copy) of the Disk object at the time of invocation for
    // later verification.
    events_.push_back(std::make_unique<AutoMountableDiskEvent>(event, disk));
  }

  void OnFormatEvent(DiskMountManager::FormatEvent event,
                     FormatError error_code,
                     const std::string& device_path,
                     const std::string& device_label) override {
    events_.push_back(std::make_unique<FormatEvent>(event, error_code,
                                                    device_path, device_label));
  }

  void OnRenameEvent(DiskMountManager::RenameEvent event,
                     RenameError error_code,
                     const std::string& device_path,
                     const std::string& device_label) override {
    events_.push_back(std::make_unique<RenameEvent>(event, error_code,
                                                    device_path, device_label));
  }

  void OnMountEvent(DiskMountManager::MountEvent event,
                    MountError error_code,
                    const DiskMountManager::MountPoint& mount_point) override {
    // Take a snapshot (copy) of a Disk object at the time of invocation.
    // It can be verified later besides the arguments.
    events_.push_back(std::make_unique<MountEvent>(
        event, error_code, mount_point,
        *manager_->disks().find(mount_point.source_path)->get()));
  }

  // Gets invocation history to be verified by testcases.
  // Verifies if the |index|th invocation is OnDeviceEvent() and returns
  // details.
  const DeviceEvent& GetDeviceEvent(size_t index) {
    DCHECK_GT(events_.size(), index);
    DCHECK_EQ(DEVICE_EVENT, events_[index]->type());
    return static_cast<const DeviceEvent&>(*events_[index]);
  }

  // Verifies if the |index|th invocation is OnAutoMountableDiskEvent() and
  // returns details.
  const AutoMountableDiskEvent& GetAutoMountableDiskEvent(size_t index) {
    DCHECK_GT(events_.size(), index);
    DCHECK_EQ(AUTO_MOUNTABLE_DISK_EVENT, events_[index]->type());
    return static_cast<const AutoMountableDiskEvent&>(*events_[index]);
  }

  // Verifies if the |index|th invocation is OnBootDeviceDiskEvent() and returns
  // details.
  const BootDeviceDiskEvent& GetBootDeviceDiskEvent(size_t index) {
    DCHECK_GT(events_.size(), index);
    DCHECK_EQ(BOOT_DEVICE_DISK_EVENT, events_[index]->type());
    return static_cast<const BootDeviceDiskEvent&>(*events_[index]);
  }

  // Verifies if the |index|th invocation is OnFormatEvent() and returns
  // details.
  const FormatEvent& GetFormatEvent(size_t index) {
    DCHECK_GT(events_.size(), index);
    DCHECK_EQ(FORMAT_EVENT, events_[index]->type());
    return static_cast<const FormatEvent&>(*events_[index]);
  }

  // Verifies if the |index|th invocation is OnRenameEvent() and returns
  // details.
  const RenameEvent& GetRenameEvent(size_t index) {
    DCHECK_GT(events_.size(), index);
    DCHECK_EQ(RENAME_EVENT, events_[index]->type());
    return static_cast<const RenameEvent&>(*events_[index]);
  }

  // Verifies if the |index|th invocation is OnMountEvent() and returns details.
  const MountEvent& GetMountEvent(size_t index) {
    DCHECK_GT(events_.size(), index);
    DCHECK_EQ(MOUNT_EVENT, events_[index]->type());
    return static_cast<const MountEvent&>(*events_[index]);
  }

  // Returns number of callback invocations happened so far.
  size_t GetEventCount() { return events_.size(); }

  // Counts the number of |MountEvent| recorded so far that matches the given
  // condition.
  size_t CountMountEvents(DiskMountManager::MountEvent mount_event_type,
                          MountError error_code,
                          const std::string& mount_path) {
    size_t num_matched = 0;
    for (const auto& it : events_) {
      if (it->type() != MOUNT_EVENT)
        continue;
      const MountEvent& mount_event = static_cast<const MountEvent&>(*it);
      if (mount_event.event == mount_event_type &&
          mount_event.error_code == error_code &&
          mount_event.mount_point.mount_path == mount_path)
        num_matched++;
    }
    return num_matched;
  }

  // Counts the number of |FormatEvent| recorded so far that matches with
  // |format_event|.
  size_t CountFormatEvents(const FormatEvent& exptected_format_event) {
    size_t num_matched = 0;
    for (const auto& it : events_) {
      if (it->type() != FORMAT_EVENT)
        continue;
      if (static_cast<const FormatEvent&>(*it) == exptected_format_event)
        num_matched++;
    }
    return num_matched;
  }

  // Counts the number of |RenameEvent| recorded so far that matches with
  // |rename_event|.
  size_t CountRenameEvents(const RenameEvent& exptected_rename_event) {
    size_t num_matched = 0;
    for (const auto& event : events_) {
      if (event->type() != RENAME_EVENT)
        continue;
      if (static_cast<const RenameEvent&>(*event) == exptected_rename_event)
        num_matched++;
    }
    return num_matched;
  }

 private:
  // Pointer to the manager object to which this |Observer| is registered.
  const DiskMountManager* manager_;

  // Records all invocations.
  std::vector<std::unique_ptr<ObserverEvent>> events_;
};

// Shift operators of ostream.
// Needed to print values in case of EXPECT_* failure in gtest.
std::ostream& operator<<(std::ostream& stream,
                         const FormatEvent& format_event) {
  return stream << format_event.DebugString();
}

std::ostream& operator<<(std::ostream& stream,
                         const RenameEvent& rename_event) {
  return stream << rename_event.DebugString();
}

class DiskMountManagerTest : public testing::Test {
 public:
  DiskMountManagerTest()
      : task_environment_(base::test::TaskEnvironment::MainThreadType::UI) {}
  ~DiskMountManagerTest() override = default;

  // Sets up test disks mount manager.
  // Initializes disk mount manager disks and mount points.
  // Adds a test observer to the disk mount manager.
  void SetUp() override {
    CrosDisksClient::InitializeFake();
    fake_cros_disks_client_ =
        static_cast<FakeCrosDisksClient*>(CrosDisksClient::Get());
    chromeos::PowerManagerClient::InitializeFake();

    DiskMountManager::Initialize();

    InitDisksAndMountPoints();

    observer_ = std::make_unique<MockDiskMountManagerObserver>(
        DiskMountManager::GetInstance());
    DiskMountManager::GetInstance()->AddObserver(observer_.get());
  }

  // Shuts down dbus thread manager and disk mount manager used in the test.
  void TearDown() override {
    DiskMountManager::GetInstance()->RemoveObserver(observer_.get());
    DiskMountManager::Shutdown();
    chromeos::PowerManagerClient::Shutdown();
    CrosDisksClient::Shutdown();
  }

 protected:
  // Checks if disk mount manager contains a mount point with specified mount
  // path.
  bool HasMountPoint(const std::string& mount_path) {
    return DiskMountManager::GetInstance()->mount_points().count(mount_path) !=
           0;
  }

 private:
  // Adds a new disk to the disk mount manager.
  void AddTestDisk(const TestDiskInfo& disk) {
    std::unique_ptr<Disk> test_disk =
        Disk::Builder()
            .SetDevicePath(disk.source_path)
            .SetMountPath(disk.mount_path)
            .SetFilePath(disk.file_path)
            .SetDeviceLabel(disk.device_label)
            .SetDriveLabel(disk.drive_label)
            .SetVendorId(disk.vendor_id)
            .SetVendorName(disk.vendor_name)
            .SetProductId(disk.product_id)
            .SetProductName(disk.product_name)
            .SetFileSystemUUID(disk.fs_uuid)
            .SetStorageDevicePath(disk.storage_device_path)
            .SetDeviceType(disk.device_type)
            .SetSizeInBytes(disk.size_in_bytes)
            .SetIsReadOnlyHardware(disk.is_read_only)
            .SetHasMedia(true)
            .SetOnRemovableDevice(true)
            .SetFileSystemType(disk.file_system_type)
            .SetIsMounted(disk.is_mounted)
            .Build();
    EXPECT_TRUE(
        DiskMountManager::GetInstance()->AddDiskForTest(std::move(test_disk)));
  }

  // Adds a new mount point to the disk mount manager.
  // If the mount point is a device mount point, disk with its source path
  // should already be added to the disk mount manager.
  void AddTestMountPoint(const DiskMountManager::MountPoint& mount_point) {
    EXPECT_TRUE(
        DiskMountManager::GetInstance()->AddMountPointForTest(mount_point));
  }

  // 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 (const TestDiskInfo& disk : kTestDisks)
      AddTestDisk(disk);

    AddTestMountPoint(
        {"/archive/source_path", "/archive/mount_path", MountType::kArchive});
    AddTestMountPoint(
        {kDevice1SourcePath, kDevice1MountPath, MountType::kDevice});
    AddTestMountPoint({kReadOnlyDeviceSourcePath, kReadOnlyDeviceMountPath,
                       MountType::kDevice});
  }

 protected:
  FakeCrosDisksClient* fake_cros_disks_client_;
  std::unique_ptr<MockDiskMountManagerObserver> observer_;

 private:
  base::test::TaskEnvironment task_environment_;
};

// Tests that the observer gets notified on attempt to format non existent mount
// point.
TEST_F(DiskMountManagerTest, Format_NotMounted) {
  DiskMountManager::GetInstance()->FormatMountedDevice(
      "/mount/non_existent", kFormatFileSystemType1, kFormatLabel1);
  ASSERT_EQ(1U, observer_->GetEventCount());
  EXPECT_EQ(FormatEvent(DiskMountManager::FORMAT_COMPLETED,
                        FormatError::kUnknownError, "/mount/non_existent",
                        kFormatLabel1),
            observer_->GetFormatEvent(0));
}

// Tests that the observer gets notified on attempt to format read-only mount
// point.
TEST_F(DiskMountManagerTest, Format_ReadOnly) {
  DiskMountManager::GetInstance()->FormatMountedDevice(
      kReadOnlyDeviceMountPath, kFormatFileSystemType1, kFormatLabel1);
  ASSERT_EQ(1U, observer_->GetEventCount());
  EXPECT_EQ(FormatEvent(DiskMountManager::FORMAT_COMPLETED,
                        FormatError::kDeviceNotAllowed,
                        kReadOnlyDeviceSourcePath, kFormatLabel1),
            observer_->GetFormatEvent(0));
}

// Tests that it is not possible to format archive mount point.
TEST_F(DiskMountManagerTest, Format_Archive) {
  DiskMountManager::GetInstance()->FormatMountedDevice(
      "/archive/mount_path", kFormatFileSystemType1, kFormatLabel1);
  ASSERT_EQ(1U, observer_->GetEventCount());
  EXPECT_EQ(FormatEvent(DiskMountManager::FORMAT_COMPLETED,
                        FormatError::kUnknownError, "/archive/source_path",
                        kFormatLabel1),
            observer_->GetFormatEvent(0));
}

// 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.

  fake_cros_disks_client_->MakeUnmountFail(
      MountError::kInsufficientPermissions);
  // Start test.
  DiskMountManager::GetInstance()->FormatMountedDevice(
      kDevice1MountPath, kFormatFileSystemType1, kFormatLabel1);

  // Cros disks will respond asynchronoulsy, so let's drain the message loop.
  base::RunLoop().RunUntilIdle();

  // Observer should be notified that unmount attempt fails and format task
  // failed to start.
  ASSERT_EQ(2U, observer_->GetEventCount());
  const MountEvent& mount_event = observer_->GetMountEvent(0);
  EXPECT_EQ(DiskMountManager::UNMOUNTING, mount_event.event);
  EXPECT_EQ(MountError::kInsufficientPermissions, mount_event.error_code);
  EXPECT_EQ(kDevice1MountPath, mount_event.mount_point.mount_path);

  EXPECT_EQ(FormatEvent(DiskMountManager::FORMAT_COMPLETED,
                        FormatError::kUnknownError, kDevice1SourcePath,
                        kFormatLabel1),
            observer_->GetFormatEvent(1));
  EXPECT_EQ(1, fake_cros_disks_client_->unmount_call_count());
  EXPECT_EQ(kDevice1MountPath,
            fake_cros_disks_client_->last_unmount_device_path());
  EXPECT_EQ(0, fake_cros_disks_client_->format_call_count());

  // The device mount should still be here.
  EXPECT_TRUE(HasMountPoint(kDevice1MountPath));
}

// 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.

  fake_cros_disks_client_->MakeFormatFail();
  // Start the test.
  DiskMountManager::GetInstance()->FormatMountedDevice(
      kDevice1MountPath, kFormatFileSystemType1, kFormatLabel1);

  // Cros disks will respond asynchronoulsy, so let's drain the message loop.
  base::RunLoop().RunUntilIdle();

  // Observer should be notified that the device was unmounted and format task
  // failed to start.
  ASSERT_EQ(2U, observer_->GetEventCount());
  const MountEvent& mount_event = observer_->GetMountEvent(0);
  EXPECT_EQ(DiskMountManager::UNMOUNTING, mount_event.event);
  EXPECT_EQ(MountError::kSuccess, mount_event.error_code);
  EXPECT_EQ(kDevice1MountPath, mount_event.mount_point.mount_path);

  EXPECT_EQ(FormatEvent(DiskMountManager::FORMAT_COMPLETED,
                        FormatError::kUnknownError, kDevice1SourcePath,
                        kFormatLabel1),
            observer_->GetFormatEvent(1));

  EXPECT_EQ(1, fake_cros_disks_client_->unmount_call_count());
  EXPECT_EQ(kDevice1MountPath,
            fake_cros_disks_client_->last_unmount_device_path());
  EXPECT_EQ(1, fake_cros_disks_client_->format_call_count());
  EXPECT_EQ(kDevice1SourcePath,
            fake_cros_disks_client_->last_format_device_path());
  EXPECT_EQ(kFormatFileSystemType1String,
            fake_cros_disks_client_->last_format_filesystem());
  EXPECT_EQ(kFormatLabel1, fake_cros_disks_client_->last_format_label());

  // The device mount should be gone.
  EXPECT_FALSE(HasMountPoint(kDevice1MountPath));
}

// 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.

  fake_cros_disks_client_->set_unmount_listener(
      base::BindRepeating(&FakeCrosDisksClient::MakeUnmountFail,
                          base::Unretained(fake_cros_disks_client_),
                          MountError::kInsufficientPermissions));
  // Start the test.
  DiskMountManager::GetInstance()->FormatMountedDevice(
      kDevice1MountPath, kFormatFileSystemType1, kFormatLabel1);
  DiskMountManager::GetInstance()->FormatMountedDevice(
      kDevice1MountPath, kFormatFileSystemType2, kFormatLabel2);

  // Cros disks will respond asynchronoulsy, so let's drain the message loop.
  base::RunLoop().RunUntilIdle();

  // 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.

  ASSERT_EQ(3U, observer_->GetEventCount());
  const MountEvent& mount_event = observer_->GetMountEvent(0);
  EXPECT_EQ(DiskMountManager::UNMOUNTING, mount_event.event);
  EXPECT_EQ(MountError::kSuccess, mount_event.error_code);
  EXPECT_EQ(kDevice1MountPath, mount_event.mount_point.mount_path);
  EXPECT_EQ(FormatEvent(DiskMountManager::FORMAT_COMPLETED,
                        FormatError::kUnknownError, kDevice1SourcePath,
                        kFormatLabel2),
            observer_->GetFormatEvent(1));
  EXPECT_EQ(FormatEvent(DiskMountManager::FORMAT_STARTED, FormatError::kSuccess,
                        kDevice1SourcePath, kFormatLabel1),
            observer_->GetFormatEvent(2));

  EXPECT_EQ(2, fake_cros_disks_client_->unmount_call_count());
  EXPECT_EQ(kDevice1MountPath,
            fake_cros_disks_client_->last_unmount_device_path());
  EXPECT_EQ(1, fake_cros_disks_client_->format_call_count());
  EXPECT_EQ(kDevice1SourcePath,
            fake_cros_disks_client_->last_format_device_path());
  EXPECT_EQ(kFormatFileSystemType1String,
            fake_cros_disks_client_->last_format_filesystem());
  EXPECT_EQ(kFormatLabel1, fake_cros_disks_client_->last_format_label());

  // The device mount should be gone.
  EXPECT_FALSE(HasMountPoint(kDevice1MountPath));
}

// Verifies a |MountEvent| with the given condition. This function only checks
// the |mount_path| in |MountPointInfo| to make sure to match the event with
// preceding mount invocations.
void VerifyMountEvent(const MountEvent& mount_event,
                      DiskMountManager::MountEvent mount_event_type,
                      MountError error_code,
                      const std::string& mount_path) {
  EXPECT_EQ(mount_event_type, mount_event.event);
  EXPECT_EQ(error_code, mount_event.error_code);
  EXPECT_EQ(mount_path, mount_event.mount_point.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.

  // Start the test.
  DiskMountManager::GetInstance()->FormatMountedDevice(
      kDevice1MountPath, kFormatFileSystemType1, kFormatLabel1);

  // Wait for Unmount and Format calls to end.
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(1, fake_cros_disks_client_->unmount_call_count());
  EXPECT_EQ(kDevice1MountPath,
            fake_cros_disks_client_->last_unmount_device_path());
  EXPECT_EQ(1, fake_cros_disks_client_->format_call_count());
  EXPECT_EQ(kDevice1SourcePath,
            fake_cros_disks_client_->last_format_device_path());
  EXPECT_EQ(kFormatFileSystemType1String,
            fake_cros_disks_client_->last_format_filesystem());
  EXPECT_EQ(kFormatLabel1, fake_cros_disks_client_->last_format_label());

  // The device should be unmounted by now.
  EXPECT_FALSE(HasMountPoint(kDevice1MountPath));

  // Send failing FORMAT_COMPLETED signal.
  // The failure is marked by ! in fromt of the path (but this should change
  // soon).
  fake_cros_disks_client_->NotifyFormatCompleted(FormatError::kUnknownError,
                                                 kDevice1SourcePath);

  // 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).
  ASSERT_EQ(3U, observer_->GetEventCount());
  VerifyMountEvent(observer_->GetMountEvent(0), DiskMountManager::UNMOUNTING,
                   MountError::kSuccess, kDevice1MountPath);
  EXPECT_EQ(FormatEvent(DiskMountManager::FORMAT_STARTED, FormatError::kSuccess,
                        kDevice1SourcePath, kFormatLabel1),
            observer_->GetFormatEvent(1));
  EXPECT_EQ(FormatEvent(DiskMountManager::FORMAT_COMPLETED,
                        FormatError::kUnknownError, kDevice1SourcePath,
                        kFormatLabel1),
            observer_->GetFormatEvent(2));
}

// Tests the case when formatting completes successfully.
TEST_F(DiskMountManagerTest, Format_FormatSuccess) {
  DiskMountManager* manager = DiskMountManager::GetInstance();
  const DiskMountManager::Disks& disks = manager->disks();

  // Set up cros disks client mocks.
  // Both unmount and format device cals are successful in this test.

  // Start the test.
  DiskMountManager::GetInstance()->FormatMountedDevice(
      kDevice1MountPath, kFormatFileSystemType1, kFormatLabel1);

  // Wait for Unmount and Format calls to end.
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(1, fake_cros_disks_client_->unmount_call_count());
  EXPECT_EQ(kDevice1MountPath,
            fake_cros_disks_client_->last_unmount_device_path());
  EXPECT_EQ(1, fake_cros_disks_client_->format_call_count());
  EXPECT_EQ(kDevice1SourcePath,
            fake_cros_disks_client_->last_format_device_path());
  EXPECT_EQ(kFormatFileSystemType1String,
            fake_cros_disks_client_->last_format_filesystem());
  EXPECT_EQ(kFormatLabel1, fake_cros_disks_client_->last_format_label());

  // The device should be unmounted by now.
  EXPECT_FALSE(HasMountPoint(kDevice1MountPath));

  // Simulate cros_disks reporting success.
  fake_cros_disks_client_->NotifyFormatCompleted(FormatError::kSuccess,
                                                 kDevice1SourcePath);

  // The observer should receive UNMOUNTING, FORMAT_STARTED and FORMAT_COMPLETED
  // events (all of them without an error set).
  ASSERT_EQ(3U, observer_->GetEventCount());
  VerifyMountEvent(observer_->GetMountEvent(0), DiskMountManager::UNMOUNTING,
                   MountError::kSuccess, kDevice1MountPath);
  EXPECT_EQ(FormatEvent(DiskMountManager::FORMAT_STARTED, FormatError::kSuccess,
                        kDevice1SourcePath, kFormatLabel1),
            observer_->GetFormatEvent(1));
  EXPECT_EQ(
      FormatEvent(DiskMountManager::FORMAT_COMPLETED, FormatError::kSuccess,
                  kDevice1SourcePath, kFormatLabel1),
      observer_->GetFormatEvent(2));

  // Disk should have new values for file system type and device label name
  EXPECT_EQ(kFormatFileSystemType1String,
            disks.find(kDevice1SourcePath)->get()->file_system_type());
  EXPECT_EQ(kFormatLabel1,
            disks.find(kDevice1SourcePath)->get()->device_label());
}

// 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).

  // Start the test.
  DiskMountManager::GetInstance()->FormatMountedDevice(
      kDevice1MountPath, kFormatFileSystemType1, kFormatLabel1);

  // Wait for Unmount and Format calls to end.
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(1, fake_cros_disks_client_->unmount_call_count());
  EXPECT_EQ(kDevice1MountPath,
            fake_cros_disks_client_->last_unmount_device_path());
  EXPECT_EQ(1, fake_cros_disks_client_->format_call_count());
  EXPECT_EQ(kDevice1SourcePath,
            fake_cros_disks_client_->last_format_device_path());
  EXPECT_EQ(kFormatFileSystemType1String,
            fake_cros_disks_client_->last_format_filesystem());
  EXPECT_EQ(kFormatLabel1, fake_cros_disks_client_->last_format_label());

  // The device should be unmounted by now.
  EXPECT_FALSE(HasMountPoint(kDevice1MountPath));

  // Simulate cros_disks reporting success.
  fake_cros_disks_client_->NotifyFormatCompleted(FormatError::kSuccess,
                                                 kDevice1SourcePath);

  // Simulate the device remounting.
  fake_cros_disks_client_->NotifyMountCompleted(
      MountError::kSuccess, kDevice1SourcePath, MountType::kDevice,
      kDevice1MountPath);

  EXPECT_TRUE(HasMountPoint(kDevice1MountPath));

  // Try formatting again.
  DiskMountManager::GetInstance()->FormatMountedDevice(
      kDevice1MountPath, kFormatFileSystemType2, kFormatLabel2);

  // Wait for Unmount and Format calls to end.
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(2, fake_cros_disks_client_->unmount_call_count());
  EXPECT_EQ(kDevice1MountPath,
            fake_cros_disks_client_->last_unmount_device_path());
  EXPECT_EQ(2, fake_cros_disks_client_->format_call_count());
  EXPECT_EQ(kDevice1SourcePath,
            fake_cros_disks_client_->last_format_device_path());
  EXPECT_EQ(kFormatFileSystemType2String,
            fake_cros_disks_client_->last_format_filesystem());
  EXPECT_EQ(kFormatLabel2, fake_cros_disks_client_->last_format_label());

  // Simulate cros_disks reporting success.
  fake_cros_disks_client_->NotifyFormatCompleted(FormatError::kSuccess,
                                                 kDevice1SourcePath);

  // 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_EQ(7U, observer_->GetEventCount());

  EXPECT_EQ(1U, observer_->CountFormatEvents(FormatEvent(
                    DiskMountManager::FORMAT_COMPLETED, FormatError::kSuccess,
                    kDevice1SourcePath, kFormatLabel1)));

  EXPECT_EQ(1U, observer_->CountFormatEvents(FormatEvent(
                    DiskMountManager::FORMAT_COMPLETED, FormatError::kSuccess,
                    kDevice1SourcePath, kFormatLabel2)));

  EXPECT_EQ(1U, observer_->CountFormatEvents(FormatEvent(
                    DiskMountManager::FORMAT_STARTED, FormatError::kSuccess,
                    kDevice1SourcePath, kFormatLabel1)));

  EXPECT_EQ(1U, observer_->CountFormatEvents(FormatEvent(
                    DiskMountManager::FORMAT_STARTED, FormatError::kSuccess,
                    kDevice1SourcePath, kFormatLabel2)));

  EXPECT_EQ(
      2U, observer_->CountMountEvents(DiskMountManager::UNMOUNTING,
                                      MountError::kSuccess, kDevice1MountPath));

  EXPECT_EQ(
      1U, observer_->CountMountEvents(DiskMountManager::MOUNTING,
                                      MountError::kSuccess, kDevice1MountPath));
}

TEST_F(DiskMountManagerTest, MountPath_RecordAccessMode) {
  DiskMountManager* manager = DiskMountManager::GetInstance();
  const std::string kSourcePath1 = kDevice1SourcePath;
  const std::string kSourcePath2 = kDevice2SourcePath;
  const std::string kSourceFormat;

  // For MountCompleted. Must be non-empty strings.
  const std::string kMountPath1 = "/media/foo";
  const std::string kMountPath2 = "/media/bar";

  // MountPath should call the given callback when the mount completes.
  base::MockCallback<DiskMountManager::MountPathCallback> mock_callback1;
  EXPECT_CALL(
      mock_callback1,
      Run(MountError::kSuccess,
          Field(&DiskMountManager::MountPoint::mount_path, kMountPath1)));

  base::MockCallback<DiskMountManager::MountPathCallback> mock_callback2;
  EXPECT_CALL(
      mock_callback2,
      Run(MountError::kSuccess,
          Field(&DiskMountManager::MountPoint::mount_path, kMountPath2)))
      .WillOnce(
          [&](MountError, const DiskMountManager::MountPoint& mount_point) {
            // Verify the disk appears read-only when the callback is invoked.
            // See below comment about the 2nd source.
            EXPECT_TRUE(manager->disks()
                            .find(mount_point.source_path)
                            ->get()
                            ->is_read_only());
          });

  manager->MountPath(kSourcePath1, kSourceFormat, std::string(), {},
                     MountType::kDevice, MountAccessMode::kReadWrite,
                     mock_callback1.Get());
  manager->MountPath(kSourcePath2, kSourceFormat, std::string(), {},
                     MountType::kDevice, MountAccessMode::kReadOnly,
                     mock_callback2.Get());
  // Simulate cros_disks reporting mount completed.
  fake_cros_disks_client_->NotifyMountCompleted(
      MountError::kSuccess, kSourcePath1, MountType::kDevice, kMountPath1);
  fake_cros_disks_client_->NotifyMountCompleted(
      MountError::kSuccess, kSourcePath2, MountType::kDevice, kMountPath2,
      true);

  // Event handlers of observers should be called.
  ASSERT_EQ(2U, observer_->GetEventCount());
  VerifyMountEvent(observer_->GetMountEvent(0), DiskMountManager::MOUNTING,
                   MountError::kSuccess, kMountPath1);
  // For the 2nd source, the disk (block device) is not read-only but the
  // test will mount it in read-only mode.
  // Observers query |disks_| from |DiskMountManager| in its event handler for
  // a mount completion event. Therefore |disks_| must be updated with correct
  // |read_only| value before notifying to observers.
  const MountEvent& secondMountEvent = observer_->GetMountEvent(1);
  EXPECT_EQ(DiskMountManager::MOUNTING, secondMountEvent.event);
  EXPECT_EQ(MountError::kSuccess, secondMountEvent.error_code);
  EXPECT_EQ(kMountPath2, secondMountEvent.mount_point.mount_path);
  // Verify if the disk appears read-only at the time of notification to
  // observers.
  EXPECT_TRUE(secondMountEvent.disk->is_read_only());

  // Verify the final state of manager->disks.
  const DiskMountManager::Disks& disks = manager->disks();
  ASSERT_GT(disks.count(kSourcePath1), 0U);
  EXPECT_FALSE(disks.find(kSourcePath1)->get()->is_read_only());
  ASSERT_GT(disks.count(kSourcePath2), 0U);
  EXPECT_TRUE(disks.find(kSourcePath2)->get()->is_read_only());
}

TEST_F(DiskMountManagerTest, MountPath_ReadOnlyDevice) {
  DiskMountManager* manager = DiskMountManager::GetInstance();
  const std::string kSourceFormat;

  base::MockCallback<DiskMountManager::MountPathCallback> mock_callback;
  EXPECT_CALL(
      mock_callback,
      Run(MountError::kSuccess, Field(&DiskMountManager::MountPoint::mount_path,
                                      kReadOnlyDeviceMountPath)));

  // Attempt to mount a read-only device in read-write mode.
  manager->MountPath(kReadOnlyDeviceSourcePath, kSourceFormat, std::string(),
                     {}, MountType::kDevice, MountAccessMode::kReadWrite,
                     mock_callback.Get());
  // Simulate cros_disks reporting mount completed.
  fake_cros_disks_client_->NotifyMountCompleted(
      MountError::kSuccess, kReadOnlyDeviceSourcePath, MountType::kDevice,
      kReadOnlyDeviceMountPath);

  // Event handlers of observers should be called.
  ASSERT_EQ(1U, observer_->GetEventCount());
  VerifyMountEvent(observer_->GetMountEvent(0), DiskMountManager::MOUNTING,
                   MountError::kSuccess, kReadOnlyDeviceMountPath);
  const DiskMountManager::Disks& disks = manager->disks();
  ASSERT_GT(disks.count(kReadOnlyDeviceSourcePath), 0U);
  // The mounted disk should preserve the read-only flag of the block device.
  EXPECT_TRUE(disks.find(kReadOnlyDeviceSourcePath)->get()->is_read_only());
}

TEST_F(DiskMountManagerTest, MountPath_DoubleCall) {
  DiskMountManager* manager = DiskMountManager::GetInstance();
  const std::string kMountPath1 = "/media/foo";

  base::MockCallback<DiskMountManager::MountPathCallback> mock_callback1;

  manager->MountPath(kDevice1SourcePath, "", "", {}, MountType::kDevice,
                     MountAccessMode::kReadWrite, mock_callback1.Get());

  {
    // While the first mount is occurring, queue up a second mount for the same
    // source. It should immediately fail.
    base::MockCallback<DiskMountManager::MountPathCallback> mock_callback2;
    EXPECT_CALL(mock_callback2, Run(MountError::kPathAlreadyMounted, _));

    manager->MountPath(kDevice1SourcePath, "", "", {}, MountType::kDevice,
                       MountAccessMode::kReadWrite, mock_callback2.Get());
  }

  // Verify the first mount can complete as expected.
  EXPECT_CALL(
      mock_callback1,
      Run(MountError::kSuccess,
          Field(&DiskMountManager::MountPoint::mount_path, kMountPath1)));
  fake_cros_disks_client_->NotifyMountCompleted(
      MountError::kSuccess, kDevice1SourcePath, MountType::kDevice,
      kMountPath1);
}

TEST_F(DiskMountManagerTest, MountPath_CallbackCallsMount) {
  DiskMountManager* manager = DiskMountManager::GetInstance();
  const std::string kMountPath1 = "/media/foo";
  const std::string kMountPath2 = "/media/bar";

  base::MockCallback<DiskMountManager::MountPathCallback> mock_callback1;

  // Try call MountPath() again in the complete callback of a MountPath() call.
  EXPECT_CALL(
      mock_callback1,
      Run(MountError::kSuccess,
          Field(&DiskMountManager::MountPoint::mount_path, kMountPath1)))
      .WillOnce([=](MountError error,
                    const DiskMountManager::MountPoint& mount_info) {
        // Try remount the same path and verify it fails.
        base::MockCallback<DiskMountManager::MountPathCallback> mock_callback2;
        EXPECT_CALL(mock_callback2, Run(MountError::kPathAlreadyMounted, _));
        manager->MountPath(kDevice1SourcePath, "", "", {}, MountType::kDevice,
                           MountAccessMode::kReadWrite, mock_callback2.Get());

        // Try mount a different path and verify it succeeds.
        base::MockCallback<DiskMountManager::MountPathCallback> mock_callback3;
        EXPECT_CALL(
            mock_callback3,
            Run(MountError::kSuccess,
                Field(&DiskMountManager::MountPoint::mount_path, kMountPath2)));
        manager->MountPath(kDevice2SourcePath, "", "", {}, MountType::kDevice,
                           MountAccessMode::kReadWrite, mock_callback3.Get());
        fake_cros_disks_client_->NotifyMountCompleted(
            MountError::kSuccess, kDevice2SourcePath, MountType::kDevice,
            kMountPath2);
      });

  manager->MountPath(kDevice1SourcePath, "", "", {}, MountType::kDevice,
                     MountAccessMode::kReadWrite, mock_callback1.Get());
  fake_cros_disks_client_->NotifyMountCompleted(
      MountError::kSuccess, kDevice1SourcePath, MountType::kDevice,
      kMountPath1);
}

TEST_F(DiskMountManagerTest, RemountRemovableDrives) {
  DiskMountManager* manager = DiskMountManager::GetInstance();
  // Initially we have 2 mounted devices.
  // kDevice1MountPath --- read-write device, mounted in read-write mode.
  // kReadOnlyDeviceMountPath --- read-only device, mounted in read-only mode.

  manager->RemountAllRemovableDrives(MountAccessMode::kReadOnly);

  // Simulate cros_disks reporting mount completed.
  fake_cros_disks_client_->NotifyMountCompleted(
      MountError::kSuccess, kDevice1SourcePath, MountType::kDevice,
      kDevice1MountPath, true);

  // Should remount disks that are not read-only by its hardware device.
  ASSERT_EQ(1U, observer_->GetEventCount());
  VerifyMountEvent(observer_->GetMountEvent(0), DiskMountManager::MOUNTING,
                   MountError::kSuccess, kDevice1MountPath);
  // The disk is remounted in read-only mode.
  EXPECT_TRUE(
      manager->FindDiskBySourcePath(kDevice1SourcePath)->is_read_only());
  // Remounted disk should also appear as read-only to observers.
  EXPECT_TRUE(observer_->GetMountEvent(0).disk->is_read_only());

  // Remount in read-write mode again.
  manager->RemountAllRemovableDrives(MountAccessMode::kReadWrite);

  // Simulate cros_disks reporting mount completed.
  fake_cros_disks_client_->NotifyMountCompleted(
      MountError::kSuccess, kDevice1SourcePath, MountType::kDevice,
      kDevice1MountPath, false);
  // Event handlers of observers should be called.
  ASSERT_EQ(2U, observer_->GetEventCount());
  VerifyMountEvent(observer_->GetMountEvent(1), DiskMountManager::MOUNTING,
                   MountError::kSuccess, kDevice1MountPath);
  // The read-write device should be remounted in read-write mode.
  EXPECT_FALSE(
      manager->FindDiskBySourcePath(kDevice1SourcePath)->is_read_only());
  // Remounted disk should also appear as writable to observers.
  EXPECT_FALSE(observer_->GetMountEvent(1).disk->is_read_only());
}

// Tests that the observer gets notified on attempt to rename non existent mount
// point.
TEST_F(DiskMountManagerTest, Rename_NotMounted) {
  DiskMountManager::GetInstance()->RenameMountedDevice("/mount/non_existent",
                                                       "MYUSB");
  ASSERT_EQ(1U, observer_->GetEventCount());
  EXPECT_EQ(
      RenameEvent(DiskMountManager::RENAME_COMPLETED,
                  RenameError::kUnknownError, "/mount/non_existent", "MYUSB"),
      observer_->GetRenameEvent(0));
}

// Tests that the observer gets notified on attempt to rename read-only mount
// point.
TEST_F(DiskMountManagerTest, Rename_ReadOnly) {
  DiskMountManager::GetInstance()->RenameMountedDevice(kReadOnlyDeviceMountPath,
                                                       "MYUSB");
  ASSERT_EQ(1U, observer_->GetEventCount());
  EXPECT_EQ(RenameEvent(DiskMountManager::RENAME_COMPLETED,
                        RenameError::kDeviceNotAllowed,
                        kReadOnlyDeviceSourcePath, "MYUSB"),
            observer_->GetRenameEvent(0));
}

// Tests that it is not possible to rename archive mount point.
TEST_F(DiskMountManagerTest, Rename_Archive) {
  DiskMountManager::GetInstance()->RenameMountedDevice("/archive/mount_path",
                                                       "MYUSB");
  ASSERT_EQ(1U, observer_->GetEventCount());
  EXPECT_EQ(
      RenameEvent(DiskMountManager::RENAME_COMPLETED,
                  RenameError::kUnknownError, "/archive/source_path", "MYUSB"),
      observer_->GetRenameEvent(0));
}

// Tests that rename fails if the device cannot be unmounted.
TEST_F(DiskMountManagerTest, Rename_FailToUnmount) {
  // Before renaming mounted device, the device should be unmounted.
  // In this test unmount will fail, and there should be no attempt to
  // rename the device.

  fake_cros_disks_client_->MakeUnmountFail(MountError::kUnknownError);
  // Start test.
  DiskMountManager::GetInstance()->RenameMountedDevice(kDevice1MountPath,
                                                       "MYUSB");

  // Cros disks will respond asynchronoulsy, so let's drain the message loop.
  base::RunLoop().RunUntilIdle();

  // Observer should be notified that unmount attempt fails and rename task
  // failed to start.
  ASSERT_EQ(2U, observer_->GetEventCount());
  const MountEvent& mount_event = observer_->GetMountEvent(0);
  EXPECT_EQ(DiskMountManager::UNMOUNTING, mount_event.event);
  EXPECT_EQ(MountError::kUnknownError, mount_event.error_code);
  EXPECT_EQ(kDevice1MountPath, mount_event.mount_point.mount_path);

  EXPECT_EQ(
      RenameEvent(DiskMountManager::RENAME_COMPLETED,
                  RenameError::kUnknownError, kDevice1SourcePath, "MYUSB"),
      observer_->GetRenameEvent(1));
  EXPECT_EQ(1, fake_cros_disks_client_->unmount_call_count());
  EXPECT_EQ(kDevice1MountPath,
            fake_cros_disks_client_->last_unmount_device_path());
  EXPECT_EQ(0, fake_cros_disks_client_->rename_call_count());

  // The device mount should still be here.
  EXPECT_TRUE(HasMountPoint(kDevice1MountPath));
}

// Tests that observer is notified when cros disks fails to start rename
// process.
TEST_F(DiskMountManagerTest, Rename_RenameFailsToStart) {
  // Before renaming mounted device, the device should be unmounted.
  // In this test, unmount will succeed, but call to Rename method will
  // fail.

  fake_cros_disks_client_->MakeRenameFail();
  // Start the test.
  DiskMountManager::GetInstance()->RenameMountedDevice(kDevice1MountPath,
                                                       "MYUSB");

  // Cros disks will respond asynchronoulsy, so let's drain the message loop.
  base::RunLoop().RunUntilIdle();

  // Observer should be notified that the device was unmounted and rename task
  // failed to start.
  ASSERT_EQ(2U, observer_->GetEventCount());
  const MountEvent& mount_event = observer_->GetMountEvent(0);
  EXPECT_EQ(DiskMountManager::UNMOUNTING, mount_event.event);
  EXPECT_EQ(MountError::kSuccess, mount_event.error_code);
  EXPECT_EQ(kDevice1MountPath, mount_event.mount_point.mount_path);

  EXPECT_EQ(
      RenameEvent(DiskMountManager::RENAME_COMPLETED,
                  RenameError::kUnknownError, kDevice1SourcePath, "MYUSB"),
      observer_->GetRenameEvent(1));

  EXPECT_EQ(1, fake_cros_disks_client_->unmount_call_count());
  EXPECT_EQ(kDevice1MountPath,
            fake_cros_disks_client_->last_unmount_device_path());
  EXPECT_EQ(1, fake_cros_disks_client_->rename_call_count());
  EXPECT_EQ(kDevice1SourcePath,
            fake_cros_disks_client_->last_rename_device_path());
  EXPECT_EQ("MYUSB", fake_cros_disks_client_->last_rename_volume_name());

  // The device mount should be gone.
  EXPECT_FALSE(HasMountPoint(kDevice1MountPath));
}

// Tests the case where there are two rename requests for the same device.
TEST_F(DiskMountManagerTest, Rename_ConcurrentRenameCalls) {
  // Only the first rename request should be processed (the second unmount
  // request fails because the device is already unmounted at that point).
  // CrosDisksClient will report that the rename process for the first request
  // is successfully started.

  fake_cros_disks_client_->set_unmount_listener(base::BindRepeating(
      &FakeCrosDisksClient::MakeUnmountFail,
      base::Unretained(fake_cros_disks_client_), MountError::kInternalError));
  // Start the test.
  DiskMountManager::GetInstance()->RenameMountedDevice(kDevice1MountPath,
                                                       "MYUSB1");
  DiskMountManager::GetInstance()->RenameMountedDevice(kDevice1MountPath,
                                                       "MYUSB2");

  // Cros disks will respond asynchronoulsy, so let's drain the message loop.
  base::RunLoop().RunUntilIdle();

  // The observer should get a RENAME_STARTED event for one rename request and a
  // RENAME_COMPLETED with an error code for the other rename request. The
  // renaming 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 rename completion signal will not be simulated,
  // so the observer should not get RENAME_COMPLETED signal.

  ASSERT_EQ(3U, observer_->GetEventCount());
  const MountEvent& mount_event = observer_->GetMountEvent(0);
  EXPECT_EQ(DiskMountManager::UNMOUNTING, mount_event.event);
  EXPECT_EQ(MountError::kSuccess, mount_event.error_code);
  EXPECT_EQ(kDevice1MountPath, mount_event.mount_point.mount_path);
  EXPECT_EQ(
      RenameEvent(DiskMountManager::RENAME_COMPLETED,
                  RenameError::kUnknownError, kDevice1SourcePath, "MYUSB2"),
      observer_->GetRenameEvent(1));
  EXPECT_EQ(RenameEvent(DiskMountManager::RENAME_STARTED, RenameError::kSuccess,
                        kDevice1SourcePath, "MYUSB1"),
            observer_->GetRenameEvent(2));

  EXPECT_EQ(2, fake_cros_disks_client_->unmount_call_count());
  EXPECT_EQ(kDevice1MountPath,
            fake_cros_disks_client_->last_unmount_device_path());
  EXPECT_EQ(1, fake_cros_disks_client_->rename_call_count());
  EXPECT_EQ(kDevice1SourcePath,
            fake_cros_disks_client_->last_rename_device_path());
  EXPECT_EQ("MYUSB1", fake_cros_disks_client_->last_rename_volume_name());

  // The device mount should be gone.
  EXPECT_FALSE(HasMountPoint(kDevice1MountPath));
}

// Tests the case when the rename process actually starts and fails.
TEST_F(DiskMountManagerTest, Rename_RenameFails) {
  // Both unmount and rename device calls are successful in this test.

  // Start the test.
  DiskMountManager::GetInstance()->RenameMountedDevice(kDevice1MountPath,
                                                       "MYUSB");

  // Wait for Unmount and Rename calls to end.
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(1, fake_cros_disks_client_->unmount_call_count());
  EXPECT_EQ(kDevice1MountPath,
            fake_cros_disks_client_->last_unmount_device_path());
  EXPECT_EQ(1, fake_cros_disks_client_->rename_call_count());
  EXPECT_EQ(kDevice1SourcePath,
            fake_cros_disks_client_->last_rename_device_path());
  EXPECT_EQ("MYUSB", fake_cros_disks_client_->last_rename_volume_name());

  // The device should be unmounted by now.
  EXPECT_FALSE(HasMountPoint(kDevice1MountPath));

  // Send failing RENAME_COMPLETED signal.
  // The failure is marked by ! in fromt of the path (but this should change
  // soon).
  fake_cros_disks_client_->NotifyRenameCompleted(RenameError::kUnknownError,
                                                 kDevice1SourcePath);

  // The observer should get notified that the device was unmounted and that
  // renaming has started.
  // After the renaming starts, the test will simulate failing
  // RENAME_COMPLETED signal, so the observer should also be notified the
  // renaming has failed (RENAME_COMPLETED event).
  ASSERT_EQ(3U, observer_->GetEventCount());
  VerifyMountEvent(observer_->GetMountEvent(0), DiskMountManager::UNMOUNTING,
                   MountError::kSuccess, kDevice1MountPath);
  EXPECT_EQ(RenameEvent(DiskMountManager::RENAME_STARTED, RenameError::kSuccess,
                        kDevice1SourcePath, "MYUSB"),
            observer_->GetRenameEvent(1));
  EXPECT_EQ(
      RenameEvent(DiskMountManager::RENAME_COMPLETED,
                  RenameError::kUnknownError, kDevice1SourcePath, "MYUSB"),
      observer_->GetRenameEvent(2));
}

// Tests the case when renaming completes successfully.
TEST_F(DiskMountManagerTest, Rename_RenameSuccess) {
  DiskMountManager* manager = DiskMountManager::GetInstance();
  const DiskMountManager::Disks& disks = manager->disks();
  // Set up cros disks client mocks.
  // Both unmount and rename device calls are successful in this test.

  // Start the test.
  DiskMountManager::GetInstance()->RenameMountedDevice(kDevice1MountPath,
                                                       "MYUSB1");

  // Wait for Unmount and Rename calls to end.
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(1, fake_cros_disks_client_->unmount_call_count());
  EXPECT_EQ(kDevice1MountPath,
            fake_cros_disks_client_->last_unmount_device_path());
  EXPECT_EQ(1, fake_cros_disks_client_->rename_call_count());
  EXPECT_EQ(kDevice1SourcePath,
            fake_cros_disks_client_->last_rename_device_path());
  EXPECT_EQ("MYUSB1", fake_cros_disks_client_->last_rename_volume_name());

  // The device should be unmounted by now.
  EXPECT_FALSE(HasMountPoint(kDevice1MountPath));

  // Simulate cros_disks reporting success.
  fake_cros_disks_client_->NotifyRenameCompleted(RenameError::kSuccess,
                                                 kDevice1SourcePath);

  // The observer should receive UNMOUNTING, RENAME_STARTED and RENAME_COMPLETED
  // events (all of them without an error set).
  ASSERT_EQ(3U, observer_->GetEventCount());
  VerifyMountEvent(observer_->GetMountEvent(0), DiskMountManager::UNMOUNTING,
                   MountError::kSuccess, kDevice1MountPath);
  EXPECT_EQ(RenameEvent(DiskMountManager::RENAME_STARTED, RenameError::kSuccess,
                        kDevice1SourcePath, "MYUSB1"),
            observer_->GetRenameEvent(1));
  EXPECT_EQ(RenameEvent(DiskMountManager::RENAME_COMPLETED,
                        RenameError::kSuccess, kDevice1SourcePath, "MYUSB1"),
            observer_->GetRenameEvent(2));

  // Disk should have new value for device label name
  EXPECT_EQ("MYUSB1", disks.find(kDevice1SourcePath)->get()->device_label());
}

// Tests that it's possible to rename the device twice in a row (this may not be
// true if the list of pending renames is not properly cleared).
TEST_F(DiskMountManagerTest, Rename_ConsecutiveRenameCalls) {
  DiskMountManager* manager = DiskMountManager::GetInstance();
  const DiskMountManager::Disks& disks = manager->disks();
  // All unmount and rename device calls are successful in this test.
  // Each of the should be made twice (once for each renaming task).

  // Start the test.
  DiskMountManager::GetInstance()->RenameMountedDevice(kDevice1MountPath,
                                                       "MYUSB");

  // Wait for Unmount and Rename calls to end.
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(1, fake_cros_disks_client_->unmount_call_count());
  EXPECT_EQ(kDevice1MountPath,
            fake_cros_disks_client_->last_unmount_device_path());
  EXPECT_EQ(1, fake_cros_disks_client_->rename_call_count());
  EXPECT_EQ(kDevice1SourcePath,
            fake_cros_disks_client_->last_rename_device_path());
  EXPECT_EQ("MYUSB", fake_cros_disks_client_->last_rename_volume_name());
  EXPECT_EQ("", disks.find(kDevice1SourcePath)->get()->base_mount_path());

  // The device should be unmounted by now.
  EXPECT_FALSE(HasMountPoint(kDevice1MountPath));

  // Simulate cros_disks reporting success.
  fake_cros_disks_client_->NotifyRenameCompleted(RenameError::kSuccess,
                                                 kDevice1SourcePath);

  // Simulate the device remounting.
  fake_cros_disks_client_->NotifyMountCompleted(
      MountError::kSuccess, kDevice1SourcePath, MountType::kDevice,
      kDevice1MountPath);

  EXPECT_TRUE(HasMountPoint(kDevice1MountPath));

  auto previousMountPath = disks.find(kDevice1SourcePath)->get()->mount_path();
  // Try renaming again.
  DiskMountManager::GetInstance()->RenameMountedDevice(kDevice1MountPath,
                                                       "MYUSB2");

  // Wait for Unmount and Rename calls to end.
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(2, fake_cros_disks_client_->unmount_call_count());
  EXPECT_EQ(kDevice1MountPath,
            fake_cros_disks_client_->last_unmount_device_path());
  EXPECT_EQ(2, fake_cros_disks_client_->rename_call_count());
  EXPECT_EQ(kDevice1SourcePath,
            fake_cros_disks_client_->last_rename_device_path());
  EXPECT_EQ("MYUSB2", fake_cros_disks_client_->last_rename_volume_name());
  // Base mount path should be set to previous mount path.
  EXPECT_EQ(previousMountPath,
            disks.find(kDevice1SourcePath)->get()->base_mount_path());

  // Simulate cros_disks reporting success.
  fake_cros_disks_client_->NotifyRenameCompleted(RenameError::kSuccess,
                                                 kDevice1SourcePath);

  // The observer should receive UNMOUNTING, RENAME_STARTED and RENAME_COMPLETED
  // events (all of them without an error set) twice (once for each renaming
  // task).
  // Also, there should be a MOUNTING event when the device remounting is
  // simulated.
  EXPECT_EQ(7U, observer_->GetEventCount());

  EXPECT_EQ(1U, observer_->CountRenameEvents(RenameEvent(
                    DiskMountManager::RENAME_COMPLETED, RenameError::kSuccess,
                    kDevice1SourcePath, "MYUSB2")));

  EXPECT_EQ(1U, observer_->CountRenameEvents(RenameEvent(
                    DiskMountManager::RENAME_COMPLETED, RenameError::kSuccess,
                    kDevice1SourcePath, "MYUSB")));

  EXPECT_EQ(1U, observer_->CountRenameEvents(RenameEvent(
                    DiskMountManager::RENAME_STARTED, RenameError::kSuccess,
                    kDevice1SourcePath, "MYUSB")));

  EXPECT_EQ(1U, observer_->CountRenameEvents(RenameEvent(
                    DiskMountManager::RENAME_STARTED, RenameError::kSuccess,
                    kDevice1SourcePath, "MYUSB2")));

  EXPECT_EQ(
      2U, observer_->CountMountEvents(DiskMountManager::UNMOUNTING,
                                      MountError::kSuccess, kDevice1MountPath));

  EXPECT_EQ(
      1U, observer_->CountMountEvents(DiskMountManager::MOUNTING,
                                      MountError::kSuccess, kDevice1MountPath));
}

void SaveUnmountResult(MountError* save_error,
                       base::OnceClosure done_callback,
                       MountError error_code) {
  *save_error = error_code;
  std::move(done_callback).Run();
}

TEST_F(DiskMountManagerTest, UnmountDeviceRecursively) {
  base::RunLoop run_loop;

  auto disk_sda =
      Disk::Builder().SetDevicePath("/dev/sda").SetIsParent(true).Build();
  EXPECT_TRUE(
      DiskMountManager::GetInstance()->AddDiskForTest(std::move(disk_sda)));

  auto disk_sda1 = Disk::Builder()
                       .SetDevicePath("/dev/sda1")
                       .SetMountPath("/mount/path1")
                       .Build();
  EXPECT_TRUE(
      DiskMountManager::GetInstance()->AddDiskForTest(std::move(disk_sda1)));

  auto disk_sda2 = Disk::Builder()
                       .SetDevicePath("/dev/sda2")
                       .SetMountPath("/mount/path2")
                       .Build();
  EXPECT_TRUE(
      DiskMountManager::GetInstance()->AddDiskForTest(std::move(disk_sda2)));

  MountError error_code = MountError::kUnknownError;
  DiskMountManager::GetInstance()->UnmountDeviceRecursively(
      "/dev/sda",
      base::BindOnce(&SaveUnmountResult, base::Unretained(&error_code),
                     run_loop.QuitClosure()));
  run_loop.Run();

  EXPECT_EQ(2, fake_cros_disks_client_->unmount_call_count());
  EXPECT_EQ(MountError::kSuccess, error_code);
}

TEST_F(DiskMountManagerTest, UnmountDeviceRecursively_NoMounted) {
  base::RunLoop run_loop;

  auto disk_sda =
      Disk::Builder().SetDevicePath("/dev/sda").SetIsParent(true).Build();
  EXPECT_TRUE(
      DiskMountManager::GetInstance()->AddDiskForTest(std::move(disk_sda)));

  auto disk_sda1 = Disk::Builder().SetDevicePath("/dev/sda1").Build();
  EXPECT_TRUE(
      DiskMountManager::GetInstance()->AddDiskForTest(std::move(disk_sda1)));

  MountError error_code = MountError::kUnknownError;
  DiskMountManager::GetInstance()->UnmountDeviceRecursively(
      "/dev/sda",
      base::BindOnce(&SaveUnmountResult, base::Unretained(&error_code),
                     run_loop.QuitClosure()));
  run_loop.Run();

  EXPECT_EQ(0, fake_cros_disks_client_->unmount_call_count());
  EXPECT_EQ(MountError::kSuccess, error_code);
}

TEST_F(DiskMountManagerTest, UnmountDeviceRecursively_NoDisk) {
  base::RunLoop run_loop;

  auto disk_sda =
      Disk::Builder().SetDevicePath("/dev/sda").SetIsParent(true).Build();
  EXPECT_TRUE(
      DiskMountManager::GetInstance()->AddDiskForTest(std::move(disk_sda)));

  auto disk_sda1 = Disk::Builder().SetDevicePath("/dev/sda1").Build();
  EXPECT_TRUE(
      DiskMountManager::GetInstance()->AddDiskForTest(std::move(disk_sda1)));

  MountError error_code = MountError::kUnknownError;
  // Unmount sdB instead of sdA.
  DiskMountManager::GetInstance()->UnmountDeviceRecursively(
      "/dev/sdb",
      base::BindOnce(&SaveUnmountResult, base::Unretained(&error_code),
                     run_loop.QuitClosure()));
  run_loop.Run();

  EXPECT_EQ(0, fake_cros_disks_client_->unmount_call_count());
  EXPECT_EQ(MountError::kInvalidDevicePath, error_code);
}

void SetUnmountError(FakeCrosDisksClient* client, MountError error_code) {
  client->MakeUnmountFail(error_code);
}

TEST_F(DiskMountManagerTest, UnmountDeviceRecursively_FailFirst) {
  base::RunLoop run_loop;

  auto disk_sda =
      Disk::Builder().SetDevicePath("/dev/sda").SetIsParent(true).Build();
  EXPECT_TRUE(
      DiskMountManager::GetInstance()->AddDiskForTest(std::move(disk_sda)));

  auto disk_sda1 = Disk::Builder()
                       .SetDevicePath("/dev/sda1")
                       .SetMountPath("/mount/path1")
                       .Build();
  EXPECT_TRUE(
      DiskMountManager::GetInstance()->AddDiskForTest(std::move(disk_sda1)));

  auto disk_sda2 = Disk::Builder()
                       .SetDevicePath("/dev/sda2")
                       .SetMountPath("/mount/path2")
                       .Build();
  EXPECT_TRUE(
      DiskMountManager::GetInstance()->AddDiskForTest(std::move(disk_sda2)));

  // Fail the first unmount, but make the second succeed.
  fake_cros_disks_client_->MakeUnmountFail(
      MountError::kInsufficientPermissions);
  fake_cros_disks_client_->set_unmount_listener(base::BindRepeating(
      &SetUnmountError, base::Unretained(fake_cros_disks_client_),
      MountError::kSuccess));

  MountError error_code = MountError::kUnknownError;
  DiskMountManager::GetInstance()->UnmountDeviceRecursively(
      "/dev/sda",
      base::BindOnce(&SaveUnmountResult, base::Unretained(&error_code),
                     run_loop.QuitClosure()));
  run_loop.Run();

  EXPECT_EQ(2, fake_cros_disks_client_->unmount_call_count());
  EXPECT_EQ(MountError::kInsufficientPermissions, error_code);
}

TEST_F(DiskMountManagerTest, UnmountDeviceRecursively_AlreadyUnmounted) {
  base::RunLoop run_loop;

  auto disk_sda =
      Disk::Builder().SetDevicePath("/dev/sda").SetIsParent(true).Build();
  EXPECT_TRUE(
      DiskMountManager::GetInstance()->AddDiskForTest(std::move(disk_sda)));

  auto disk_sda1 = Disk::Builder()
                       .SetDevicePath("/dev/sda1")
                       .SetMountPath("/mount/path1")
                       .Build();
  EXPECT_TRUE(
      DiskMountManager::GetInstance()->AddDiskForTest(std::move(disk_sda1)));

  // Fail the unmount with "not mounted".
  fake_cros_disks_client_->MakeUnmountFail(MountError::kPathNotMounted);

  MountError error_code = MountError::kUnknownError;
  DiskMountManager::GetInstance()->UnmountDeviceRecursively(
      "/dev/sda",
      base::BindOnce(&SaveUnmountResult, base::Unretained(&error_code),
                     run_loop.QuitClosure()));
  run_loop.Run();

  EXPECT_EQ(1, fake_cros_disks_client_->unmount_call_count());
  EXPECT_EQ(MountError::kSuccess, error_code);
}

TEST_F(DiskMountManagerTest, Mount_MountUnsetsFirstMount) {
  DiskMountManager* manager = DiskMountManager::GetInstance();
  const Disk* device1 = manager->FindDiskBySourcePath(kDevice1SourcePath);
  EXPECT_TRUE(device1->is_first_mount());

  fake_cros_disks_client_->NotifyMountCompleted(
      MountError::kSuccess, kDevice1SourcePath, MountType::kDevice,
      kDevice1MountPath);

  EXPECT_FALSE(device1->is_first_mount());
}

TEST_F(DiskMountManagerTest, Mount_RemountPreservesFirstMount) {
  DiskMountManager* manager = DiskMountManager::GetInstance();
  EXPECT_TRUE(
      manager->FindDiskBySourcePath(kDevice1SourcePath)->is_first_mount());

  std::unique_ptr<dbus::Response> response = dbus::Response::CreateEmpty();
  DiskInfo disk_info(kDevice1SourcePath, response.get());
  fake_cros_disks_client_->set_next_get_device_properties_disk_info(&disk_info);
  fake_cros_disks_client_->NotifyMountEvent(MountEventType::kDiskAdded,
                                            kDevice1SourcePath);

  // NotifyMountEvent indirectly invokes CrosDisksClient::GetDeviceProperties,
  // which responds asynchronously.
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(1, fake_cros_disks_client_->get_device_properties_success_count());
  EXPECT_TRUE(
      manager->FindDiskBySourcePath(kDevice1SourcePath)->is_first_mount());

  fake_cros_disks_client_->NotifyMountCompleted(
      MountError::kSuccess, kDevice1SourcePath, MountType::kDevice,
      kDevice1MountPath);
  EXPECT_FALSE(
      manager->FindDiskBySourcePath(kDevice1SourcePath)->is_first_mount());

  fake_cros_disks_client_->NotifyMountEvent(MountEventType::kDiskAdded,
                                            kDevice1SourcePath);

  // NotifyMountEvent indirectly invokes CrosDisksClient::GetDeviceProperties,
  // which responds asynchronously.
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(2, fake_cros_disks_client_->get_device_properties_success_count());
  EXPECT_FALSE(
      manager->FindDiskBySourcePath(kDevice1SourcePath)->is_first_mount());
}

TEST_F(DiskMountManagerTest, Mount_DefersDuringGetDeviceProperties) {
  DiskMountManager* manager = DiskMountManager::GetInstance();

  // When a disk is added, we call GetDeviceProperties() before updating our
  // DiskMap. If the disk is mounted before this asynchronous call returns, we
  // defer sending the mount event so that clients are able to access the disk
  // information immediately.

  fake_cros_disks_client_->NotifyMountEvent(MountEventType::kDiskRemoved,
                                            kDevice1SourcePath);
  EXPECT_EQ(nullptr, manager->FindDiskBySourcePath(kDevice1SourcePath));

  std::unique_ptr<dbus::Response> response = dbus::Response::CreateEmpty();
  DiskInfo disk_info(kDevice1SourcePath, response.get());
  fake_cros_disks_client_->set_next_get_device_properties_disk_info(&disk_info);
  fake_cros_disks_client_->NotifyMountEvent(MountEventType::kDiskAdded,
                                            kDevice1SourcePath);
  fake_cros_disks_client_->NotifyMountCompleted(
      MountError::kSuccess, kDevice1SourcePath, MountType::kDevice,
      kDevice1MountPath);

  // The mount event will not have fired yet as we are still waiting for
  // GetDeviceProperties() to return.
  EXPECT_EQ(
      0u, observer_->CountMountEvents(DiskMountManager::MOUNTING,
                                      MountError::kSuccess, kDevice1MountPath));
  base::RunLoop().RunUntilIdle();

  // We have fired 3 events: disk removed -> disk added -> mounting
  const MountEvent& mount_event = observer_->GetMountEvent(2);
  EXPECT_EQ(DiskMountManager::MOUNTING, mount_event.event);
  EXPECT_EQ(kDevice1MountPath, mount_event.mount_point.mount_path);
  // The test OnMountEvent() finds the matching disk when it is called.
  EXPECT_NE(nullptr, mount_event.disk);
}

}  // namespace

}  // namespace disks
}  // namespace ash

redpajama_set_name: RedPajamaGithub


There are hundreds of methods for doing panel lines. The best method is what works best for you. What you are comfortable with, what you are used to using. It's a good idea to try out several techniques before settling down on your favorite technique. In the past, I have used gundam markers, very fine mechanical pencils, and very fine technical pens; but I have settled on the enamel (oil) wash method as my primary panel line method. It's very cost efficient in comparison to some of the other techniques. But there are very specific steps that need to be taken for this process to be successful. Again, there are a hundred different ways to panel line, this just one method – and it is VERY specific.
The first step is to gloss coat the surface. This protects the layer of paint and provides a smooth surface for the wash solution. Pledge with future shine (Future floor polish) and spray cans of Mr Top Coat Gloss are acrylic based clears. They will take a full week to fully cure. Mr Super Clear in bottles or spray cans or other brands of lacquer clear gloss is stronger and will dry/cure much faster than acrylics. When spraying clears, and deciding on acrylic and lacquers, take note of the paint that the clear is going over. Some paints don't play well with another; but proper airbrushing/spraying technique will avert most problems.
Now with gloss and flats discussed, we can move on to the actual panel lining. The first step here is to create the wash solution. I'm using a small dot of paint, again, a reference to the cost effectiveness. The small dot is thinned with rosonol lighter fluid. Too much lighter fluid will cause the paint to break down, so if that happens, just add a little more paint and mix the solution. Ideally, you want a solution that looks like ink. A little practice and you will the comfortable with the consistency. The wash solution will evaporate as you work, take note if the wash solution becomes too think, just add more lighter fluid to revive the solution.
In the following video, I demonstrate why the wash should be applied over a gloss surface as opposed to a flat surface. After applying the wash solution, I generally let the part sit and dry before coming back and doing the clean up.
Again, it is important to note that the clear gloss is either acrylic or lacquer and fully cured and the wash solution is enamel. The real trick to this technique is the use of the different paint type properties. But I do recommend experimenting a little yourself to see how the technique works; this will teach you why the technique works. With a little practice, the panel lining process is one of the quickest and easiest steps in model building.
For most Gundam kits, paint is an option. You can easily get away with spraying some gloss coats over the bare plastic and do the above panel lines. Then if you want a final flat coat finish, just spray a flat coat over the parts and this will give you a kit that looks painted, but isn't in the traditional sense. A little discussion in cutting technique is highlighted with the following videos. For these examples, I'm using spray cans, take note of the technique for spraying the parts while using the spray cans. The same technique should be used when spraying colors out of spray cans.
Here's a video for spraying a flat coat.
A comparison between the flat coat and a gloss. Notice the tonal differences.
Another use of the enamel wash is to bring out details. For resin figure kits, I have this sword piece clear coated with a lacquer gloss to protect the underlying paint. With my wash solution, I just apply it over the surface and the wash immediately brings out some of the details.
And here's the clean up.
and it works great, we use a Tamiya primer, let cure for an hour, with Tamiya Acrylic paint and the custom thinner, let cure for 4 to 8 hours, and you can't even scratch it off with your finger nail.
Any comments on customize primer? Or any tips of how to make clear coat stick with no primer.
Reason why I'm asking is I'm building a series of the Formula type Gundams (F90, RXF91, etc…) that was released back in the 1990s that feature "System Injection" and I want to take advantage of that feature, so I don't want to prime over it and have to mask everything and paint it over.
It is because the little bottle you speak of is just acrylic topcoat… unfortunately while acrylics are less harmful than Lacqure paints they don't disolve that top layer of plastic which is part of why lacqure sprays work so well.
is it possible to spray matte coat over the gloss coat after doing enamel wash?
and will it affect the enamel wash?
I always spray a matte coat over the parts after doing the enamel wash. Just let the enamel wash dry for a few days, then spray the matte coat..
I usually apply a gloss coat as a protective measure, but so long as the mr color paint layer has fully cured, the lighter fluid shouldn't affect it. I recommend testing this out on a test part. But since they are different paint types, the lighter fluid really shouldn't have an ill affect on the paint.


Uprooting of trees for any development activity has to "kept to the minimum", Union environment secretary CK Mishra said on Wednesday.
No trees would be cut in the process of redevelopment of seven colonies in South Delhi, Union Minister Hardeep Singh Puri said on Thursday.
Union home minister Rajnath Singh will inaugurate the regional hub complex of the elite anti-terror force National Security Guard (NSG) at Ibrahimpatnam.
The Asian Track Cycling Championship that is scheduled to start in 10 days at the at the Indira Gandhi Indoor Stadium has been thrown into jeopardy after the wooden velodrome has been damaged due to leakage in the roof after heavy downpour lashed national capital on Thursday.
A special court has accepted CBI's closure report in an alleged corruption case relating to the 2010 Commonwealth Games against several officials of the Central Public Works Department (CPWD), Delhi Development Authority (DDA), a private firm and its director.
With terror threat looming large, there has been a heightened level of security preparedness at Yamuna floodplains where the World Culture Festival 2016, being organised by Sri Sri Ravi Shankar's Art of Living, is due to kick off from 11 March.
The house of UPA Chairperson Sonia Gandhi has ACs and fancy halogen lights--and that really doesn't come cheap.
Informed sources told IANS that Modi, who will be India's 14th prime minister, may shift to the new address soon after taking oath or the following day.
A sprawling bungalow on Motilal Nehru Place will be the abode of Prime Minister Manmohan Singh once he retires after the Lok Sabha elections.


text:
Q: print df.head doesn't print entire header with all the column names Am printing the head of an excel file, but while i use the line print (df.head()) in the terminal and also in my Sublime IDE i got only first column and last one.
pls take a look below.

I see those ... and i would like to see al of the info from those columns etc. Can someone pls advise how i can do this?

redpajama_set_name: RedPajamaStackExchange


text:
Strauss: Ashes captain?
Vic Marks
Test victories have clouded Flintoff's role when he returns, says Vic Marks
Sat 12 Aug 2006 20.02 EDT First published on Sat 12 Aug 2006 20.02 EDT
England's selectors have been around long enough not to be too smug. But they could surely allow themselves a quiet smile after the Headingley Test.
The series was secured, their selections vindicated. It rarely works out quite so smoothly. Chris Read, catapulted back into the side, scored 93 runs and kept with a panache that was rarely a characteristic of his predecessor.
Sajid Mahmood took six wickets, five more than Steve Harmison, whose one for 204 in the match was an ugly contrast to his 11 for 76 at Old Trafford, and four more than Matthew Hoggard. Sajid was flattered by his second innings figures but sticking with him was clearly justified. And there is the continuing progress of Monty Panesar, who is now inked into the team. Even the doubting Duncan Fletcher has used superlatives about him.
So, as the Oval Test approaches on Thursday, everything is hunky-dory. Expect the same England team to play there and, such is the buoyancy of the side, reflected in their brilliant out-cricket at Leeds, expect the same result.
However, there is some tricky juggling to be done by the selectors down the line. Andrew Flintoff, Ashley Giles and James Anderson have been named in England's initial 30 for the Champions Trophy. This, by no means, guarantees their presence in India in October, but indicates they could be fit in the foreseeable future - and long before Michael Vaughan and Simon Jones. By 7 September, the selectors must whittle that 30 down to 14. Then comes the dilemma of what to do when a few of the Ashes heroes return.
Every side in the world craves a Flintoff. So no problems there. But should he resume as captain? When Andrew Strauss was leading England's one-day side to defeat so regularly against Sri Lanka the answer was an unequivocal 'Yes'. Now that Strauss has overseen two emphatic Test victories over Pakistan and hit two smooth centuries, the answer becomes 'Not necessarily'.
In Australia, so much will depend on Flintoff's fitness. Provided we are as certain as we can be that he is fully recovered, then Flintoff should resume the captaincy. That was the original, logical plan six weeks ago and it is only confusing to sway from it after every England defeat or victory. If Flintoff's fitness is a problem, England are now in the fortunate position of knowing that they have a very able deputy in Strauss.
Flintoff was characteristically upbeat yesterday about his prospects. 'Yes,' he said, he would be fit for Brisbane. 'My rehab is meant to finish mid-October,' he told Sky Sports' Cricket AM, 'and the specialist said it would be a 12-week course.'
Flintoff had an operation on his knee two weeks ago and hobbled off his crutches on Monday. 'Since then,' he said, 'I've been down in Devon at Bovey Castle doing a bit of cycling, a bit of swimming with the physio and, touch wood, it's going all right.'
There are other variables. What if Ashley Giles is fit? Who should be the left-arm spinner in the team in Brisbane? I would judge Panesar to be the better bowler at the moment, which does not necessarily mean that he should play. England agonise still over who bats at eight. In an Ashes series the current occupant, Mahmood, is hardly a reassuring figure. If Anderson were reckoned to be a better bowler than Mahmood, then Hoggard might bat at eight. Aaargh.
The upshot is likely to be this: if England opt for their five-man attack in Brisbane, then a fit Giles will probably be included. If they go for the four-man attack that has served them so well in the past two Tests against Pakistan, they are likely to include Panesar alongside Harmison, Hoggard and Flintoff.

redpajama_set_name: RedPajamaCommonCrawl


text:
Third anonymous grand juror in Breonna Taylor case: 'No additional charges were allowed'
LOUISVILLE, Ky. — A third anonymous grand juror in the Breonna Taylor case has come forward to promote "truth and transparency" about the process, according to a news release from attorney Kevin Glogower's office.
The third grand juror "firmly supports the fact that no additional charges were allowed at the conclusion of their service," the news release said.
The original two anonymous grand jurors said earlier this week that more Louisville Metro Police officers should face criminal charges for their role in the attempted drug search on March 13 that ended in Taylor's death.
The grand jury brought three counts of felony wanton endangerment against former Detective Brett Hankison, one of three LMPD officers to return fire at Taylor's apartment after her boyfriend, Kenneth Walker, fired what he later called a "warning" shot at officers and struck an officer in the thigh.
No officers face charges for Taylor's death.
The 26-year-old, who was unarmed, was shot six times by officers returning fire and died in her hallway.
Earlier:2 grand jurors say charges should have been filed against other cops
Hankison is instead charged in connection with bullets that traveled into a neighboring apartment occupied by three people during the shooting.
One of the anonymous grand jurors — the second to come forward — said there was "an uproar" when the special prosecutor said the only charges for consideration were the three counts of wanton endangerment.
"Do I feel that justice was done?" grand juror No. 1 said Wednesday. "No. I feel that there was quite a bit more that could have been done or should have been presented for us to deliberate on."
Hankison has pleaded not guilty.
Sgt. Jonathan Mattingly, the officer who was shot, and Detective Myles Cosgrove fired six and 16 shots respectively. Hankison fired 10.
Top headlines:Mattingly files counterclaim against Taylor's boyfriend for assault
Attorney General Daniel Cameron, whose office presented grand jurors with the charges, has said Mattingly and Cosgrove didn't face criminal charges because they were acting in self defense, under state law.
The first two jurors have said they felt the need to come forward after Cameron publicly characterized the grand jury as agreeing with that justification.
"This was all Cameron," grand juror No. 2 told "CBS Morning News" on Wednesday. "This was up to him. We didn't get a choice in that at all. So, I was livid. By the time I heard what he was saying, everything that came out of his mouth, I was saying 'liar.' Because we didn't agree to anything."
Darcy Costello: 502-582-4834; dcostello@courier-journal.com; Twitter: @dctello.


text:
Q: Multiple MSQL tables and foreign keys I wanted to know what is the better approach (performance wise) when working with Yii and mysql tables:

*

*1 table with ALL the columns

*multiple tables (distribute the columns across them) with foreign keys relations

basically, what i'm asking is:
Is there any performance degradation when a single table has many relations since Yii will fetch all the corresponding tables into the object when you do a query?
A more general question is: what is good practice and smarter approach when dealing with 1 vs multiple tables and foreign keys (again performance..)

A: If performance was the only criteria in your design strategy, you would choose 1. A very bad choice, that would give you a lot of problems in a later stage of your problems.
Databases should be designed with normalization in mind, so without any doubt, you must choose option 2!
Update
Example of normalized and not normalized: suppose we have a table with dvd rentals, and want to keep track of who rented the dvd:
flat table:
CREATE TABLE DVD (
  DvdId INT NOT NULL AUTO_INCREMENT,
  DvdTitle varchar(64),
  Rental1 varchar(64),
  Rental2 varchar(64),
  Rental3 varchar(64),
  Primary Key(DvdId)
);

There is room for 3 rental entries, after that there is trouble. Beside that, when there is a extra field needed for the phone number of the customers, 3 extra fields must be made.
First step normalization:
CREATE TABLE DVD (
  DvdId INT NOT NULL AUTO_INCREMENT,
  DvdTitle varchar(64),
  Primary Key(DvdId)
);

CREATE TABLE Rentals (
  RentalId INT NOT NULL AUTO_INCREMENT,
  DvdId INT NOT NULL,
  CustomerName varchar(64),
  RentalDate DateTime,
  Primary Key(RentalId)
);

This is better, but still not fully normalized. What if a customer rents the same dvd twice. Then there would be a double customer entry in the rentals table.
Final:
CREATE TABLE DVD (
  DvdId INT NOT NULL AUTO_INCREMENT,
  DvdTitle varchar(64),
  Primary Key(DvdId)
);

CREATE TABLE Rentals (
  RentalId INT NOT NULL AUTO_INCREMENT,
  DvdId INT NOT NULL,
  CustomerId INT NOT NULL,
  RentalDate DateTime,
  Primary Key(RentalId)
);

CREATE TABLE Customers (
  CustomerId INT NOT NULL AUTO_INCREMENT,
  CustomerName varchar(64),
  Primary Key(CustomerId)
);

A: The better option is to design your database properly complying normalisation rules.
Here are some quick links about them:
http://databases.about.com/od/specificproducts/a/normalization.htm
http://moryadesigns.wordpress.com/2009/09/08/rules-of-data-normalization-sql/
1NF Eliminate Repeating Groups – Make a separate table for each set of
related attributes, and give each table a primary key.
2NF Eliminate Redundant Data – If an attribute depends on only part of a
multi-valued key, remove it to a separate table.
3NF Eliminate Columns Not Dependent On Key – If attributes do not
contribute to a description of the key, remove them to a separate table.
BCNF    Boyce-Codd Normal Form – If there are non-trivial dependencies between candidate key
attributes, separate them out into distinct tables.
4NF Isolate Independent Multiple Relationships – No table may contain
two or more 1:n or n:m relationships that are not directly related.
5NF Isolate Semantically Related Multiple Relationships – There may be
practical constrains on information that justify separating logically related many-to-many
relationships.
ONF Optimal Normal Form – a model limited to only simple (elemental)
facts, as expressed in Object Role Model notation.
DKNF    Domain-Key Normal Form – a model free from all modification anomalies.
Yii uses extensive lazy-loading and the will not load related elements until you ask for them and use them.


With experience working with a variety of Honda's, including Integra DC2/DC5, Civic EG6/EK9/EP3/FN2/FK2, Accord, S2000, Legacy, Concerto, Jazz etc, we have a vast knowledge of the mark.
We have worked on many of the different engines, including B-series B16, B18, D16, K-series K20, F-series F20, H-series, H22.
We have carried out engine rebuilds and tuning, catering for naturally aspirated, to supercharged and turbocharged modifications.
Its not only the Engine and gearbox's, we can also carry out suspension modifications and adjustments, as well as wiring modifications for aftermarket ECU's, intercooler manufacture & fitting and charge pipework and exhausts.


Small Business and Family Enterprise Ombudsman Kate Carnell has welcomed the new $5 billion Future Drought Fund.
The fund will help farmers prepare for future droughts in Australia.
It was announced at the national drought summit in Canberra last month. The summit brought together state and federal governments, weather experts, farm lobbyists and charities.
In particular, Ms Carnell praised the fund's focus on expanded mental-health services, especially for smaller-scale farmers.
"Running a farm or a small business during an extensive period of drought is one of the toughest situations to be in as a business owner," she said.
"This new fund will deliver crucial water-infrastructure and drought-resilience projects. But it will also provide more access for more people to mental-health services and support.
"Never underestimate the importance of mental health. We know that one in five Australians will experience a mental health issue at some stage in their life.
"For farmers, many experience high levels of stress for a prolonged period of time. (Mental-health charity) Beyond Blue research shows male farmers die by suicide at rates significantly higher than for the general population and non-farming rural males.
The $15.5 million funding boost for mental-health services will go towards early intervention and community wellbeing services.
The government has also pledged $30 million to key charities to provide support to at least 10,000 households facing hardship.


text:
Paul Singer drops Madeleine Albright's firm amid Argentina conflict
By Michelle Celarier
October 15, 2014 | 12:54pm
Madeleine Albright Getty Images
Madeleine Albright backtracks on 'go to hell' warning
Hillary's henchwomen have a dirty secret
Hillary's quest to be politics' greatest globetrotter
Power couple linked to Madeleine Albright accused of fraud
Former Secretary of State Madeleine Albright and hedge fund billionaire Paul Singer have parted ways.
Albright, who has parlayed her extensive diplomatic ties into both a consulting firm and a hedge fund that invests in emerging markets, was hired by Singer to help in his rancorous debt dispute with Argentina, as reported exclusively by The Post.
Albright, Singer hoped, would be able to bring Argentina officials to the bargaining table.
The assignment backfired when Argentina President Cristina Kirchner publicly attacked Albright's firm for meddling in the country's affairs.
Albright's firm said Singer had a five-point plan to destabilize the Argentine government if it didn't sit down and negotiate with the holdout creditors led by Singer, who won a court battle to get $1.6 billion, Kirchner claimed.
Albright's partner told officials that holdouts would hire journalists in Argentina and would delay negotiations until a new government linked to Singer's interests was in power following the 2015 presidential election, the fiery Argentine president said.
Albright's firm, the Albright Stonebridge Group, denied delivering such a plan. But Argentina's rebuff led Singer and Albright to end their association—at least for now.
Singer's Elliott Management retained the 77-year old diplomat on a "short-term assignment" that was terminated because "the Argentines don't appear don't appear to be interested" in what she is saying, a source close to the billionaire investor's firm said.
Elliott declined comment on its association with Albright. The diplomat didn't return calls for comment.
Working for Singer in his battle with Argentina isn't the only high-profile controversy that Albright has become embroiled in — and appears to be shying away from.
Albright, long a paid booster for Herbalife, the diet shake seller under investigation for pyramid concerns, hasn't appeared at one of the company's massive pep rallies, called "Extravaganzas," in over a year.
Albright had been a regular at such events in the past.
In April, following news of a federal criminal probe into whether the Los Angeles company operates as a pyramid scheme, Albright huddled with Herbalife CEO Michael Johnson.
Albright had expressed concern that her reputation could be sullied if she was boostering a company that was found to be operating illegally, sources said.
Herbalife has consistently denied it operates as a pyramid scheme and hasn't been accused by any regulator or law enforcement agency of any wrongdoing.
Hedge fund investor Bill Ackman has been pressing a $2 billion short against the company — alleging the company is a fraud.
The last appearance by Albright at an Extravaganza was the September 2013 event in Cologne, Germany. There, she danced on stage wearing red shoes.
Given Albright's Czech roots, she was notably missing at this September's event in Prague.
Albright's long-running relationship with Herbalife has been lucrative — earning her firm an estimated $10 million over the past six years, sources said.
Albright has certainly been instrumental in opening doors for Herbalife around the world — notably in China.
Despite the somewhat lower profile, Albright is still working for Herbalife, the company said.
Albright's firm did not return calls for comment.
Filed under argentina , hedge funds , madeleine albright , paul singer
HBO to launch Netflix-like streaming service next year
John Crudele
NYC's 'Silver' lining: Larry Silverstein dishes on tech, WeWork
Fine food buyers in stockpiling frenzy before Trump tariffs start
Jonathon Trugman
Competition is needed to improve health insurance options for Americans
Trending Now in Business
Saudi prince may be responsible for stealing Jeff Bezos' nudes
Shaving startup Harry's tries to soothe worries about razor deal
Uber lets drivers hike fares at California airports


The Supreme Judicial Court recently concluded that where a criminal defendant challenges the legality of a traffic stop for speeding, the police must articulate specific facts that support the conclusion that the defendant's speed was greater that reasonable.
In the 2016 case of Commonwealth v. Teixeira-Furtado, the police stopped a car for speeding. At about the same time, the defendant passenger jumped out of the passenger seat and ran away. When he was caught soon afterwards, he was found to be in possession of a firearm.
Defense counsel moved to suppress the firearm as evidence at trial, arguing that there was no lawful basis for the traffic stop to begin with. The police officers testified at the suppression hearing, and their testimony in support of the traffic stop was that the vehicle carrying the defendant had been travelling at a speed greater than reasonable. The motion judge ordered the firearm suppressed. On appeal, the Appeals Court held that the stop was constitutionally permissible, and overturned the motion judge's allowance of the suppression motion.
The Court set forth examples of testimony that would have supported the officers' conclusion as to speeding, such as an estimate of the vehicle's actual speed, its speed compared to other traffic at the time, any radar or LIDAR readings, or the posted speed limit for that particular location. Other helpful testimony would include the traffic on the road, the use being made of the road by pedestrians or others, or other relevant safety considerations, suggested the SJC.
The legal basis for the SJC's decision is that when the police stop a car for speeding, they have effected a constitutional "seizure." Pursuant to long-standing constitutional precedent, such a seizure must be based on – and supported by – "articulable facts sufficient to warrant a reasonably prudent person in the police officer's position in forming that conclusion." Because the officers did not testify as to facts supporting their conclusion that the vehicle was travelling at an unreasonable speed, their seizure of the defendant was unconstitutional.
The case reminds the criminal defense practitioner that rather than accepting at face value the conclusions drawn by police officers, it is sometimes helpful to delve deeper, into the actual basis and underpinning of what might turn out to have been merely a hunch. As the case law makes clear, in this context, "[a] hunch will not suffice." Commonwealth v. Wren, 391 Mass. 705, 707 (1984).