chromium/ash/components/arc/session/arc_vm_client_adapter_unittest.cc

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

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "ash/components/arc/session/arc_vm_client_adapter.h"

#include <inttypes.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>

#include <limits>
#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "ash/components/arc/arc_features.h"
#include "ash/components/arc/arc_util.h"
#include "ash/components/arc/session/arc_bridge_service.h"
#include "ash/components/arc/session/arc_dlc_installer.h"
#include "ash/components/arc/session/arc_service_manager.h"
#include "ash/components/arc/session/arc_session.h"
#include "ash/components/arc/session/file_system_status.h"
#include "ash/components/arc/test/connection_holder_util.h"
#include "ash/components/arc/test/fake_app_host.h"
#include "ash/components/arc/test/fake_app_instance.h"
#include "ash/constants/ash_features.h"
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/posix/safe_strerror.h"
#include "base/process/process_metrics.h"
#include "base/ranges/algorithm.h"
#include "base/run_loop.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/task/current_thread.h"
#include "base/test/bind.h"
#include "base/test/scoped_chromeos_version_info.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/scoped_run_loop_timeout.h"
#include "base/time/time.h"
#include "base/uuid.h"
#include "chromeos/ash/components/cryptohome/cryptohome_parameters.h"
#include "chromeos/ash/components/dbus/concierge/fake_concierge_client.h"
#include "chromeos/ash/components/dbus/debug_daemon/debug_daemon_client.h"
#include "chromeos/ash/components/dbus/debug_daemon/fake_debug_daemon_client.h"
#include "chromeos/ash/components/dbus/patchpanel/fake_patchpanel_client.h"
#include "chromeos/ash/components/dbus/session_manager/fake_session_manager_client.h"
#include "chromeos/ash/components/dbus/upstart/fake_upstart_client.h"
#include "components/user_manager/fake_user_manager.h"
#include "components/user_manager/scoped_user_manager.h"
#include "components/user_manager/user_names.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/cleanup/cleanup.h"

namespace arc {
namespace {

constexpr const char kArcVmBootNotificationServerAddressPrefix[] =
    "\0test_arcvm_boot_notification_server";

// Disk path contained in CreateDiskImageResponse().
constexpr const char kCreatedDiskImagePath[] = "test/data.img";

constexpr const char kUserIdHash[] = "this_is_a_valid_user_id_hash";
constexpr const char kSerialNumber[] = "AAAABBBBCCCCDDDD1234";
constexpr int64_t kCid = 123;

StartParams GetPopulatedStartParams() {
  StartParams params;
  params.native_bridge_experiment = false;
  params.lcd_density = 240;
  params.arc_file_picker_experiment = true;
  params.play_store_auto_update =
      StartParams::PlayStoreAutoUpdate::AUTO_UPDATE_ON;
  params.arc_custom_tabs_experiment = true;
  params.num_cores_disabled = 2;
  return params;
}

UpgradeParams GetPopulatedUpgradeParams() {
  UpgradeParams params;
  params.account_id = "fee1dead";
  params.skip_boot_completed_broadcast = true;
  params.packages_cache_mode = UpgradeParams::PackageCacheMode::COPY_ON_INIT;
  params.skip_gms_core_cache = true;
  params.management_transition = ArcManagementTransition::CHILD_TO_REGULAR;
  params.locale = "en-US";
  params.preferred_languages = {"en_US", "en", "ja"};
  params.is_demo_session = true;
  params.demo_session_apps_path = base::FilePath("/pato/to/demo.apk");
  return params;
}

vm_tools::concierge::CreateDiskImageResponse CreateDiskImageResponse(
    vm_tools::concierge::DiskImageStatus status) {
  vm_tools::concierge::CreateDiskImageResponse res;
  res.set_status(status);
  res.set_disk_path(base::FilePath(kCreatedDiskImagePath).AsUTF8Unsafe());
  return res;
}

std::string GenerateAbstractAddress() {
  std::string address(kArcVmBootNotificationServerAddressPrefix,
                      sizeof(kArcVmBootNotificationServerAddressPrefix) - 1);
  return address.append("-" +
                        base::Uuid::GenerateRandomV4().AsLowercaseString());
}

bool HasDiskImage(const vm_tools::concierge::StartArcVmRequest& request,
                  const std::string& disk_path) {
  for (const auto& disk : request.disks()) {
    if (disk.path() == disk_path) {
      return true;
    }
  }
  return false;
}

// A debugd client that can fail to start Concierge.
// TODO(khmel): Merge the feature to FakeDebugDaemonClient.
class TestDebugDaemonClient : public ash::FakeDebugDaemonClient {
 public:
  TestDebugDaemonClient() = default;

  TestDebugDaemonClient(const TestDebugDaemonClient&) = delete;
  TestDebugDaemonClient& operator=(const TestDebugDaemonClient&) = delete;

  ~TestDebugDaemonClient() override = default;

  void BackupArcBugReport(const cryptohome::AccountIdentifier& id,
                          chromeos::VoidDBusMethodCallback callback) override {
    backup_arc_bug_report_called_ = true;
    std::move(callback).Run(backup_arc_bug_report_result_);
  }

  bool backup_arc_bug_report_called() const {
    return backup_arc_bug_report_called_;
  }
  void set_backup_arc_bug_report_result(bool result) {
    backup_arc_bug_report_result_ = result;
  }

 private:
  bool backup_arc_bug_report_called_ = false;
  bool backup_arc_bug_report_result_ = true;
};

// A concierge that remembers the parameter passed to StartArcVm.
// TODO(khmel): Merge the feature to FakeConciergeClient.
class TestConciergeClient : public ash::FakeConciergeClient {
 public:
  static void Initialize() { new TestConciergeClient(); }

  TestConciergeClient(const TestConciergeClient&) = delete;
  TestConciergeClient& operator=(const TestConciergeClient&) = delete;

  ~TestConciergeClient() override = default;

  void StopVm(const vm_tools::concierge::StopVmRequest& request,
              chromeos::DBusMethodCallback<vm_tools::concierge::StopVmResponse>
                  callback) override {
    ++stop_vm_call_count_;
    stop_vm_request_ = request;
    ash::FakeConciergeClient::StopVm(request, std::move(callback));
    if (on_stop_vm_callback_ && (stop_vm_call_count_ == callback_count_)) {
      std::move(on_stop_vm_callback_).Run();
    }
  }

  void StartArcVm(
      const vm_tools::concierge::StartArcVmRequest& request,
      chromeos::DBusMethodCallback<vm_tools::concierge::StartVmResponse>
          callback) override {
    start_arc_vm_request_ = request;
    ash::FakeConciergeClient::StartArcVm(request, std::move(callback));
  }

  void ReclaimVmMemory(
      const vm_tools::concierge::ReclaimVmMemoryRequest& request,
      chromeos::DBusMethodCallback<vm_tools::concierge::ReclaimVmMemoryResponse>
          callback) override {
    ++reclaim_vm_count_;
    reclaim_vm_request_ = request;
    ash::FakeConciergeClient::ReclaimVmMemory(request, std::move(callback));
  }

  int stop_vm_call_count() const { return stop_vm_call_count_; }

  const vm_tools::concierge::StartArcVmRequest& start_arc_vm_request() const {
    return start_arc_vm_request_;
  }

  const vm_tools::concierge::StopVmRequest& stop_vm_request() const {
    return stop_vm_request_;
  }

  const vm_tools::concierge::ReclaimVmMemoryRequest& reclaim_vm_request()
      const {
    return reclaim_vm_request_;
  }

  int reclaim_vm_call_count() const { return reclaim_vm_count_; }

  // Set a callback to be run when stop_vm_call_count() == count.
  void set_on_stop_vm_callback(base::OnceClosure callback, int count) {
    on_stop_vm_callback_ = std::move(callback);
    DCHECK_NE(0, count);
    callback_count_ = count;
  }

 private:
  TestConciergeClient()
      : ash::FakeConciergeClient(/*fake_cicerone_client=*/nullptr) {}

  int stop_vm_call_count_ = 0;
  // When callback_count_ == 0, the on_stop_vm_callback_ is not run.
  int callback_count_ = 0;
  vm_tools::concierge::StartArcVmRequest start_arc_vm_request_;
  vm_tools::concierge::StopVmRequest stop_vm_request_;
  vm_tools::concierge::ReclaimVmMemoryRequest reclaim_vm_request_;
  int reclaim_vm_count_ = 0;
  base::OnceClosure on_stop_vm_callback_;
};

// A fake ArcVmBootNotificationServer that listens on an UDS and records
// connections and the data sent to it.
class TestArcVmBootNotificationServer
    : public base::MessagePumpForUI::FdWatcher {
 public:
  TestArcVmBootNotificationServer() = default;
  ~TestArcVmBootNotificationServer() override { Stop(); }
  TestArcVmBootNotificationServer(const TestArcVmBootNotificationServer&) =
      delete;
  TestArcVmBootNotificationServer& operator=(
      const TestArcVmBootNotificationServer&) = delete;

  // Creates a socket and binds it to a name in the abstract namespace, then
  // starts listening to the socket on another thread.
  void Start(const std::string& abstract_addr) {
    fd_.reset(socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0));
    ASSERT_TRUE(fd_.is_valid())
        << "open failed with " << base::safe_strerror(errno);

    sockaddr_un addr{.sun_family = AF_UNIX};
    ASSERT_LT(abstract_addr.size(), sizeof(addr.sun_path))
        << "abstract_addr is too long: " << abstract_addr;
    ASSERT_EQ('\0', abstract_addr[0])
        << "abstract_addr is not abstract: " << abstract_addr;
    memset(addr.sun_path, 0, sizeof(addr.sun_path));
    memcpy(addr.sun_path, abstract_addr.data(), abstract_addr.size());
    LOG(INFO) << "Abstract address: \\0" << &(addr.sun_path[1]);

    ASSERT_EQ(HANDLE_EINTR(bind(fd_.get(), reinterpret_cast<sockaddr*>(&addr),
                                sizeof(sockaddr_un))),
              0)
        << "bind failed with " << base::safe_strerror(errno);
    ASSERT_EQ(HANDLE_EINTR(listen(fd_.get(), 5)), 0)
        << "listen failed with " << base::safe_strerror(errno);
    controller_ =
        std::make_unique<base::MessagePumpForUI::FdWatchController>(FROM_HERE);
    ASSERT_TRUE(base::CurrentUIThread::Get()->WatchFileDescriptor(
        fd_.get(), true, base::MessagePumpForUI::WATCH_READ, controller_.get(),
        this));
  }

  // Release the socket.
  void Stop() {
    controller_.reset(nullptr);
    fd_.reset(-1);
  }

  int connection_count() { return num_connections_; }

  std::string received_data() { return received_; }

  // base::MessagePumpForUI::FdWatcher overrides
  void OnFileCanReadWithoutBlocking(int fd) override {
    base::ScopedFD client_fd(HANDLE_EINTR(accept(fd_.get(), nullptr, nullptr)));
    ASSERT_TRUE(client_fd.is_valid());

    ++num_connections_;

    // Attempt to read from connection until EOF
    std::string out;
    char buf[256];
    while (true) {
      ssize_t len = HANDLE_EINTR(read(client_fd.get(), buf, sizeof(buf)));
      if (len <= 0) {
        break;
      }
      out.append(buf, len);
    }
    received_.append(out);
  }

  void OnFileCanWriteWithoutBlocking(int fd) override {}

 private:
  base::ScopedFD fd_;
  std::unique_ptr<base::MessagePumpForUI::FdWatchController> controller_;
  int num_connections_ = 0;
  std::string received_;
};

class FakeDemoModeDelegate : public ArcClientAdapter::DemoModeDelegate {
 public:
  FakeDemoModeDelegate() = default;
  ~FakeDemoModeDelegate() override = default;
  FakeDemoModeDelegate(const FakeDemoModeDelegate&) = delete;
  FakeDemoModeDelegate& operator=(const FakeDemoModeDelegate&) = delete;

  void EnsureResourcesLoaded(base::OnceClosure callback) override {
    std::move(callback).Run();
  }

