Commit 6670570c authored by Joel Hockey's avatar Joel Hockey Committed by Commit Bot

FilesApp crostini integration test

* Test verifies fake root is shown, then mounts container
  and verifies that correct volume and contents are displayed.
  Unmounts volume, and verifies that fake root is shown again.
* Sets prefs / features for crostini and registers mount callback
  with FakeCrosDisksClient to simulate sshfs mounting.

Bug: 845075
Cq-Include-Trybots: luci.chromium.try:closure_compilation
Change-Id: Ice3f96430ef063a9942fdd2c40e99b30b35b0c50
Reviewed-on: https://chromium-review.googlesource.com/1094574
Commit-Queue: Joel Hockey <joelhockey@chromium.org>
Reviewed-by: default avatarNoel Gordon <noel@chromium.org>
Reviewed-by: default avatarSteven Bennetts <stevenjb@chromium.org>
Reviewed-by: default avatarStuart Langley <slangley@chromium.org>
Reviewed-by: default avatarSam McNally <sammc@chromium.org>
Cr-Commit-Position: refs/heads/master@{#571344}
parent 67550354
......@@ -433,6 +433,11 @@ WRAPPED_INSTANTIATE_TEST_CASE_P(
TestCase("showPasteIntoCurrentFolder"),
TestCase("showSelectAllInCurrentFolder")));
WRAPPED_INSTANTIATE_TEST_CASE_P(
Crostini, /* crostini.js */
FilesAppBrowserTest,
::testing::Values(TestCase("mountCrostiniContainer")));
// Structure to describe an account info.
struct TestAccountInfo {
const char* const gaia_id;
......
......@@ -20,6 +20,8 @@
#include "base/threading/thread_restrictions.h"
#include "base/time/time.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chromeos/crostini/crostini_manager.h"
#include "chrome/browser/chromeos/crostini/crostini_pref_names.h"
#include "chrome/browser/chromeos/drive/file_system_util.h"
#include "chrome/browser/chromeos/file_manager/app_id.h"
#include "chrome/browser/chromeos/file_manager/mount_test_util.h"
......@@ -30,11 +32,14 @@
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/notifications/notification_display_service_tester.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chromeos/chromeos_switches.h"
#include "chromeos/components/drivefs/drivefs_host.h"
#include "chromeos/components/drivefs/fake_drivefs.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/fake_cros_disks_client.h"
#include "components/drive/chromeos/file_system_interface.h"
#include "components/drive/service/fake_drive_service.h"
#include "content/public/browser/browser_thread.h"
......@@ -89,7 +94,7 @@ struct AddEntriesMessage {
struct TestEntryInfo;
// Represents the various volumes available for adding entries.
enum TargetVolume { LOCAL_VOLUME, DRIVE_VOLUME, USB_VOLUME };
enum TargetVolume { LOCAL_VOLUME, DRIVE_VOLUME, CROSTINI_VOLUME, USB_VOLUME };
// Represents the different types of entries (e.g. file, folder).
enum EntryType { FILE, DIRECTORY };
......@@ -128,6 +133,8 @@ struct AddEntriesMessage {
*volume = LOCAL_VOLUME;
else if (value == "drive")
*volume = DRIVE_VOLUME;
else if (value == "crostini")
*volume = CROSTINI_VOLUME;
else if (value == "usb")
*volume = USB_VOLUME;
else
......@@ -439,6 +446,31 @@ class DownloadsTestVolume : public LocalTestVolume {
DISALLOW_COPY_AND_ASSIGN(DownloadsTestVolume);
};
// CrostiniTestVolume: local test volume for the "Linux Files" directory.
class CrostiniTestVolume : public LocalTestVolume {
public:
CrostiniTestVolume() : LocalTestVolume("Crostini") {}
~CrostiniTestVolume() override = default;
// Create root dir so entries can be created, but volume is not mounted.
bool Initialize(Profile* profile) { return CreateRootDirectory(profile); }
bool Mount(Profile* profile) override {
return CreateRootDirectory(profile) &&
VolumeManager::Get(profile)->RegisterCrostiniDirectoryForTesting(
root_path());
}
const base::FilePath& mount_path() const { return root_path(); }
void Unmount(Profile* profile) {
VolumeManager::Get(profile)->RemoveSshfsCrostiniVolume(root_path());
}
private:
DISALLOW_COPY_AND_ASSIGN(CrostiniTestVolume);
};
// FakeTestVolume: local test volume with a given volume and device type.
class FakeTestVolume : public LocalTestVolume {
public:
......@@ -813,9 +845,15 @@ void FileManagerBrowserTestBase::SetUpCommandLine(
command_line->AppendSwitch(chromeos::switches::kDisableZipArchiverUnpacker);
command_line->AppendSwitch(chromeos::switches::kDisableZipArchiverPacker);
std::vector<base::Feature> enabled_features;
if (!IsGuestModeTest()) {
enabled_features.emplace_back(features::kCrostini);
enabled_features.emplace_back(features::kExperimentalCrostiniUI);
}
if (IsDriveFsTest()) {
feature_list_.InitAndEnableFeature(drive::kDriveFs);
enabled_features.emplace_back(drive::kDriveFs);
}
feature_list_.InitWithFeatures(enabled_features, {});
extensions::ExtensionApiTest::SetUpCommandLine(command_line);
}
......@@ -861,6 +899,18 @@ void FileManagerBrowserTestBase::SetUpInProcessBrowserTestFixture() {
}
}
base::FilePath FileManagerBrowserTestBase::MaybeMountCrostini(
const std::string& source_path,
const std::vector<std::string>& mount_options) {
GURL source_url(source_path);
DCHECK(source_url.is_valid());
if (source_url.scheme() != "sshfs") {
return {};
}
CHECK(crostini_volume_->Mount(profile()));
return crostini_volume_->mount_path();
}
void FileManagerBrowserTestBase::SetUpOnMainThread() {
extensions::ExtensionApiTest::SetUpOnMainThread();
CHECK(profile());
......@@ -875,6 +925,21 @@ void FileManagerBrowserTestBase::SetUpOnMainThread() {
drive_volume_ = drive_volumes_[profile()->GetOriginalProfile()].get();
drive_volume_->ConfigureShareUrlBase(share_url_base);
test_util::WaitUntilDriveMountPointIsAdded(profile());
// Init crostini. Set prefs to enable crostini and register CustomMountPointCallback.
// TODO(joelhockey): It would be better if the crostini interface allowed
// for testing without such tight coupling.
crostini_volume_ = std::make_unique<CrostiniTestVolume>();
browser()->profile()->GetPrefs()->SetBoolean(
crostini::prefs::kCrostiniEnabled, true);
crostini::CrostiniManager::GetInstance()->set_skip_restart_for_testing();
chromeos::DBusThreadManager* dbus_thread_manager =
chromeos::DBusThreadManager::Get();
static_cast<chromeos::FakeCrosDisksClient*>(
dbus_thread_manager->GetCrosDisksClient())
->AddCustomMountPointCallback(
base::BindRepeating(&FileManagerBrowserTestBase::MaybeMountCrostini,
base::Unretained(this)));
}
display_service_ =
......@@ -1033,6 +1098,11 @@ void FileManagerBrowserTestBase::OnCommand(const std::string& name,
case AddEntriesMessage::LOCAL_VOLUME:
local_volume_->CreateEntry(*message.entries[i]);
break;
case AddEntriesMessage::CROSTINI_VOLUME:
CHECK(crostini_volume_);
ASSERT_TRUE(crostini_volume_->Initialize(profile()));
crostini_volume_->CreateEntry(*message.entries[i]);
break;
case AddEntriesMessage::DRIVE_VOLUME:
if (drive_volume_) {
drive_volume_->CreateEntry(*message.entries[i]);
......@@ -1076,6 +1146,11 @@ void FileManagerBrowserTestBase::OnCommand(const std::string& name,
return;
}
if (name == "unmountCrostini") {
crostini_volume_->Unmount(profile());
return;
}
if (name == "useCellularNetwork") {
net::NetworkChangeNotifier::NotifyObserversOfMaxBandwidthChangeForTests(
net::NetworkChangeNotifier::GetMaxBandwidthMbpsForConnectionSubtype(
......
......@@ -24,6 +24,7 @@ enum GuestMode { NOT_IN_GUEST_MODE, IN_GUEST_MODE, IN_INCOGNITO };
class DriveTestVolume;
class FakeTestVolume;
class LocalTestVolume;
class CrostiniTestVolume;
class FileManagerBrowserTestBase : public extensions::ExtensionApiTest {
protected:
......@@ -79,9 +80,16 @@ class FileManagerBrowserTestBase : public extensions::ExtensionApiTest {
const base::DictionaryValue& value,
std::string* output);
// Called during tests to mount a crostini volume if needed. Returns the mount
// path of the volume.
base::FilePath MaybeMountCrostini(
const std::string& source_path,
const std::vector<std::string>& mount_options);
base::test::ScopedFeatureList feature_list_;
std::unique_ptr<LocalTestVolume> local_volume_;
std::unique_ptr<CrostiniTestVolume> crostini_volume_;
std::map<Profile*, std::unique_ptr<DriveTestVolume>> drive_volumes_;
DriveTestVolume* drive_volume_ = nullptr;
std::unique_ptr<FakeTestVolume> usb_volume_;
......
......@@ -578,6 +578,21 @@ bool VolumeManager::RegisterDownloadsDirectoryForTesting(
return success;
}
bool VolumeManager::RegisterCrostiniDirectoryForTesting(
const base::FilePath& path) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
bool success =
storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem(
file_manager::util::GetCrostiniMountPointName(profile_),
storage::kFileSystemTypeNativeLocal, storage::FileSystemMountOption(),
path);
DoMountEvent(
success ? chromeos::MOUNT_ERROR_NONE : chromeos::MOUNT_ERROR_INVALID_PATH,
Volume::CreateForSshfsCrostini(path));
return true;
}
void VolumeManager::AddVolumeForTesting(const base::FilePath& path,
VolumeType volume_type,
chromeos::DeviceType device_type,
......
......@@ -285,10 +285,17 @@ class VolumeManager : public KeyedService,
// Add sshfs crostini volume mounted at specified path.
void AddSshfsCrostiniVolume(const base::FilePath& sshfs_mount_path);
// Removes specified sshfs crostini mount.
void RemoveSshfsCrostiniVolume(const base::FilePath& sshfs_mount_path);
// For testing purpose, registers a native local file system pointing to
// |path| with DOWNLOADS type, and adds its volume info.
bool RegisterDownloadsDirectoryForTesting(const base::FilePath& path);
// For testing purpose, registers a native local file system pointing to
// |path| with CROSTINI type, and adds its volume info.
bool RegisterCrostiniDirectoryForTesting(const base::FilePath& path);
// For testing purpose, adds a volume info pointing to |path|, with TESTING
// type. Assumes that the mount point is already registered.
void AddVolumeForTesting(const base::FilePath& path,
......@@ -361,7 +368,6 @@ class VolumeManager : public KeyedService,
void DoUnmountEvent(chromeos::MountError error_code, const Volume& volume);
void OnExternalStorageDisabledChangedUnmountCallback(
chromeos::MountError error_code);
void RemoveSshfsCrostiniVolume(const base::FilePath& sshfs_mount_path);
// Returns the path of the mount point for drive.
base::FilePath GetDriveMountPointPath() const;
......
......@@ -102,7 +102,7 @@ void FakeDriveFs::RegisterMountingForAccountId(
chromeos::DBusThreadManager::Get();
static_cast<chromeos::FakeCrosDisksClient*>(
dbus_thread_manager->GetCrosDisksClient())
->SetCustomMountPointCallback(base::BindRepeating(&MaybeMountDriveFs));
->AddCustomMountPointCallback(base::BindRepeating(&MaybeMountDriveFs));
GetRegisteredFakeDriveFsIntances().emplace_back(std::move(account_id_getter),
weak_factory_.GetWeakPtr());
......
......@@ -64,7 +64,7 @@ FakeDriveFsLauncherClient::FakeDriveFsLauncherClient(
chromeos::DBusThreadManager::Get();
static_cast<chromeos::FakeCrosDisksClient*>(
dbus_thread_manager->GetCrosDisksClient())
->SetCustomMountPointCallback(
->AddCustomMountPointCallback(
base::BindRepeating(&FakeDriveFsLauncherClient::MaybeMountDriveFs,
base::Unretained(this)));
}
......
......@@ -103,9 +103,12 @@ void FakeCrosDisksClient::Mount(const std::string& source_path,
base::FilePath::FromUTF8Unsafe(mount_label));
break;
case MOUNT_TYPE_NETWORK_STORAGE:
if (custom_mount_point_callback_) {
mounted_path =
custom_mount_point_callback_.Run(source_path, mount_options);
// Call all registered callbacks until mounted_path is non-empty.
for (auto const& callback : custom_mount_point_callbacks_) {
mounted_path = callback.Run(source_path, mount_options);
if (!mounted_path.empty()) {
break;
}
}
break;
case MOUNT_TYPE_INVALID:
......@@ -233,9 +236,9 @@ void FakeCrosDisksClient::NotifyRenameCompleted(
observer.OnRenameCompleted(error_code, device_path);
}
void FakeCrosDisksClient::SetCustomMountPointCallback(
void FakeCrosDisksClient::AddCustomMountPointCallback(
FakeCrosDisksClient::CustomMountPointCallback custom_mount_point_callback) {
custom_mount_point_callback_ = std::move(custom_mount_point_callback);
custom_mount_point_callbacks_.emplace_back(custom_mount_point_callback);
}
} // namespace chromeos
......@@ -75,7 +75,10 @@ class CHROMEOS_EXPORT FakeCrosDisksClient : public CrosDisksClient {
void NotifyRenameCompleted(RenameError error_code,
const std::string& device_path);
void SetCustomMountPointCallback(
// Add a callback to be executed when a Mount call is made to a URI
// source_path. The mount point from the first non empty result will be used
// in the order added.
void AddCustomMountPointCallback(
CustomMountPointCallback custom_mount_point_callback);
// Returns how many times Unmount() was called.
......@@ -163,7 +166,7 @@ class CHROMEOS_EXPORT FakeCrosDisksClient : public CrosDisksClient {
std::string last_rename_volume_name_;
bool rename_success_;
std::set<base::FilePath> mounted_paths_;
CustomMountPointCallback custom_mount_point_callback_;
std::vector<CustomMountPointCallback> custom_mount_point_callbacks_;
base::WeakPtrFactory<FakeCrosDisksClient> weak_ptr_factory_;
......
......@@ -150,6 +150,17 @@ var BASIC_DRIVE_ENTRY_SET = [
ENTRIES.testSharedDocument
];
/**
* Basic entry set for the local crostini volume.
* @type {!Array<!TestEntryInfo>}
* @const
*/
var BASIC_CROSTINI_ENTRY_SET = [
ENTRIES.hello,
ENTRIES.world,
ENTRIES.desktop,
];
/**
* More complex entry set for Drive that includes entries with varying
* permissions (such as read-only entries).
......
// 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.
'use strict';
testcase.mountCrostiniContainer = function() {
const fake = '#directory-tree .tree-item [root-type-icon="crostini"]';
const real = '#directory-tree .tree-item [volume-type-icon="crostini"]';
let appId;
StepsRunner.run([
function() {
setupAndWaitUntilReady(
null, RootPath.DOWNLOADS, this.next, [ENTRIES.hello], []);
},
function(results) {
// Add entries to crostini volume, but do not mount.
appId = results.windowId;
addEntries(['crostini'], BASIC_CROSTINI_ENTRY_SET, this.next);
},
function() {
// Linux Files fake root is shown.
remoteCall.waitForElement(appId, fake).then(this.next);
},
function() {
// Mount crostini, and ensure real root and files are shown.
remoteCall.callRemoteTestUtil('fakeMouseClick', appId, [fake]);
remoteCall.waitForElement(appId, real).then(this.next);
},
function() {
const files = TestEntryInfo.getExpectedRows(BASIC_CROSTINI_ENTRY_SET);
remoteCall.waitForFiles(appId, files).then(this.next);
},
function() {
// Unmount and ensure fake root is shown.
chrome.test.sendMessage(JSON.stringify({name: 'unmountCrostini'}));
remoteCall.waitForElement(appId, fake).then(this.next);
},
function() {
checkIfNoErrorsOccured(this.next);
},
]);
};
......@@ -43,7 +43,7 @@ var DIRECTORY = {
ENTRIES.directoryA.getExpectedRow(), ENTRIES.directoryD.getExpectedRow()
],
name: 'Drive',
navItem: '#tree-item-autogen-id-4',
navItem: '#tree-item-autogen-id-5',
treeItem: TREEITEM_DRIVE
},
A: {
......@@ -295,13 +295,13 @@ testcase.traverseFolderShortcuts = function() {
windowId, DIRECTORY.Drive, DIRECTORY.Drive).then(this.next);
},
// Press Ctrl+5 to select 5th shortcut.
// Press Ctrl+6 to select 5th shortcut.
// Current directory should be D.
// Shortcut to C should be selected.
function() {
remoteCall.callRemoteTestUtil(
'fakeKeyDown', windowId,
['#file-list', '5', 'U+0034', true, false, false], this.next);
['#file-list', '6', 'U+0034', true, false, false], this.next);
},
function(result) {
chrome.test.assertTrue(result);
......
......@@ -16,6 +16,7 @@
"file_manager/context_menu.js",
"file_manager/copy_between_windows.js",
"file_manager/create_new_folder.js",
"file_manager/crostini.js",
"file_manager/delete.js",
"file_manager/details_panel.js",
"file_manager/directory_tree_context_menu.js",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment