chromium/ash/components/arc/disk_space/arc_disk_space_bridge_unittest.cc

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ash/components/arc/disk_space/arc_disk_space_bridge.h"

#include <vector>

#include "ash/components/arc/arc_util.h"
#include "ash/components/arc/session/arc_bridge_service.h"
#include "ash/components/arc/session/arc_service_manager.h"
#include "ash/components/arc/test/connection_holder_util.h"
#include "ash/components/arc/test/fake_disk_space_instance.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind.h"
#include "base/test/scoped_chromeos_version_info.h"
#include "base/test/test_future.h"
#include "chromeos/ash/components/dbus/spaced/fake_spaced_client.h"
#include "chromeos/ash/components/dbus/spaced/spaced.pb.h"
#include "chromeos/ash/components/dbus/spaced/spaced_client.h"
#include "chromeos/ash/components/dbus/userdataauth/fake_userdataauth_client.h"
#include "chromeos/ash/components/dbus/userdataauth/userdataauth_client.h"
#include "components/user_prefs/test/test_browser_context_with_prefs.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace arc {
namespace {

constexpr int64_t kGiB = 1024 * 1024 * 1024;
constexpr int64_t kInitialFreeDiskSpace = 30 * kGiB;

base::test::ScopedChromeOSVersionInfo SetArcAndroidSdkVersionForTesting(
    int version) {
  return base::test::ScopedChromeOSVersionInfo(
      base::StringPrintf("CHROMEOS_ARC_ANDROID_SDK_VERSION=%d", version),
      base::Time::Now());
}

class ArcDiskSpaceBridgeTest : public testing::Test {
 protected:
  ArcDiskSpaceBridgeTest() = default;
  ArcDiskSpaceBridgeTest(const ArcDiskSpaceBridgeTest&) = delete;
  ArcDiskSpaceBridgeTest& operator=(const ArcDiskSpaceBridgeTest&) = delete;
  ~ArcDiskSpaceBridgeTest() override = default;

  const FakeDiskSpaceInstance* disk_space_instance() const {
    return &disk_space_instance_;
  }
  ArcDiskSpaceBridge* bridge() { return bridge_.get(); }

  void SetUp() override {
    ash::SpacedClient::InitializeFake();
    ash::FakeSpacedClient::Get()->set_free_disk_space(kInitialFreeDiskSpace);
    ash::UserDataAuthClient::InitializeFake();

    bridge_ = std::make_unique<ArcDiskSpaceBridge>(
        &context_, arc_service_manager_.arc_bridge_service());

    ArcServiceManager::Get()->arc_bridge_service()->disk_space()->SetInstance(
        &disk_space_instance_);
    WaitForInstanceReady(
        ArcServiceManager::Get()->arc_bridge_service()->disk_space());
  }

  void TearDown() override {
    bridge_.reset();
    ash::UserDataAuthClient::Shutdown();
    ash::SpacedClient::Shutdown();
  }