  base::FilePath GetDemoAppsPath() override { return base::FilePath(); }
};

class ArcVmClientAdapterTest : public testing::Test,
                               public ArcClientAdapter::Observer {
 public:
  ArcVmClientAdapterTest() {
    // Use the same VLOG() level as production. Note that
    // arc_vm_client_adapter.cc defines ENABLED_VLOG_LEVEL 1, which is respected
    // at compile time.
    logging::SetMinLogLevel(-1);

    // Create and set new fake clients every time to reset clients' status.
    test_debug_daemon_client_ = std::make_unique<TestDebugDaemonClient>();
    ash::DebugDaemonClient::SetInstanceForTest(test_debug_daemon_client_.get());
    TestConciergeClient::Initialize();
    ash::UpstartClient::InitializeFake();
  }

  ArcVmClientAdapterTest(const ArcVmClientAdapterTest&) = delete;
  ArcVmClientAdapterTest& operator=(const ArcVmClientAdapterTest&) = delete;

  ~ArcVmClientAdapterTest() override {
    ash::UpstartClient::Shutdown();
    ash::ConciergeClient::Shutdown();
    ash::DebugDaemonClient::SetInstanceForTest(nullptr);
    test_debug_daemon_client_.reset();
  }

  void SetUp() override {
    run_loop_ = std::make_unique<base::RunLoop>();
    adapter_ = CreateArcVmClientAdapterForTesting(base::BindRepeating(
        &ArcVmClientAdapterTest::RewriteStatus, base::Unretained(this)));
    adapter_->AddObserver(this);
    ASSERT_TRUE(dir_.CreateUniqueTempDir());

    host_rootfs_writable_ = false;
    system_image_ext_format_ = false;

    // The fake client returns VM_STATUS_STARTING by default. Change it
    // to VM_STATUS_RUNNING which is used by ARCVM.
    vm_tools::concierge::StartVmResponse start_vm_response;
    start_vm_response.set_status(vm_tools::concierge::VM_STATUS_RUNNING);
    auto* vm_info = start_vm_response.mutable_vm_info();
    vm_info->set_cid(kCid);
    GetTestConciergeClient()->set_start_vm_response(start_vm_response);

    // Reset to the original behavior.
    SetArcVmBootNotificationServerFdForTesting(std::nullopt);

    const std::string abstract_addr(GenerateAbstractAddress());
    boot_server_ = std::make_unique<TestArcVmBootNotificationServer>();
    boot_server_->Start(abstract_addr);
    SetArcVmBootNotificationServerAddressForTesting(
        abstract_addr,
        // connect_timeout_limit
        base::Milliseconds(100),
        // connect_sleep_duration_initial
        base::Milliseconds(20));

    ash::PatchPanelClient::InitializeFake();
    ash::SessionManagerClient::InitializeFake();

    adapter_->SetDemoModeDelegate(&demo_mode_delegate_);
    app_host_ = std::make_unique<FakeAppHost>(arc_bridge_service()->app());
    app_instance_ = std::make_unique<FakeAppInstance>(app_host_.get());
    arc_dlc_installer_ = std::make_unique<ArcDlcInstaller>();

    auto fake_user_manager = std::make_unique<user_manager::FakeUserManager>();
    scoped_user_manager_ = std::make_unique<user_manager::ScopedUserManager>(
        std::move(fake_user_manager));
  }

  void TearDown() override {
    scoped_user_manager_.reset();
    arc_dlc_installer_.reset();
    ash::PatchPanelClient::Shutdown();
    ash::SessionManagerClient::Shutdown();
    adapter_->RemoveObserver(this);
    adapter_.reset();
    run_loop_.reset();
  }

  // ArcClientAdapter::Observer:
  void ArcInstanceStopped(bool is_system_shutdown) override {
    is_system_shutdown_ = is_system_shutdown;
    run_loop()->Quit();
  }

  void ExpectTrueThenQuit(bool result) {
    EXPECT_TRUE(result);
    run_loop()->Quit();
  }

  void ExpectFalseThenQuit(bool result) {
    EXPECT_FALSE(result);
    run_loop()->Quit();
  }

  void ExpectTrue(bool result) { EXPECT_TRUE(result); }

  void ExpectFalse(bool result) { EXPECT_FALSE(result); }

 protected:
  void SetAccountId(const AccountId& account_id) {
    arc_service_manager_.set_account_id(account_id);
  }

  void SetValidUserInfo() { SetUserInfo(kUserIdHash, kSerialNumber); }

  void SetUserInfo(const std::string& hash, const std::string& serial) {
    adapter()->SetUserInfo(
        cryptohome::Identification(user_manager::StubAccountId()), hash,
        serial);
  }

  void StartMiniArcWithParams(bool expect_success, StartParams params) {
    StartMiniArcWithParamsAndUser(expect_success, std::move(params),
                                  kUserIdHash, kSerialNumber);
  }

  void StartMiniArcWithParamsAndUser(bool expect_success,
                                     StartParams params,
                                     const std::string& hash,
                                     const std::string& serial) {
    SetUserInfo(hash, serial);
    adapter()->StartMiniArc(
        std::move(params),
        base::BindOnce(expect_success
                           ? &ArcVmClientAdapterTest::ExpectTrueThenQuit
                           : &ArcVmClientAdapterTest::ExpectFalseThenQuit,
                       base::Unretained(this)));

    run_loop()->Run();
    RecreateRunLoop();
  }

  void UpgradeArcWithParams(bool expect_success, UpgradeParams params) {
    adapter()->UpgradeArc(
        std::move(params),
        base::BindOnce(expect_success
                           ? &ArcVmClientAdapterTest::ExpectTrueThenQuit
                           : &ArcVmClientAdapterTest::ExpectFalseThenQuit,
                       base::Unretained(this)));
    run_loop()->Run();
    RecreateRunLoop();
  }

  void UpgradeArcWithParamsAndStopVmCount(bool expect_success,
                                          UpgradeParams params,
                                          int run_until_stop_vm_count) {
    GetTestConciergeClient()->set_on_stop_vm_callback(run_loop()->QuitClosure(),
                                                      run_until_stop_vm_count);
    adapter()->UpgradeArc(
        std::move(params),
        base::BindOnce(expect_success ? &ArcVmClientAdapterTest::ExpectTrue
                                      : &ArcVmClientAdapterTest::ExpectFalse,
                       base::Unretained(this)));
    run_loop()->Run();
    RecreateRunLoop();
  }

  // Starts mini instance with the default StartParams.
  void StartMiniArc() { StartMiniArcWithParams(true, {}); }

  // Upgrades the instance with the default UpgradeParams.
  void UpgradeArc(bool expect_success) {
    UpgradeArcWithParams(expect_success, {});
  }

  void SendVmStartedSignal() {
    vm_tools::concierge::VmStartedSignal signal;
    signal.set_name(kArcVmName);
    for (auto& observer : GetTestConciergeClient()->vm_observer_list()) {
      observer.OnVmStarted(signal);
    }
  }

  void SendVmStartedSignalNotForArcVm() {
    vm_tools::concierge::VmStartedSignal signal;
    signal.set_name("penguin");
    for (auto& observer : GetTestConciergeClient()->vm_observer_list()) {
      observer.OnVmStarted(signal);
    }
  }

  void SendVmStoppedSignalForCid(vm_tools::concierge::VmStopReason reason,
                                 int64_t cid) {
    vm_tools::concierge::VmStoppedSignal signal;
    signal.set_name(kArcVmName);
    signal.set_cid(cid);
    signal.set_reason(reason);
    for (auto& observer : GetTestConciergeClient()->vm_observer_list()) {
      observer.OnVmStopped(signal);
    }
  }

  void SendVmStoppedSignal(vm_tools::concierge::VmStopReason reason) {
    SendVmStoppedSignalForCid(reason, kCid);
  }

  void SendVmStoppedSignalNotForArcVm(
      vm_tools::concierge::VmStopReason reason) {
    vm_tools::concierge::VmStoppedSignal signal;
    signal.set_name("penguin");
    signal.set_cid(kCid);
    signal.set_reason(reason);
    for (auto& observer : GetTestConciergeClient()->vm_observer_list()) {
      observer.OnVmStopped(signal);
    }
  }

  void SendNameOwnerChangedSignal() {
    for (auto& observer : GetTestConciergeClient()->observer_list()) {
      observer.ConciergeServiceStopped();
    }
  }

  void InjectUpstartStartJobFailure(const std::string& job_name_to_fail) {
    auto* upstart_client = ash::FakeUpstartClient::Get();
    upstart_client->set_start_job_cb(base::BindLambdaForTesting(
        [job_name_to_fail](const std::string& job_name,
                           const std::vector<std::string>& env) {
          // Return success unless |job_name| is |job_name_to_fail|.
          return ash::FakeUpstartClient::StartJobResult(job_name !=
                                                        job_name_to_fail);
        }));
  }

  void InjectUpstartStopJobFailure(const std::string& job_name_to_fail) {
    auto* upstart_client = ash::FakeUpstartClient::Get();
    upstart_client->set_stop_job_cb(base::BindLambdaForTesting(
        [job_name_to_fail](const std::string& job_name,
                           const std::vector<std::string>& env) {
          // Return success unless |job_name| is |job_name_to_fail|.
          return job_name != job_name_to_fail;
        }));
  }

  // We expect ConciergeClient::StopVm to have been called two times,
  // once to clear a stale VM in StartMiniArc(), and another on this
  // call to StopArcInstance().
  void StopArcInstance() {
    adapter()->StopArcInstance(/*on_shutdown=*/false,
                               /*should_backup_log=*/false);
    run_loop()->RunUntilIdle();
    EXPECT_EQ(2, GetTestConciergeClient()->stop_vm_call_count());
    EXPECT_FALSE(is_system_shutdown().has_value());

    RecreateRunLoop();
    SendVmStoppedSignal(vm_tools::concierge::STOP_VM_REQUESTED);
    run_loop()->Run();
    ASSERT_TRUE(is_system_shutdown().has_value());
    EXPECT_FALSE(is_system_shutdown().value());
  }

  // Checks that ArcVmClientAdapter has requested to stop the VM (after an
  // error in UpgradeArc).
  // We expect ConciergeClient::StopVm to have been called two times,
  // once to clear a stale VM in StartMiniArc(), and another after some
  // error condition.
  void ExpectArcStopped() {
    EXPECT_EQ(2, GetTestConciergeClient()->stop_vm_call_count());
    EXPECT_FALSE(is_system_shutdown().has_value());
    RecreateRunLoop();
    SendVmStoppedSignal(vm_tools::concierge::STOP_VM_REQUESTED);
    run_loop()->Run();
    ASSERT_TRUE(is_system_shutdown().has_value());
    EXPECT_FALSE(is_system_shutdown().value());
  }

  void RecreateRunLoop() { run_loop_ = std::make_unique<base::RunLoop>(); }

  base::RunLoop* run_loop() { return run_loop_.get(); }
  ArcClientAdapter* adapter() { return adapter_.get(); }

  const std::optional<bool>& is_system_shutdown() const {
    return is_system_shutdown_;
  }
  void reset_is_system_shutdown() { is_system_shutdown_ = std::nullopt; }
  TestConciergeClient* GetTestConciergeClient() {
    return static_cast<TestConciergeClient*>(ash::ConciergeClient::Get());
  }

  TestDebugDaemonClient* test_debug_daemon_client() {
    return test_debug_daemon_client_.get();
  }

  TestArcVmBootNotificationServer* boot_notification_server() {
    return boot_server_.get();
  }

  void set_block_apex_path(base::FilePath block_apex_path) {
    block_apex_path_ = block_apex_path;
  }

  void set_host_rootfs_writable(bool host_rootfs_writable) {
    host_rootfs_writable_ = host_rootfs_writable;
  }

  void set_system_image_ext_format(bool system_image_ext_format) {
    system_image_ext_format_ = system_image_ext_format;
  }

  ArcBridgeService* arc_bridge_service() {
    return arc_service_manager_.arc_bridge_service();
  }
  FakeAppInstance* app_instance() { return app_instance_.get(); }

 private:
  void RewriteStatus(FileSystemStatus* status) {
    status->set_block_apex_path_for_testing(block_apex_path_);
    status->set_host_rootfs_writable_for_testing(host_rootfs_writable_);
    status->set_system_image_ext_format_for_testing(system_image_ext_format_);
  }

  std::unique_ptr<base::RunLoop> run_loop_;
  std::unique_ptr<ArcClientAdapter> adapter_;
  std::optional<bool> is_system_shutdown_;

  content::BrowserTaskEnvironment browser_task_environment_;
  base::ScopedTempDir dir_;
  ArcServiceManager arc_service_manager_;

  // Variables to override the value in FileSystemStatus.
  base::FilePath block_apex_path_;
  bool host_rootfs_writable_;
  bool system_image_ext_format_;

  std::unique_ptr<TestArcVmBootNotificationServer> boot_server_;

  FakeDemoModeDelegate demo_mode_delegate_;
  std::unique_ptr<FakeAppHost> app_host_;
  std::unique_ptr<FakeAppInstance> app_instance_;
  std::unique_ptr<ArcDlcInstaller> arc_dlc_installer_;
  std::unique_ptr<TestDebugDaemonClient> test_debug_daemon_client_;
  std::unique_ptr<user_manager::ScopedUserManager> scoped_user_manager_;
};

// Tests that SetUserInfo() doesn't crash.
TEST_F(ArcVmClientAdapterTest, SetUserInfo) {
  SetUserInfo(kUserIdHash, kSerialNumber);
}

// Tests that SetUserInfo() doesn't crash even when empty strings are passed.
// Currently, ArcSessionRunner's tests call SetUserInfo() that way.
// TODO(khmel): Once ASR's tests are fixed, remove this test and use DCHECKs
// in SetUserInfo().
TEST_F(ArcVmClientAdapterTest, SetUserInfoEmpty) {
  adapter()->SetUserInfo(cryptohome::Identification(), std::string(),
                         std::string());
}

// Tests that StartMiniArc() succeeds by default.
TEST_F(ArcVmClientAdapterTest, StartMiniArc) {
  StartMiniArc();
  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);
  // No GetVmInfo call is expected
  EXPECT_EQ(0, GetTestConciergeClient()->get_vm_info_call_count());
  // Expect StopVm() to be called  once in StartMiniArc to clear stale
  // VM.
  EXPECT_EQ(1, GetTestConciergeClient()->stop_vm_call_count());

  StopArcInstance();
}

TEST_F(ArcVmClientAdapterTest, StartMiniArcEmptyUserIdHash) {
  StartMiniArcWithParamsAndUser(false, {}, std::string(), kSerialNumber);

  EXPECT_EQ(0, GetTestConciergeClient()->start_arc_vm_call_count());
  // No GetVmInfo call is expected
  EXPECT_EQ(0, GetTestConciergeClient()->get_vm_info_call_count());
  // Expect StopVm() to be called  once in StartMiniArc to clear stale
  // VM.
  EXPECT_EQ(1, GetTestConciergeClient()->stop_vm_call_count());
}

// Tests that StartMiniArc() still succeeds without the feature.
TEST_F(ArcVmClientAdapterTest, StartMiniArc_WithPerVCpuCoreScheduling) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatureState(kEnablePerVmCoreScheduling,
                                    false /* use */);

  StartMiniArc();
  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);

  StopArcInstance();
}

// Tests that StartMiniArc() still succeeds even when Upstart fails to stop
// the arcvm-post-login-services job.
TEST_F(ArcVmClientAdapterTest, StartMiniArc_StopArcVmPostLoginServicesJobFail) {
  // Inject failure to FakeUpstartClient.
  InjectUpstartStopJobFailure(kArcVmPostLoginServicesJobName);

  StartMiniArc();
  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);

  StopArcInstance();
}

// Tests that StartMiniArc() still succeeds even when Upstart fails to stop
// arcvm-media-sharing-services.
TEST_F(ArcVmClientAdapterTest,
       StartMiniArc_StopArcVmMediaSharingServicesJobFail) {
  // Inject failure to FakeUpstartClient.
  InjectUpstartStopJobFailure(kArcVmMediaSharingServicesJobName);

  StartMiniArc();
  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);

  StopArcInstance();
}

// Tests that StartMiniArc() still succeeds even when Upstart fails to stop
// arcvm-data-migrator.
TEST_F(ArcVmClientAdapterTest, StartMiniArc_StopArcVmDataMigratorJobFail) {
  // Inject failure to FakeUpstartClient.
  InjectUpstartStopJobFailure(kArcVmDataMigratorJobName);

  StartMiniArc();
  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);

  StopArcInstance();
}

// Tests that StartMiniArc() fails when Upstart fails to start the job.
TEST_F(ArcVmClientAdapterTest, StartMiniArc_StartArcVmPerBoardFeaturesJobFail) {
  // Inject failure to FakeUpstartClient.
  InjectUpstartStartJobFailure(kArcVmPerBoardFeaturesJobName);

  StartMiniArcWithParams(false, {});

  // Confirm that no VM is started.
  EXPECT_EQ(GetTestConciergeClient()->start_arc_vm_call_count(), 0);
}

// Tests that StartMiniArc() fails if Upstart fails to start
// arcvm-pre-login-services.
TEST_F(ArcVmClientAdapterTest, StartMiniArc_StartArcVmPreLoginServicesJobFail) {
  // Inject failure to FakeUpstartClient.
  InjectUpstartStartJobFailure(kArcVmPreLoginServicesJobName);

  StartMiniArcWithParams(false, {});
  EXPECT_EQ(GetTestConciergeClient()->start_arc_vm_call_count(), 0);
}

// Tests that StartMiniArc() succeeds if Upstart fails to stop
// arcvm-pre-login-services.
TEST_F(ArcVmClientAdapterTest, StartMiniArc_StopArcVmPreLoginServicesJobFail) {
  // Inject failure to FakeUpstartClient.
  InjectUpstartStopJobFailure(kArcVmPreLoginServicesJobName);

  StartMiniArc();
  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);

  StopArcInstance();
}

// Tests that |kArcVmPreLoginServicesJobName| is properly stopped and then
// started in StartMiniArc().
TEST_F(ArcVmClientAdapterTest, StartMiniArc_JobRestart) {
  ash::FakeUpstartClient::Get()->StartRecordingUpstartOperations();
  StartMiniArc();

  const auto& ops =
      ash::FakeUpstartClient::Get()->GetRecordedUpstartOperationsForJob(
          kArcVmPreLoginServicesJobName);
  ASSERT_EQ(ops.size(), 2u);
  EXPECT_EQ(ops[0].type, ash::FakeUpstartClient::UpstartOperationType::STOP);
  EXPECT_EQ(ops[1].type, ash::FakeUpstartClient::UpstartOperationType::START);
}

// Tests that StopArcInstance() eventually notifies the observer.
TEST_F(ArcVmClientAdapterTest, StopArcInstance) {
  StartMiniArc();
  UpgradeArc(true);

  adapter()->StopArcInstance(/*on_shutdown=*/false,
                             /*should_backup_log=*/false);
  run_loop()->RunUntilIdle();
  EXPECT_EQ(2, GetTestConciergeClient()->stop_vm_call_count());
  // The callback for StopVm D-Bus reply does NOT call ArcInstanceStopped when
  // the D-Bus call result is successful.
  EXPECT_FALSE(is_system_shutdown().has_value());

  // Instead, vm_concierge explicitly notifies Chrome of the VM termination.
  RecreateRunLoop();
  SendVmStoppedSignal(vm_tools::concierge::STOP_VM_REQUESTED);
  run_loop()->Run();
  // ..and that calls ArcInstanceStopped.
  ASSERT_TRUE(is_system_shutdown().has_value());
  EXPECT_FALSE(is_system_shutdown().value());
}

