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

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

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

#include <memory>
#include <utility>

#include "ash/components/arc/arc_util.h"
#include "ash/components/arc/test/fake_arc_session.h"
#include "base/command_line.h"
#include "base/run_loop.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/task_environment.h"
#include "chromeos/ash/components/dbus/session_manager/session_manager_client.h"
#include "mojo/public/cpp/system/message_pipe.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/cleanup/cleanup.h"

namespace arc {

namespace {

constexpr int kContainerStarting =
    static_cast<int>(ArcContainerLifetimeEvent::CONTAINER_STARTING);
constexpr int kContainerFailedToStart =
    static_cast<int>(ArcContainerLifetimeEvent::CONTAINER_FAILED_TO_START);
constexpr int kContainerCrashedEarly =
    static_cast<int>(ArcContainerLifetimeEvent::CONTAINER_CRASHED_EARLY);
constexpr int kContainerCrashed =
    static_cast<int>(ArcContainerLifetimeEvent::CONTAINER_CRASHED);
constexpr char kDefaultLocale[] = "en-US";

UpgradeParams DefaultUpgradeParams() {
  UpgradeParams params;
  params.locale = kDefaultLocale;
  return params;
}

class DoNothingObserver : public ArcSessionRunner::Observer {
 public:
  void OnSessionStopped(ArcStopReason reason, bool restarting) override {
    // Do nothing.
  }
  void OnSessionRestarting() override {
    // Do nothing.
  }
};

}  // namespace

class ArcSessionRunnerTest : public testing::Test,
                             public ArcSessionRunner::Observer {
 public:
  ArcSessionRunnerTest()
      : task_environment_(base::test::TaskEnvironment::MainThreadType::UI) {}

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

  void SetUp() override {
    ash::SessionManagerClient::InitializeFakeInMemory();

    stop_reason_ = ArcStopReason::SHUTDOWN;
    restarting_ = false;
    stopped_called_ = false;
    restarting_called_ = false;

    // We inject FakeArcSession here so we do not need task_runner.
    arc_session_runner_ = std::make_unique<ArcSessionRunner>(
        base::BindRepeating(FakeArcSession::Create));
    arc_session_runner_->AddObserver(this);
  }

  void TearDown() override {
    arc_session_runner_->RemoveObserver(this);
    arc_session_runner_.reset();

    ash::SessionManagerClient::Shutdown();
  }

  ArcSessionRunner* arc_session_runner() { return arc_session_runner_.get(); }

  FakeArcSession* arc_session() {
    return static_cast<FakeArcSession*>(
        arc_session_runner_->GetArcSessionForTesting());
  }

  ArcStopReason stop_reason() {
    EXPECT_TRUE(stopped_called());
    return stop_reason_;
  }

  bool restarting() {
    EXPECT_TRUE(stopped_called());
    return restarting_;
  }

  bool stopped_called() { return stopped_called_; }
  bool restarting_called() { return restarting_called_; }

  void ResetArcSessionFactory(
      const ArcSessionRunner::ArcSessionFactory& factory) {
    arc_session_runner_->RemoveObserver(this);
    arc_session_runner_ = std::make_unique<ArcSessionRunner>(factory);
    arc_session_runner_->AddObserver(this);
  }

  static std::unique_ptr<ArcSession> CreateSuspendedArcSession() {
    auto arc_session = std::make_unique<FakeArcSession>();
    arc_session->SuspendBoot();
    return arc_session;
  }

  static std::unique_ptr<ArcSession> CreateBootFailureArcSession(
      ArcStopReason reason) {
    auto arc_session = std::make_unique<FakeArcSession>();
    arc_session->EnableBootFailureEmulation(reason);
    return arc_session;
  }

 private:
  // ArcSessionRunner::Observer:
  void OnSessionStopped(ArcStopReason stop_reason, bool restarting) override {
    // The instance is already destructed in
    // ArcSessionRunner::OnSessionStopped().
    stop_reason_ = stop_reason;
    restarting_ = restarting;
    stopped_called_ = true;
    restarting_called_ = false;
  }
  void OnSessionRestarting() override { restarting_called_ = true; }

  ArcStopReason stop_reason_;
  bool restarting_;
  bool stopped_called_;
  bool restarting_called_;
  std::unique_ptr<ArcSessionRunner> arc_session_runner_;
  base::test::TaskEnvironment task_environment_;
};

// Exercises the basic functionality of the ArcSessionRunner. Observer should
// be notified.
TEST_F(ArcSessionRunnerTest, Basic) {
  class Observer : public ArcSessionRunner::Observer {
   public:
    Observer() = default;

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

    bool stopped_called() const { return stopped_called_; }

    // ArcSessionRunner::Observer:
    void OnSessionStopped(ArcStopReason reason, bool restarting) override {
      stopped_called_ = true;
    }
    void OnSessionRestarting() override {}

   private:
    bool stopped_called_ = false;
  };

  arc_session_runner()->ResumeRunner();
  Observer observer;
  arc_session_runner()->AddObserver(&observer);
  absl::Cleanup teardown = [this, &observer] {
    arc_session_runner()->RemoveObserver(&observer);
  };

  EXPECT_FALSE(arc_session());

  arc_session_runner()->RequestUpgrade(DefaultUpgradeParams());
  ASSERT_TRUE(arc_session());
  EXPECT_TRUE(arc_session()->is_running());

  arc_session_runner()->RequestStop();
  EXPECT_FALSE(arc_session());
  EXPECT_TRUE(observer.stopped_called());
}

// If the ArcSessionRunner accepts a request to stop ARC instance, it should
// stop it, even mid-startup.
TEST_F(ArcSessionRunnerTest, StopMidStartup) {
  ResetArcSessionFactory(
      base::BindRepeating(&ArcSessionRunnerTest::CreateSuspendedArcSession));
  arc_session_runner()->ResumeRunner();
  EXPECT_FALSE(arc_session());

  arc_session_runner()->RequestUpgrade(DefaultUpgradeParams());
  ASSERT_TRUE(arc_session());
  EXPECT_FALSE(arc_session()->is_running());

  arc_session_runner()->RequestStop();
  EXPECT_FALSE(arc_session());
  EXPECT_FALSE(restarting());
}

// Does the same for mini instance.
TEST_F(ArcSessionRunnerTest, StopMidStartup_MiniInstance) {
  ResetArcSessionFactory(
      base::BindRepeating(&ArcSessionRunnerTest::CreateSuspendedArcSession));
  arc_session_runner()->ResumeRunner();
  EXPECT_FALSE(arc_session());

  arc_session_runner()->RequestStartMiniInstance();
  ASSERT_TRUE(arc_session());
  EXPECT_FALSE(arc_session()->is_running());

  arc_session_runner()->RequestStop();
  EXPECT_FALSE(arc_session());
}

// Tests the case where the runner is not resumed yet.
TEST_F(ArcSessionRunnerTest, ExpansionPending) {
  EXPECT_FALSE(arc_session());
  // When it's not resumed yet, starting instance will be pending.
  arc_session_runner()->RequestStartMiniInstance();
  EXPECT_FALSE(arc_session());

  // It is resumed when they are generated.
  arc_session_runner()->ResumeRunner();
  ASSERT_TRUE(arc_session());
  EXPECT_FALSE(arc_session()->is_running());
}

// Does the same test with a full instance.
TEST_F(ArcSessionRunnerTest, ExpansionPending_FullInstance) {
  EXPECT_FALSE(arc_session());
  arc_session_runner()->RequestStartMiniInstance();
  EXPECT_FALSE(arc_session());
  arc_session_runner()->RequestUpgrade(DefaultUpgradeParams());
  EXPECT_FALSE(arc_session());

  arc_session_runner()->ResumeRunner();
  ASSERT_TRUE(arc_session());
  EXPECT_TRUE(arc_session()->is_running());
}

// Checks the case where RequestStop() is called before resume.
TEST_F(ArcSessionRunnerTest, ExpansionPending_StopMidStartup) {
  EXPECT_FALSE(arc_session());
  arc_session_runner()->RequestStartMiniInstance();
  EXPECT_FALSE(arc_session());
  arc_session_runner()->RequestStop();
  EXPECT_FALSE(arc_session());

  arc_session_runner()->ResumeRunner();
  EXPECT_FALSE(arc_session());
}

// If the boot procedure is failed, then restarting mechanism should not
// triggered.
TEST_F(ArcSessionRunnerTest, BootFailure) {
  ResetArcSessionFactory(
      base::BindRepeating(&ArcSessionRunnerTest::CreateBootFailureArcSession,
                          ArcStopReason::GENERIC_BOOT_FAILURE));
  arc_session_runner()->ResumeRunner();
  EXPECT_FALSE(arc_session());

  arc_session_runner()->RequestUpgrade(DefaultUpgradeParams());
  EXPECT_EQ(ArcStopReason::GENERIC_BOOT_FAILURE, stop_reason());
  EXPECT_FALSE(arc_session());
  EXPECT_FALSE(restarting());
}

// Does the same with the mini instance.
TEST_F(ArcSessionRunnerTest, BootFailure_MiniInstance) {
  ResetArcSessionFactory(
      base::BindRepeating(&ArcSessionRunnerTest::CreateBootFailureArcSession,
                          ArcStopReason::GENERIC_BOOT_FAILURE));
  arc_session_runner()->ResumeRunner();
  EXPECT_FALSE(arc_session());

  // If starting the mini instance fails, arc_session_runner()'s state goes back
  // to STOPPED, but its observers won't be notified.
  arc_session_runner()->RequestStartMiniInstance();
  arc_session()->EmulateMiniContainerStart();
  EXPECT_FALSE(arc_session());
  EXPECT_FALSE(stopped_called());

  // Also make sure that RequestUpgrade() works just fine after the boot
  // failure.
  ResetArcSessionFactory(base::BindRepeating(FakeArcSession::Create));
  arc_session_runner()->ResumeRunner();
  arc_session_runner()->RequestUpgrade(DefaultUpgradeParams());
  ASSERT_TRUE(arc_session());
  EXPECT_TRUE(arc_session()->is_running());
}

// Similary, CRASH should do same for GENERIC_BOOT_FAILURE case, because
// in mini instance, Mojo connection should not be established.
TEST_F(ArcSessionRunnerTest, Crash_MiniInstance) {
  ResetArcSessionFactory(
      base::BindRepeating(&ArcSessionRunnerTest::CreateBootFailureArcSession,
                          ArcStopReason::CRASH));
  arc_session_runner()->ResumeRunner();
  EXPECT_FALSE(arc_session());

  // If starting the mini instance fails, arc_session_runner()'s state goes back
  // to STOPPED, but its observers won't be notified.
  arc_session_runner()->RequestStartMiniInstance();
  arc_session()->EmulateMiniContainerStart();
  EXPECT_FALSE(arc_session());
  EXPECT_FALSE(stopped_called());
}

// Tests that RequestUpgrade works after calling RequestStart.
TEST_F(ArcSessionRunnerTest, Upgrade) {
  arc_session_runner()->ResumeRunner();
  EXPECT_FALSE(arc_session());

  arc_session_runner()->RequestStartMiniInstance();
  ASSERT_TRUE(arc_session());
  EXPECT_FALSE(arc_session()->is_running());

  arc_session_runner()->RequestUpgrade(DefaultUpgradeParams());
  ASSERT_TRUE(arc_session());
  EXPECT_TRUE(arc_session()->is_running());
}

// If the instance is stopped, it should be re-started.
TEST_F(ArcSessionRunnerTest, Restart) {
  arc_session_runner()->ResumeRunner();
  arc_session_runner()->SetRestartDelayForTesting(base::TimeDelta());
  EXPECT_FALSE(arc_session());

  arc_session_runner()->RequestUpgrade(DefaultUpgradeParams());
  ASSERT_TRUE(arc_session());
  EXPECT_TRUE(arc_session()->is_running());

  // Simulate a connection loss.
  ASSERT_TRUE(arc_session());
  arc_session()->StopWithReason(ArcStopReason::CRASH);
  EXPECT_FALSE(arc_session());
  EXPECT_TRUE(restarting());
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(restarting_called());
  ASSERT_TRUE(arc_session());
  EXPECT_TRUE(arc_session()->is_running());
  // Checks if the restart retains the original parameter.
  EXPECT_EQ(std::string(kDefaultLocale), arc_session()->upgrade_locale_param());

  arc_session_runner()->RequestStop();
  EXPECT_FALSE(arc_session());
}

TEST_F(ArcSessionRunnerTest, GracefulStop) {
  arc_session_runner()->ResumeRunner();
  arc_session_runner()->SetRestartDelayForTesting(base::TimeDelta());
  EXPECT_FALSE(arc_session());

  arc_session_runner()->RequestUpgrade(DefaultUpgradeParams());
  ASSERT_TRUE(arc_session());
  EXPECT_TRUE(arc_session()->is_running());

  // Graceful stop.
  arc_session_runner()->RequestStop();
  EXPECT_EQ(ArcStopReason::SHUTDOWN, stop_reason());
  EXPECT_FALSE(restarting());
  EXPECT_FALSE(restarting_called());
  EXPECT_FALSE(arc_session());
}

TEST_F(ArcSessionRunnerTest, Shutdown) {
  arc_session_runner()->ResumeRunner();
  arc_session_runner()->SetRestartDelayForTesting(base::TimeDelta());
  EXPECT_FALSE(arc_session());

  arc_session_runner()->RequestUpgrade(DefaultUpgradeParams());
  ASSERT_TRUE(arc_session());
  EXPECT_TRUE(arc_session()->is_running());

  // Simulate shutdown.
  arc_session_runner()->OnShutdown();
  EXPECT_EQ(ArcStopReason::SHUTDOWN, stop_reason());
  EXPECT_FALSE(arc_session());
}

// Removing the same observer more than once should be okay.
TEST_F(ArcSessionRunnerTest, RemoveObserverTwice) {
  arc_session_runner()->ResumeRunner();
  EXPECT_FALSE(arc_session());

  DoNothingObserver do_nothing_observer;
  arc_session_runner()->AddObserver(&do_nothing_observer);
  // Call RemoveObserver() twice.
  arc_session_runner()->RemoveObserver(&do_nothing_observer);
  arc_session_runner()->RemoveObserver(&do_nothing_observer);
}

// Removing an unknown observer should be allowed.
TEST_F(ArcSessionRunnerTest, RemoveUnknownObserver) {
  arc_session_runner()->ResumeRunner();
  EXPECT_FALSE(arc_session());

  DoNothingObserver do_nothing_observer;
  arc_session_runner()->RemoveObserver(&do_nothing_observer);
}

// Tests UMA recording on mini instance -> full instance -> shutdown case.
TEST_F(ArcSessionRunnerTest, UmaRecording_StartUpgradeShutdown) {
  base::HistogramTester tester;

  arc_session_runner()->ResumeRunner();
  arc_session_runner()->RequestStartMiniInstance();
  tester.ExpectUniqueSample("Arc.ContainerLifetimeEvent", kContainerStarting,
                            1 /* count of the sample */);

  // Boot continue should not increase the count.
  arc_session_runner()->RequestUpgrade(DefaultUpgradeParams());
  tester.ExpectUniqueSample("Arc.ContainerLifetimeEvent", kContainerStarting,
                            1);

  // "0" should be recorded as a restart count on shutdown.
  arc_session_runner()->OnShutdown();
  tester.ExpectUniqueSample("Arc.ContainerRestartAfterCrashCount",
                            0 /* sample */, 1 /* count of the sample */);
}

// Tests UMA recording on full instance -> shutdown case.
TEST_F(ArcSessionRunnerTest, UmaRecording_StartShutdown) {
  base::HistogramTester tester;

  arc_session_runner()->ResumeRunner();
  arc_session_runner()->RequestUpgrade(DefaultUpgradeParams());
  tester.ExpectUniqueSample("Arc.ContainerLifetimeEvent", kContainerStarting,
                            1);
  // "0" should be recorded as a restart count on shutdown.
  arc_session_runner()->OnShutdown();
  tester.ExpectUniqueSample("Arc.ContainerRestartAfterCrashCount", 0, 1);
}

// Tests UMA recording on mini instance -> full instance -> crash -> shutdown
// case.
TEST_F(ArcSessionRunnerTest, UmaRecording_CrashTwice) {
  base::HistogramTester tester;

  arc_session_runner()->ResumeRunner();
  arc_session_runner()->SetRestartDelayForTesting(base::TimeDelta());
  EXPECT_FALSE(arc_session());

  arc_session_runner()->RequestStartMiniInstance();
  tester.ExpectUniqueSample("Arc.ContainerLifetimeEvent", kContainerStarting,
                            1);
  arc_session_runner()->RequestUpgrade(DefaultUpgradeParams());

  // Stop the instance with CRASH.
  arc_session()->StopWithReason(ArcStopReason::CRASH);
  tester.ExpectBucketCount("Arc.ContainerLifetimeEvent", kContainerCrashed, 1);
  tester.ExpectTotalCount("Arc.ContainerLifetimeEvent", 2);

  // Restart the instance, then crash the instance again. The second CRASH
  // should not affect Arc.ContainerLifetimeEvent.
  base::RunLoop().RunUntilIdle();
  arc_session()->StopWithReason(ArcStopReason::CRASH);
  tester.ExpectBucketCount("Arc.ContainerLifetimeEvent", kContainerCrashed, 1);
  tester.ExpectTotalCount("Arc.ContainerLifetimeEvent", 2);

  // However, "2" should be recorded as a restart count on shutdown.
  base::RunLoop().RunUntilIdle();
  arc_session_runner()->OnShutdown();
  tester.ExpectUniqueSample("Arc.ContainerRestartAfterCrashCount", 2, 1);
}

// Tests UMA recording on mini instance -> crash -> shutdown case.
TEST_F(ArcSessionRunnerTest, UmaRecording_CrashMini) {
  base::HistogramTester tester;

  arc_session_runner()->ResumeRunner();
  arc_session_runner()->RequestStartMiniInstance();
  tester.ExpectUniqueSample("Arc.ContainerLifetimeEvent", kContainerStarting,
                            1);

  // Stop the instance with CRASH.
  arc_session()->StopWithReason(ArcStopReason::CRASH);
  tester.ExpectBucketCount("Arc.ContainerLifetimeEvent", kContainerCrashedEarly,
                           1);
  tester.ExpectTotalCount("Arc.ContainerLifetimeEvent", 2);

  // In this case, no restart happened. "0" should be recorded.
  arc_session_runner()->OnShutdown();
  tester.ExpectUniqueSample("Arc.ContainerRestartAfterCrashCount", 0, 1);
}

// Tests UMA recording on mini instance -> boot fail -> shutdown case.
TEST_F(ArcSessionRunnerTest, UmaRecording_BootFail) {
  base::HistogramTester tester;

  arc_session_runner()->ResumeRunner();
  arc_session_runner()->RequestStartMiniInstance();
  tester.ExpectUniqueSample("Arc.ContainerLifetimeEvent", kContainerStarting,
                            1);

  arc_session()->StopWithReason(ArcStopReason::GENERIC_BOOT_FAILURE);
  tester.ExpectBucketCount("Arc.ContainerLifetimeEvent",
                           kContainerFailedToStart, 1);
  tester.ExpectTotalCount("Arc.ContainerLifetimeEvent", 2);

  // No restart happened. "0" should be recorded.
  arc_session_runner()->OnShutdown();
  tester.ExpectUniqueSample("Arc.ContainerRestartAfterCrashCount", 0, 1);
}

// Tests UMA recording on full instance -> low disk -> shutdown case.
TEST_F(ArcSessionRunnerTest, UmaRecording_LowDisk) {
  base::HistogramTester tester;

  arc_session_runner()->ResumeRunner();
  arc_session_runner()->RequestUpgrade(DefaultUpgradeParams());
  tester.ExpectUniqueSample("Arc.ContainerLifetimeEvent", kContainerStarting,
                            1);

  // We don't record UMA for LOW_DISK_SPACE.
  arc_session()->StopWithReason(ArcStopReason::LOW_DISK_SPACE);
  tester.ExpectUniqueSample("Arc.ContainerLifetimeEvent", kContainerStarting,
                            1);

  // No restart happened. "0" should be recorded.
  arc_session_runner()->OnShutdown();
  tester.ExpectUniqueSample("Arc.ContainerRestartAfterCrashCount", 0, 1);
}

}  // namespace arc