 private:
  content::BrowserTaskEnvironment task_environment_;
  ArcServiceManager arc_service_manager_;
  FakeDiskSpaceInstance disk_space_instance_;
  user_prefs::TestBrowserContextWithPrefs context_;
  std::unique_ptr<ArcDiskSpaceBridge> bridge_;
};

TEST_F(ArcDiskSpaceBridgeTest, IsQuotaSupported_Supported) {
  ash::FakeSpacedClient::Get()->set_quota_supported(true);
  ash::FakeUserDataAuthClient::TestApi::Get()->set_arc_quota_supported(true);

  base::test::TestFuture<bool> future;
  bridge()->IsQuotaSupported(future.GetCallback());
  EXPECT_TRUE(future.Get());
}

TEST_F(ArcDiskSpaceBridgeTest, IsQuotaSupported_NotSupportedInSpaced) {
  ash::FakeSpacedClient::Get()->set_quota_supported(false);
  ash::FakeUserDataAuthClient::TestApi::Get()->set_arc_quota_supported(true);

  base::test::TestFuture<bool> future;
  bridge()->IsQuotaSupported(future.GetCallback());
  EXPECT_FALSE(future.Get());
}

TEST_F(ArcDiskSpaceBridgeTest, IsQuotaSupported_NotSupportedInCryptohome) {
  ash::FakeSpacedClient::Get()->set_quota_supported(true);
  ash::FakeUserDataAuthClient::TestApi::Get()->set_arc_quota_supported(false);

  base::test::TestFuture<bool> future;
  bridge()->IsQuotaSupported(future.GetCallback());
  EXPECT_FALSE(future.Get());
}

TEST_F(ArcDiskSpaceBridgeTest, GetQuotaCurrentSpaceForGid_Success) {
  const std::vector<std::pair<uint32_t, int64_t>>
      valid_android_gid_and_expected_space = {
          {kAndroidGidStart, 100},
          {(kAndroidGidStart + kAndroidGidEnd) / 2, 200},
          {kAndroidGidEnd, 300},
      };
  for (const auto& [gid, space] : valid_android_gid_and_expected_space) {
    ash::FakeSpacedClient::Get()->set_quota_current_space_gid(
        gid + kArcGidShift, space);
  }

  for (const auto& [gid, space] : valid_android_gid_and_expected_space) {
    base::test::TestFuture<int64_t> future;
    bridge()->GetQuotaCurrentSpaceForGid(gid, future.GetCallback());
    EXPECT_EQ(future.Get(), space);
  }
}

TEST_F(ArcDiskSpaceBridgeTest, GetQuotaCurrentSpaceForGid_InvalidId) {
  constexpr uint32_t kInvalidAndroidGid = kAndroidGidEnd + 1;

  base::test::TestFuture<int64_t> future;
  bridge()->GetQuotaCurrentSpaceForGid(kInvalidAndroidGid,
                                       future.GetCallback());
  EXPECT_EQ(future.Get(), -1);
}

class ArcDiskSpaceBridgeWithArcVersionTest
    : public ArcDiskSpaceBridgeTest,
      public ::testing::WithParamInterface<int> {};

TEST_P(ArcDiskSpaceBridgeWithArcVersionTest,
       GetQuotaCurrentSpaceForUid_Success) {
  const int arc_sdk_version = GetParam();
  const auto scoped_version_info =
      SetArcAndroidSdkVersionForTesting(arc_sdk_version);
  const uint32_t kAndroidUidEnd = arc_sdk_version < kArcVersionT
                                      ? kAndroidUidEndBeforeT
                                      : kAndroidUidEndAfterT;

  const std::vector<std::pair<uint32_t, int64_t>>
      valid_android_uid_and_expected_space = {
          {kAndroidUidStart, 100},
          {(kAndroidUidStart + kAndroidUidEnd) / 2, 200},
          {kAndroidUidEnd, 300},
      };
  for (const auto& [uid, space] : valid_android_uid_and_expected_space) {
    ash::FakeSpacedClient::Get()->set_quota_current_space_uid(
        uid + kArcUidShift, space);
  }

  for (const auto& [uid, space] : valid_android_uid_and_expected_space) {
    base::test::TestFuture<int64_t> future;
    bridge()->GetQuotaCurrentSpaceForUid(uid, future.GetCallback());
    EXPECT_EQ(future.Get(), space);
  }
}

TEST_P(ArcDiskSpaceBridgeWithArcVersionTest,
       GetQuotaCurrentSpaceForUid_InvalidId) {
  const int arc_sdk_version = GetParam();
  const auto scoped_version_info =
      SetArcAndroidSdkVersionForTesting(arc_sdk_version);
  const uint32_t kAndroidUidEnd = arc_sdk_version < kArcVersionT
                                      ? kAndroidUidEndBeforeT
                                      : kAndroidUidEndAfterT;

  const uint32_t kInvalidAndroidUid = kAndroidUidEnd + 1;

  base::test::TestFuture<int64_t> future;
  bridge()->GetQuotaCurrentSpaceForUid(kInvalidAndroidUid,
                                       future.GetCallback());
  EXPECT_EQ(future.Get(), -1);
}

TEST_P(ArcDiskSpaceBridgeWithArcVersionTest,
       GetQuotaCurrentSpaceForProjectId_Success) {
  const int arc_sdk_version = GetParam();
  const auto scoped_version_info =
      SetArcAndroidSdkVersionForTesting(arc_sdk_version);
  const uint32_t kProjectIdForAndroidAppsEnd =
      arc_sdk_version < kArcVersionT ? kProjectIdForAndroidAppsEndBeforeT
                                     : kProjectIdForAndroidAppsEndAfterT;

  const std::vector<std::pair<uint32_t, int64_t>>
      valid_android_project_id_and_expected_space = {
          {kProjectIdForAndroidFilesStart, 100},
          {(kProjectIdForAndroidFilesStart + kProjectIdForAndroidFilesEnd) / 2,
           200},
          {kProjectIdForAndroidFilesEnd, 300},
          {kProjectIdForAndroidAppsStart, 400},
          {(kProjectIdForAndroidAppsStart + kProjectIdForAndroidAppsEnd) / 2,
           500},
          {kProjectIdForAndroidAppsEnd, 600},
      };
  for (const auto& [project_id, space] :
       valid_android_project_id_and_expected_space) {
    ash::FakeSpacedClient::Get()->set_quota_current_space_project_id(project_id,
                                                                     space);
  }

  for (const auto& [project_id, space] :
       valid_android_project_id_and_expected_space) {
    base::test::TestFuture<int64_t> future;
    bridge()->GetQuotaCurrentSpaceForProjectId(project_id,
                                               future.GetCallback());
    EXPECT_EQ(future.Get(), space);
  }
}

TEST_P(ArcDiskSpaceBridgeWithArcVersionTest,
       GetQuotaCurrentSpaceForProjectId_Invalid) {
  const int arc_sdk_version = GetParam();
  const auto scoped_version_info =
      SetArcAndroidSdkVersionForTesting(arc_sdk_version);
  const uint32_t kProjectIdForAndroidAppsEnd =
      arc_sdk_version < kArcVersionT ? kProjectIdForAndroidAppsEndBeforeT
                                     : kProjectIdForAndroidAppsEndAfterT;

  const std::vector<uint32_t> invalid_android_project_id = {
      kProjectIdForAndroidFilesStart - 1,
      kProjectIdForAndroidFilesEnd + 1,
      kProjectIdForAndroidAppsStart - 1,
      kProjectIdForAndroidAppsEnd + 1,
  };

  for (const auto project_id : invalid_android_project_id) {
    base::test::TestFuture<int64_t> future;
    bridge()->GetQuotaCurrentSpaceForProjectId(project_id,
                                               future.GetCallback());
    EXPECT_EQ(future.Get(), -1);
  }
}

TEST_P(ArcDiskSpaceBridgeWithArcVersionTest, GetQuotaCurrentSpacesForIds) {
  const int arc_sdk_version = GetParam();
  const auto scoped_version_info =
      SetArcAndroidSdkVersionForTesting(arc_sdk_version);
  const uint32_t kAndroidUidEnd = arc_sdk_version < kArcVersionT
                                      ? kAndroidUidEndBeforeT
                                      : kAndroidUidEndAfterT;
  const uint32_t kProjectIdForAndroidAppsEnd =
      arc_sdk_version < kArcVersionT ? kProjectIdForAndroidAppsEndBeforeT
                                     : kProjectIdForAndroidAppsEndAfterT;

  const std::vector<std::pair<uint32_t, int64_t>>
      valid_android_uid_and_expected_space = {
          {kAndroidUidStart, 100},
          {(kAndroidUidStart + kAndroidUidEnd) / 2, 200},
          {kAndroidUidEnd, 300},
      };
  const std::vector<std::pair<uint32_t, int64_t>>
      valid_android_gid_and_expected_space = {
          {kAndroidGidStart, 100},
          {(kAndroidGidStart + kAndroidGidEnd) / 2, 200},
          {kAndroidGidEnd, 300},
      };
  const std::vector<std::pair<uint32_t, int64_t>>
      valid_android_project_id_and_expected_space = {
          {kProjectIdForAndroidFilesStart, 100},
          {(kProjectIdForAndroidFilesStart + kProjectIdForAndroidFilesEnd) / 2,
           200},
          {kProjectIdForAndroidFilesEnd, 300},
          {kProjectIdForAndroidAppsStart, 400},
          {(kProjectIdForAndroidAppsStart + kProjectIdForAndroidAppsEnd) / 2,
           500},
          {kProjectIdForAndroidAppsEnd, 600},
      };

  std::vector<uint32_t> valid_uids;
  std::vector<uint32_t> valid_gids;
  std::vector<uint32_t> valid_project_ids;
  for (const auto& [uid, space] : valid_android_uid_and_expected_space) {
    valid_uids.push_back(uid);
    ash::FakeSpacedClient::Get()->set_quota_current_space_uid(
        uid + kArcUidShift, space);
  }
  for (const auto& [gid, space] : valid_android_gid_and_expected_space) {
    valid_gids.push_back(gid);
    ash::FakeSpacedClient::Get()->set_quota_current_space_gid(
        gid + kArcGidShift, space);
  }
  for (const auto& [project_id, space] :
       valid_android_project_id_and_expected_space) {
    valid_project_ids.push_back(project_id);
    ash::FakeSpacedClient::Get()->set_quota_current_space_project_id(project_id,
                                                                     space);
  }

  // Check that GetQuotaCurrentSpacesForIds returns null when there is an
  // invalid ID in any of the input lists of IDs.
  std::vector<uint32_t> invalid_uids(valid_uids);
  invalid_uids.push_back(kAndroidUidEnd + 1);
  base::test::TestFuture<mojom::QuotaSpacesPtr> invalid_uid_future;
  bridge()->GetQuotaCurrentSpacesForIds(invalid_uids, valid_gids,
                                        valid_project_ids,
                                        invalid_uid_future.GetCallback());
  EXPECT_TRUE(invalid_uid_future.Get().is_null());

  std::vector<uint32_t> invalid_gids(valid_gids);
  invalid_gids.push_back(kAndroidGidEnd + 1);
  base::test::TestFuture<mojom::QuotaSpacesPtr> invalid_gid_future;
  bridge()->GetQuotaCurrentSpacesForIds(valid_uids, invalid_gids,
                                        valid_project_ids,
                                        invalid_gid_future.GetCallback());
  EXPECT_TRUE(invalid_gid_future.Get().is_null());

  std::vector<uint32_t> invalid_project_ids(valid_project_ids);
  invalid_project_ids.push_back(kProjectIdForAndroidAppsEnd + 1);
  base::test::TestFuture<mojom::QuotaSpacesPtr> invalid_project_id_future;
  bridge()->GetQuotaCurrentSpacesForIds(
      valid_uids, valid_gids, invalid_project_ids,
      invalid_project_id_future.GetCallback());
  EXPECT_TRUE(invalid_project_id_future.Get().is_null());

  // Check the behavior of GetQuotaCurrentSpacesForIds for the lists of all
  // valid IDs.
  base::test::TestFuture<mojom::QuotaSpacesPtr> success_future;
  bridge()->GetQuotaCurrentSpacesForIds(
      valid_uids, valid_gids, valid_project_ids, success_future.GetCallback());
  EXPECT_FALSE(success_future.Get().is_null());
  for (size_t i = 0; i < valid_uids.size(); i++) {
    EXPECT_EQ(success_future.Get()->curspaces_for_uids[i],
              valid_android_uid_and_expected_space[i].second);
  }
  for (size_t i = 0; i < valid_gids.size(); i++) {
    EXPECT_EQ(success_future.Get()->curspaces_for_gids[i],
              valid_android_gid_and_expected_space[i].second);
  }
  for (size_t i = 0; i < valid_project_ids.size(); i++) {
    EXPECT_EQ(success_future.Get()->curspaces_for_project_ids[i],
              valid_android_project_id_and_expected_space[i].second);
  }
}

INSTANTIATE_TEST_SUITE_P(ArcDiskSpaceBridgeTestForR,
                         ArcDiskSpaceBridgeWithArcVersionTest,
                         testing::Values(kArcVersionR));

INSTANTIATE_TEST_SUITE_P(ArcDiskSpaceBridgeTestForT,
                         ArcDiskSpaceBridgeWithArcVersionTest,
                         testing::Values(kArcVersionT));

TEST_F(ArcDiskSpaceBridgeTest, GetApplicationsSize) {
  ASSERT_NE(nullptr, bridge());
  base::test::TestFuture<bool, mojom::ApplicationsSizePtr> future;
  EXPECT_TRUE(bridge()->GetApplicationsSize(future.GetCallback()));
  EXPECT_EQ(1u, disk_space_instance()->num_get_applications_size_called());
  EXPECT_TRUE(future.Get<0>());
}

TEST_F(ArcDiskSpaceBridgeTest, SendResizeStorageBalloon) {
  // After the instance is ready, the bridge sets the initial balloon size.
  EXPECT_EQ(disk_space_instance()->free_space_bytes(),
            kInitialFreeDiskSpace - kStorageBalloonFreeSpaceBufferSizeInBytes);

  // Chrome receives StatefulDiskSpaceUpdate D-Bus signal with an updated
  // host-side free disk space.
  constexpr int64_t kNewFreeDiskSpace = 29 * kGiB;
  spaced::StatefulDiskSpaceUpdate update;
  update.set_free_space_bytes(kNewFreeDiskSpace);
  update.set_state(spaced::StatefulDiskSpaceState::NORMAL);
  ash::FakeSpacedClient::Get()->SendStatefulDiskSpaceUpdate(update);

  // After receiving the signal, the balloon size is updated accordingly.
  EXPECT_EQ(disk_space_instance()->free_space_bytes(),
            kNewFreeDiskSpace - kStorageBalloonFreeSpaceBufferSizeInBytes);
}

}  // namespace
}  // namespace arc