// b/164816080 This test ensures that a new vm instance that is
// created while handling the shutting down of the previous instance,
// doesn't incorrectly receive the shutdown event as well.
TEST_F(ArcVmClientAdapterTest, DoesNotGetArcInstanceStoppedOnNestedInstance) {
  using RunLoopFactory = base::RepeatingCallback<base::RunLoop*()>;

  class Observer : public ArcClientAdapter::Observer {
   public:
    Observer(RunLoopFactory run_loop_factory, Observer* child_observer)
        : run_loop_factory_(run_loop_factory),
          child_observer_(child_observer) {}
    Observer(const Observer&) = delete;
    Observer& operator=(const Observer&) = delete;

    ~Observer() override {
      if (child_observer_ && nested_adapter_) {
        nested_adapter_->RemoveObserver(child_observer_);
      }
    }

    bool stopped_called() const { return stopped_called_; }

    // ArcClientAdapter::Observer:
    void ArcInstanceStopped(bool is_system_shutdown) override {
      stopped_called_ = true;

      if (child_observer_) {
        nested_adapter_ = CreateArcVmClientAdapterForTesting(base::DoNothing());
        nested_adapter_->AddObserver(child_observer_);
        nested_adapter_->SetUserInfo(
            cryptohome::Identification(user_manager::StubAccountId()),
            kUserIdHash, kSerialNumber);
        nested_adapter_->SetDemoModeDelegate(&demo_mode_delegate_);

        base::RunLoop* run_loop = run_loop_factory_.Run();
        nested_adapter_->StartMiniArc({}, QuitClosure(run_loop));
        run_loop->Run();

        run_loop = run_loop_factory_.Run();
        nested_adapter_->UpgradeArc({}, QuitClosure(run_loop));
        run_loop->Run();
      }
    }

   private:
    base::OnceCallback<void(bool)> QuitClosure(base::RunLoop* run_loop) {
      return base::BindOnce(
          [](base::RunLoop* run_loop, bool result) { run_loop->Quit(); },
          run_loop);
    }

    base::RepeatingCallback<base::RunLoop*()> const run_loop_factory_;
    const raw_ptr<Observer> child_observer_;
    std::unique_ptr<ArcClientAdapter> nested_adapter_;
    FakeDemoModeDelegate demo_mode_delegate_;
    bool stopped_called_ = false;
  };

  StartMiniArc();
  UpgradeArc(true);

  RunLoopFactory run_loop_factory = base::BindLambdaForTesting([this]() {
    RecreateRunLoop();
    return run_loop();
  });

  Observer child_observer(run_loop_factory, nullptr);
  Observer parent_observer(run_loop_factory, &child_observer);
  adapter()->AddObserver(&parent_observer);
  absl::Cleanup teardown = [this, &parent_observer] {
    adapter()->RemoveObserver(&parent_observer);
  };

  SendVmStoppedSignal(vm_tools::concierge::STOP_VM_REQUESTED);

  EXPECT_TRUE(parent_observer.stopped_called());
  EXPECT_FALSE(child_observer.stopped_called());
}

// Tests that StopArcInstance() initiates ARC log backup.
TEST_F(ArcVmClientAdapterTest, StopArcInstance_WithLogBackup) {
  StartMiniArc();
  UpgradeArc(true);

  adapter()->StopArcInstance(/*on_shutdown=*/false, /*should_backup_log=*/true);
  run_loop()->RunUntilIdle();
  EXPECT_EQ(2, GetTestConciergeClient()->stop_vm_call_count());
  // The callback for StopVm D-Bus reply does NOT call ArcInstanceStopped when
  // the D-Bus call result is successful.
  EXPECT_FALSE(is_system_shutdown().has_value());

  // Instead, vm_concierge explicitly notifies Chrome of the VM termination.
  RecreateRunLoop();
  SendVmStoppedSignal(vm_tools::concierge::STOP_VM_REQUESTED);
  run_loop()->Run();
  // ..and that calls ArcInstanceStopped.
  ASSERT_TRUE(is_system_shutdown().has_value());
  EXPECT_FALSE(is_system_shutdown().value());
}

TEST_F(ArcVmClientAdapterTest, StopArcInstance_WithLogBackup_BackupFailed) {
  StartMiniArc();
  UpgradeArc(true);

  EXPECT_FALSE(test_debug_daemon_client()->backup_arc_bug_report_called());
  test_debug_daemon_client()->set_backup_arc_bug_report_result(false);

  adapter()->StopArcInstance(/*on_shutdown=*/false, /*should_backup_log=*/true);
  run_loop()->RunUntilIdle();
  EXPECT_EQ(2, GetTestConciergeClient()->stop_vm_call_count());
  // The callback for StopVm D-Bus reply does NOT call ArcInstanceStopped when
  // the D-Bus call result is successful.
  EXPECT_FALSE(is_system_shutdown().has_value());

  // Instead, vm_concierge explicitly notifies Chrome of the VM termination.
  RecreateRunLoop();
  SendVmStoppedSignal(vm_tools::concierge::STOP_VM_REQUESTED);
  run_loop()->Run();

  EXPECT_TRUE(test_debug_daemon_client()->backup_arc_bug_report_called());
  // ..and that calls ArcInstanceStopped.
  ASSERT_TRUE(is_system_shutdown().has_value());
  EXPECT_FALSE(is_system_shutdown().value());
}

// Tests that StopArcInstance() called during shutdown doesn't do anything.
TEST_F(ArcVmClientAdapterTest, StopArcInstance_OnShutdown) {
  StartMiniArc();
  UpgradeArc(true);

  adapter()->StopArcInstance(/*on_shutdown=*/true, /*should_backup_log=*/false);
  run_loop()->RunUntilIdle();
  EXPECT_EQ(1, GetTestConciergeClient()->stop_vm_call_count());
  EXPECT_FALSE(is_system_shutdown().has_value());
}

// Tests that StopArcInstance() immediately notifies the observer on failure.
TEST_F(ArcVmClientAdapterTest, StopArcInstance_Fail) {
  StartMiniArc();
  UpgradeArc(true);

  // Inject failure.
  vm_tools::concierge::StopVmResponse response;
  response.set_success(false);
  GetTestConciergeClient()->set_stop_vm_response(response);

  adapter()->StopArcInstance(/*on_shutdown=*/false,
                             /*should_backup_log=*/false);

  run_loop()->Run();
  EXPECT_EQ(2, GetTestConciergeClient()->stop_vm_call_count());

  // The callback for StopVm D-Bus reply does call ArcInstanceStopped when
  // the D-Bus call result is NOT successful.
  ASSERT_TRUE(is_system_shutdown().has_value());
  EXPECT_FALSE(is_system_shutdown().value());
}

// Test that StopArcInstance() stops the VM if only mini-ARCVM
// is called.
TEST_F(ArcVmClientAdapterTest, StopArcInstance_StopMiniVm) {
  StartMiniArc();

  adapter()->StopArcInstance(/*on_shutdown=*/false,
                             /*should_backup_log*/ false);
  run_loop()->RunUntilIdle();

  // No GetVmInfo call is expected.
  EXPECT_EQ(0, GetTestConciergeClient()->get_vm_info_call_count());
  // Expect StopVm() to be called twice; once in StartMiniArc to clear stale
  // VM, and again on StopArcInstance().
  EXPECT_EQ(2, GetTestConciergeClient()->stop_vm_call_count());
  EXPECT_EQ(kUserIdHash,
            GetTestConciergeClient()->stop_vm_request().owner_id());
}

// Tests that UpgradeArc() handles arcvm-post-login-services startup failures
// properly.
TEST_F(ArcVmClientAdapterTest, UpgradeArc_StartArcVmPostLoginServicesFailure) {
  StartMiniArc();

  // Inject failure to FakeUpstartClient.
  InjectUpstartStartJobFailure(kArcVmPostLoginServicesJobName);

  UpgradeArcWithParamsAndStopVmCount(false, {}, /*run_until_stop_vm_count=*/2);

  ExpectArcStopped();
}

// Tests that StartMiniArc()'s JOB_STOP_AND_START for
// |kArcVmPreLoginServicesJobName| does not have DISABLE_UREADAHEAD variable
// by default.
TEST_F(ArcVmClientAdapterTest, StartMiniArc_UreadaheadByDefault) {
  StartParams start_params(GetPopulatedStartParams());
  ash::FakeUpstartClient::Get()->StartRecordingUpstartOperations();
  StartMiniArcWithParams(true, std::move(start_params));

  const auto& ops =
      ash::FakeUpstartClient::Get()->GetRecordedUpstartOperationsForJob(
          kArcVmPreLoginServicesJobName);
  ASSERT_EQ(ops.size(), 2u);
  EXPECT_EQ(ops[0].type, ash::FakeUpstartClient::UpstartOperationType::STOP);
  EXPECT_EQ(ops[1].type, ash::FakeUpstartClient::UpstartOperationType::START);
  EXPECT_TRUE(ops[1].env.empty());
}

// Tests that StartMiniArc() handles arcvm-post-vm-start-services stop failures
// properly.
TEST_F(ArcVmClientAdapterTest,
       StartMiniArc_StopArcVmPostVmStartServicesFailure) {
  // Inject failure to FakeUpstartClient.
  InjectUpstartStopJobFailure(kArcVmPostVmStartServicesJobName);

  // StartMiniArc should still succeed.
  StartMiniArc();

  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);
  EXPECT_FALSE(is_system_shutdown().has_value());

  // Make sure StopVm() is called only once, to stop existing VMs on
  // StartMiniArc().
  EXPECT_EQ(1, GetTestConciergeClient()->stop_vm_call_count());
}

// Tests that UpgradeArc() handles arcvm-post-vm-start-services startup
// failures properly.
TEST_F(ArcVmClientAdapterTest,
       UpgradeArc_StartArcVmPostVmStartServicesFailure) {
  StartMiniArc();

  // Inject failure to FakeUpstartClient.
  InjectUpstartStartJobFailure(kArcVmPostVmStartServicesJobName);
  // UpgradeArc should fail and the VM should be stoppped.
  UpgradeArcWithParamsAndStopVmCount(false, {}, /*run_until_stop_vm_count=*/2);
  ExpectArcStopped();
}

// Tests that a "Failed Adb Sideload response" case is handled properly.
TEST_F(ArcVmClientAdapterTest, UpgradeArc_FailedAdbResponse) {
  StartMiniArc();

  // Ask the Fake Session Manager to return a failed Adb Sideload response.
  ash::FakeSessionManagerClient::Get()->set_adb_sideload_response(
      ash::FakeSessionManagerClient::AdbSideloadResponseCode::FAILED);

  UpgradeArcWithParamsAndStopVmCount(false, {}, /*run_until_stop_vm_count=*/2);
  ExpectArcStopped();
}

// Tests that a "Need_Powerwash Adb Sideload response" case is handled properly.
TEST_F(ArcVmClientAdapterTest, UpgradeArc_NeedPowerwashAdbResponse) {
  StartMiniArc();

  // Ask the Fake Session Manager to return a Need_Powerwash Adb Sideload
  // response.
  ash::FakeSessionManagerClient::Get()->set_adb_sideload_response(
      ash::FakeSessionManagerClient::AdbSideloadResponseCode::NEED_POWERWASH);
  UpgradeArc(true);
  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);
  EXPECT_FALSE(is_system_shutdown().has_value());
  EXPECT_TRUE(base::Contains(boot_notification_server()->received_data(),
                             "ro.boot.enable_adb_sideloading=0"));
}

// Tests that adb sideloading is disabled by default.
TEST_F(ArcVmClientAdapterTest, UpgradeArc_AdbSideloadingPropertyDefault) {
  StartMiniArc();

  UpgradeArc(true);
  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);
  EXPECT_FALSE(is_system_shutdown().has_value());
  EXPECT_TRUE(base::Contains(boot_notification_server()->received_data(),
                             "ro.boot.enable_adb_sideloading=0"));
}

// Tests that adb sideloading can be controlled via session_manager.
TEST_F(ArcVmClientAdapterTest, UpgradeArc_AdbSideloadingPropertyEnabled) {
  StartMiniArc();

  ash::FakeSessionManagerClient::Get()->set_adb_sideload_enabled(true);
  UpgradeArc(true);
  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);
  EXPECT_FALSE(is_system_shutdown().has_value());
  EXPECT_TRUE(base::Contains(boot_notification_server()->received_data(),
                             "ro.boot.enable_adb_sideloading=1"));
}

TEST_F(ArcVmClientAdapterTest, UpgradeArc_AdbSideloadingPropertyDisabled) {
  StartMiniArc();

  ash::FakeSessionManagerClient::Get()->set_adb_sideload_enabled(false);
  UpgradeArc(true);
  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);
  EXPECT_FALSE(is_system_shutdown().has_value());
  EXPECT_TRUE(base::Contains(boot_notification_server()->received_data(),
                             "ro.boot.enable_adb_sideloading=0"));
}

// Tests that "no serial" failure is handled properly.
TEST_F(ArcVmClientAdapterTest, UpgradeArc_NoSerial) {
  // Don't set the serial number.
  StartMiniArcWithParamsAndUser(true, {}, kUserIdHash, std::string());
  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);

  UpgradeArcWithParamsAndStopVmCount(false, {}, /*run_until_stop_vm_count=*/2);
  ExpectArcStopped();
}

TEST_F(ArcVmClientAdapterTest, StartMiniArc_StopExistingVmFailure) {
  // Inject failure.
  vm_tools::concierge::StopVmResponse response;
  response.set_success(false);
  GetTestConciergeClient()->set_stop_vm_response(response);

  StartMiniArcWithParams(false, {});

  EXPECT_EQ(GetTestConciergeClient()->start_arc_vm_call_count(), 0);
  EXPECT_FALSE(is_system_shutdown().has_value());
}

TEST_F(ArcVmClientAdapterTest, StartMiniArc_StopExistingVmFailureEmptyReply) {
  // Inject failure.
  GetTestConciergeClient()->set_stop_vm_response(std::nullopt);

  StartMiniArcWithParams(false, {});

  EXPECT_EQ(GetTestConciergeClient()->start_arc_vm_call_count(), 0);
  EXPECT_FALSE(is_system_shutdown().has_value());
}

// Tests that ConciergeClient::WaitForServiceToBeAvailable() failure is handled
// properly.
TEST_F(ArcVmClientAdapterTest, StartMiniArc_WaitForConciergeAvailableFailure) {
  // Inject failure.
  GetTestConciergeClient()->set_wait_for_service_to_be_available_response(
      false);

  StartMiniArcWithParams(false, {});
  EXPECT_EQ(GetTestConciergeClient()->start_arc_vm_call_count(), 0);
  EXPECT_FALSE(is_system_shutdown().has_value());
}

// Tests that StartArcVm() failure is handled properly.
TEST_F(ArcVmClientAdapterTest, StartMiniArc_StartArcVmFailure) {
  // Inject failure to StartArcVm().
  vm_tools::concierge::StartVmResponse start_vm_response;
  start_vm_response.set_status(vm_tools::concierge::VM_STATUS_UNKNOWN);
  GetTestConciergeClient()->set_start_vm_response(start_vm_response);

  StartMiniArcWithParams(false, {});

  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);
  EXPECT_FALSE(is_system_shutdown().has_value());
}

TEST_F(ArcVmClientAdapterTest, StartMiniArc_StartArcVmFailureEmptyReply) {
  // Inject failure to StartArcVm(). This emulates D-Bus timeout situations.
  GetTestConciergeClient()->set_start_vm_response(std::nullopt);

  StartMiniArcWithParams(false, {});

  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);
  EXPECT_FALSE(is_system_shutdown().has_value());
}

// Tests that successful StartArcVm() call is handled properly.
TEST_F(ArcVmClientAdapterTest, UpgradeArc_Success) {
  StartMiniArc();
  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);
  EXPECT_FALSE(is_system_shutdown().has_value());
  UpgradeArc(true);

  StopArcInstance();
}

