chromium/chrome/browser/ash/borealis/borealis_context_manager_unittest.cc

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

#include "chrome/browser/ash/borealis/borealis_context_manager_impl.h"

#include <memory>

#include "base/containers/queue.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "chrome/browser/ash/borealis/borealis_context_manager.h"
#include "chrome/browser/ash/borealis/borealis_metrics.h"
#include "chrome/browser/ash/borealis/borealis_task.h"
#include "chrome/browser/ash/borealis/testing/callback_factory.h"
#include "chrome/browser/ash/guest_os/dbus_test_helper.h"
#include "chrome/browser/ash/guest_os/guest_os_stability_monitor.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/sessions/exit_type_service.h"
#include "chrome/test/base/testing_profile.h"
#include "chromeos/ash/components/dbus/concierge/fake_concierge_client.h"
#include "chromeos/ash/components/dbus/dbus_thread_manager.h"
#include "chromeos/ash/components/dbus/seneschal/seneschal_client.h"
#include "components/user_manager/scoped_user_manager.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gmock/include/gmock/gmock.h"

namespace borealis {
namespace {

MATCHER(IsSuccessResult, "") {
  return arg.has_value() && arg.value()->vm_name() == "test_vm_name";
}

MATCHER(IsFailureResult, "") {
  return !arg.has_value() &&
         arg.error().error() ==
             borealis::BorealisStartupResult::kStartVmFailed &&
         arg.error().description() == "Something went wrong!";
}

class MockTask : public BorealisTask {
 public:
  explicit MockTask(bool success)
      : BorealisTask("MockTask"), success_(success) {}
  void RunInternal(BorealisContext* context) override {
    if (success_) {
      context->set_vm_name("test_vm_name");
      Complete(borealis::BorealisStartupResult::kSuccess, "");
    } else {
      // Just use a random error.
      Complete(borealis::BorealisStartupResult::kStartVmFailed,
               "Something went wrong!");
    }
  }
  bool success_ = true;
};

class BorealisContextManagerImplForTesting : public BorealisContextManagerImpl {
 public:
  BorealisContextManagerImplForTesting(Profile* profile,
                                       uint tasks,
                                       bool success)
      : BorealisContextManagerImpl(profile), tasks_(tasks), success_(success) {}

 private:
  base::queue<std::unique_ptr<BorealisTask>> GetTasks() override {
    base::queue<std::unique_ptr<BorealisTask>> task_queue;
    for (size_t i = 0; i < tasks_; i++) {
      if (!success_ && tasks_ > 1 && i == 0) {
        // If we are testing the case for multiple tasks, and at least one of
        // them fails, we want the first task to succeed.
        task_queue.push(std::make_unique<MockTask>(/*success=*/true));
      } else {
        task_queue.push(std::make_unique<MockTask>(/*success=*/success_));
      }
    }
    return (task_queue);
  }

