blob: b05bf1bd730e3e02dea39d90ca2f162b182d7225 [file] [log] [blame]
// Copyright 2020 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 <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/files/file.h"
#include "base/files/file_util.h"
#include "base/path_service.h"
#include "base/strings/utf_string_conversions.h"
#include "base/system/sys_info.h"
#include "chrome/browser/chromeos/arc/session/arc_session_manager.h"
#include "chrome/browser/chromeos/arc/test/test_arc_session_manager.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/scoped_set_running_on_chromeos_for_testing.h"
#include "chrome/browser/ui/webui/settings/chromeos/calculator/size_calculator_test_api.h"
#include "chrome/browser/ui/webui/settings/chromeos/device_storage_handler.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/webui_url_constants.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "components/arc/arc_service_manager.h"
#include "components/arc/test/fake_arc_session.h"
#include "content/public/browser/web_ui_data_source.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_web_ui.h"
#include "storage/browser/file_system/external_mount_points.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/text/bytes_formatting.h"
namespace chromeos {
namespace settings {
namespace {
const char kLsbRelease[] =
"CHROMEOS_RELEASE_NAME=Chrome OS\n"
"CHROMEOS_RELEASE_VERSION=1.2.3.4\n";
class TestStorageHandler : public StorageHandler {
public:
explicit TestStorageHandler(Profile* profile,
content::WebUIDataSource* html_source)
: StorageHandler(profile, html_source) {}
// Pull WebUIMessageHandler::set_web_ui() into public so tests can call it.
using StorageHandler::RoundByteSize;
using StorageHandler::set_web_ui;
};
class StorageHandlerTest : public testing::Test {
public:
StorageHandlerTest() = default;
~StorageHandlerTest() override = default;
void SetUp() override {
// Need to initialize DBusThreadManager before ArcSessionManager's
// constructor calls DBusThreadManager::Get().
chromeos::DBusThreadManager::Initialize();
// The storage handler requires an instance of DiskMountManager,
// ArcServiceManager and ArcSessionManager.
chromeos::disks::DiskMountManager::InitializeForTesting(
new file_manager::FakeDiskMountManager);
arc_service_manager_ = std::make_unique<arc::ArcServiceManager>();
arc_session_manager_ = arc::CreateTestArcSessionManager(
std::make_unique<arc::ArcSessionRunner>(
base::BindRepeating(arc::FakeArcSession::Create)));
// Initialize profile.
profile_manager_ = std::make_unique<TestingProfileManager>(
TestingBrowserProcess::GetGlobal());
ASSERT_TRUE(profile_manager_->SetUp());
profile_ = profile_manager_->CreateTestingProfile("p1");
// Initialize storage handler.
content::WebUIDataSource* html_source =
content::WebUIDataSource::Create(chrome::kChromeUIOSSettingsHost);
handler_ = std::make_unique<TestStorageHandler>(profile_, html_source);
handler_->set_web_ui(&web_ui_);
handler_->AllowJavascriptForTesting();
// Initialize tests APIs.
size_stat_test_api_ = std::make_unique<calculator::SizeStatTestAPI>(
handler_.get(), new calculator::SizeStatCalculator(profile_));
my_files_size_test_api_ = std::make_unique<calculator::MyFilesSizeTestAPI>(
handler_.get(), new calculator::MyFilesSizeCalculator(profile_));
browsing_data_size_test_api_ =
std::make_unique<calculator::BrowsingDataSizeTestAPI>(
handler_.get(),
new calculator::BrowsingDataSizeCalculator(profile_));
apps_size_test_api_ = std::make_unique<calculator::AppsSizeTestAPI>(
handler_.get(), new calculator::AppsSizeCalculator(profile_));
crostini_size_test_api_ = std::make_unique<calculator::CrostiniSizeTestAPI>(
handler_.get(), new calculator::CrostiniSizeCalculator(profile_));
other_users_size_test_api_ =
std::make_unique<calculator::OtherUsersSizeTestAPI>(
handler_.get(), new calculator::OtherUsersSizeCalculator());
// Create and register My files directory.
// By emulating chromeos running, GetMyFilesFolderForProfile will return the
// profile's temporary location instead of $HOME/Downloads.
chromeos::ScopedSetRunningOnChromeOSForTesting fake_release(kLsbRelease,
base::Time());
const base::FilePath my_files_path =
file_manager::util::GetMyFilesFolderForProfile(profile_);
CHECK(base::CreateDirectory(my_files_path));
CHECK(storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem(
file_manager::util::GetDownloadsMountPointName(profile_),
storage::kFileSystemTypeNativeLocal, storage::FileSystemMountOption(),
my_files_path));
}
void TearDown() override {
handler_.reset();
size_stat_test_api_.reset();
my_files_size_test_api_.reset();
browsing_data_size_test_api_.reset();
apps_size_test_api_.reset();
crostini_size_test_api_.reset();
other_users_size_test_api_.reset();
arc_session_manager_.reset();
arc_service_manager_.reset();
chromeos::disks::DiskMountManager::Shutdown();
storage::ExternalMountPoints::GetSystemInstance()->RevokeAllFileSystems();
chromeos::DBusThreadManager::Shutdown();
}
protected:
// From a given amount of total size and available size as input, returns the
// space state determined by the OnGetSizeState function.
int GetSpaceState(int64_t* total_size, int64_t* available_size) {
size_stat_test_api_->SimulateOnGetSizeStat(total_size, available_size);
task_environment_.RunUntilIdle();
const base::Value* dictionary =
GetWebUICallbackMessage("storage-size-stat-changed");
EXPECT_TRUE(dictionary) << "No 'storage-size-stat-changed' callback";
int space_state = dictionary->FindKey("spaceState")->GetInt();
return space_state;
}
// Expects a callback message with a given |event_name|. A non null
// base::Value is returned if the callback message is found and has associated
// data.
const base::Value* GetWebUICallbackMessage(const std::string& event_name) {
for (auto it = web_ui_.call_data().rbegin();
it != web_ui_.call_data().rend(); ++it) {
const content::TestWebUI::CallData* data = it->get();
std::string name;
if (data->function_name() != "cr.webUIListenerCallback" ||
!data->arg1()->GetAsString(&name)) {
continue;
}
if (name == event_name)
return data->arg2();
}
return nullptr;
}
// Get the path to file manager's test data directory.
base::FilePath GetTestDataFilePath(const std::string& file_name) {
// Get the path to file manager's test data directory.
base::FilePath source_dir;
CHECK(base::PathService::Get(base::DIR_SOURCE_ROOT, &source_dir));
base::FilePath test_data_dir = source_dir.AppendASCII("chrome")
.AppendASCII("test")
.AppendASCII("data")
.AppendASCII("chromeos")
.AppendASCII("file_manager");
// Return full test data path to the given |file_name|.
return test_data_dir.Append(base::FilePath::FromUTF8Unsafe(file_name));
}
// Copy a file from the file manager's test data directory to the specified
// target_path.
void AddFile(const std::string& file_name,
int64_t expected_size,
base::FilePath target_path) {
const base::FilePath entry_path = GetTestDataFilePath(file_name);
target_path = target_path.AppendASCII(file_name);
ASSERT_TRUE(base::CopyFile(entry_path, target_path))
<< "Copy from " << entry_path.value() << " to " << target_path.value()
<< " failed.";
// Verify file size.
base::stat_wrapper_t stat;
const int res = base::File::Lstat(target_path.value().c_str(), &stat);
ASSERT_FALSE(res < 0) << "Couldn't stat" << target_path.value();
ASSERT_EQ(expected_size, stat.st_size);
}
std::unique_ptr<TestStorageHandler> handler_;
content::TestWebUI web_ui_;
content::BrowserTaskEnvironment task_environment_;
std::unique_ptr<TestingProfileManager> profile_manager_;
Profile* profile_;
std::unique_ptr<calculator::SizeStatTestAPI> size_stat_test_api_;
std::unique_ptr<calculator::MyFilesSizeTestAPI> my_files_size_test_api_;
std::unique_ptr<calculator::BrowsingDataSizeTestAPI>
browsing_data_size_test_api_;
std::unique_ptr<calculator::AppsSizeTestAPI> apps_size_test_api_;
std::unique_ptr<calculator::CrostiniSizeTestAPI> crostini_size_test_api_;
std::unique_ptr<calculator::OtherUsersSizeTestAPI> other_users_size_test_api_;
private:
std::unique_ptr<arc::ArcServiceManager> arc_service_manager_;
std::unique_ptr<arc::ArcSessionManager> arc_session_manager_;
DISALLOW_COPY_AND_ASSIGN(StorageHandlerTest);
};
TEST_F(StorageHandlerTest, RoundByteSize) {
static const struct {
int64_t bytes;
const char* expected;
} cases[] = {
{0, "0 B"},
{3, "4 B"},
{4, "4 B"},
{5, "8 B"},
{8 * 1024 - 1, "8.0 KB"},
{8 * 1024, "8.0 KB"},
{8 * 1024 + 1, "16.0 KB"},
{31 * 1024 * 1024, "32.0 MB"},
{32 * 1024 * 1024, "32.0 MB"},
{50 * 1024 * 1024, "64.0 MB"},
{65LL * 1024 * 1024 * 1024, "128 GB"},
{130LL * 1024 * 1024 * 1024, "256 GB"},
{130LL * 1024 * 1024 * 1024, "256 GB"},
{1LL * 1024 * 1024 * 1024 * 1024, "1.0 TB"},
{1LL * 1024 * 1024 * 1024 * 1024 + 1, "2.0 TB"},
{(1LL << 61) + 1, "4,096 PB"},
};
for (auto& c : cases) {
int64_t rounded_bytes = handler_->RoundByteSize(c.bytes);
EXPECT_EQ(base::ASCIIToUTF16(c.expected), ui::FormatBytes(rounded_bytes));
}
}
TEST_F(StorageHandlerTest, GlobalSizeStat) {
// Get local filesystem storage statistics.
const base::FilePath mount_path =
file_manager::util::GetMyFilesFolderForProfile(profile_);
int64_t total_size = base::SysInfo::AmountOfTotalDiskSpace(mount_path);
int64_t available_size = base::SysInfo::AmountOfFreeDiskSpace(mount_path);
// Round the total size.
int64_t rounded_total_size = handler_->RoundByteSize(total_size);
int64_t used_size = rounded_total_size - available_size;
double used_ratio = static_cast<double>(used_size) / rounded_total_size;
// Get statistics from storage handler's UpdateSizeStat.
size_stat_test_api_->StartCalculation();
task_environment_.RunUntilIdle();
const base::Value* dictionary =
GetWebUICallbackMessage("storage-size-stat-changed");
ASSERT_TRUE(dictionary) << "No 'storage-size-stat-changed' callback";
const std::string& storage_handler_available_size =
dictionary->FindKey("availableSize")->GetString();
const std::string& storage_handler_used_size =
dictionary->FindKey("usedSize")->GetString();
double storage_handler_used_ratio =
dictionary->FindKey("usedRatio")->GetDouble();
EXPECT_EQ(ui::FormatBytes(available_size),
base::ASCIIToUTF16(storage_handler_available_size));
EXPECT_EQ(ui::FormatBytes(used_size),
base::ASCIIToUTF16(storage_handler_used_size));
double diff = used_ratio > storage_handler_used_ratio
? used_ratio - storage_handler_used_ratio
: storage_handler_used_ratio - used_ratio;
// Running the test while writing data on disk (~400MB/s), the difference
// between the values returned by the two AmountOfFreeDiskSpace calls is never
// more than 100KB. By expecting diff to be less than 100KB /
// rounded_total_size, the test is very unlikely to be flaky.
EXPECT_LE(diff, static_cast<double>(100 * 1024) / rounded_total_size);
}
TEST_F(StorageHandlerTest, StorageSpaceState) {
// Less than 512 MB available, space state is critically low.
int64_t total_size = 1024 * 1024 * 1024;
int64_t available_size = 512 * 1024 * 1024 - 1;
int space_state = GetSpaceState(&total_size, &available_size);
EXPECT_EQ(static_cast<int>(StorageSpaceState::kStorageSpaceCriticallyLow),
space_state);
// Less than 1GB available, space state is low.
available_size = 512 * 1024 * 1024;
space_state = GetSpaceState(&total_size, &available_size);
EXPECT_EQ(static_cast<int>(StorageSpaceState::kStorageSpaceLow), space_state);
available_size = 1024 * 1024 * 1024 - 1;
space_state = GetSpaceState(&total_size, &available_size);
EXPECT_EQ(static_cast<int>(StorageSpaceState::kStorageSpaceLow), space_state);
// From 1GB, normal space state.
available_size = 1024 * 1024 * 1024;
space_state = GetSpaceState(&total_size, &available_size);
EXPECT_EQ(static_cast<int>(StorageSpaceState::kStorageSpaceNormal),
space_state);
}
TEST_F(StorageHandlerTest, MyFilesSize) {
base::ScopedAllowBlockingForTesting allow_blocking;
const base::FilePath my_files_path =
file_manager::util::GetMyFilesFolderForProfile(profile_);
const base::FilePath downloads_path =
file_manager::util::GetDownloadsFolderForProfile(profile_);
const base::FilePath android_files_path =
profile_->GetPath().Append("AndroidFiles");
const base::FilePath android_files_download_path =
android_files_path.Append("Download");
// Create directories.
CHECK(base::CreateDirectory(downloads_path));
CHECK(base::CreateDirectory(android_files_path));
CHECK(base::CreateDirectory(android_files_download_path));
// Register android files mount point.
CHECK(storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem(
file_manager::util::GetAndroidFilesMountPointName(),
storage::kFileSystemTypeNativeLocal, storage::FileSystemMountOption(),
android_files_path));
// Add files in My files and android files.
AddFile("random.bin", 8092, my_files_path); // ~7.9 KB
AddFile("tall.pdf", 15271, android_files_path); // ~14.9 KB
// Add file in Downloads and simulate bind mount with
// [android files]/Download.
AddFile("video.ogv", 59943, downloads_path); // ~58.6 KB
AddFile("video.ogv", 59943, android_files_download_path);
// Calculate My files size.
my_files_size_test_api_->StartCalculation();
task_environment_.RunUntilIdle();
const base::Value* callback =
GetWebUICallbackMessage("storage-my-files-size-changed");
ASSERT_TRUE(callback) << "No 'storage-my-files-size-changed' callback";
// Check return value.
EXPECT_EQ("81.4 KB", callback->GetString());
}
TEST_F(StorageHandlerTest, AppsExtensionsSize) {
// The data for apps and extensions apps_size_test_api_installed from the
// webstore is stored in the Extensions folder. Add data at a random location
// in the Extensions folder and check UI callback message.
const base::FilePath extensions_data_path =
profile_->GetPath()
.AppendASCII("Extensions")
.AppendASCII("fake_extension_id");
CHECK(base::CreateDirectory(extensions_data_path));
AddFile("id3Audio.mp3", 180999, extensions_data_path); // ~177 KB
// Calculate web store apps and extensions size.
apps_size_test_api_->StartCalculation();
task_environment_.RunUntilIdle();
const base::Value* callback =
GetWebUICallbackMessage("storage-apps-size-changed");
ASSERT_TRUE(callback) << "No 'storage-apps-size-changed' callback";
// Check return value.
EXPECT_EQ("177 KB", callback->GetString());
// Simulate android apps size callback.
// 592840 + 25284 + 9987 = 628111 ~613 KB.
apps_size_test_api_->SimulateOnGetAndroidAppsSize(true /* succeeded */,
592840, 25284, 9987);
task_environment_.RunUntilIdle();
callback = GetWebUICallbackMessage("storage-apps-size-changed");
ASSERT_TRUE(callback) << "No 'storage-apps-size-changed' callback";
// Check return value.
EXPECT_EQ("790 KB", callback->GetString());
// Add more data in the Extensions folder. Android is not running and the size
// of android apps is back down to 0 B.
AddFile("video_long.ogv", 230096, extensions_data_path); // ~225 KB
// Calculate web store apps and extensions size.
apps_size_test_api_->StartCalculation();
task_environment_.RunUntilIdle();
callback = GetWebUICallbackMessage("storage-apps-size-changed");
ASSERT_TRUE(callback) << "No 'storage-apps-size-changed' callback";
// Check return value.
EXPECT_EQ("401 KB", callback->GetString());
}
TEST_F(StorageHandlerTest, SystemSize) {
// The "System" row on the storage page displays the difference between the
// total amount of used space and the sum of the sizes of the different
// storage items of the storage page (My files, Browsing data, apps etc...)
// This test simulates callbacks from each one of these storage items; the
// calculation of the "System" size should only happen when all of the other
// storage items have been calculated.
const int64_t KB = 1024;
const int64_t MB = 1024 * KB;
const int64_t GB = 1024 * MB;
const int64_t TB = 1024 * GB;
// Simulate size stat callback.
int64_t total_size = TB;
int64_t available_size = 100 * GB;
size_stat_test_api_->SimulateOnGetSizeStat(&total_size, &available_size);
const base::Value* callback =
GetWebUICallbackMessage("storage-size-stat-changed");
ASSERT_TRUE(callback) << "No 'storage-size-stat-changed' callback";
EXPECT_EQ("100 GB", callback->FindKey("availableSize")->GetString());
EXPECT_EQ("924 GB", callback->FindKey("usedSize")->GetString());
// Expect no system size callback until every other item has been updated.
ASSERT_FALSE(GetWebUICallbackMessage("storage-system-size-changed"));
// Simulate my files size callback.
my_files_size_test_api_->SimulateOnGetTotalBytes(400 * GB);
callback = GetWebUICallbackMessage("storage-my-files-size-changed");
ASSERT_TRUE(callback) << "No 'storage-my-files-size-changed' callback";
EXPECT_EQ("400 GB", callback->GetString());
ASSERT_FALSE(GetWebUICallbackMessage("storage-system-size-changed"));
// Simulate browsing data callbacks. Has to be called with
// both |is_data_site| = true and false.
browsing_data_size_test_api_->SimulateOnGetBrowsingDataSize(
true /* is_site_data */, 10 * GB);
ASSERT_FALSE(GetWebUICallbackMessage("storage-browsing-data-size-changed"));
ASSERT_FALSE(GetWebUICallbackMessage("storage-system-size-changed"));
browsing_data_size_test_api_->SimulateOnGetBrowsingDataSize(
false /* is_site_data */, 14 * GB);
callback = GetWebUICallbackMessage("storage-browsing-data-size-changed");
ASSERT_TRUE(callback) << "No 'storage-browsing-data-size-changed' callback";
EXPECT_EQ("24.0 GB", callback->GetString());
ASSERT_FALSE(GetWebUICallbackMessage("storage-system-size-changed"));
// Simulate apps and extensions size callbacks.
apps_size_test_api_->SimulateOnGetAppsSize(29 * GB);
apps_size_test_api_->SimulateOnGetAndroidAppsSize(false, 0, 0, 0);
callback = GetWebUICallbackMessage("storage-apps-size-changed");
ASSERT_TRUE(callback) << "No 'storage-apps-size-changed' callback";
EXPECT_EQ("29.0 GB", callback->GetString());
ASSERT_FALSE(GetWebUICallbackMessage("storage-system-size-changed"));
apps_size_test_api_->SimulateOnGetAndroidAppsSize(
true /* succeeded */, 724 * MB, 100 * MB, 200 * MB);
callback = GetWebUICallbackMessage("storage-apps-size-changed");
ASSERT_TRUE(callback) << "No 'storage-apps-size-changed' callback";
EXPECT_EQ("30.0 GB", callback->GetString());
ASSERT_FALSE(GetWebUICallbackMessage("storage-system-size-changed"));
// Simulate crostini size callback.
crostini_size_test_api_->SimulateOnGetCrostiniSize(50 * GB);
callback = GetWebUICallbackMessage("storage-crostini-size-changed");
ASSERT_TRUE(callback) << "No 'storage-crostini-size-changed' callback";
EXPECT_EQ("50.0 GB", callback->GetString());
ASSERT_FALSE(GetWebUICallbackMessage("storage-system-size-changed"));
// Simulate other users size callback. No callback message until the sizes of
// every users is calculated.
std::vector<int64_t> other_user_sizes =
std::vector<int64_t>{200 * GB, 50 * GB, 50 * GB};
other_users_size_test_api_->InitializeOtherUserSize(other_user_sizes.size());
for (std::size_t i = 0; i < other_user_sizes.size(); i++) {
cryptohome::BaseReply result;
result.set_error(cryptohome::CRYPTOHOME_ERROR_NOT_SET);
cryptohome::GetAccountDiskUsageReply* usage_reply =
result.MutableExtension(cryptohome::GetAccountDiskUsageReply::reply);
usage_reply->set_size(other_user_sizes[i]);
base::Optional<cryptohome::BaseReply> reply = std::move(result);
other_users_size_test_api_->SimulateOnGetOtherUserSize(reply);
if (i < other_user_sizes.size() - 1) {
ASSERT_FALSE(GetWebUICallbackMessage("storage-other-users-size-changed"));
ASSERT_FALSE(GetWebUICallbackMessage("storage-system-size-changed"));
} else {
// When the size of the last user's cryptohome is calculated, we expect a
// callback with the "Other users" size.
callback = GetWebUICallbackMessage("storage-other-users-size-changed");
ASSERT_TRUE(callback) << "No 'storage-other-users-size-changed' callback";
EXPECT_EQ("300 GB", callback->GetString());
// Every item size has been calculated, system size should also be
// updated.
callback = GetWebUICallbackMessage("storage-system-size-changed");
ASSERT_TRUE(callback) << "No 'storage-system-size-changed' callback";
EXPECT_EQ("120 GB", callback->GetString());
}
}
// If there's an error while calculating the size of browsing data, the size
// of browsing data and system should be displayed as "Unknown".
browsing_data_size_test_api_->SimulateOnGetBrowsingDataSize(
true /* is_site_data */, -1);
callback = GetWebUICallbackMessage("storage-browsing-data-size-changed");
ASSERT_TRUE(callback) << "No 'storage-browsing-data-size-changed' callback";
EXPECT_EQ("Unknown", callback->GetString());
// The missing 24.0 GB of browsing data should be reflected in the system
// section instead. We expect the displayed size to be 100 + 24 GB.
callback = GetWebUICallbackMessage("storage-system-size-changed");
ASSERT_TRUE(callback) << "No 'storage-system-size-changed' callback";
EXPECT_EQ("144 GB", callback->GetString());
// No error while recalculating browsing data size, the UI should be updated
// with the right sizes.
browsing_data_size_test_api_->SimulateOnGetBrowsingDataSize(
true /* is_site_data */, 10 * GB);
callback = GetWebUICallbackMessage("storage-browsing-data-size-changed");
ASSERT_TRUE(callback) << "No 'storage-browsing-data-size-changed' callback";
EXPECT_EQ("24.0 GB", callback->GetString());
callback = GetWebUICallbackMessage("storage-system-size-changed");
ASSERT_TRUE(callback) << "No 'storage-system-size-changed' callback";
EXPECT_EQ("120 GB", callback->GetString());
}
} // namespace
} // namespace settings
} // namespace chromeos