// Try to start and upgrade the instance with more params.
TEST_F(ArcVmClientAdapterTest, StartUpgradeArc_VariousParams) {
  StartParams start_params(GetPopulatedStartParams());
  StartMiniArcWithParams(true, std::move(start_params));

  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);
  EXPECT_FALSE(is_system_shutdown().has_value());

  UpgradeParams params(GetPopulatedUpgradeParams());
  UpgradeArcWithParams(true, std::move(params));
}

// Try to start and upgrade the instance with slightly different params
// than StartUpgradeArc_VariousParams for better code coverage.
TEST_F(ArcVmClientAdapterTest, StartUpgradeArc_VariousParams2) {
  StartParams start_params(GetPopulatedStartParams());
  // Use slightly different params than StartUpgradeArc_VariousParams.
  start_params.play_store_auto_update =
      StartParams::PlayStoreAutoUpdate::AUTO_UPDATE_OFF;

  StartMiniArcWithParams(true, std::move(start_params));

  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);
  EXPECT_FALSE(is_system_shutdown().has_value());

  UpgradeParams params(GetPopulatedUpgradeParams());
  // Use slightly different params than StartUpgradeArc_VariousParams.
  params.packages_cache_mode =
      UpgradeParams::PackageCacheMode::SKIP_SETUP_COPY_ON_INIT;
  params.management_transition = ArcManagementTransition::REGULAR_TO_CHILD;
  params.preferred_languages = {"en_US"};

  UpgradeArcWithParams(true, std::move(params));
}

// Try to start and upgrade the instance with demo mode enabled.
TEST_F(ArcVmClientAdapterTest, StartUpgradeArc_DemoMode) {
  constexpr char kDemoImage[] =
      "/run/imageloader/demo-mode-resources/0.0.1.7/android_demo_apps.squash";
  base::FilePath apps_path = base::FilePath(kDemoImage);

  class TestDemoDelegate : public ArcClientAdapter::DemoModeDelegate {
   public:
    explicit TestDemoDelegate(base::FilePath apps_path)
        : apps_path_(apps_path) {}
    ~TestDemoDelegate() override = default;

    void EnsureResourcesLoaded(base::OnceClosure callback) override {
      std::move(callback).Run();
    }

    base::FilePath GetDemoAppsPath() override { return apps_path_; }

   private:
    base::FilePath apps_path_;
  };

  TestDemoDelegate delegate(apps_path);
  adapter()->SetDemoModeDelegate(&delegate);
  StartMiniArc();

  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);
  EXPECT_FALSE(is_system_shutdown().has_value());

  // Verify the request.
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  // Make sure disks have the squashfs image.
  EXPECT_TRUE(HasDiskImage(request, kDemoImage));

  UpgradeParams params(GetPopulatedUpgradeParams());
  // Enable demo mode.
  params.is_demo_session = true;

  UpgradeArcWithParams(true, std::move(params));
  EXPECT_TRUE(base::Contains(boot_notification_server()->received_data(),
                             "ro.boot.arc_demo_mode=1"));
}

TEST_F(ArcVmClientAdapterTest, StartUpgradeArc_DisableMediaStoreMaintenance) {
  StartParams start_params(GetPopulatedStartParams());
  start_params.disable_media_store_maintenance = true;
  StartMiniArcWithParams(true, std::move(start_params));
  UpgradeParams params(GetPopulatedUpgradeParams());
  UpgradeArcWithParams(true, std::move(params));
  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);
  EXPECT_FALSE(is_system_shutdown().has_value());
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_TRUE(
      request.mini_instance_request().disable_media_store_maintenance());
}

TEST_F(ArcVmClientAdapterTest, StartUpgradeArc_ArcVmUreadaheadMode) {
  StartParams start_params(GetPopulatedStartParams());
  StartMiniArcWithParams(true, std::move(start_params));
  UpgradeParams params(GetPopulatedUpgradeParams());
  UpgradeArcWithParams(true, std::move(params));
  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);
  EXPECT_FALSE(is_system_shutdown().has_value());
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_EQ(request.ureadahead_mode(),
            vm_tools::concierge::StartArcVmRequest::UREADAHEAD_MODE_READAHEAD);
}

TEST_F(ArcVmClientAdapterTest, StartMiniArc_EnablePaiGeneration) {
  StartParams start_params(GetPopulatedStartParams());
  start_params.arc_generate_play_auto_install = true;
  StartMiniArcWithParams(true, std::move(start_params));
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_TRUE(request.mini_instance_request().arc_generate_pai());
}

TEST_F(ArcVmClientAdapterTest, StartMiniArc_PaiGenerationDefaultDisabled) {
  StartMiniArcWithParams(true, GetPopulatedStartParams());
  // No androidboot property should be generated.
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_FALSE(request.mini_instance_request().arc_generate_pai());
}

// Tests that StartArcVm() is called with valid parameters.
TEST_F(ArcVmClientAdapterTest, StartMiniArc_StartArcVmParams) {
  StartMiniArc();
  ASSERT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);

  // Verify parameters
  const auto& params = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_EQ("arcvm", params.name());
  EXPECT_LT(0u, params.cpus());
  // Make sure vendor.raw.img is passed.
  EXPECT_LE(1, params.disks_size());
}

// Tests that crosvm crash is handled properly.
TEST_F(ArcVmClientAdapterTest, CrosvmCrash) {
  StartMiniArc();
  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);
  EXPECT_FALSE(is_system_shutdown().has_value());
  UpgradeArc(true);

  // Kill crosvm and verify StopArcInstance is called.
  SendVmStoppedSignal(vm_tools::concierge::VM_EXITED);
  run_loop()->Run();
  ASSERT_TRUE(is_system_shutdown().has_value());
  EXPECT_FALSE(is_system_shutdown().value());
}

// Tests that vm_concierge shutdown is handled properly.
TEST_F(ArcVmClientAdapterTest, ConciergeShutdown) {
  StartMiniArc();
  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);
  EXPECT_FALSE(is_system_shutdown().has_value());
  UpgradeArc(true);

  // vm_concierge sends a VmStoppedSignal when shutting down.
  SendVmStoppedSignal(vm_tools::concierge::SERVICE_SHUTDOWN);
  run_loop()->Run();
  ASSERT_TRUE(is_system_shutdown().has_value());
  EXPECT_TRUE(is_system_shutdown().value());

  // Verify StopArcInstance is NOT called when vm_concierge stops since
  // the observer has already been called.
  RecreateRunLoop();
  reset_is_system_shutdown();
  SendNameOwnerChangedSignal();
  run_loop()->RunUntilIdle();
  EXPECT_FALSE(is_system_shutdown().has_value());
}

// Tests that vm_concierge crash is handled properly.
TEST_F(ArcVmClientAdapterTest, ConciergeCrash) {
  StartMiniArc();
  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);
  EXPECT_FALSE(is_system_shutdown().has_value());
  UpgradeArc(true);

  // Kill vm_concierge and verify StopArcInstance is called.
  SendNameOwnerChangedSignal();
  run_loop()->Run();
  ASSERT_TRUE(is_system_shutdown().has_value());
  EXPECT_FALSE(is_system_shutdown().value());
}

// Tests the case where crosvm crashes, then vm_concierge crashes too.
TEST_F(ArcVmClientAdapterTest, CrosvmAndConciergeCrashes) {
  StartMiniArc();
  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);
  EXPECT_FALSE(is_system_shutdown().has_value());
  UpgradeArc(true);

  // Kill crosvm and verify StopArcInstance is called.
  SendVmStoppedSignal(vm_tools::concierge::VM_EXITED);
  run_loop()->Run();
  ASSERT_TRUE(is_system_shutdown().has_value());
  EXPECT_FALSE(is_system_shutdown().value());

  // Kill vm_concierge and verify StopArcInstance is NOT called since
  // the observer has already been called.
  RecreateRunLoop();
  reset_is_system_shutdown();
  SendNameOwnerChangedSignal();
  run_loop()->RunUntilIdle();
  EXPECT_FALSE(is_system_shutdown().has_value());
}

// Tests the case where a unknown VmStopped signal is sent to Chrome.
TEST_F(ArcVmClientAdapterTest, VmStoppedSignal_UnknownCid) {
  StartMiniArc();
  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);
  EXPECT_FALSE(is_system_shutdown().has_value());
  UpgradeArc(true);

  SendVmStoppedSignalForCid(vm_tools::concierge::STOP_VM_REQUESTED,
                            42);  // unknown CID
  run_loop()->RunUntilIdle();
  EXPECT_FALSE(is_system_shutdown().has_value());
}

// Tests the case where a stale VmStopped signal is sent to Chrome.
TEST_F(ArcVmClientAdapterTest, VmStoppedSignal_Stale) {
  SendVmStoppedSignalForCid(vm_tools::concierge::STOP_VM_REQUESTED, 42);
  run_loop()->RunUntilIdle();
  EXPECT_FALSE(is_system_shutdown().has_value());
}

// Tests the case where a VmStopped signal not for ARCVM (e.g. Termina) is sent
// to Chrome.
TEST_F(ArcVmClientAdapterTest, VmStoppedSignal_Termina) {
  SendVmStoppedSignalNotForArcVm(vm_tools::concierge::STOP_VM_REQUESTED);
  run_loop()->RunUntilIdle();
  EXPECT_FALSE(is_system_shutdown().has_value());
}

// Tests that receiving VmStarted signal is no-op.
TEST_F(ArcVmClientAdapterTest, VmStartedSignal) {
  SendVmStartedSignal();
  run_loop()->RunUntilIdle();
  RecreateRunLoop();
  SendVmStartedSignalNotForArcVm();
  run_loop()->RunUntilIdle();
}

// Tests that ConciergeServiceStarted() doesn't crash.
TEST_F(ArcVmClientAdapterTest, TestConciergeServiceStarted) {
  GetTestConciergeClient()->NotifyConciergeStarted();
}

// Tests that the kernel parameter does not include "rw" by default.
TEST_F(ArcVmClientAdapterTest, KernelParam_RO) {
  set_host_rootfs_writable(false);
  set_system_image_ext_format(false);
  StartMiniArc();
  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);

  // Check "rw" is not in |params|.
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_FALSE(request.rootfs_writable());
}

// Tests that the kernel parameter does include "rw" when '/' is writable and
// the image is in ext4.
TEST_F(ArcVmClientAdapterTest, KernelParam_RW) {
  set_host_rootfs_writable(true);
  set_system_image_ext_format(true);
  StartMiniArc();
  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);

  // Check "rw" is in |params|.
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_TRUE(request.rootfs_writable());
}

// Tests that CreateArcVmClientAdapter() doesn't crash.
TEST_F(ArcVmClientAdapterTest, TestCreateArcVmClientAdapter) {
  CreateArcVmClientAdapter();
}

TEST_F(ArcVmClientAdapterTest, DefaultBlockSize) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatureState(arc::kUseDefaultBlockSize, true /* use */);

  StartParams start_params(GetPopulatedStartParams());
  StartMiniArcWithParams(true, std::move(start_params));
  EXPECT_EQ(
      0u, GetTestConciergeClient()->start_arc_vm_request().rootfs_block_size());
}

TEST_F(ArcVmClientAdapterTest, SpecifyBlockSize) {
  StartParams start_params(GetPopulatedStartParams());
  StartMiniArcWithParams(true, std::move(start_params));
  EXPECT_EQ(
      4096u,
      GetTestConciergeClient()->start_arc_vm_request().rootfs_block_size());
}

TEST_F(ArcVmClientAdapterTest, VirtioBlkMultipleWorkers_Disabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndDisableFeature(arc::kEnableVirtioBlkMultipleWorkers);

  StartParams start_params(GetPopulatedStartParams());
  StartMiniArcWithParams(true, std::move(start_params));

  // All disks should have multiple workers disabled.
  EXPECT_FALSE(GetTestConciergeClient()
                   ->start_arc_vm_request()
                   .rootfs_multiple_workers());
  for (const auto& disk :
       GetTestConciergeClient()->start_arc_vm_request().disks()) {
    EXPECT_FALSE(disk.multiple_workers());
  }
}

TEST_F(ArcVmClientAdapterTest, VirtioBlkMultipleWorkers_Enabled_NoBlkData) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(arc::kEnableVirtioBlkMultipleWorkers);

  StartParams start_params(GetPopulatedStartParams());
  start_params.use_virtio_blk_data = false;
  StartMiniArcWithParams(true, std::move(start_params));

  // rootfs should have multiple workers enabled.
  EXPECT_TRUE(GetTestConciergeClient()
                  ->start_arc_vm_request()
                  .rootfs_multiple_workers());
  // No other disks should have multiple workers enabled.
  for (const auto& disk :
       GetTestConciergeClient()->start_arc_vm_request().disks()) {
    EXPECT_FALSE(disk.multiple_workers());
  }
}

TEST_F(ArcVmClientAdapterTest, VirtioBlkMultipleWorkers_Enabled_BlkData) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(arc::kEnableVirtioBlkMultipleWorkers);

  GetTestConciergeClient()->set_create_disk_image_response(
      CreateDiskImageResponse(vm_tools::concierge::DISK_STATUS_CREATED));

  StartParams start_params(GetPopulatedStartParams());
  start_params.use_virtio_blk_data = true;
  StartMiniArcWithParams(true, std::move(start_params));

  const auto& req = GetTestConciergeClient()->start_arc_vm_request();

  // rootfs should have multiple workers enabled.
  EXPECT_TRUE(req.rootfs_multiple_workers());
  EXPECT_TRUE(HasDiskImage(req, kCreatedDiskImagePath));
  for (const auto& disk :
       GetTestConciergeClient()->start_arc_vm_request().disks()) {
    // The data disk should have multiple workers enabled.
    if (disk.path() == kCreatedDiskImagePath) {
      EXPECT_TRUE(disk.multiple_workers());
    } else {
      EXPECT_FALSE(disk.multiple_workers());
    }
  }
}

TEST_F(ArcVmClientAdapterTest, VirtioBlkForData_Disabled) {
  GetTestConciergeClient()->set_create_disk_image_response(
      CreateDiskImageResponse(vm_tools::concierge::DISK_STATUS_CREATED));

  StartParams start_params(GetPopulatedStartParams());
  start_params.use_virtio_blk_data = false;
  StartMiniArcWithParams(true, std::move(start_params));
  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);

  // CreateDiskImage() should NOT be called.
  EXPECT_EQ(GetTestConciergeClient()->create_disk_image_call_count(), 0);

  // StartArcVmRequest should NOT contain a disk created by CreateDiskImage().
  const auto& req = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_FALSE(HasDiskImage(req, kCreatedDiskImagePath));
  EXPECT_FALSE(req.enable_virtio_blk_data());
}

TEST_F(ArcVmClientAdapterTest, VirtioBlkForData_CreateDiskimageResponseEmpty) {
  // CreateDiskImage() returns an empty response.
  GetTestConciergeClient()->set_create_disk_image_response(std::nullopt);

  // StartArcVm should NOT be called.
  StartParams start_params(GetPopulatedStartParams());
  start_params.use_virtio_blk_data = true;
  StartMiniArcWithParams(false, std::move(start_params));
  EXPECT_EQ(GetTestConciergeClient()->start_arc_vm_call_count(), 0);

  EXPECT_EQ(GetTestConciergeClient()->create_disk_image_call_count(), 1);
}

TEST_F(ArcVmClientAdapterTest, VirtioBlkForData_CreateDiskImageStatusFailed) {
  GetTestConciergeClient()->set_create_disk_image_response(
      CreateDiskImageResponse(vm_tools::concierge::DISK_STATUS_FAILED));

  StartParams start_params(GetPopulatedStartParams());
  start_params.use_virtio_blk_data = true;

  // StartArcVm should NOT be called.
  StartMiniArcWithParams(false, std::move(start_params));
  EXPECT_EQ(GetTestConciergeClient()->start_arc_vm_call_count(), 0);

  EXPECT_EQ(GetTestConciergeClient()->create_disk_image_call_count(), 1);
}

TEST_F(ArcVmClientAdapterTest, VirtioBlkForData_CreateDiskImageStatusCreated) {
  GetTestConciergeClient()->set_create_disk_image_response(
      CreateDiskImageResponse(vm_tools::concierge::DISK_STATUS_CREATED));

  StartParams start_params(GetPopulatedStartParams());
  start_params.use_virtio_blk_data = true;
  StartMiniArcWithParams(true, std::move(start_params));
  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);

  EXPECT_EQ(GetTestConciergeClient()->create_disk_image_call_count(), 1);

  // StartArcVmRequest should contain a disk path created by CreateDiskImage().
  const auto& req = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_TRUE(HasDiskImage(req, kCreatedDiskImagePath));
  EXPECT_TRUE(req.enable_virtio_blk_data());
}

TEST_F(ArcVmClientAdapterTest, VirtioBlkForData_CreateDiskImageStatusExists) {
  GetTestConciergeClient()->set_create_disk_image_response(
      CreateDiskImageResponse(vm_tools::concierge::DISK_STATUS_EXISTS));

  StartParams start_params = GetPopulatedStartParams();
  start_params.use_virtio_blk_data = true;
  StartMiniArcWithParams(true, std::move(start_params));
  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);

  EXPECT_EQ(GetTestConciergeClient()->create_disk_image_call_count(), 1);

  // StartArcVmRequest should contain a disk path created by CreateDiskImage().
  const auto& req = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_TRUE(HasDiskImage(req, kCreatedDiskImagePath));
  EXPECT_TRUE(req.enable_virtio_blk_data());
}

TEST_F(ArcVmClientAdapterTest, VirtioBlkForData_LvmSupported) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(arc::kLvmApplicationContainers);

  StartParams start_params(GetPopulatedStartParams());
  start_params.use_virtio_blk_data = true;
  StartMiniArcWithParams(true, std::move(start_params));
  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);

  // CreateDiskImage() should NOT be called.
  EXPECT_EQ(GetTestConciergeClient()->create_disk_image_call_count(), 0);

  // StartArcVmRequest should contain the LVM-provided disk path.
  const auto& req = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_TRUE(req.enable_virtio_blk_data());
  const std::string expected_lvm_disk_path =
      base::StringPrintf("/dev/mapper/vm/dmcrypt-%s-arcvm",
                         std::string(kUserIdHash).substr(0, 8).c_str());
  const auto& disks = req.disks();
  auto it =
      base::ranges::find_if(disks, [&expected_lvm_disk_path](const auto& disk) {
        return disk.path() == expected_lvm_disk_path;
      });
  EXPECT_NE(it, disks.end());
  // O_DIRECT option should always be enabled on LVM-provided disk images.
  EXPECT_TRUE(it->o_direct());
}

TEST_F(ArcVmClientAdapterTest, VirtioBlkForData_OverrideUseLvm) {
  // ArcVirtioBlkDataConfigOverride:use_lvm/true should override
  // ArcLvmApplicationContainers flag.
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeaturesAndParameters(
      /*enabled_features=*/{{arc::kVirtioBlkDataConfigOverride,
                             {{"use_lvm", "true"}}}},
      /*disabled_features=*/{arc::kLvmApplicationContainers});

  StartParams start_params(GetPopulatedStartParams());
  start_params.use_virtio_blk_data = true;
  StartMiniArcWithParams(true, std::move(start_params));
  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);

  // CreateDiskImage() should NOT be called.
  EXPECT_EQ(GetTestConciergeClient()->create_disk_image_call_count(), 0);

  // StartArcVmRequest should contain the LVM-provided disk path.
  const auto& req = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_TRUE(req.enable_virtio_blk_data());
  const std::string expected_lvm_disk_path =
      base::StringPrintf("/dev/mapper/vm/dmcrypt-%s-arcvm",
                         std::string(kUserIdHash).substr(0, 8).c_str());
  const auto& disks = req.disks();
  auto it =
      base::ranges::find_if(disks, [&expected_lvm_disk_path](const auto& disk) {
        return disk.path() == expected_lvm_disk_path;
      });
  EXPECT_NE(it, disks.end());
  // O_DIRECT option should always be enabled on LVM-provided disk images.
  EXPECT_TRUE(it->o_direct());
}

TEST_F(ArcVmClientAdapterTest, VirtioBlkForData_NoLvmForEphemeralCryptohome) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(arc::kLvmApplicationContainers);

  // Set the guest account, whose cryptohome data is ephemeral.
  SetAccountId(user_manager::GuestAccountId());

  GetTestConciergeClient()->set_create_disk_image_response(
      CreateDiskImageResponse(vm_tools::concierge::DISK_STATUS_CREATED));

  StartParams start_params(GetPopulatedStartParams());
  start_params.use_virtio_blk_data = true;
  StartMiniArcWithParams(true, std::move(start_params));
  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);

  EXPECT_EQ(GetTestConciergeClient()->create_disk_image_call_count(), 1);

  // LVM shouldn't be used for ephemeral user even if LvmApplicationContainers
  // feature is enabled.
  const auto& req = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_TRUE(HasDiskImage(req, kCreatedDiskImagePath));
  EXPECT_TRUE(req.enable_virtio_blk_data());
}

TEST_F(ArcVmClientAdapterTest, DataBlockIoScheduler_Enabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeaturesAndParameters(
      {{arc::kBlockIoScheduler, {{"data_block_io_scheduler", "true"}}}}, {});

  StartParams start_params(GetPopulatedStartParams());
  start_params.use_virtio_blk_data = true;

  StartMiniArcWithParams(true, std::move(start_params));

  const auto& req = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_TRUE(req.enable_data_block_io_scheduler());
}

TEST_F(ArcVmClientAdapterTest, DataBlockIoScheduler_Disabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeaturesAndParameters(
      // Disabled.
      {{arc::kBlockIoScheduler, {{"data_block_io_scheduler", "false"}}}}, {});

  StartParams start_params(GetPopulatedStartParams());
  start_params.use_virtio_blk_data = true;

  StartMiniArcWithParams(true, std::move(start_params));

  const auto& req = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_FALSE(req.enable_data_block_io_scheduler());
}

TEST_F(ArcVmClientAdapterTest, DataBlockIoScheduler_VirtioBlkDataIsDisabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeaturesAndParameters(
      {{arc::kBlockIoScheduler, {{"data_block_io_scheduler", "true"}}}}, {});

  StartParams start_params(GetPopulatedStartParams());
  // virtio-blk /data is disabled.
  start_params.use_virtio_blk_data = false;

  StartMiniArcWithParams(true, std::move(start_params));

  const auto& req = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_FALSE(req.enable_data_block_io_scheduler());
}

TEST_F(ArcVmClientAdapterTest, DataBlockIoScheduler_DisabledForLvm) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeaturesAndParameters(
      // Uses LVM.
      {{arc::kLvmApplicationContainers, {}},
       {arc::kBlockIoScheduler, {{"data_block_io_scheduler", "true"}}}},
      {});

  StartParams start_params(GetPopulatedStartParams());
  start_params.use_virtio_blk_data = true;

  StartMiniArcWithParams(true, std::move(start_params));

  const auto& req = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_FALSE(req.enable_data_block_io_scheduler());
}

TEST_F(ArcVmClientAdapterTest, MetadataDisk_DisabledForArcT) {
  // Metadata disk should not be requested for ARC T.
  base::test::ScopedChromeOSVersionInfo version(
      "CHROMEOS_ARC_ANDROID_SDK_VERSION=33", base::Time::Now());

  StartParams start_params(GetPopulatedStartParams());
  StartMiniArcWithParams(true, std::move(start_params));
  const auto& req = GetTestConciergeClient()->start_arc_vm_request();

  const std::string metadta_disk_path =
      base::StringPrintf("/run/daemon-store/crosvm/%s/YXJjdm0=.metadata.img",
                         std::string(kUserIdHash).c_str());
  EXPECT_FALSE(HasDiskImage(req, metadta_disk_path));
}

TEST_F(ArcVmClientAdapterTest, MetadataDisk_EnabledForArcU) {
  // Metadata disk should be requested for ARC U.
  base::test::ScopedChromeOSVersionInfo version(
      "CHROMEOS_ARC_ANDROID_SDK_VERSION=34", base::Time::Now());

  StartParams start_params(GetPopulatedStartParams());
  StartMiniArcWithParams(true, std::move(start_params));
  const auto& req = GetTestConciergeClient()->start_arc_vm_request();

  const std::string metadta_disk_path =
      base::StringPrintf("/run/daemon-store/crosvm/%s/YXJjdm0=.metadata.img",
                         std::string(kUserIdHash).c_str());
  EXPECT_TRUE(HasDiskImage(req, metadta_disk_path));
}

TEST_F(ArcVmClientAdapterTest, ArcErofsImagesDisabled) {
  StartParams start_params(GetPopulatedStartParams());
  StartMiniArcWithParams(true, std::move(start_params));
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_FALSE(request.rootfs_o_direct());  // system image
  EXPECT_FALSE(request.disks(0).o_direct());  // vendor image
}

TEST_F(ArcVmClientAdapterTest, ArcErofsImagesEnabled) {
  base::CommandLine::ForCurrentProcess()->InitFromArgv(
      {"", "--arc-erofs"});
  StartParams start_params(GetPopulatedStartParams());
  StartMiniArcWithParams(true, std::move(start_params));
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_TRUE(request.rootfs_o_direct());  // system image
  EXPECT_TRUE(request.disks(0).o_direct());  // vendor image
}

// Tests that the binary translation type is set to None when no library is
// enabled by USE flags.
TEST_F(ArcVmClientAdapterTest, BinaryTranslationTypeNone) {
  StartParams start_params(GetPopulatedStartParams());
  StartMiniArcWithParams(true, std::move(start_params));
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_EQ(
      request.native_bridge_experiment(),
      vm_tools::concierge::StartArcVmRequest::BINARY_TRANSLATION_TYPE_NONE);
}

// Tests that the binary translation type is set to Houdini when only 32-bit
// Houdini library is enabled by USE flags.
TEST_F(ArcVmClientAdapterTest, BinaryTranslationTypeHoudini) {
  base::CommandLine::ForCurrentProcess()->InitFromArgv(
      {"", "--enable-houdini"});
  StartParams start_params(GetPopulatedStartParams());
  StartMiniArcWithParams(true, std::move(start_params));
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_EQ(
      request.native_bridge_experiment(),
      vm_tools::concierge::StartArcVmRequest::BINARY_TRANSLATION_TYPE_HOUDINI);
}

// Tests that the binary translation type is set to Houdini when only 64-bit
// Houdini library is enabled by USE flags.
TEST_F(ArcVmClientAdapterTest, BinaryTranslationTypeHoudini64) {
  base::CommandLine::ForCurrentProcess()->InitFromArgv(
      {"", "--enable-houdini64"});
  StartParams start_params(GetPopulatedStartParams());
  StartMiniArcWithParams(true, std::move(start_params));
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_EQ(
      request.native_bridge_experiment(),
      vm_tools::concierge::StartArcVmRequest::BINARY_TRANSLATION_TYPE_HOUDINI);
}

// Tests that the binary translation type is set to NDK translation when only
// 32-bit NDK translation library is enabled by USE flags.
TEST_F(ArcVmClientAdapterTest, BinaryTranslationTypeNdkTranslation) {
  base::CommandLine::ForCurrentProcess()->InitFromArgv(
      {"", "--enable-ndk-translation"});
  StartParams start_params(GetPopulatedStartParams());
  StartMiniArcWithParams(true, std::move(start_params));
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_EQ(request.native_bridge_experiment(),
            vm_tools::concierge::StartArcVmRequest::
                BINARY_TRANSLATION_TYPE_NDK_TRANSLATION);
}

// Tests that the binary translation type is set to NDK translation when only
// 64-bit NDK translation library is enabled by USE flags.
TEST_F(ArcVmClientAdapterTest, BinaryTranslationTypeNdkTranslation64) {
  base::CommandLine::ForCurrentProcess()->InitFromArgv(
      {"", "--enable-ndk-translation64"});
  StartParams start_params(GetPopulatedStartParams());
  StartMiniArcWithParams(true, std::move(start_params));
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_EQ(request.native_bridge_experiment(),
            vm_tools::concierge::StartArcVmRequest::
                BINARY_TRANSLATION_TYPE_NDK_TRANSLATION);
}

// Tests that the binary translation type is set to NDK translation when both
// Houdini and NDK translation libraries are enabled by USE flags, and the
// parameter start_params.native_bridge_experiment is set to true.
TEST_F(ArcVmClientAdapterTest, BinaryTranslationTypeNativeBridgeExperiment) {
  base::CommandLine::ForCurrentProcess()->InitFromArgv(
      {"", "--enable-houdini", "--enable-ndk-translation"});
  StartParams start_params(GetPopulatedStartParams());
  start_params.native_bridge_experiment = true;
  StartMiniArcWithParams(true, std::move(start_params));
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_EQ(request.native_bridge_experiment(),
            vm_tools::concierge::StartArcVmRequest::
                BINARY_TRANSLATION_TYPE_NDK_TRANSLATION);
}

// Tests that the binary translation type is set to Houdini when both Houdini
// and NDK translation libraries are enabled by USE flags, and the parameter
// start_params.native_bridge_experiment is set to false.
TEST_F(ArcVmClientAdapterTest, BinaryTranslationTypeNoNativeBridgeExperiment) {
  base::CommandLine::ForCurrentProcess()->InitFromArgv(
      {"", "--enable-houdini", "--enable-ndk-translation"});
  StartParams start_params(GetPopulatedStartParams());
  start_params.native_bridge_experiment = false;
  StartMiniArcWithParams(true, std::move(start_params));
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_EQ(
      request.native_bridge_experiment(),
      vm_tools::concierge::StartArcVmRequest::BINARY_TRANSLATION_TYPE_HOUDINI);
}

// Tests that the "generate" command line switches the mode.
TEST_F(ArcVmClientAdapterTest, TestGetArcVmUreadaheadModeGenerate) {
  base::CommandLine::ForCurrentProcess()->InitFromArgv(
      {"", "--arcvm-ureadahead-mode=generate"});
  StartParams start_params(GetPopulatedStartParams());
  StartMiniArcWithParams(true, std::move(start_params));
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_EQ(request.ureadahead_mode(),
            vm_tools::concierge::StartArcVmRequest::UREADAHEAD_MODE_GENERATE);
}

// Tests that the "disabled" command line disables both readahead and generate.
TEST_F(ArcVmClientAdapterTest, TestGetArcVmUreadaheadModeDisabled) {
  base::CommandLine::ForCurrentProcess()->InitFromArgv(
      {"", "--arcvm-ureadahead-mode=disabled"});
  StartParams start_params(GetPopulatedStartParams());
  StartMiniArcWithParams(true, std::move(start_params));
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_EQ(request.ureadahead_mode(),
            vm_tools::concierge::StartArcVmRequest::UREADAHEAD_MODE_DISABLED);
}

// Tests that ArcVmClientAdapter connects to the boot notification server
// twice: once in StartMiniArc to check that it is listening, and the second
// time in UpgradeArc to send props.
TEST_F(ArcVmClientAdapterTest, TestConnectToBootNotificationServer) {
  StartMiniArc();
  EXPECT_EQ(boot_notification_server()->connection_count(), 1);
  EXPECT_TRUE(boot_notification_server()->received_data().empty());

  UpgradeArcWithParams(/*expect_success=*/true, GetPopulatedUpgradeParams());
  EXPECT_EQ(boot_notification_server()->connection_count(), 2);
  EXPECT_FALSE(boot_notification_server()->received_data().empty());

  // Compare received data to expected output
  std::string expected_props = base::StringPrintf(
      "CID=%" PRId64 "\n%s", kCid,
      base::JoinString(
          GenerateUpgradePropsForTesting(GetPopulatedUpgradeParams(),
                                         kSerialNumber, "ro.boot"),
          "\n")
          .c_str());
  EXPECT_EQ(boot_notification_server()->received_data(), expected_props);
}