  uint tasks_ = 0;
  bool success_ = true;
};

using StartupCallbackFactory =
    StrictCallbackFactory<void(BorealisContextManager::ContextOrFailure)>;

class BorealisContextManagerTest : public testing::Test,
                                   protected guest_os::FakeVmServicesHelper {
 public:
  BorealisContextManagerTest() = default;
  BorealisContextManagerTest(const BorealisContextManagerTest&) = delete;
  BorealisContextManagerTest& operator=(const BorealisContextManagerTest&) =
      delete;
  ~BorealisContextManagerTest() override = default;

 protected:
  void SetUp() override {
    CreateProfile();
    histogram_tester_ = std::make_unique<base::HistogramTester>();
  }

  void TearDown() override {
    profile_.reset();
    histogram_tester_.reset();
  }

  void SendVmStartedSignal() {
    auto* concierge_client = ash::FakeConciergeClient::Get();

    vm_tools::concierge::VmStartedSignal signal;
    signal.set_name("test_vm_name");
    signal.set_owner_id(
        ash::ProfileHelper::GetUserIdHashFromProfile(profile_.get()));
    concierge_client->NotifyVmStarted(signal);
  }

  void SendVmStoppedSignal() {
    auto* concierge_client = ash::FakeConciergeClient::Get();

    vm_tools::concierge::VmStoppedSignal signal;
    signal.set_name("test_vm_name");
    signal.set_owner_id(
        ash::ProfileHelper::GetUserIdHashFromProfile(profile_.get()));
    concierge_client->NotifyVmStopped(signal);
  }

  content::BrowserTaskEnvironment task_environment_;
  std::unique_ptr<TestingProfile> profile_;
  std::unique_ptr<base::HistogramTester> histogram_tester_;

 private:
  void CreateProfile() {
    TestingProfile::Builder profile_builder;
    profile_builder.SetProfileName("defaultprofile");
    profile_ = profile_builder.Build();
  }
};

TEST_F(BorealisContextManagerTest, GetTasksReturnsCorrectTaskList) {
  BorealisContextManagerImpl context_manager(profile_.get());
  base::queue<std::unique_ptr<BorealisTask>> tasks = context_manager.GetTasks();
  EXPECT_FALSE(tasks.empty());
}

TEST_F(BorealisContextManagerTest, NoTasksImpliesSuccess) {
  StartupCallbackFactory callback_expectation;

  BorealisContextManagerImplForTesting context_manager(
      profile_.get(), /*tasks=*/0, /*success=*/true);
  EXPECT_CALL(callback_expectation, Call(testing::_))
      .WillOnce(
          testing::Invoke([](BorealisContextManager::ContextOrFailure result) {
            EXPECT_TRUE(result.has_value());
            // Even with no tasks, the context will give the VM a name.
            EXPECT_EQ(result.value()->vm_name(), "borealis");
          }));
  context_manager.StartBorealis(callback_expectation.BindOnce());
  task_environment_.RunUntilIdle();
}

TEST_F(BorealisContextManagerTest, StartupSucceedsForSuccessfulTask) {
  StartupCallbackFactory callback_expectation;

  BorealisContextManagerImplForTesting context_manager(
      profile_.get(), /*tasks=*/1, /*success=*/true);
  EXPECT_CALL(callback_expectation, Call(IsSuccessResult()));
  context_manager.StartBorealis(callback_expectation.BindOnce());
  task_environment_.RunUntilIdle();
}

TEST_F(BorealisContextManagerTest, StartupSucceedsForSuccessfulGroupOfTasks) {
  StartupCallbackFactory callback_expectation;

  BorealisContextManagerImplForTesting context_manager(
      profile_.get(), /*tasks=*/3, /*success=*/true);
  EXPECT_CALL(callback_expectation, Call(IsSuccessResult()));
  context_manager.StartBorealis(callback_expectation.BindOnce());
  task_environment_.RunUntilIdle();
}

TEST_F(BorealisContextManagerTest, StartupFailsForUnsuccessfulTask) {
  StartupCallbackFactory callback_expectation;

  BorealisContextManagerImplForTesting context_manager(
      profile_.get(), /*tasks=*/1, /*success=*/false);
  EXPECT_CALL(callback_expectation, Call(IsFailureResult()));
  context_manager.StartBorealis(callback_expectation.BindOnce());
  task_environment_.RunUntilIdle();
}

TEST_F(BorealisContextManagerTest, StartupFailsForUnsuccessfulGroupOfTasks) {
  StartupCallbackFactory callback_expectation;
  BorealisContextManagerImplForTesting context_manager(
      profile_.get(), /*tasks=*/3, /*success=*/false);
  EXPECT_CALL(callback_expectation, Call(IsFailureResult()));
  context_manager.StartBorealis(callback_expectation.BindOnce());
  task_environment_.RunUntilIdle();
}

TEST_F(BorealisContextManagerTest, MultipleSuccessfulStartupsAllCallbacksRan) {
  StartupCallbackFactory callback_expectation_1;
  StartupCallbackFactory callback_expectation_2;

  BorealisContextManagerImplForTesting context_manager(
      profile_.get(), /*tasks=*/1, /*success=*/true);
  EXPECT_CALL(callback_expectation_1, Call(IsSuccessResult()));
  EXPECT_CALL(callback_expectation_2, Call(IsSuccessResult()));
  context_manager.StartBorealis(callback_expectation_1.BindOnce());
  context_manager.StartBorealis(callback_expectation_2.BindOnce());
  task_environment_.RunUntilIdle();
}

TEST_F(BorealisContextManagerTest,
       MultipleUnsuccessfulStartupsAllCallbacksRan) {
  StartupCallbackFactory callback_expectation_1;
  StartupCallbackFactory callback_expectation_2;

  BorealisContextManagerImplForTesting context_manager(
      profile_.get(), /*tasks=*/1, /*success=*/false);
  EXPECT_CALL(callback_expectation_1, Call(IsFailureResult()));
  EXPECT_CALL(callback_expectation_2, Call(IsFailureResult()));
  context_manager.StartBorealis(callback_expectation_1.BindOnce());
  context_manager.StartBorealis(callback_expectation_2.BindOnce());
  task_environment_.RunUntilIdle();
}

TEST_F(BorealisContextManagerTest, StartupSucceedsMetricsRecorded) {
  BorealisContextManagerImplForTesting context_manager(
      profile_.get(), /*tasks=*/1, /*success=*/true);
  context_manager.StartBorealis(base::DoNothing());
  task_environment_.RunUntilIdle();

  histogram_tester_->ExpectTotalCount(kBorealisStartupNumAttemptsHistogram, 1);
  histogram_tester_->ExpectUniqueSample(kBorealisStartupResultHistogram,
                                        BorealisStartupResult::kSuccess, 1);
  histogram_tester_->ExpectTotalCount(kBorealisStartupOverallTimeHistogram, 1);
}

TEST_F(BorealisContextManagerTest, StartupFailsMetricsRecorded) {
  BorealisContextManagerImplForTesting context_manager(
      profile_.get(), /*tasks=*/1, /*success=*/false);
  context_manager.StartBorealis(base::DoNothing());
  task_environment_.RunUntilIdle();

  histogram_tester_->ExpectTotalCount(kBorealisStartupNumAttemptsHistogram, 1);
  histogram_tester_->ExpectUniqueSample(kBorealisStartupResultHistogram,
                                        BorealisStartupResult::kStartVmFailed,
                                        1);
  histogram_tester_->ExpectTotalCount(kBorealisStartupOverallTimeHistogram, 0);
}

class NeverCompletingContextManager : public BorealisContextManagerImpl {
 public:
  explicit NeverCompletingContextManager(Profile* profile)
      : BorealisContextManagerImpl(profile) {}

