blob: a02b6f1da592708117180aaf49dd27fe679713f7 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/chromeos/guest_os/guest_os_share_path.h"
#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/run_loop.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/chromeos/crostini/crostini_manager.h"
#include "chrome/browser/chromeos/crostini/crostini_pref_names.h"
#include "chrome/browser/chromeos/crostini/crostini_util.h"
#include "chrome/browser/chromeos/file_manager/fake_disk_mount_manager.h"
#include "chrome/browser/chromeos/file_manager/path_util.h"
#include "chrome/browser/chromeos/file_manager/volume_manager.h"
#include "chrome/browser/chromeos/file_manager/volume_manager_factory.h"
#include "chrome/browser/chromeos/file_system_provider/service_factory.h"
#include "chrome/browser/chromeos/guest_os/guest_os_pref_names.h"
#include "chrome/browser/chromeos/login/users/fake_chrome_user_manager.h"
#include "chrome/common/chrome_features.h"
#include "chrome/test/base/testing_profile.h"
#include "chromeos/constants/chromeos_features.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/fake_cicerone_client.h"
#include "chromeos/dbus/fake_concierge_client.h"
#include "chromeos/dbus/fake_seneschal_client.h"
#include "chromeos/dbus/seneschal/seneschal_service.pb.h"
#include "chromeos/disks/disk_mount_manager.h"
#include "components/account_id/account_id.h"
#include "components/drive/drive_pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/user_manager/scoped_user_manager.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "storage/browser/fileapi/external_mount_points.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
// Creates a new VolumeManager for tests.
// By default, VolumeManager KeyedService is null for testing.
std::unique_ptr<KeyedService> BuildVolumeManager(
content::BrowserContext* context) {
return std::make_unique<file_manager::VolumeManager>(
Profile::FromBrowserContext(context),
nullptr /* drive_integration_service */,
nullptr /* power_manager_client */,
chromeos::disks::DiskMountManager::GetInstance(),
nullptr /* file_system_provider_service */,
file_manager::VolumeManager::GetMtpStorageInfoCallback());
}
} // namespace
namespace guest_os {
class GuestOsSharePathTest : public testing::Test {
public:
const bool PERSIST_YES = true;
const bool PERSIST_NO = false;
enum class Persist { NO, YES };
enum class SeneschalClientCalled { NO, YES };
enum class Success { NO, YES };
void SharePathCallback(
const std::string& expected_vm_name,
Persist expected_persist,
SeneschalClientCalled expected_seneschal_client_called,
const vm_tools::seneschal::SharePathRequest::StorageLocation*
expected_seneschal_storage_location,
const std::string& expected_seneschal_path,
Success expected_success,
const std::string& expected_failure_reason,
const base::FilePath& container_path,
bool success,
const std::string& failure_reason) {
const base::DictionaryValue* prefs =
profile()->GetPrefs()->GetDictionary(prefs::kGuestOSPathsSharedToVms);
EXPECT_TRUE(prefs->HasKey(shared_path_.value()));
EXPECT_EQ(prefs->FindKey(shared_path_.value())->GetList().size(), 1U);
EXPECT_EQ(prefs->FindKey(shared_path_.value())->GetList()[0].GetString(),
crostini::kCrostiniDefaultVmName);
if (expected_persist == Persist::YES) {
EXPECT_EQ(prefs->size(), 2U);
EXPECT_TRUE(prefs->HasKey(share_path_.value()));
EXPECT_EQ(prefs->FindKey(share_path_.value())->GetList().size(), 1U);
EXPECT_EQ(prefs->FindKey(share_path_.value())->GetList()[0].GetString(),
expected_vm_name);
} else {
EXPECT_EQ(prefs->size(), 1U);
}
EXPECT_EQ(fake_seneschal_client_->share_path_called(),
expected_seneschal_client_called == SeneschalClientCalled::YES);
if (expected_seneschal_client_called == SeneschalClientCalled::YES) {
EXPECT_EQ(
fake_seneschal_client_->last_share_path_request().storage_location(),
*expected_seneschal_storage_location);
EXPECT_EQ(fake_seneschal_client_->last_share_path_request()
.shared_path()
.path(),
expected_seneschal_path);
}
EXPECT_EQ(success, expected_success == Success::YES);
EXPECT_EQ(failure_reason, expected_failure_reason);
run_loop()->Quit();
}
void SeneschalSharePathCallback(
const std::string& expected_operation,
const base::FilePath& expected_path,
const std::string& expected_vm_name,
Persist expected_persist,
SeneschalClientCalled expected_seneschal_client_called,
const vm_tools::seneschal::SharePathRequest::StorageLocation*
expected_seneschal_storage_location,
const std::string& expected_seneschal_path,
Success expected_success,
const std::string& expected_failure_reason,
const std::string& operation,
const base::FilePath& cros_path,
const base::FilePath& container_path,
bool success,
const std::string& failure_reason) {
EXPECT_EQ(expected_operation, operation);
EXPECT_EQ(expected_path, cros_path);
SharePathCallback(
expected_vm_name, expected_persist, expected_seneschal_client_called,
expected_seneschal_storage_location, expected_seneschal_path,
expected_success, expected_failure_reason, container_path, success,
failure_reason);
}
void SharePersistedPathsCallback(bool success,
const std::string& failure_reason) {
EXPECT_TRUE(success);
EXPECT_EQ(profile()
->GetPrefs()
->GetDictionary(prefs::kGuestOSPathsSharedToVms)
->size(),
2U);
run_loop()->Quit();
}
void SharePathErrorVmNotRunningCallback(base::OnceClosure closure,
bool success,
std::string failure_reason) {
EXPECT_FALSE(fake_seneschal_client_->share_path_called());
EXPECT_EQ(success, false);
EXPECT_EQ(failure_reason, "Cannot share, VM not running");
std::move(closure).Run();
}
void UnsharePathCallback(
const base::FilePath& path,
Persist expected_persist,
SeneschalClientCalled expected_seneschal_client_called,
const std::string& expected_seneschal_path,
Success expected_success,
const std::string& expected_failure_reason,
bool success,
const std::string& failure_reason) {
const base::DictionaryValue* prefs =
profile()->GetPrefs()->GetDictionary(prefs::kGuestOSPathsSharedToVms);
if (expected_persist == Persist::YES) {
EXPECT_TRUE(prefs->HasKey(path.value()));
} else {
EXPECT_FALSE(prefs->HasKey(path.value()));
}
EXPECT_EQ(fake_seneschal_client_->unshare_path_called(),
expected_seneschal_client_called == SeneschalClientCalled::YES);
if (expected_seneschal_client_called == SeneschalClientCalled::YES) {
EXPECT_EQ(fake_seneschal_client_->last_unshare_path_request().path(),
expected_seneschal_path);
}
EXPECT_EQ(success, expected_success == Success::YES);
EXPECT_EQ(failure_reason, expected_failure_reason);
run_loop()->Quit();
}
void SeneschalUnsharePathCallback(
const std::string expected_operation,
const base::FilePath& expected_path,
Persist expected_persist,
SeneschalClientCalled expected_seneschal_client_called,
const std::string& expected_seneschal_path,
Success expected_success,
const std::string& expected_failure_reason,
const std::string& operation,
const base::FilePath& cros_path,
const base::FilePath& container_path,
bool success,
const std::string& failure_reason) {
EXPECT_EQ(expected_operation, operation);
EXPECT_EQ(expected_path, cros_path);
UnsharePathCallback(cros_path, expected_persist,
expected_seneschal_client_called,
expected_seneschal_path, expected_success,
expected_failure_reason, success, failure_reason);
}
GuestOsSharePathTest() {
chromeos::DBusThreadManager::Initialize();
fake_concierge_client_ = static_cast<chromeos::FakeConciergeClient*>(
chromeos::DBusThreadManager::Get()->GetConciergeClient());
fake_seneschal_client_ = static_cast<chromeos::FakeSeneschalClient*>(
chromeos::DBusThreadManager::Get()->GetSeneschalClient());
}
~GuestOsSharePathTest() override { chromeos::DBusThreadManager::Shutdown(); }
void SetUpVolume() {
// Setup Downloads and path to share, which depend on MyFilesVolume flag,
// thus can't be on SetUp.
chromeos::disks::DiskMountManager::InitializeForTesting(
new file_manager::FakeDiskMountManager);
file_manager::VolumeManagerFactory::GetInstance()->SetTestingFactory(
profile(), base::BindRepeating(&BuildVolumeManager));
root_ = file_manager::util::GetMyFilesFolderForProfile(profile());
file_manager::VolumeManager::Get(profile())
->RegisterDownloadsDirectoryForTesting(root_);
share_path_ = root_.Append("path-to-share");
shared_path_ = root_.Append("already-shared");
ASSERT_TRUE(base::CreateDirectory(shared_path_));
DictionaryPrefUpdate update(profile()->GetPrefs(),
prefs::kGuestOSPathsSharedToVms);
base::DictionaryValue* shared_paths = update.Get();
base::Value termina(base::Value::Type::LIST);
termina.GetList().emplace_back(
base::Value(crostini::kCrostiniDefaultVmName));
shared_paths->SetKey(shared_path_.value(), std::move(termina));
volume_downloads_ = file_manager::Volume::CreateForDownloads(root_);
guest_os_share_path_->RegisterSharedPath(crostini::kCrostiniDefaultVmName,
shared_path_);
// Run threads now to allow watcher for shared_path_ to start.
thread_bundle_.RunUntilIdle();
}
void SetUp() override {
run_loop_ = std::make_unique<base::RunLoop>();
profile_ = std::make_unique<TestingProfile>();
guest_os_share_path_ = GuestOsSharePath::GetForProfile(profile());
// Setup for DriveFS.
scoped_user_manager_ = std::make_unique<user_manager::ScopedUserManager>(
std::make_unique<chromeos::FakeChromeUserManager>());
account_id_ = AccountId::FromUserEmailGaiaId(
profile()->GetProfileUserName(), "12345");
GetFakeUserManager()->AddUser(account_id_);
profile()->GetPrefs()->SetString(drive::prefs::kDriveFsProfileSalt, "a");
drivefs_ =
base::FilePath("/media/fuse/drivefs-84675c855b63e12f384d45f033826980");
// Create 'vm-running' VM instance which is running.
crostini::CrostiniManager::GetForProfile(profile())->AddRunningVmForTesting(
"vm-running");
}
void TearDown() override {
// Shutdown GuestOsSharePath to schedule FilePathWatchers to be destroyed,
// then run thread bundle to ensure they are.
guest_os_share_path_->Shutdown();
thread_bundle_.RunUntilIdle();
run_loop_.reset();
scoped_user_manager_.reset();
profile_.reset();
}
chromeos::FakeChromeUserManager* GetFakeUserManager() const {
return static_cast<chromeos::FakeChromeUserManager*>(
user_manager::UserManager::Get());
}
protected:
base::RunLoop* run_loop() { return run_loop_.get(); }
Profile* profile() { return profile_.get(); }
base::FilePath root_;
base::FilePath share_path_;
base::FilePath shared_path_;
base::FilePath drivefs_;
std::unique_ptr<file_manager::Volume> volume_downloads_;
// Owned by chromeos::DBusThreadManager
chromeos::FakeSeneschalClient* fake_seneschal_client_;
chromeos::FakeConciergeClient* fake_concierge_client_;
content::TestBrowserThreadBundle thread_bundle_;
std::unique_ptr<base::RunLoop> run_loop_;
std::unique_ptr<TestingProfile> profile_;
GuestOsSharePath* guest_os_share_path_;
base::test::ScopedFeatureList features_;
std::unique_ptr<user_manager::ScopedUserManager> scoped_user_manager_;
AccountId account_id_;
private:
DISALLOW_COPY_AND_ASSIGN(GuestOsSharePathTest);
};
TEST_F(GuestOsSharePathTest, SuccessDownloadsRoot) {
features_.InitWithFeatures({}, {chromeos::features::kMyFilesVolume});
SetUpVolume();
guest_os_share_path_->SharePath(
"vm-running", root_, PERSIST_NO,
base::BindOnce(&GuestOsSharePathTest::SharePathCallback,
base::Unretained(this), "vm-running", Persist::NO,
SeneschalClientCalled::YES,
&vm_tools::seneschal::SharePathRequest::DOWNLOADS, "",
Success::YES, ""));
run_loop()->Run();
}
TEST_F(GuestOsSharePathTest, SuccessMyFilesRoot) {
features_.InitWithFeatures({chromeos::features::kMyFilesVolume}, {});
SetUpVolume();
base::FilePath my_files =
file_manager::util::GetMyFilesFolderForProfile(profile());
guest_os_share_path_->SharePath(
"vm-running", my_files, PERSIST_NO,
base::BindOnce(&GuestOsSharePathTest::SharePathCallback,
base::Unretained(this), "vm-running", Persist::NO,
SeneschalClientCalled::YES,
&vm_tools::seneschal::SharePathRequest::MY_FILES, "",
Success::YES, ""));
run_loop()->Run();
}
TEST_F(GuestOsSharePathTest, SuccessNoPersist) {
features_.InitWithFeatures({chromeos::features::kMyFilesVolume}, {});
SetUpVolume();
guest_os_share_path_->SharePath(
"vm-running", share_path_, PERSIST_NO,
base::BindOnce(&GuestOsSharePathTest::SharePathCallback,
base::Unretained(this), "vm-running", Persist::NO,
SeneschalClientCalled::YES,
&vm_tools::seneschal::SharePathRequest::MY_FILES,
"path-to-share", Success::YES, ""));
run_loop()->Run();
}
TEST_F(GuestOsSharePathTest, SuccessPersist) {
features_.InitWithFeatures({chromeos::features::kMyFilesVolume}, {});
SetUpVolume();
guest_os_share_path_->SharePath(
"vm-running", share_path_, PERSIST_YES,
base::BindOnce(&GuestOsSharePathTest::SharePathCallback,
base::Unretained(this), "vm-running", Persist::YES,
SeneschalClientCalled::YES,
&vm_tools::seneschal::SharePathRequest::MY_FILES,
"path-to-share", Success::YES, ""));
run_loop()->Run();
}
TEST_F(GuestOsSharePathTest, SuccessPluginVm) {
features_.InitWithFeatures({chromeos::features::kMyFilesVolume}, {});
SetUpVolume();
guest_os_share_path_->SharePath(
"PvmDefault", share_path_, PERSIST_NO,
base::BindOnce(&GuestOsSharePathTest::SharePathCallback,
base::Unretained(this), "PvmDefault", Persist::NO,
SeneschalClientCalled::YES,
&vm_tools::seneschal::SharePathRequest::MY_FILES,
"path-to-share", Success::YES, ""));
run_loop()->Run();
}
TEST_F(GuestOsSharePathTest, SuccessDriveFsMyDrive) {
features_.InitWithFeatures({chromeos::features::kDriveFs}, {});
SetUpVolume();
guest_os_share_path_->SharePath(
"vm-running", drivefs_.Append("root").Append("my"), PERSIST_NO,
base::BindOnce(&GuestOsSharePathTest::SharePathCallback,
base::Unretained(this), "vm-running", Persist::NO,
SeneschalClientCalled::YES,
&vm_tools::seneschal::SharePathRequest::DRIVEFS_MY_DRIVE,
"my", Success::YES, ""));
run_loop()->Run();
}
TEST_F(GuestOsSharePathTest, FailureDriveFsDisabled) {
features_.InitWithFeatures({}, {chromeos::features::kDriveFs});
SetUpVolume();
guest_os_share_path_->SharePath(
"vm-running", drivefs_.Append("root").Append("my"), PERSIST_NO,
base::BindOnce(&GuestOsSharePathTest::SharePathCallback,
base::Unretained(this), "vm-running", Persist::NO,
SeneschalClientCalled::NO, nullptr, "my", Success::NO,
"Path is not allowed"));
run_loop()->Run();
}
TEST_F(GuestOsSharePathTest, SuccessDriveFsMyDriveRoot) {
features_.InitWithFeatures({chromeos::features::kDriveFs}, {});
SetUpVolume();
guest_os_share_path_->SharePath(
"vm-running", drivefs_.Append("root"), PERSIST_NO,
base::BindOnce(&GuestOsSharePathTest::SharePathCallback,
base::Unretained(this), "vm-running", Persist::NO,
SeneschalClientCalled::YES,
&vm_tools::seneschal::SharePathRequest::DRIVEFS_MY_DRIVE,
"", Success::YES, ""));
run_loop()->Run();
}
TEST_F(GuestOsSharePathTest, FailDriveFsRoot) {
features_.InitWithFeatures({chromeos::features::kDriveFs}, {});
SetUpVolume();
guest_os_share_path_->SharePath(
"vm-running", drivefs_, PERSIST_NO,
base::BindOnce(&GuestOsSharePathTest::SharePathCallback,
base::Unretained(this), "vm-running", Persist::NO,
SeneschalClientCalled::NO, nullptr, "", Success::NO,
"Path is not allowed"));
run_loop()->Run();
}
TEST_F(GuestOsSharePathTest, SuccessDriveFsTeamDrives) {
features_.InitWithFeatures({chromeos::features::kDriveFs}, {});
SetUpVolume();
guest_os_share_path_->SharePath(
"vm-running", drivefs_.Append("team_drives").Append("team"), PERSIST_NO,
base::BindOnce(
&GuestOsSharePathTest::SharePathCallback, base::Unretained(this),
"vm-running", Persist::NO, SeneschalClientCalled::YES,
&vm_tools::seneschal::SharePathRequest::DRIVEFS_TEAM_DRIVES, "team",
Success::YES, ""));
run_loop()->Run();
}
// TODO(crbug.com/917920): Enable when DriveFS enforces allowed write paths.
TEST_F(GuestOsSharePathTest, DISABLED_SuccessDriveFsComputersGrandRoot) {
features_.InitWithFeatures({chromeos::features::kDriveFs}, {});
SetUpVolume();
guest_os_share_path_->SharePath(
"vm-running", drivefs_.Append("Computers"), PERSIST_NO,
base::BindOnce(&GuestOsSharePathTest::SharePathCallback,
base::Unretained(this), "vm-running", Persist::NO,
SeneschalClientCalled::YES,
&vm_tools::seneschal::SharePathRequest::DRIVEFS_COMPUTERS,
"pc", Success::YES, ""));
run_loop()->Run();
}
// TODO(crbug.com/917920): Remove when DriveFS enforces allowed write paths.
TEST_F(GuestOsSharePathTest, Bug917920DriveFsComputersGrandRoot) {
features_.InitWithFeatures({chromeos::features::kDriveFs}, {});
SetUpVolume();
guest_os_share_path_->SharePath(
"vm-running", drivefs_.Append("Computers"), PERSIST_NO,
base::BindOnce(&GuestOsSharePathTest::SharePathCallback,
base::Unretained(this), "vm-running", Persist::NO,
SeneschalClientCalled::NO, nullptr, "", Success::NO,
"Path is not allowed"));
run_loop()->Run();
}
// TODO(crbug.com/917920): Enable when DriveFS enforces allowed write paths.
TEST_F(GuestOsSharePathTest, DISABLED_SuccessDriveFsComputerRoot) {
features_.InitWithFeatures({chromeos::features::kDriveFs}, {});
SetUpVolume();
guest_os_share_path_->SharePath(
"vm-running", drivefs_.Append("Computers").Append("pc"), PERSIST_NO,
base::BindOnce(&GuestOsSharePathTest::SharePathCallback,
base::Unretained(this), "vm-running", Persist::NO,
SeneschalClientCalled::YES,
&vm_tools::seneschal::SharePathRequest::DRIVEFS_COMPUTERS,
"pc", Success::YES, ""));
run_loop()->Run();
}
// TODO(crbug.com/917920): Remove when DriveFS enforces allowed write paths.
TEST_F(GuestOsSharePathTest, Bug917920DriveFsComputerRoot) {
features_.InitWithFeatures({chromeos::features::kDriveFs}, {});
SetUpVolume();
guest_os_share_path_->SharePath(
"vm-running", drivefs_.Append("Computers").Append("pc"), PERSIST_NO,
base::BindOnce(&GuestOsSharePathTest::SharePathCallback,
base::Unretained(this), "vm-running", Persist::NO,
SeneschalClientCalled::NO, nullptr, "", Success::NO,
"Path is not allowed"));
run_loop()->Run();
}
TEST_F(GuestOsSharePathTest, SuccessDriveFsComputersLevel3) {
features_.InitWithFeatures({chromeos::features::kDriveFs}, {});
SetUpVolume();
guest_os_share_path_->SharePath(
"vm-running",
drivefs_.Append("Computers").Append("pc").Append("SyncFolder"),
PERSIST_NO,
base::BindOnce(&GuestOsSharePathTest::SharePathCallback,
base::Unretained(this), "vm-running", Persist::NO,
SeneschalClientCalled::YES,
&vm_tools::seneschal::SharePathRequest::DRIVEFS_COMPUTERS,
"pc/SyncFolder", Success::YES, ""));
run_loop()->Run();
}
TEST_F(GuestOsSharePathTest, FailDriveFsTrash) {
features_.InitWithFeatures({chromeos::features::kDriveFs}, {});
SetUpVolume();
guest_os_share_path_->SharePath(
"vm-running", drivefs_.Append(".Trash").Append("in-the-trash"),
PERSIST_NO,
base::BindOnce(&GuestOsSharePathTest::SharePathCallback,
base::Unretained(this), "vm-running", Persist::NO,
SeneschalClientCalled::NO, nullptr, "", Success::NO,
"Path is not allowed"));
run_loop()->Run();
}
TEST_F(GuestOsSharePathTest, SuccessRemovable) {
SetUpVolume();
guest_os_share_path_->SharePath(
"vm-running", base::FilePath("/media/removable/MyUSB"), PERSIST_NO,
base::BindOnce(&GuestOsSharePathTest::SharePathCallback,
base::Unretained(this), "vm-running", Persist::NO,
SeneschalClientCalled::YES,
&vm_tools::seneschal::SharePathRequest::REMOVABLE, "MyUSB",
Success::YES, ""));
run_loop()->Run();
}
TEST_F(GuestOsSharePathTest, FailRemovableRoot) {
SetUpVolume();
guest_os_share_path_->SharePath(
"vm-running", base::FilePath("/media/removable"), PERSIST_NO,
base::BindOnce(&GuestOsSharePathTest::SharePathCallback,
base::Unretained(this), "vm-running", Persist::NO,
SeneschalClientCalled::NO, nullptr, "", Success::NO,
"Path is not allowed"));
run_loop()->Run();
}
TEST_F(GuestOsSharePathTest, SharePathErrorSeneschal) {
features_.InitWithFeatures(
{chromeos::features::kMyFilesVolume, features::kCrostini}, {});
GetFakeUserManager()->LoginUser(account_id_);
SetUpVolume();
vm_tools::concierge::StartVmResponse start_vm_response;
start_vm_response.set_status(vm_tools::concierge::VM_STATUS_RUNNING);
start_vm_response.mutable_vm_info()->set_seneschal_server_handle(123);
fake_concierge_client_->set_start_vm_response(start_vm_response);
vm_tools::seneschal::SharePathResponse share_path_response;
share_path_response.set_success(false);
share_path_response.set_failure_reason("test failure");
fake_seneschal_client_->set_share_path_response(share_path_response);
guest_os_share_path_->SharePath(
"error-seneschal", share_path_, PERSIST_YES,
base::BindOnce(&GuestOsSharePathTest::SharePathCallback,
base::Unretained(this), "error-seneschal", Persist::YES,
SeneschalClientCalled::YES,
&vm_tools::seneschal::SharePathRequest::MY_FILES,
"path-to-share", Success::NO, "test failure"));
run_loop()->Run();
}
TEST_F(GuestOsSharePathTest, SharePathErrorPathNotAbsolute) {
SetUpVolume();
const base::FilePath path("not/absolute/dir");
guest_os_share_path_->SharePath(
"vm-running", path, PERSIST_YES,
base::BindOnce(&GuestOsSharePathTest::SharePathCallback,
base::Unretained(this), "vm-running", Persist::NO,
SeneschalClientCalled::NO, nullptr, "", Success::NO,
"Path must be absolute"));
run_loop()->Run();
}
TEST_F(GuestOsSharePathTest, SharePathErrorReferencesParent) {
SetUpVolume();
const base::FilePath path("/path/../references/parent");
guest_os_share_path_->SharePath(
"vm-running", path, PERSIST_NO,
base::BindOnce(&GuestOsSharePathTest::SharePathCallback,
base::Unretained(this), "vm-running", Persist::NO,
SeneschalClientCalled::NO, nullptr, "", Success::NO,
"Path must be absolute"));
run_loop()->Run();
}
TEST_F(GuestOsSharePathTest, SharePathErrorNotUnderDownloads) {
SetUpVolume();
const base::FilePath path("/not/under/downloads");
guest_os_share_path_->SharePath(
"vm-running", path, PERSIST_YES,
base::BindOnce(&GuestOsSharePathTest::SharePathCallback,
base::Unretained(this), "vm-running", Persist::NO,
SeneschalClientCalled::NO, nullptr, "", Success::NO,
"Path is not allowed"));
run_loop()->Run();
}
TEST_F(GuestOsSharePathTest, SharePathVmToBeRestarted) {
features_.InitWithFeatures(
{chromeos::features::kMyFilesVolume, features::kCrostini}, {});
GetFakeUserManager()->LoginUser(account_id_);
SetUpVolume();
guest_os_share_path_->SharePath(
"vm-to-be-started", share_path_, PERSIST_YES,
base::BindOnce(&GuestOsSharePathTest::SharePathCallback,
base::Unretained(this), "vm-to-be-started", Persist::YES,
SeneschalClientCalled::YES,
&vm_tools::seneschal::SharePathRequest::MY_FILES,
"path-to-share", Success::YES, ""));
run_loop()->Run();
}
TEST_F(GuestOsSharePathTest, SharePathErrorVmCouldNotBeStarted) {
SetUpVolume();
vm_tools::concierge::StartVmResponse start_vm_response;
start_vm_response.set_status(vm_tools::concierge::VM_STATUS_FAILURE);
fake_concierge_client_->set_start_vm_response(start_vm_response);
guest_os_share_path_->SharePath(
"error-vm-could-not-be-started", share_path_, PERSIST_YES,
base::BindOnce(&GuestOsSharePathTest::SharePathCallback,
base::Unretained(this), "error-vm-could-not-be-started",
Persist::YES, SeneschalClientCalled::NO, nullptr, "",
Success::NO, "VM could not be started"));
run_loop()->Run();
}
TEST_F(GuestOsSharePathTest, SharePersistedPaths) {
SetUpVolume();
base::FilePath share_path2_ = root_.AppendASCII("path-to-share-2");
ASSERT_TRUE(base::CreateDirectory(share_path2_));
crostini::CrostiniManager::GetForProfile(profile())->AddRunningVmForTesting(
crostini::kCrostiniDefaultVmName);
base::Value shared_paths(base::Value::Type::DICTIONARY);
base::Value vms(base::Value::Type::LIST);
vms.GetList().emplace_back(base::Value(crostini::kCrostiniDefaultVmName));
shared_paths.SetKey(share_path_.value(), std::move(vms));
base::Value vms2(base::Value::Type::LIST);
vms2.GetList().emplace_back(base::Value(crostini::kCrostiniDefaultVmName));
shared_paths.SetKey(share_path2_.value(), std::move(vms2));
profile()->GetPrefs()->Set(prefs::kGuestOSPathsSharedToVms, shared_paths);
guest_os_share_path_->SharePersistedPaths(
crostini::kCrostiniDefaultVmName,
base::BindOnce(&GuestOsSharePathTest::SharePersistedPathsCallback,
base::Unretained(this)));
run_loop()->Run();
}
TEST_F(GuestOsSharePathTest, RegisterPersistedPaths) {
base::Value shared_paths(base::Value::Type::DICTIONARY);
SetUpVolume();
profile()->GetPrefs()->Set(prefs::kGuestOSPathsSharedToVms, shared_paths);
guest_os_share_path_->RegisterPersistedPath("v1", base::FilePath("/a/a/a"));
const base::DictionaryValue* prefs =
profile()->GetPrefs()->GetDictionary(prefs::kGuestOSPathsSharedToVms);
EXPECT_EQ(prefs->size(), 1U);
EXPECT_EQ(prefs->FindKey("/a/a/a")->GetList().size(), 1U);
EXPECT_EQ(prefs->FindKey("/a/a/a")->GetList()[0].GetString(), "v1");
// Adding the same path again for same VM should not cause any changes.
guest_os_share_path_->RegisterPersistedPath("v1", base::FilePath("/a/a/a"));
prefs = profile()->GetPrefs()->GetDictionary(prefs::kGuestOSPathsSharedToVms);
EXPECT_EQ(prefs->size(), 1U);
EXPECT_EQ(prefs->FindKey("/a/a/a")->GetList().size(), 1U);
// Adding the same path for a new VM adds to the vm list.
guest_os_share_path_->RegisterPersistedPath("v2", base::FilePath("/a/a/a"));
EXPECT_EQ(prefs->size(), 1U);
EXPECT_EQ(prefs->FindKey("/a/a/a")->GetList().size(), 2U);
EXPECT_EQ(prefs->FindKey("/a/a/a")->GetList()[0].GetString(), "v1");
EXPECT_EQ(prefs->FindKey("/a/a/a")->GetList()[1].GetString(), "v2");
// Add more paths.
guest_os_share_path_->RegisterPersistedPath("v1", base::FilePath("/a/a/b"));
guest_os_share_path_->RegisterPersistedPath("v1", base::FilePath("/a/a/c"));
guest_os_share_path_->RegisterPersistedPath("v1", base::FilePath("/a/b/a"));
guest_os_share_path_->RegisterPersistedPath("v1", base::FilePath("/b/a/a"));
EXPECT_EQ(prefs->size(), 5U);
EXPECT_EQ(prefs->FindKey("/a/a/a")->GetList().size(), 2U);
EXPECT_EQ(prefs->FindKey("/a/a/b")->GetList().size(), 1U);
EXPECT_EQ(prefs->FindKey("/a/a/c")->GetList().size(), 1U);
EXPECT_EQ(prefs->FindKey("/a/b/a")->GetList().size(), 1U);
EXPECT_EQ(prefs->FindKey("/b/a/a")->GetList().size(), 1U);
// Adding /a/a should remove /a/a/a, /a/a/b, /a/a/c.
guest_os_share_path_->RegisterPersistedPath("v1", base::FilePath("/a/a"));
EXPECT_EQ(prefs->size(), 4U);
EXPECT_EQ(prefs->FindKey("/a/a/a")->GetList().size(), 1U);
EXPECT_EQ(prefs->FindKey("/a/a/a")->GetList()[0].GetString(), "v2");
EXPECT_EQ(prefs->FindKey("/a/b/a")->GetList().size(), 1U);
EXPECT_EQ(prefs->FindKey("/b/a/a")->GetList().size(), 1U);
EXPECT_EQ(prefs->FindKey("/a/a")->GetList().size(), 1U);
EXPECT_EQ(prefs->FindKey("/a/a")->GetList()[0].GetString(), "v1");
// Adding /a should remove /a/a, /a/b/a.
guest_os_share_path_->RegisterPersistedPath("v1", base::FilePath("/a"));
EXPECT_EQ(prefs->size(), 3U);
EXPECT_EQ(prefs->FindKey("/a/a/a")->GetList().size(), 1U);
EXPECT_EQ(prefs->FindKey("/a/a/a")->GetList()[0].GetString(), "v2");
EXPECT_EQ(prefs->FindKey("/b/a/a")->GetList().size(), 1U);
EXPECT_EQ(prefs->FindKey("/a")->GetList().size(), 1U);
EXPECT_EQ(prefs->FindKey("/a")->GetList()[0].GetString(), "v1");
// Adding / should remove all others.
guest_os_share_path_->RegisterPersistedPath("v1", base::FilePath("/"));
EXPECT_EQ(prefs->size(), 2U);
EXPECT_EQ(prefs->FindKey("/a/a/a")->GetList().size(), 1U);
EXPECT_EQ(prefs->FindKey("/a/a/a")->GetList()[0].GetString(), "v2");
EXPECT_EQ(prefs->FindKey("/")->GetList().size(), 1U);
EXPECT_EQ(prefs->FindKey("/")->GetList()[0].GetString(), "v1");
// Add / for v2.
guest_os_share_path_->RegisterPersistedPath("v2", base::FilePath("/"));
EXPECT_EQ(prefs->size(), 1U);
EXPECT_EQ(prefs->FindKey("/")->GetList().size(), 2U);
EXPECT_EQ(prefs->FindKey("/")->GetList()[0].GetString(), "v1");
EXPECT_EQ(prefs->FindKey("/")->GetList()[1].GetString(), "v2");
}
TEST_F(GuestOsSharePathTest, UnsharePathSuccess) {
features_.InitWithFeatures({chromeos::features::kMyFilesVolume}, {});
SetUpVolume();
DictionaryPrefUpdate update(profile()->GetPrefs(),
prefs::kGuestOSPathsSharedToVms);
base::DictionaryValue* shared_paths = update.Get();
base::Value vms(base::Value::Type::LIST);
vms.GetList().emplace_back(base::Value("vm-running"));
shared_paths->SetKey(shared_path_.value(), std::move(vms));
guest_os_share_path_->UnsharePath(
"vm-running", shared_path_, true,
base::BindOnce(&GuestOsSharePathTest::UnsharePathCallback,
base::Unretained(this), shared_path_, Persist::NO,
SeneschalClientCalled::YES, "MyFiles/already-shared",
Success::YES, ""));
run_loop()->Run();
}
TEST_F(GuestOsSharePathTest, UnsharePathRoot) {
features_.InitWithFeatures({chromeos::features::kMyFilesVolume}, {});
SetUpVolume();
guest_os_share_path_->UnsharePath(
"vm-running", root_, true,
base::BindOnce(&GuestOsSharePathTest::UnsharePathCallback,
base::Unretained(this), root_, Persist::NO,
SeneschalClientCalled::YES, "MyFiles", Success::YES, ""));
run_loop()->Run();
}
TEST_F(GuestOsSharePathTest, UnsharePathVmNotRunning) {
SetUpVolume();
DictionaryPrefUpdate update(profile()->GetPrefs(),
prefs::kGuestOSPathsSharedToVms);
base::DictionaryValue* shared_paths = update.Get();
base::Value vms(base::Value::Type::LIST);
vms.GetList().emplace_back(base::Value("vm-not-running"));
shared_paths->SetKey(shared_path_.value(), std::move(vms));
guest_os_share_path_->UnsharePath(
"vm-not-running", shared_path_, true,
base::BindOnce(&GuestOsSharePathTest::UnsharePathCallback,
base::Unretained(this), shared_path_, Persist::NO,
SeneschalClientCalled::NO, "", Success::YES,
"VM not running"));
run_loop()->Run();
}
TEST_F(GuestOsSharePathTest, UnsharePathPluginVmNotRunning) {
SetUpVolume();
DictionaryPrefUpdate update(profile()->GetPrefs(),
prefs::kGuestOSPathsSharedToVms);
base::DictionaryValue* shared_paths = update.Get();
base::Value vms(base::Value::Type::LIST);
vms.GetList().emplace_back(base::Value("PvmDefault"));
shared_paths->SetKey(shared_path_.value(), std::move(vms));
guest_os_share_path_->UnsharePath(
"PvmDefault", shared_path_, true,
base::BindOnce(&GuestOsSharePathTest::UnsharePathCallback,
base::Unretained(this), shared_path_, Persist::NO,
SeneschalClientCalled::NO, "", Success::YES,
"PluginVm not running"));
run_loop()->Run();
}
TEST_F(GuestOsSharePathTest, UnsharePathInvalidPath) {
SetUpVolume();
base::FilePath invalid("invalid/path");
guest_os_share_path_->UnsharePath(
"vm-running", invalid, true,
base::BindOnce(&GuestOsSharePathTest::UnsharePathCallback,
base::Unretained(this), invalid, Persist::NO,
SeneschalClientCalled::NO, "", Success::NO,
"Invalid path to unshare"));
run_loop()->Run();
}
TEST_F(GuestOsSharePathTest, MigratePersistedPathsToMultiVM) {
SetUpVolume();
base::ListValue shared_paths = base::ListValue();
base::FilePath downloads_file = profile()->GetPath().Append("Downloads/file");
shared_paths.AppendString(downloads_file.value());
base::FilePath not_downloads("/not/downloads");
shared_paths.AppendString(not_downloads.value());
profile()->GetPrefs()->Set(prefs::kCrostiniSharedPaths, shared_paths);
GuestOsSharePath::MigratePersistedPathsToMultiVM(profile()->GetPrefs());
EXPECT_EQ(
profile()->GetPrefs()->GetList(prefs::kCrostiniSharedPaths)->GetSize(),
0U);
const base::DictionaryValue* prefs =
profile()->GetPrefs()->GetDictionary(prefs::kGuestOSPathsSharedToVms);
EXPECT_EQ(prefs->size(), 2U);
EXPECT_EQ(prefs->FindKey(downloads_file.value())->GetList().size(), 1U);
EXPECT_EQ(prefs->FindKey(downloads_file.value())->GetList()[0].GetString(),
"termina");
EXPECT_EQ(prefs->FindKey(not_downloads.value())->GetList().size(), 1U);
EXPECT_EQ(prefs->FindKey(not_downloads.value())->GetList()[0].GetString(),
"termina");
}
TEST_F(GuestOsSharePathTest, GetPersistedSharedPaths) {
SetUpVolume();
// path1:['vm1'], path2:['vm2'], path3:['vm3'], path12:['vm1','vm2']
base::Value shared_paths(base::Value::Type::DICTIONARY);
base::FilePath path1("/path1");
base::Value path1vms(base::Value::Type::LIST);
path1vms.GetList().emplace_back(base::Value("vm1"));
shared_paths.SetKey(path1.value(), std::move(path1vms));
base::FilePath path2("/path2");
base::Value path2vms(base::Value::Type::LIST);
path2vms.GetList().emplace_back(base::Value("vm2"));
shared_paths.SetKey(path2.value(), std::move(path2vms));
base::FilePath path3("/path3");
base::Value path3vms(base::Value::Type::LIST);
path3vms.GetList().emplace_back(base::Value("vm3"));
shared_paths.SetKey(path3.value(), std::move(path3vms));
base::FilePath path12("/path12");
base::Value path12vms(base::Value::Type::LIST);
path12vms.GetList().emplace_back(base::Value("vm1"));
path12vms.GetList().emplace_back(base::Value("vm2"));
shared_paths.SetKey(path12.value(), std::move(path12vms));
profile()->GetPrefs()->Set(prefs::kGuestOSPathsSharedToVms, shared_paths);
std::vector<base::FilePath> paths =
guest_os_share_path_->GetPersistedSharedPaths("vm1");
std::sort(paths.begin(), paths.end());
EXPECT_EQ(paths.size(), 2U);
EXPECT_EQ(paths[0], path1);
EXPECT_EQ(paths[1], path12);
paths = guest_os_share_path_->GetPersistedSharedPaths("vm2");
std::sort(paths.begin(), paths.end());
EXPECT_EQ(paths.size(), 2U);
EXPECT_EQ(paths[0], path12);
EXPECT_EQ(paths[1], path2);
paths = guest_os_share_path_->GetPersistedSharedPaths("vm3");
EXPECT_EQ(paths.size(), 1U);
EXPECT_EQ(paths[0], path3);
paths = guest_os_share_path_->GetPersistedSharedPaths("vm4");
EXPECT_EQ(paths.size(), 0U);
}
TEST_F(GuestOsSharePathTest, ShareOnMountSuccessParentMount) {
features_.InitWithFeatures({chromeos::features::kMyFilesVolume}, {});
SetUpVolume();
crostini::CrostiniManager::GetForProfile(profile())->AddRunningVmForTesting(
crostini::kCrostiniDefaultVmName);
guest_os_share_path_->set_seneschal_callback_for_testing(base::BindRepeating(
&GuestOsSharePathTest::SeneschalSharePathCallback, base::Unretained(this),
"share-on-mount", shared_path_, crostini::kCrostiniDefaultVmName,
Persist::NO, SeneschalClientCalled::YES,
&vm_tools::seneschal::SharePathRequest::MY_FILES, "already-shared",
Success::YES, ""));
guest_os_share_path_->OnVolumeMounted(chromeos::MountError::MOUNT_ERROR_NONE,
*volume_downloads_);
run_loop_->Run();
}
TEST_F(GuestOsSharePathTest, ShareOnMountSuccessSelfMount) {
features_.InitWithFeatures({chromeos::features::kMyFilesVolume}, {});
SetUpVolume();
crostini::CrostiniManager::GetForProfile(profile())->AddRunningVmForTesting(
crostini::kCrostiniDefaultVmName);
auto volume_shared_path =
file_manager::Volume::CreateForDownloads(shared_path_);
guest_os_share_path_->set_seneschal_callback_for_testing(base::BindRepeating(
&GuestOsSharePathTest::SeneschalSharePathCallback, base::Unretained(this),
"share-on-mount", shared_path_, crostini::kCrostiniDefaultVmName,
Persist::NO, SeneschalClientCalled::YES,
&vm_tools::seneschal::SharePathRequest::MY_FILES, "already-shared",
Success::YES, ""));
guest_os_share_path_->OnVolumeMounted(chromeos::MountError::MOUNT_ERROR_NONE,
*volume_shared_path);
run_loop()->Run();
}
TEST_F(GuestOsSharePathTest, ShareOnMountVmNotRunning) {
SetUpVolume();
// Test mount.
guest_os_share_path_->OnVolumeMounted(chromeos::MountError::MOUNT_ERROR_NONE,
*volume_downloads_);
EXPECT_EQ(fake_seneschal_client_->share_path_called(), false);
// Test unmount.
guest_os_share_path_->OnVolumeUnmounted(
chromeos::MountError::MOUNT_ERROR_NONE, *volume_downloads_);
EXPECT_EQ(fake_seneschal_client_->share_path_called(), false);
}
TEST_F(GuestOsSharePathTest, ShareOnMountVolumeUnrelated) {
SetUpVolume();
auto volume_unrelated_ = file_manager::Volume::CreateForDownloads(
base::FilePath("/unrelated/path"));
// Test mount.
guest_os_share_path_->OnVolumeMounted(chromeos::MountError::MOUNT_ERROR_NONE,
*volume_unrelated_);
EXPECT_EQ(fake_seneschal_client_->share_path_called(), false);
// Test unmount.
guest_os_share_path_->OnVolumeUnmounted(
chromeos::MountError::MOUNT_ERROR_NONE, *volume_unrelated_);
EXPECT_EQ(fake_seneschal_client_->share_path_called(), false);
}
TEST_F(GuestOsSharePathTest, UnshareOnUnmountSuccessParentMount) {
features_.InitWithFeatures({chromeos::features::kMyFilesVolume}, {});
SetUpVolume();
crostini::CrostiniManager::GetForProfile(profile())->AddRunningVmForTesting(
crostini::kCrostiniDefaultVmName);
guest_os_share_path_->set_seneschal_callback_for_testing(base::BindRepeating(
&GuestOsSharePathTest::SeneschalUnsharePathCallback,
base::Unretained(this), "unshare-on-unmount", shared_path_, Persist::YES,
SeneschalClientCalled::YES, "MyFiles/already-shared", Success::YES, ""));
guest_os_share_path_->OnVolumeUnmounted(
chromeos::MountError::MOUNT_ERROR_NONE, *volume_downloads_);
run_loop()->Run();
}
TEST_F(GuestOsSharePathTest, UnshareOnUnmountSuccessSelfMount) {
features_.InitWithFeatures({chromeos::features::kMyFilesVolume}, {});
SetUpVolume();
crostini::CrostiniManager::GetForProfile(profile())->AddRunningVmForTesting(
crostini::kCrostiniDefaultVmName);
auto volume_shared_path =
file_manager::Volume::CreateForDownloads(shared_path_);
guest_os_share_path_->set_seneschal_callback_for_testing(base::BindRepeating(
&GuestOsSharePathTest::SeneschalUnsharePathCallback,
base::Unretained(this), "unshare-on-unmount", shared_path_, Persist::YES,
SeneschalClientCalled::YES, "MyFiles/already-shared", Success::YES, ""));
guest_os_share_path_->OnVolumeUnmounted(
chromeos::MountError::MOUNT_ERROR_NONE, *volume_shared_path);
run_loop()->Run();
}
TEST_F(GuestOsSharePathTest, UnshareOnDeleteMountExists) {
features_.InitWithFeatures({chromeos::features::kMyFilesVolume}, {});
SetUpVolume();
crostini::CrostiniManager::GetForProfile(profile())->AddRunningVmForTesting(
crostini::kCrostiniDefaultVmName);
ASSERT_TRUE(base::DeleteFile(shared_path_, false));
guest_os_share_path_->set_seneschal_callback_for_testing(base::BindRepeating(
&GuestOsSharePathTest::SeneschalUnsharePathCallback,
base::Unretained(this), "unshare-on-delete", shared_path_, Persist::NO,
SeneschalClientCalled::YES, "MyFiles/already-shared", Success::YES, ""));
run_loop()->Run();
}
TEST_F(GuestOsSharePathTest, UnshareOnDeleteMountRemoved) {
features_.InitWithFeatures({chromeos::features::kMyFilesVolume}, {});
SetUpVolume();
crostini::CrostiniManager::GetForProfile(profile())->AddRunningVmForTesting(
crostini::kCrostiniDefaultVmName);
// Rename root_ rather than delete to mimic atomic removal of mount.
base::FilePath renamed =
root_.DirName().Append(root_.BaseName().value() + ".tmp");
ASSERT_TRUE(base::Move(root_, renamed));
guest_os_share_path_->set_seneschal_callback_for_testing(base::BindRepeating(
&GuestOsSharePathTest::SeneschalUnsharePathCallback,
base::Unretained(this), "ignore-delete-before-unmount", shared_path_,
Persist::YES, SeneschalClientCalled::NO, "MyFiles/already-shared",
Success::YES, ""));
run_loop()->Run();
}
} // namespace guest_os