// Tests that StartMiniArc fails when the boot notification server is not
// listening.
TEST_F(ArcVmClientAdapterTest, TestBootNotificationServerIsNotListening) {
  boot_notification_server()->Stop();
  // Change timeout to 26 seconds to allow for exponential backoff.
  base::test::ScopedRunLoopTimeout timeout(FROM_HERE, base::Seconds(26));

  StartMiniArcWithParams(false, {});
}

// Tests that UpgradeArc() fails when sending the upgrade props
// to the boot notification server fails.
TEST_F(ArcVmClientAdapterTest, UpgradeArc_SendPropFail) {
  StartMiniArc();

  // Let ConnectToArcVmBootNotificationServer() return an invalid FD.
  SetArcVmBootNotificationServerFdForTesting(-1);

  UpgradeArcWithParamsAndStopVmCount(false, {}, /*run_until_stop_vm_count=*/2);
  ExpectArcStopped();
}

// Tests that UpgradeArc() fails when sending the upgrade props
// to the boot notification server fails.
TEST_F(ArcVmClientAdapterTest, UpgradeArc_SendPropFailNotWritable) {
  StartMiniArc();

  // Let ConnectToArcVmBootNotificationServer() return dup(STDIN_FILENO) which
  // is not writable.
  SetArcVmBootNotificationServerFdForTesting(STDIN_FILENO);

  UpgradeArcWithParamsAndStopVmCount(false, {}, /*run_until_stop_vm_count=*/2);
  ExpectArcStopped();
}

TEST_F(ArcVmClientAdapterTest, DisableDownloadProviderDefault) {
  StartParams start_params(GetPopulatedStartParams());
  StartMiniArcWithParams(true, std::move(start_params));
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  // Not expected arc_disable_download_provider in properties.
  EXPECT_FALSE(request.mini_instance_request().disable_download_provider());
}

TEST_F(ArcVmClientAdapterTest, DisableDownloadProviderEnforced) {
  StartParams start_params(GetPopulatedStartParams());
  start_params.disable_download_provider = true;
  StartMiniArcWithParams(true, std::move(start_params));
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_TRUE(request.mini_instance_request().disable_download_provider());
}

TEST_F(ArcVmClientAdapterTest, BroadcastPreANRDefault) {
  StartMiniArc();
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_FALSE(request.enable_broadcast_anr_prenotify());
}

TEST_F(ArcVmClientAdapterTest, BroadcastPreANREnabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatureState(arc::kVmBroadcastPreNotifyANR, true);

  StartMiniArc();
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_TRUE(request.enable_broadcast_anr_prenotify());
}

TEST_F(ArcVmClientAdapterTest, TrimVmMemory_Success) {
  SetValidUserInfo();
  vm_tools::concierge::ReclaimVmMemoryResponse response;
  response.set_success(true);
  GetTestConciergeClient()->set_reclaim_vm_memory_response(response);

  bool result = false;
  std::string reason("non empty");
  adapter()->TrimVmMemory(
      base::BindLambdaForTesting(
          [&result, &reason](bool success, const std::string& failure_reason) {
            result = success;
            reason = failure_reason;
          }),
      0);
  run_loop()->RunUntilIdle();
  EXPECT_TRUE(result);
  EXPECT_TRUE(reason.empty());
  EXPECT_EQ(GetTestConciergeClient()->reclaim_vm_call_count(), 1);
  EXPECT_EQ(GetTestConciergeClient()->reclaim_vm_request().page_limit(), 0);
}

TEST_F(ArcVmClientAdapterTest, TrimVmMemory_LimitPagesHonored) {
  StartMiniArc();
  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);
  EXPECT_FALSE(is_system_shutdown().has_value());
  UpgradeArc(true);

  vm_tools::concierge::ReclaimVmMemoryResponse response;
  response.set_success(true);
  GetTestConciergeClient()->set_reclaim_vm_memory_response(response);

  bool result = false;
  std::string reason("non empty");
  adapter()->TrimVmMemory(
      base::BindLambdaForTesting(
          [&result, &reason](bool success, const std::string& failure_reason) {
            result = success;
            reason = failure_reason;
          }),
      1234);
  run_loop()->RunUntilIdle();
  EXPECT_TRUE(result);
  EXPECT_TRUE(reason.empty());

  // Verify that exactly one call was done to underlying layer, and that
  // the specified parameter value was passed in.
  EXPECT_EQ(GetTestConciergeClient()->reclaim_vm_call_count(), 1);
  EXPECT_EQ(GetTestConciergeClient()->reclaim_vm_request().page_limit(), 1234);

  StopArcInstance();
}

TEST_F(ArcVmClientAdapterTest, TrimVmMemory_Failure) {
  SetValidUserInfo();

  constexpr const char kReason[] = "This is the reason";
  vm_tools::concierge::ReclaimVmMemoryResponse response;
  response.set_success(false);
  response.set_failure_reason(kReason);
  GetTestConciergeClient()->set_reclaim_vm_memory_response(response);

  bool result = true;
  std::string reason;
  adapter()->TrimVmMemory(
      base::BindLambdaForTesting(
          [&result, &reason](bool success, const std::string& failure_reason) {
            result = success;
            reason = failure_reason;
          }),
      0);
  run_loop()->RunUntilIdle();
  EXPECT_FALSE(result);
  EXPECT_EQ(kReason, reason);
}

TEST_F(ArcVmClientAdapterTest, TrimVmMemory_EmptyResponse) {
  SetValidUserInfo();

  // By default, the fake concierge client returns an empty response.
  // This is to make sure TrimMemoty() can handle such a response.
  bool result = true;
  std::string reason;
  adapter()->TrimVmMemory(
      base::BindLambdaForTesting(
          [&result, &reason](bool success, const std::string& failure_reason) {
            result = success;
            reason = failure_reason;
          }),
      0);
  run_loop()->RunUntilIdle();
  EXPECT_FALSE(result);
  EXPECT_FALSE(reason.empty());
}

TEST_F(ArcVmClientAdapterTest, TrimVmMemory_EmptyUserIdHash) {
  adapter()->SetUserInfo(cryptohome::Identification(), std::string(),
                         std::string());

  constexpr const char kReason[] = "This is the reason";
  vm_tools::concierge::ReclaimVmMemoryResponse response;
  response.set_success(false);
  response.set_failure_reason(kReason);
  GetTestConciergeClient()->set_reclaim_vm_memory_response(response);

  bool result = true;
  std::string reason;
  adapter()->TrimVmMemory(
      base::BindLambdaForTesting(
          [&result, &reason](bool success, const std::string& failure_reason) {
            result = success;
            reason = failure_reason;
          }),
      0);
  run_loop()->RunUntilIdle();
  EXPECT_FALSE(result);
  // When |user_id_hash_| is empty, the call will fail without talking to
  // Concierge.
  EXPECT_NE(kReason, reason);
  EXPECT_FALSE(reason.empty());
}

TEST_F(ArcVmClientAdapterTest, ArcVmUseHugePagesEnabled) {
  base::CommandLine::ForCurrentProcess()->InitFromArgv(
      {"", "--arcvm-use-hugepages"});
  StartParams start_params(GetPopulatedStartParams());
  StartMiniArcWithParams(true, std::move(start_params));
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_TRUE(request.use_hugepages());
}

TEST_F(ArcVmClientAdapterTest, ArcVmLockGuestMemoryEnabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(kLockGuestMemory);
  StartParams start_params(GetPopulatedStartParams());
  StartMiniArcWithParams(true, std::move(start_params));
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_TRUE(request.lock_guest_memory());
}

TEST_F(ArcVmClientAdapterTest, ArcVmMemoryOptionsDisabled) {
  StartParams start_params(GetPopulatedStartParams());
  StartMiniArcWithParams(true, std::move(start_params));
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  // Verify that both options are disabled by default.
  EXPECT_FALSE(request.use_hugepages());
  EXPECT_FALSE(request.lock_guest_memory());
}

// Test that StartArcVmRequest has no memory_mib field when kVmMemorySize is
// disabled.
TEST_F(ArcVmClientAdapterTest, ArcVmMemorySizeDisabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndDisableFeature(kVmMemorySize);
  StartParams start_params(GetPopulatedStartParams());
  StartMiniArcWithParams(true, std::move(start_params));
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_EQ(request.memory_mib(), 0u);
}

// Test that StartArcVmRequest has `memory_mib == system memory` when
// kVmMemorySize is enabled with no maximum and shift_mib := 0.
TEST_F(ArcVmClientAdapterTest, ArcVmMemorySizeEnabledBig) {
  base::test::ScopedFeatureList feature_list;
  base::FieldTrialParams params;
  params["shift_mib"] = "0";
  feature_list.InitAndEnableFeatureWithParameters(kVmMemorySize, params);
  base::SystemMemoryInfoKB info;
  ASSERT_TRUE(base::GetSystemMemoryInfo(&info));
  const uint32_t total_mib = info.total / 1024;
  StartParams start_params(GetPopulatedStartParams());
  StartMiniArcWithParams(true, std::move(start_params));
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_EQ(request.memory_mib(), total_mib);
}

// Test that StartArcVmRequest has `memory_mib == system memory - 1024` when
// kVmMemorySize is enabled with no maximum and shift_mib := -1024.
TEST_F(ArcVmClientAdapterTest, ArcVmMemorySizeEnabledSmall) {
  base::test::ScopedFeatureList feature_list;
  base::FieldTrialParams params;
  params["shift_mib"] = "-1024";
  feature_list.InitAndEnableFeatureWithParameters(kVmMemorySize, params);
  base::SystemMemoryInfoKB info;
  ASSERT_TRUE(base::GetSystemMemoryInfo(&info));
  const uint32_t total_mib = info.total / 1024;
  StartParams start_params(GetPopulatedStartParams());
  StartMiniArcWithParams(true, std::move(start_params));
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_EQ(request.memory_mib(), total_mib - 1024);
}

// Test that StartArcVmRequest has memory_mib unset when kVmMemorySize is
// enabled, but the requested size is too low (due to max_mib being lower than
// the 2048 safety minimum).
TEST_F(ArcVmClientAdapterTest, ArcVmMemorySizeEnabledLow) {
  base::test::ScopedFeatureList feature_list;
  base::FieldTrialParams params;
  params["shift_mib"] = "0";
  params["max_mib"] = "1024";
  feature_list.InitAndEnableFeatureWithParameters(kVmMemorySize, params);
  StartParams start_params(GetPopulatedStartParams());
  StartMiniArcWithParams(true, std::move(start_params));
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  // The 1024 max_mib is below the 2048 MiB safety cut-off, so we expect
  // memory_mib to be unset.
  EXPECT_EQ(request.memory_mib(), 0u);
}

// Test that StartArcVmRequest has `memory_mib == 2049` when kVmMemorySize is
// enabled with max_mib := 2049.
// NOTE: requires that the test running system has more than 2049 MiB of RAM.
TEST_F(ArcVmClientAdapterTest, ArcVmMemorySizeEnabledMax) {
  base::test::ScopedFeatureList feature_list;
  base::FieldTrialParams params;
  params["shift_mib"] = "0";
  params["max_mib"] = "2049";  // Above the 2048 minimum cut-off.
  feature_list.InitAndEnableFeatureWithParameters(kVmMemorySize, params);
  StartParams start_params(GetPopulatedStartParams());
  StartMiniArcWithParams(true, std::move(start_params));
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_EQ(request.memory_mib(), 2049u);
}

// Test that ARCMVM size is set by ram_percentage.
TEST_F(ArcVmClientAdapterTest, ArcVmMemorySizeWithPercentageParam) {
  base::test::ScopedFeatureList feature_list;
  base::FieldTrialParams params;
  params["ram_percentage"] = "25";
  feature_list.InitAndEnableFeatureWithParameters(kVmMemorySize, params);
  base::SystemMemoryInfoKB info;
  ASSERT_TRUE(base::GetSystemMemoryInfo(&info));
  const uint32_t total_mib = info.total / 1024;
  StartParams start_params(GetPopulatedStartParams());
  StartMiniArcWithParams(true, std::move(start_params));
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  // shift_mib is -500 by default
  EXPECT_EQ(request.memory_mib(), total_mib / 4 - 500);
}

// Test that ARCMVM size is set by both ram_percentage and shift_mib.
TEST_F(ArcVmClientAdapterTest, ArcVmMemorySizeWithPercentageParamAndShiftMiB) {
  base::test::ScopedFeatureList feature_list;
  base::FieldTrialParams params;
  params["ram_percentage"] = "25";
  params["shift_mib"] = "-512";
  feature_list.InitAndEnableFeatureWithParameters(kVmMemorySize, params);
  base::SystemMemoryInfoKB info;
  ASSERT_TRUE(base::GetSystemMemoryInfo(&info));
  const uint32_t total_mib = info.total / 1024;
  StartParams start_params(GetPopulatedStartParams());
  StartMiniArcWithParams(true, std::move(start_params));
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_EQ(request.memory_mib(), total_mib / 4 - 512);
}

// Test that StartArcVmRequest has no memory_mib field when getting system
// memory info fails.
TEST_F(ArcVmClientAdapterTest, ArcVmMemorySizeEnabledNoSystemMemoryInfo) {
  // Inject the failure.
  class TestDelegate : public ArcVmClientAdapterDelegate {
    bool GetSystemMemoryInfo(base::SystemMemoryInfoKB* info) override {
      return false;
    }
  };
  SetArcVmClientAdapterDelegateForTesting(adapter(),
                                          std::make_unique<TestDelegate>());

  base::test::ScopedFeatureList feature_list;
  base::FieldTrialParams params;
  params["shift_mib"] = "0";
  feature_list.InitAndEnableFeatureWithParameters(kVmMemorySize, params);
  StartParams start_params(GetPopulatedStartParams());
  StartMiniArcWithParams(true, std::move(start_params));
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_EQ(request.memory_mib(), 0u);
}

// Test that StartArcVmRequest::memory_mib is limited to k32bitVmRamMaxMib when
// crosvm is a 32-bit process.
// TODO(khmel): Remove this once crosvm becomes 64 bit binary on ARM.
TEST_F(ArcVmClientAdapterTest, ArcVmMemorySizeEnabledOn32Bit) {
  class TestDelegate : public ArcVmClientAdapterDelegate {
    bool GetSystemMemoryInfo(base::SystemMemoryInfoKB* info) override {
      // Return a value larger than k32bitVmRamMaxMib to verify that the VM
      // memory size is actually limited.
      info->total = (k32bitVmRamMaxMib + 1000) * 1024;
      return true;
    }
    bool IsCrosvm32bit() override { return true; }
  };
  SetArcVmClientAdapterDelegateForTesting(adapter(),
                                          std::make_unique<TestDelegate>());

  base::test::ScopedFeatureList feature_list;
  base::FieldTrialParams params;
  params["shift_mib"] = "0";
  feature_list.InitAndEnableFeatureWithParameters(kVmMemorySize, params);
  StartParams start_params(GetPopulatedStartParams());
  StartMiniArcWithParams(true, std::move(start_params));
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();

  EXPECT_EQ(request.memory_mib(), k32bitVmRamMaxMib);
}

// Test that the request passes an empty disk for the demo image
// or the block apex composite disk when they are not present.
// There should be two empty disks (/dev/block/vdc and /dev/block/vdd)
// and they should have path /dev/null.
TEST_F(ArcVmClientAdapterTest, ArcVmEmptyVirtualDisksExist) {
  StartMiniArc();

  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_EQ(request.disks(1).path(), "/dev/null");
  EXPECT_EQ(request.disks(2).path(), "/dev/null");
}