 private:
  class NeverCompletingTask : public BorealisTask {
   public:
    NeverCompletingTask() : BorealisTask("NeverCompletingTask") {}
    void RunInternal(BorealisContext* context) override {}
  };

  base::queue<std::unique_ptr<BorealisTask>> GetTasks() override {
    base::queue<std::unique_ptr<BorealisTask>> queue;
    queue.push(std::make_unique<NeverCompletingTask>());
    return queue;
  }
};

using ShutdownCallbackFactory =
    StrictCallbackFactory<void(BorealisShutdownResult)>;

TEST_F(BorealisContextManagerTest, ShutDownCancelsRequestsAndTerminatesVm) {
  StartupCallbackFactory callback_expectation;
  EXPECT_CALL(callback_expectation, Call(testing::_))
      .WillOnce(
          testing::Invoke([](BorealisContextManager::ContextOrFailure result) {
            EXPECT_FALSE(result.has_value());
            EXPECT_EQ(result.error().error(),
                      BorealisStartupResult::kCancelled);
          }));

  ShutdownCallbackFactory shutdown_callback_handler;
  EXPECT_CALL(shutdown_callback_handler,
              Call(BorealisShutdownResult::kSuccess));

  NeverCompletingContextManager context_manager(profile_.get());
  context_manager.StartBorealis(callback_expectation.BindOnce());
  context_manager.ShutDownBorealis(shutdown_callback_handler.BindOnce());
  task_environment_.RunUntilIdle();

  ash::FakeConciergeClient* fake_concierge_client =
      ash::FakeConciergeClient::Get();
  EXPECT_GE(fake_concierge_client->stop_vm_call_count(), 1);
  histogram_tester_->ExpectTotalCount(kBorealisShutdownNumAttemptsHistogram, 1);
  histogram_tester_->ExpectUniqueSample(kBorealisShutdownResultHistogram,
                                        BorealisShutdownResult::kSuccess, 1);
}

TEST_F(BorealisContextManagerTest, ShutdownWhenNotRunningCompletesImmediately) {
  ShutdownCallbackFactory shutdown_callback_handler;
  EXPECT_CALL(shutdown_callback_handler,
              Call(BorealisShutdownResult::kSuccess));

  NeverCompletingContextManager context_manager(profile_.get());
  context_manager.ShutDownBorealis(shutdown_callback_handler.BindOnce());
}

TEST_F(BorealisContextManagerTest, FailureToShutdownReportsError) {
  BorealisContextManagerImplForTesting context_manager(
      profile_.get(), /*tasks=*/0, /*success=*/true);
  context_manager.StartBorealis(base::DoNothing());
  task_environment_.RunUntilIdle();
  ASSERT_TRUE(context_manager.IsRunning());

  vm_tools::concierge::StopVmResponse response;
  response.set_success(false);
  response.set_failure_reason("expected failure");
  ash::FakeConciergeClient* fake_concierge_client =
      ash::FakeConciergeClient::Get();
  fake_concierge_client->set_stop_vm_response(std::move(response));

  ShutdownCallbackFactory shutdown_callback_handler;
  EXPECT_CALL(shutdown_callback_handler, Call(BorealisShutdownResult::kFailed));
  context_manager.ShutDownBorealis(shutdown_callback_handler.BindOnce());
  task_environment_.RunUntilIdle();
}

class MockContextManager : public BorealisContextManagerImpl {
 public:
  explicit MockContextManager(Profile* profile)
      : BorealisContextManagerImpl(profile) {}

  ~MockContextManager() override = default;

  MOCK_METHOD(base::queue<std::unique_ptr<BorealisTask>>, GetTasks, (), ());
};

class TaskThatDoesSomethingAfterCompletion : public BorealisTask {
 public:
  explicit TaskThatDoesSomethingAfterCompletion(base::OnceClosure something)
      : BorealisTask("TaskThatDoesSomethingAfterCompletion"),
        something_(std::move(something)) {}

  void RunInternal(BorealisContext* context) override {
    Complete(BorealisStartupResult::kSuccess, "");
    std::move(something_).Run();
  }

  base::OnceClosure something_;
};

TEST_F(BorealisContextManagerTest, TasksCanOutliveCompletion) {
  testing::StrictMock<MockContextManager> context_manager(profile_.get());
  StartupCallbackFactory callback_expectation;
  testing::StrictMock<testing::MockFunction<void()>> something_expectation;

  EXPECT_CALL(context_manager, GetTasks).WillOnce(testing::Invoke([&]() {
    base::queue<std::unique_ptr<BorealisTask>> tasks;
    tasks.push(std::make_unique<TaskThatDoesSomethingAfterCompletion>(
        base::BindOnce(&testing::MockFunction<void()>::Call,
                       base::Unretained(&something_expectation))));
    return tasks;
  }));
  EXPECT_CALL(something_expectation, Call());
  EXPECT_CALL(callback_expectation, Call(testing::_));
  context_manager.StartBorealis(callback_expectation.BindOnce());
  task_environment_.RunUntilIdle();
}

TEST_F(BorealisContextManagerTest, ShouldNotLogVmStoppedWhenNotRunning) {
  SendVmStoppedSignal();
  histogram_tester_->ExpectUniqueSample(kBorealisStabilityHistogram,
                                        guest_os::FailureClasses::VmStopped, 0);
}

TEST_F(BorealisContextManagerTest, ShouldNotLogVmStoppedDuringStartup) {
  StartupCallbackFactory callback_expectation;

  BorealisContextManagerImplForTesting context_manager(
      profile_.get(), /*tasks=*/1, /*success=*/true);
  context_manager.StartBorealis(callback_expectation.BindOnce());

  SendVmStoppedSignal();

  histogram_tester_->ExpectUniqueSample(kBorealisStabilityHistogram,
                                        guest_os::FailureClasses::VmStopped, 0);
}

TEST_F(BorealisContextManagerTest, ShouldNotLogVmStoppedWhenExpected) {
  StartupCallbackFactory callback_expectation;

  BorealisContextManagerImplForTesting context_manager(
      profile_.get(), /*tasks=*/1, /*success=*/true);
  EXPECT_CALL(callback_expectation, Call(IsSuccessResult()));
  context_manager.StartBorealis(callback_expectation.BindOnce());
  task_environment_.RunUntilIdle();

  context_manager.ShutDownBorealis(base::DoNothing());
  SendVmStoppedSignal();

  histogram_tester_->ExpectUniqueSample(kBorealisStabilityHistogram,
                                        guest_os::FailureClasses::VmStopped, 0);

  // Wait for shutdown to complete before destructing |shutdown_callback|.
  task_environment_.RunUntilIdle();
}

TEST_F(BorealisContextManagerTest, LogVmStoppedWhenUnexpected) {
  StartupCallbackFactory callback_expectation;

  BorealisContextManagerImplForTesting context_manager(
      profile_.get(), /*tasks=*/1, /*success=*/true);
  EXPECT_CALL(callback_expectation, Call(IsSuccessResult()));
  context_manager.StartBorealis(callback_expectation.BindOnce());
  task_environment_.RunUntilIdle();

  SendVmStoppedSignal();
  histogram_tester_->ExpectUniqueSample(kBorealisStabilityHistogram,
                                        guest_os::FailureClasses::VmStopped, 1);
}

TEST_F(BorealisContextManagerTest, VmShutsDownAfterChromeCrashes) {
  ash::FakeConciergeClient* fake_concierge_client =
      ash::FakeConciergeClient::Get();

  // Ensure that GetVmInfo returns success - a VM "still running".
  SendVmStartedSignal();

  ExitTypeService::GetInstanceForProfile(profile_.get())
      ->SetLastSessionExitTypeForTest(ExitType::kCrashed);
  BorealisContextManagerImpl context_manager(profile_.get());
  task_environment_.RunUntilIdle();
  EXPECT_GE(fake_concierge_client->stop_vm_call_count(), 1);
}

}  // namespace
}  // namespace borealis