// Test that block apex disk path exists when the composite disk payload
// exists.
TEST_F(ArcVmClientAdapterTest, ArcVmBlockApexDiskExists) {
  constexpr const char path[] = "/opt/google/vms/android/apex/payload.img";
  set_block_apex_path(base::FilePath(path));
  StartMiniArc();
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_TRUE(base::Contains(request.disks(), path,
                             [](const auto& p) { return p.path(); }));
}

// Test that the block apex disk path isn't included when it doesn't exist.
TEST_F(ArcVmClientAdapterTest, ArcVmNoBlockApexDisk) {
  constexpr const char path[] = "/opt/google/vms/android/apex/payload.img";
  StartMiniArc();
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_FALSE(base::Contains(request.disks(), path,
                              [](const auto& p) { return p.path(); }));
}

// Tests that OnConnectionReady() calls the ArcVmCompleteBoot call D-Bus method.
TEST_F(ArcVmClientAdapterTest, OnConnectionReady) {
  StartParams start_params(GetPopulatedStartParams());
  StartMiniArcWithParams(true, std::move(start_params));
  UpgradeArc(true);

  // This calls ArcVmClientAdapter::OnConnectionReady().
  arc_bridge_service()->app()->SetInstance(app_instance());
  WaitForInstanceReady(arc_bridge_service()->app());

  EXPECT_EQ(1, GetTestConciergeClient()->arcvm_complete_boot_call_count());
}

// Tests that ArcVmCompleteBoot failure won't crash the adapter.
TEST_F(ArcVmClientAdapterTest, OnConnectionReady_ArcVmCompleteBootFailure) {
  StartParams start_params(GetPopulatedStartParams());
  StartMiniArcWithParams(true, std::move(start_params));
  UpgradeArc(true);

  // Inject the failure.
  std::optional<vm_tools::concierge::ArcVmCompleteBootResponse> response;
  response.emplace();
  response->set_result(
      vm_tools::concierge::ArcVmCompleteBootResult::BAD_REQUEST);
  GetTestConciergeClient()->set_arcvm_complete_boot_response(response);

  // This calls ArcVmClientAdapter::OnConnectionReady().
  arc_bridge_service()->app()->SetInstance(app_instance());
  WaitForInstanceReady(arc_bridge_service()->app());

  EXPECT_EQ(1, GetTestConciergeClient()->arcvm_complete_boot_call_count());
}

// Tests that null ArcVmCompleteBoot reply won't crash the adapter.
TEST_F(ArcVmClientAdapterTest,
       OnConnectionReady_ArcVmCompleteBootFailureNullReply) {
  StartParams start_params(GetPopulatedStartParams());
  StartMiniArcWithParams(true, std::move(start_params));
  UpgradeArc(true);

  // Inject the failure.
  GetTestConciergeClient()->set_arcvm_complete_boot_response(std::nullopt);

  // This calls ArcVmClientAdapter::OnConnectionReady().
  arc_bridge_service()->app()->SetInstance(app_instance());
  WaitForInstanceReady(arc_bridge_service()->app());

  EXPECT_EQ(1, GetTestConciergeClient()->arcvm_complete_boot_call_count());
}

TEST_F(ArcVmClientAdapterTest, UpgradeArc_EnableArcNearbyShare_Default) {
  StartMiniArc();
  EXPECT_EQ(boot_notification_server()->connection_count(), 1);
  EXPECT_TRUE(boot_notification_server()->received_data().empty());

  UpgradeArcWithParams(/*expect_success=*/true, GetPopulatedUpgradeParams());
  EXPECT_EQ(boot_notification_server()->connection_count(), 2);
  EXPECT_FALSE(boot_notification_server()->received_data().empty());
  EXPECT_TRUE(base::Contains(boot_notification_server()->received_data(),
                             "ro.boot.enable_arc_nearby_share=1"));
}

TEST_F(ArcVmClientAdapterTest, UpgradeArc_EnableArcNearbyShare_Enabled) {
  StartMiniArc();
  EXPECT_EQ(boot_notification_server()->connection_count(), 1);
  EXPECT_TRUE(boot_notification_server()->received_data().empty());

  UpgradeParams upgrade_params = GetPopulatedUpgradeParams();
  upgrade_params.enable_arc_nearby_share = true;
  UpgradeArcWithParams(/*expect_success=*/true, upgrade_params);
  EXPECT_EQ(boot_notification_server()->connection_count(), 2);
  EXPECT_FALSE(boot_notification_server()->received_data().empty());
  EXPECT_TRUE(base::Contains(boot_notification_server()->received_data(),
                             "ro.boot.enable_arc_nearby_share=1"));
}

TEST_F(ArcVmClientAdapterTest, UpgradeArc_EnableArcNearbyShare_Disabled) {
  StartMiniArc();
  EXPECT_EQ(boot_notification_server()->connection_count(), 1);
  EXPECT_TRUE(boot_notification_server()->received_data().empty());

  UpgradeParams upgrade_params = GetPopulatedUpgradeParams();
  upgrade_params.enable_arc_nearby_share = false;
  UpgradeArcWithParams(/*expect_success=*/true, upgrade_params);
  EXPECT_EQ(boot_notification_server()->connection_count(), 2);
  EXPECT_FALSE(boot_notification_server()->received_data().empty());
  EXPECT_TRUE(base::Contains(boot_notification_server()->received_data(),
                             "ro.boot.enable_arc_nearby_share=0"));
}

TEST_F(ArcVmClientAdapterTest,
       StartArc_EnableConsumerAutoUpdateToggle_Default) {
  StartMiniArc();
  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);
  EXPECT_FALSE(is_system_shutdown().has_value());
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_TRUE(
      request.mini_instance_request().enable_consumer_auto_update_toggle());
}

TEST_F(ArcVmClientAdapterTest,
       StartArc_EnableConsumerAutoUpdateToggle_Enabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(
      ash::features::kConsumerAutoUpdateToggleAllowed);
  StartMiniArc();
  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);
  EXPECT_FALSE(is_system_shutdown().has_value());
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_TRUE(
      request.mini_instance_request().enable_consumer_auto_update_toggle());
}

TEST_F(ArcVmClientAdapterTest,
       StartArc_EnableConsumerAutoUpdateToggle_Disabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndDisableFeature(
      ash::features::kConsumerAutoUpdateToggleAllowed);
  StartMiniArc();
  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);
  EXPECT_FALSE(is_system_shutdown().has_value());
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_FALSE(
      request.mini_instance_request().enable_consumer_auto_update_toggle());
}

TEST_F(ArcVmClientAdapterTest, StartArc_EnablePrivacyHubForChrome_Default) {
  StartMiniArc();
  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);
  EXPECT_FALSE(is_system_shutdown().has_value());
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_FALSE(request.mini_instance_request().enable_privacy_hub_for_chrome());
}

TEST_F(ArcVmClientAdapterTest, StartArc_EnablePrivacyHubForChrome_Enabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(ash::features::kCrosPrivacyHub);
  StartMiniArc();
  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);
  EXPECT_FALSE(is_system_shutdown().has_value());
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_TRUE(request.mini_instance_request().enable_privacy_hub_for_chrome());
}

TEST_F(ArcVmClientAdapterTest, StartArc_EnablePrivacyHubForChrome_Disabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndDisableFeature(ash::features::kCrosPrivacyHub);
  StartMiniArc();
  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);
  EXPECT_FALSE(is_system_shutdown().has_value());
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_FALSE(request.mini_instance_request().enable_privacy_hub_for_chrome());
}

TEST_F(ArcVmClientAdapterTest, StartMiniArc_ArcSwitchToKeymint_Default) {
  StartMiniArc();
  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);
  EXPECT_FALSE(is_system_shutdown().has_value());
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_FALSE(request.mini_instance_request().arc_switch_to_keymint());
}

TEST_F(ArcVmClientAdapterTest, StartMiniArc_EnableArcAttestation_Default) {
  StartMiniArc();
  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);
  EXPECT_FALSE(is_system_shutdown().has_value());
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_FALSE(request.mini_instance_request().enable_arc_attestation());
}

TEST_F(ArcVmClientAdapterTest, StartMiniArc_EnableArcAttestation_Enabled) {
  base::test::ScopedChromeOSVersionInfo version(
      base::StringPrintf("CHROMEOS_ARC_ANDROID_SDK_VERSION=%d",
                         arc::kArcVersionT),
      base::Time::Now());
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatureStates(
      {{arc::kEnableArcAttestation, true}, {arc::kSwitchToKeyMintOnT, true}});
  StartMiniArc();

  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);
  EXPECT_FALSE(is_system_shutdown().has_value());
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_TRUE(request.mini_instance_request().enable_arc_attestation());
}

TEST_F(ArcVmClientAdapterTest, StartMiniArc_EnableArcAttestation_DisabledOnR) {
  base::test::ScopedChromeOSVersionInfo version(
      base::StringPrintf("CHROMEOS_ARC_ANDROID_SDK_VERSION=%d",
                         arc::kArcVersionR),
      base::Time::Now());
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatureStates(
      {{arc::kEnableArcAttestation, true}, {arc::kSwitchToKeyMintOnT, true}});
  StartMiniArc();

  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);
  EXPECT_FALSE(is_system_shutdown().has_value());
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_FALSE(request.mini_instance_request().enable_arc_attestation());
}

TEST_F(ArcVmClientAdapterTest, StartMiniArc_EnableArcAttestation_DisabledOnT) {
  base::test::ScopedChromeOSVersionInfo version(
      base::StringPrintf("CHROMEOS_ARC_ANDROID_SDK_VERSION=%d",
                         arc::kArcVersionT),
      base::Time::Now());
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatureStates(
      {{arc::kEnableArcAttestation, false}, {arc::kSwitchToKeyMintOnT, true}});
  StartMiniArc();

  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);
  EXPECT_FALSE(is_system_shutdown().has_value());
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_FALSE(request.mini_instance_request().enable_arc_attestation());
}

TEST_F(ArcVmClientAdapterTest,
       StartMiniArc_EnableArcAttestation_KeymintDisabled) {
  base::test::ScopedChromeOSVersionInfo version(
      base::StringPrintf("CHROMEOS_ARC_ANDROID_SDK_VERSION=%d",
                         arc::kArcVersionT),
      base::Time::Now());
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatureState(arc::kSwitchToKeyMintOnT, false);
  StartMiniArc();

  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);
  EXPECT_FALSE(is_system_shutdown().has_value());
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_FALSE(request.mini_instance_request().enable_arc_attestation());
}

// Test that the value of swappiness is default value when kGuestZram is
// disabled.
TEST_F(ArcVmClientAdapterTest, ArcGuestZramDisabledSwappiness) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndDisableFeature(kGuestSwap);
  StartParams start_params(GetPopulatedStartParams());
  StartMiniArcWithParams(true, std::move(start_params));
  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);
  EXPECT_FALSE(is_system_shutdown().has_value());
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_EQ(0, request.guest_swappiness());
}

// Test that StartArcVmRequest has correct swappiness value.
TEST_F(ArcVmClientAdapterTest, ArcGuestZramSwappinessValid) {
  base::test::ScopedFeatureList feature_list;
  base::FieldTrialParams params;
  params["swappiness"] = "90";
  params["size"] = base::NumberToString(256 * 1024 * 1024);
  params["size_percentage"] = "0";
  feature_list.InitAndEnableFeatureWithParameters(kGuestSwap, params);
  StartParams start_params(GetPopulatedStartParams());
  StartMiniArcWithParams(true, std::move(start_params));
  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);
  EXPECT_FALSE(is_system_shutdown().has_value());
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_EQ(90, request.guest_swappiness());
  EXPECT_EQ(256u, request.guest_zram_mib());
}

TEST_F(ArcVmClientAdapterTest, ArcGuestZramSizeByPercentage_5GbSystem) {
  class TestDelegate : public ArcVmClientAdapterDelegate {
    bool GetSystemMemoryInfo(base::SystemMemoryInfoKB* info) override {
      info->total = 5 * 1024 * 1024;
      return true;
    }
    bool IsCrosvm32bit() override { return false; }
  };
  SetArcVmClientAdapterDelegateForTesting(adapter(),
                                          std::make_unique<TestDelegate>());
  base::test::ScopedFeatureList feature_list;
  base::FieldTrialParams params;
  params["size"] = "2000";  // Should be ignored
  params["size_percentage"] = "50";

  feature_list.InitAndEnableFeatureWithParameters(kGuestSwap, params);
  StartParams start_params(GetPopulatedStartParams());
  StartMiniArcWithParams(true, std::move(start_params));

  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  // As shift_mib for memory size is -500 by default,
  // 5GB system should result in 4.5GB VM size => 2.25GB ZRAM.
  EXPECT_EQ(2310u, request.guest_zram_mib());
}

TEST_F(ArcVmClientAdapterTest, ArcGuestZramSizeByPercentage_4GbSystem) {
  class TestDelegate : public ArcVmClientAdapterDelegate {
    bool GetSystemMemoryInfo(base::SystemMemoryInfoKB* info) override {
      info->total = 4 * 1024 * 1024;
      return true;
    }
    bool IsCrosvm32bit() override { return false; }
  };
  SetArcVmClientAdapterDelegateForTesting(adapter(),
                                          std::make_unique<TestDelegate>());
  base::test::ScopedFeatureList feature_list;
  base::FieldTrialParams params;
  params["size"] = "2000";  // Should be ignored
  params["size_percentage"] = "50";

  feature_list.InitAndEnableFeatureWithParameters(kGuestSwap, params);
  StartParams start_params(GetPopulatedStartParams());
  StartMiniArcWithParams(true, std::move(start_params));

  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  // As shift_mib for memory size is -500 by default,
  // 4GB system should result in 3.5GB VM size => 1.75GB ZRAM.
  EXPECT_EQ(1798u, request.guest_zram_mib());
}

TEST_F(ArcVmClientAdapterTest, ArcGuestZramSizeByPercentage_CustomMem) {
  class TestDelegate : public ArcVmClientAdapterDelegate {
    bool GetSystemMemoryInfo(base::SystemMemoryInfoKB* info) override {
      info->total = 6 * 1024 * 1024;
      return true;
    }
    bool IsCrosvm32bit() override { return false; }
  };
  SetArcVmClientAdapterDelegateForTesting(adapter(),
                                          std::make_unique<TestDelegate>());
  base::test::ScopedFeatureList feature_list;
  base::FieldTrialParams params;

  feature_list.InitWithFeaturesAndParameters(
      {{kGuestSwap, {{"size_percentage", "50"}}},
       {kVmMemorySize, {{"shift_mib", "-2048"}}}},
      {});
  StartParams start_params(GetPopulatedStartParams());
  StartMiniArcWithParams(true, std::move(start_params));

  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  // 6GB system with -2GB shift results in 4GB VM size => 2GB ZRAM.
  EXPECT_EQ(2048u, request.guest_zram_mib());
}

// Test that StartArcVmRequest has no matching command line flag
// when kVmMemoryPSIReports is disabled.
TEST_F(ArcVmClientAdapterTest, ArcVmMemoryPSIReportsDisabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndDisableFeature(kVmMemoryPSIReports);
  StartParams start_params(GetPopulatedStartParams());
  StartMiniArcWithParams(true, std::move(start_params));
  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);
  EXPECT_FALSE(is_system_shutdown().has_value());
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_EQ(request.vm_memory_psi_period(), -1);
}

// Test that StartArcVmRequest has correct  command line flag
// when kVmMemoryPSIReports is enabled.
TEST_F(ArcVmClientAdapterTest, ArcVmMemoryPSIReportsEnabled) {
  base::test::ScopedFeatureList feature_list;
  base::FieldTrialParams params;
  params["period"] = "300";
  feature_list.InitAndEnableFeatureWithParameters(kVmMemoryPSIReports, params);
  StartParams start_params(GetPopulatedStartParams());
  StartMiniArcWithParams(true, std::move(start_params));
  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);
  EXPECT_FALSE(is_system_shutdown().has_value());
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_EQ(request.vm_memory_psi_period(), 300);
}

struct DalvikMemoryProfileTestParam {
  // Requested profile.
  StartParams::DalvikMemoryProfile profile;
  // Name of profile that is expected.
  const char* profile_name;
  StartArcMiniInstanceRequest::DalvikMemoryProfile arc_profile;
};

constexpr DalvikMemoryProfileTestParam kDalvikMemoryProfileTestCases[] = {
    {StartParams::DalvikMemoryProfile::DEFAULT, "4G",
     StartArcMiniInstanceRequest_DalvikMemoryProfile_MEMORY_PROFILE_DEFAULT},
    {StartParams::DalvikMemoryProfile::M4G, "4G",
     StartArcMiniInstanceRequest_DalvikMemoryProfile_MEMORY_PROFILE_4G},
    {StartParams::DalvikMemoryProfile::M8G, "8G",
     StartArcMiniInstanceRequest_DalvikMemoryProfile_MEMORY_PROFILE_8G},
    {StartParams::DalvikMemoryProfile::M16G, "16G",
     StartArcMiniInstanceRequest_DalvikMemoryProfile_MEMORY_PROFILE_16G}};

class ArcVmClientAdapterDalvikMemoryProfileTest
    : public ArcVmClientAdapterTest,
      public testing::WithParamInterface<DalvikMemoryProfileTestParam> {};

INSTANTIATE_TEST_SUITE_P(All,
                         ArcVmClientAdapterDalvikMemoryProfileTest,
                         ::testing::ValuesIn(kDalvikMemoryProfileTestCases));

TEST_P(ArcVmClientAdapterDalvikMemoryProfileTest, Profile) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatureState(arc::kUseDalvikMemoryProfile,
                                    true /* use */);

  const auto& test_param = GetParam();
  StartParams start_params(GetPopulatedStartParams());
  start_params.dalvik_memory_profile = test_param.profile;
  StartMiniArcWithParams(true, std::move(start_params));
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_EQ(request.mini_instance_request().dalvik_memory_profile(),
            test_param.arc_profile);
}

TEST_F(ArcVmClientAdapterTest, ArcVmTTSCachingDefault) {
  StartParams start_params(GetPopulatedStartParams());
  StartMiniArcWithParams(true, std::move(start_params));
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_FALSE(request.mini_instance_request().enable_tts_caching());
}

TEST_F(ArcVmClientAdapterTest, ArcVmTTSCachingEnabled) {
  StartParams start_params(GetPopulatedStartParams());
  start_params.enable_tts_caching = true;
  StartMiniArcWithParams(true, std::move(start_params));
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_TRUE(request.mini_instance_request().enable_tts_caching());
}

TEST_F(ArcVmClientAdapterTest, ArcVmLcdDensity) {
  StartParams start_params(GetPopulatedStartParams());
  start_params.lcd_density = 480;
  StartMiniArcWithParams(true, std::move(start_params));
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_EQ(480, request.mini_instance_request().lcd_density());
}

TEST_F(ArcVmClientAdapterTest, ArcVmPlayStoreAutoUpdateOn) {
  StartParams start_params(GetPopulatedStartParams());
  start_params.play_store_auto_update =
      StartParams::PlayStoreAutoUpdate::AUTO_UPDATE_ON;
  StartMiniArcWithParams(true, std::move(start_params));
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_EQ(StartArcMiniInstanceRequest_PlayStoreAutoUpdate_AUTO_UPDATE_ON,
            request.mini_instance_request().play_store_auto_update());
}

TEST_F(ArcVmClientAdapterTest, ArcVmPlayStoreAutoUpdateOff) {
  StartParams start_params(GetPopulatedStartParams());
  start_params.play_store_auto_update =
      StartParams::PlayStoreAutoUpdate::AUTO_UPDATE_OFF;
  StartMiniArcWithParams(true, std::move(start_params));
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_EQ(StartArcMiniInstanceRequest_PlayStoreAutoUpdate_AUTO_UPDATE_OFF,
            request.mini_instance_request().play_store_auto_update());
}

TEST_F(ArcVmClientAdapterTest, ArcVmPlayStoreAutoUpdateDefault) {
  StartParams start_params(GetPopulatedStartParams());
  start_params.play_store_auto_update =
      StartParams::PlayStoreAutoUpdate::AUTO_UPDATE_DEFAULT;
  StartMiniArcWithParams(true, std::move(start_params));
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_EQ(StartArcMiniInstanceRequest_PlayStoreAutoUpdate_AUTO_UPDATE_DEFAULT,
            request.mini_instance_request().play_store_auto_update());
}

TEST_F(ArcVmClientAdapterTest, ConvertUpgradeParams_SkipTtsCacheSetup) {
  StartMiniArc();
  UpgradeParams upgrade_params = GetPopulatedUpgradeParams();
  upgrade_params.skip_tts_cache = true;
  UpgradeArcWithParams(true, std::move(upgrade_params));
  EXPECT_TRUE(base::Contains(boot_notification_server()->received_data(),
                             "ro.boot.skip_tts_cache=1"));
}

TEST_F(ArcVmClientAdapterTest, ConvertUpgradeParams_EnableTtsCacheSetup) {
  StartMiniArc();
  UpgradeParams upgrade_params = GetPopulatedUpgradeParams();
  upgrade_params.skip_tts_cache = false;
  UpgradeArcWithParams(true, std::move(upgrade_params));
  EXPECT_TRUE(base::Contains(boot_notification_server()->received_data(),
                             "ro.boot.skip_tts_cache=0"));
}

TEST_F(ArcVmClientAdapterTest, mglruReclaimDisabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatureState(arc::kMglruReclaim, false);
  StartMiniArcWithParams(true, GetPopulatedStartParams());
  auto req = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_EQ(req.mglru_reclaim_interval(), 0);
  EXPECT_EQ(req.mglru_reclaim_swappiness(), 0);
}

TEST_F(ArcVmClientAdapterTest, mglruReclaimEnabled) {
  base::test::ScopedFeatureList feature_list;
  base::FieldTrialParams params;
  params["interval"] = "30000";
  params["swappiness"] = "100";
  feature_list.InitAndEnableFeatureWithParameters(kMglruReclaim, params);
  StartMiniArcWithParams(true, GetPopulatedStartParams());
  auto req = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_EQ(req.mglru_reclaim_interval(), 30000);
  EXPECT_EQ(req.mglru_reclaim_swappiness(), 100);
}

TEST_F(ArcVmClientAdapterTest, LazyWebViewInitEnabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatureState(kEnableLazyWebViewInit, true);
  StartParams start_params(GetPopulatedStartParams());

  StartMiniArcWithParams(true, std::move(start_params));

  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_TRUE(request.enable_web_view_zygote_lazy_init());
}

TEST_F(ArcVmClientAdapterTest, LazyWebViewInitDisabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatureState(kEnableLazyWebViewInit, false);
  StartParams start_params(GetPopulatedStartParams());

  StartMiniArcWithParams(true, std::move(start_params));

  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_FALSE(request.enable_web_view_zygote_lazy_init());
}

TEST_F(ArcVmClientAdapterTest, ArcKeyboardShortcutHelperIntegrationEnabled) {
  StartParams start_params(GetPopulatedStartParams());
  start_params.enable_keyboard_shortcut_helper_integration = true;
  StartMiniArcWithParams(true, std::move(start_params));
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_TRUE(request.enable_keyboard_shortcut_helper_integration());
}

TEST_F(ArcVmClientAdapterTest, ArcKeyboardShortcutHelperIntegrationDisabled) {
  StartParams start_params(GetPopulatedStartParams());
  start_params.enable_keyboard_shortcut_helper_integration = false;
  StartMiniArcWithParams(true, std::move(start_params));
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_FALSE(request.enable_keyboard_shortcut_helper_integration());
}

TEST_F(ArcVmClientAdapterTest, ArcFilePickerExperimentFalse) {
  StartParams start_params(GetPopulatedStartParams());
  start_params.arc_file_picker_experiment = false;
  StartMiniArcWithParams(true, std::move(start_params));
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_FALSE(request.mini_instance_request().arc_file_picker_experiment());
}

TEST_F(ArcVmClientAdapterTest, ArcFilePickerExperimentTrue) {
  StartParams start_params(GetPopulatedStartParams());
  start_params.arc_file_picker_experiment = true;
  StartMiniArcWithParams(true, std::move(start_params));
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_TRUE(request.mini_instance_request().arc_file_picker_experiment());
}

TEST_F(ArcVmClientAdapterTest, ArcCustomTabsExperimentFalse) {
  StartParams start_params(GetPopulatedStartParams());
  start_params.arc_custom_tabs_experiment = false;
  StartMiniArcWithParams(true, std::move(start_params));
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_FALSE(request.mini_instance_request().arc_custom_tabs_experiment());
}

TEST_F(ArcVmClientAdapterTest, ArcCustomTabsExperimentTrue) {
  StartParams start_params(GetPopulatedStartParams());
  start_params.arc_custom_tabs_experiment = true;
  StartMiniArcWithParams(true, std::move(start_params));
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_TRUE(request.mini_instance_request().arc_custom_tabs_experiment());
}

TEST_F(ArcVmClientAdapterTest, StartMiniArc_ArcSignedIn) {
  StartParams start_params(GetPopulatedStartParams());
  start_params.arc_signed_in = true;
  StartMiniArcWithParams(true, std::move(start_params));
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_TRUE(request.mini_instance_request().arc_signed_in());
}

TEST_F(ArcVmClientAdapterTest, StartMiniArc_ArcSignedInDisabled) {
  StartMiniArcWithParams(true, GetPopulatedStartParams());
  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_FALSE(request.mini_instance_request().arc_signed_in());
}

TEST_F(ArcVmClientAdapterTest, ArcPriorityAppLmkDelayDisabled) {
  StartMiniArc();
  UpgradeParams upgrade_params = GetPopulatedUpgradeParams();
  upgrade_params.enable_priority_app_lmk_delay = false;
  UpgradeArcWithParams(true, std::move(upgrade_params));
  EXPECT_FALSE(base::Contains(boot_notification_server()->received_data(),
                              "ro.boot.arc.lmk.enable_priority_app_delay"));
  EXPECT_FALSE(
      base::Contains(boot_notification_server()->received_data(),
                     "ro.boot.arc.lmk.priority_app_delay_duration_sec"));
  EXPECT_FALSE(base::Contains(boot_notification_server()->received_data(),
                              "ro.boot.arc.lmk.priority_apps"));
}

TEST_F(ArcVmClientAdapterTest, ArcPriorityAppLmkDelayEnabled_NoApp) {
  StartMiniArc();
  UpgradeParams upgrade_params = GetPopulatedUpgradeParams();
  upgrade_params.enable_priority_app_lmk_delay = true;
  upgrade_params.priority_app_lmk_delay_list = "";
  UpgradeArcWithParams(true, std::move(upgrade_params));
  EXPECT_FALSE(base::Contains(boot_notification_server()->received_data(),
                              "ro.boot.arc.lmk.enable_priority_app_delay"));
  EXPECT_FALSE(
      base::Contains(boot_notification_server()->received_data(),
                     "ro.boot.arc.lmk.priority_app_delay_duration_sec"));
  EXPECT_FALSE(base::Contains(boot_notification_server()->received_data(),
                              "ro.boot.arc.lmk.priority_apps"));
}

TEST_F(ArcVmClientAdapterTest, ArcPriorityAppLmkDelayEnabled_SomeApp) {
  StartMiniArc();
  UpgradeParams upgrade_params = GetPopulatedUpgradeParams();
  upgrade_params.enable_priority_app_lmk_delay = true;
  upgrade_params.priority_app_lmk_delay_list =
      "com.example.app1,com.example.app2";
  upgrade_params.priority_app_lmk_delay_second = 60;
  UpgradeArcWithParams(true, std::move(upgrade_params));
  EXPECT_TRUE(base::Contains(boot_notification_server()->received_data(),
                             "ro.boot.arc.lmk.enable_priority_app_delay=1"));
  EXPECT_TRUE(base::Contains(
      boot_notification_server()->received_data(),
      "ro.boot.arc.lmk.priority_apps=com.example.app1,com.example.app2"));
  EXPECT_TRUE(
      base::Contains(boot_notification_server()->received_data(),
                     "ro.boot.arc.lmk.priority_app_delay_duration_sec=60"));
}

TEST_F(ArcVmClientAdapterTest, ArcLmkPerceptibleMinStateUpdateDisabled) {
  StartMiniArc();
  UpgradeParams upgrade_params = GetPopulatedUpgradeParams();
  upgrade_params.enable_lmk_perceptible_min_state_update = false;
  UpgradeArcWithParams(true, std::move(upgrade_params));
  EXPECT_FALSE(base::Contains(boot_notification_server()->received_data(),
                              "ro.boot.arc.lmk.perceptible_min_state_update"));
}

TEST_F(ArcVmClientAdapterTest, ArcLmkPerceptibleMinStateUpdateEnabled) {
  StartMiniArc();
  UpgradeParams upgrade_params = GetPopulatedUpgradeParams();
  upgrade_params.enable_lmk_perceptible_min_state_update = true;
  UpgradeArcWithParams(true, std::move(upgrade_params));
  EXPECT_TRUE(base::Contains(boot_notification_server()->received_data(),
                             "ro.boot.arc.lmk.perceptible_min_state_update=1"));
}

TEST_F(ArcVmClientAdapterTest, DefaultDexOptCacheSetup) {
  StartMiniArc();
  UpgradeParams upgrade_params = GetPopulatedUpgradeParams();
  upgrade_params.skip_tts_cache = false;
  UpgradeArcWithParams(true, std::move(upgrade_params));
  EXPECT_FALSE(base::Contains(boot_notification_server()->received_data(),
                              "ro.boot.skip_dexopt_cache"));
}

TEST_F(ArcVmClientAdapterTest, SkipDexOptCacheSetupArcT) {
  base::test::ScopedChromeOSVersionInfo version(
      "CHROMEOS_ARC_ANDROID_SDK_VERSION=33", base::Time::Now());
  StartMiniArc();
  UpgradeParams upgrade_params = GetPopulatedUpgradeParams();
  upgrade_params.skip_dexopt_cache = true;
  UpgradeArcWithParams(true, std::move(upgrade_params));
  EXPECT_TRUE(base::Contains(boot_notification_server()->received_data(),
                             "ro.boot.skip_dexopt_cache=1"));
}

TEST_F(ArcVmClientAdapterTest, SkipDexOptCacheSetupArcR) {
  base::test::ScopedChromeOSVersionInfo version(
      "CHROMEOS_ARC_ANDROID_SDK_VERSION=30", base::Time::Now());
  StartMiniArc();
  UpgradeParams upgrade_params = GetPopulatedUpgradeParams();
  upgrade_params.skip_dexopt_cache = true;
  UpgradeArcWithParams(true, std::move(upgrade_params));
  EXPECT_FALSE(base::Contains(boot_notification_server()->received_data(),
                              "ro.boot.skip_dexopt_cache"));
}

TEST_F(ArcVmClientAdapterTest, VirtualSwapDevice_Enabled) {
  base::FieldTrialParams params;
  params["size"] = base::NumberToString(256 * 1024 * 1024);
  params["virtual_swap_enabled"] = "true";
  params["virtual_swap_interval_ms"] = "1000";
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeatureWithParameters(kGuestSwap, params);

  StartParams start_params(GetPopulatedStartParams());
  StartMiniArcWithParams(true, std::move(start_params));

  const auto& request = GetTestConciergeClient()->start_arc_vm_request();
  EXPECT_EQ(0u, request.guest_zram_mib());
  EXPECT_EQ(1000u, request.virtual_swap_config().swap_interval_ms());
  EXPECT_EQ(256u, request.virtual_swap_config().size_mib());
}

}  // namespace
}  // namespace arc