| // 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 <fuchsia/feedback/cpp/fidl.h> |
| #include <fuchsia/feedback/cpp/fidl_test_base.h> |
| #include <fuchsia/hardware/power/statecontrol/cpp/fidl.h> |
| #include <fuchsia/hardware/power/statecontrol/cpp/fidl_test_base.h> |
| #include <fuchsia/io/cpp/fidl.h> |
| #include <fuchsia/recovery/cpp/fidl.h> |
| #include <fuchsia/recovery/cpp/fidl_test_base.h> |
| #include <lib/fidl/cpp/interface_request.h> |
| #include <lib/fpromise/result.h> |
| #include <lib/sys/cpp/outgoing_directory.h> |
| #include <lib/sys/cpp/service_directory.h> |
| |
| #include <memory> |
| #include <string_view> |
| #include <tuple> |
| |
| #include "base/files/file_util.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/fuchsia/scoped_service_binding.h" |
| #include "base/functional/bind.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/message_loop/message_pump_type.h" |
| #include "base/run_loop.h" |
| #include "base/strings/strcat.h" |
| #include "base/test/task_environment.h" |
| #include "base/threading/sequence_bound.h" |
| #include "base/threading/thread.h" |
| #include "chromecast/public/reboot_shlib.h" |
| #include "chromecast/system/reboot/reboot_fuchsia.h" |
| #include "chromecast/system/reboot/reboot_util.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace chromecast { |
| namespace { |
| |
| using ::testing::Eq; |
| using ::testing::Ne; |
| |
| using fuchsia::feedback::RebootReason; |
| using StateControlRebootReason = |
| fuchsia::hardware::power::statecontrol::RebootReason; |
| |
| struct RebootReasonParam { |
| RebootReason reason; |
| RebootShlib::RebootSource source; |
| bool graceful; |
| StateControlRebootReason state_control_reason = |
| StateControlRebootReason::USER_REQUEST; |
| }; |
| |
| const RebootReasonParam kRebootReasonParams[] = { |
| {RebootReason::COLD, RebootShlib::RebootSource::FORCED, false}, |
| {RebootReason::BRIEF_POWER_LOSS, RebootShlib::RebootSource::FORCED, false}, |
| {RebootReason::BROWNOUT, RebootShlib::RebootSource::FORCED, false}, |
| {RebootReason::KERNEL_PANIC, RebootShlib::RebootSource::FORCED, false}, |
| {RebootReason::SYSTEM_OUT_OF_MEMORY, |
| RebootShlib::RebootSource::REPEATED_OOM, false}, |
| {RebootReason::HARDWARE_WATCHDOG_TIMEOUT, |
| RebootShlib::RebootSource::HW_WATCHDOG, false}, |
| {RebootReason::HARDWARE_WATCHDOG_TIMEOUT, |
| RebootShlib::RebootSource::HW_WATCHDOG, false}, |
| {RebootReason::SOFTWARE_WATCHDOG_TIMEOUT, |
| RebootShlib::RebootSource::WATCHDOG, false}, |
| |
| // Graceful reboot reasons. |
| {RebootReason::USER_REQUEST, RebootShlib::RebootSource::API, true, |
| StateControlRebootReason::USER_REQUEST}, |
| {RebootReason::SYSTEM_UPDATE, RebootShlib::RebootSource::OTA, true, |
| StateControlRebootReason::SYSTEM_UPDATE}, |
| {RebootReason::HIGH_TEMPERATURE, RebootShlib::RebootSource::OVERHEAT, true, |
| StateControlRebootReason::HIGH_TEMPERATURE}, |
| {RebootReason::SESSION_FAILURE, RebootShlib::RebootSource::SW_OTHER, true}, |
| }; |
| |
| constexpr char kStartedOnce[] = "component-started-once"; |
| constexpr char kGracefulTeardown[] = "component-graceful-teardown"; |
| |
| struct RestartReasonParam { |
| RebootShlib::RebootSource source; |
| bool graceful; |
| const char* file; |
| }; |
| |
| const RestartReasonParam kRestartReasonParams[] = { |
| {RebootShlib::RebootSource::UNGRACEFUL_RESTART, false, kStartedOnce}, |
| {RebootShlib::RebootSource::GRACEFUL_RESTART, true, kGracefulTeardown}, |
| }; |
| |
| class FakeAdmin |
| : public fuchsia::hardware::power::statecontrol::testing::Admin_TestBase { |
| public: |
| explicit FakeAdmin(sys::OutgoingDirectory* outgoing_directory) |
| : binding_(outgoing_directory, this) {} |
| |
| void GetLastRebootReason(StateControlRebootReason* reason) { |
| *reason = last_reboot_reason_; |
| } |
| |
| private: |
| void Reboot(StateControlRebootReason reason, RebootCallback callback) final { |
| last_reboot_reason_ = reason; |
| callback(fpromise::ok()); |
| } |
| |
| void NotImplemented_(const std::string& name) final { |
| ADD_FAILURE() << "NotImplemented_: " << name; |
| } |
| |
| base::ScopedServiceBinding<fuchsia::hardware::power::statecontrol::Admin> |
| binding_; |
| StateControlRebootReason last_reboot_reason_; |
| }; |
| |
| class FakeLastRebootInfoProvider |
| : public fuchsia::feedback::testing::LastRebootInfoProvider_TestBase { |
| public: |
| explicit FakeLastRebootInfoProvider( |
| sys::OutgoingDirectory* outgoing_directory) |
| : binding_(outgoing_directory, this) {} |
| |
| void SetLastReboot(fuchsia::feedback::LastReboot last_reboot) { |
| last_reboot_ = std::move(last_reboot); |
| } |
| |
| private: |
| void Get(GetCallback callback) final { callback(std::move(last_reboot_)); } |
| |
| void NotImplemented_(const std::string& name) final { |
| ADD_FAILURE() << "NotImplemented_: " << name; |
| } |
| |
| base::ScopedServiceBinding<fuchsia::feedback::LastRebootInfoProvider> |
| binding_; |
| fuchsia::feedback::LastReboot last_reboot_; |
| }; |
| |
| class FakeFactoryReset |
| : public fuchsia::recovery::testing::FactoryReset_TestBase { |
| public: |
| explicit FakeFactoryReset(sys::OutgoingDirectory* outgoing_directory) |
| : binding_(outgoing_directory, this) {} |
| |
| void reset_called(bool* reset_called) { *reset_called = reset_called_; } |
| |
| private: |
| void Reset(ResetCallback callback) final { |
| reset_called_ = true; |
| callback(ZX_OK); |
| } |
| |
| void NotImplemented_(const std::string& name) final { |
| ADD_FAILURE() << "NotImplemented_: " << name; |
| } |
| |
| base::ScopedServiceBinding<fuchsia::recovery::FactoryReset> binding_; |
| bool reset_called_ = false; |
| }; |
| |
| class RebootFuchsiaTest : public ::testing::Test { |
| public: |
| RebootFuchsiaTest() |
| : task_environment_(base::test::TaskEnvironment::MainThreadType::IO), |
| thread_("FakeLastRebootInfoProvider_Thread") { |
| CHECK(thread_.StartWithOptions( |
| base::Thread::Options(base::MessagePumpType::IO, 0))); |
| } |
| |
| void SetUp() override { |
| // Create incoming (service) and outgoing directories that are connected. |
| fidl::InterfaceHandle<fuchsia::io::Directory> directory; |
| |
| // The thread handling fidl calls to the fake service must also be the |
| // thread that we start the serve operation on. Since all fakes require the |
| // same output directory handle, we post a task here to begin the serve |
| // operation, then flush the task runner queue to ensure that output |
| // directory is safe to pass to the fakes. |
| thread_.task_runner()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&RebootFuchsiaTest::ServeOutgoingDirectory, |
| base::Unretained(this), directory.NewRequest())); |
| thread_.FlushForTesting(); |
| |
| // Initialize and publish fake fidl services. |
| admin_ = base::SequenceBound<FakeAdmin>(thread_.task_runner(), |
| outgoing_directory_.get()); |
| last_reboot_info_provider_ = |
| base::SequenceBound<FakeLastRebootInfoProvider>( |
| thread_.task_runner(), outgoing_directory_.get()); |
| factory_reset_service_ = base::SequenceBound<FakeFactoryReset>( |
| thread_.task_runner(), outgoing_directory_.get()); |
| |
| // Ensure that the services above finish publishing themselves. |
| thread_.FlushForTesting(); |
| |
| // Use a service directory backed by the fakes above for tests. |
| incoming_directory_ = |
| std::make_unique<sys::ServiceDirectory>(std::move(directory)); |
| InitializeRebootShlib({}, incoming_directory_.get()); |
| EXPECT_TRUE(dir_.CreateUniqueTempDir()); |
| full_path_ = InitializeFlagFileDirForTesting(dir_.GetPath()); |
| } |
| |
| StateControlRebootReason GetLastRebootReason() { |
| StateControlRebootReason reason; |
| admin_.AsyncCall(&FakeAdmin::GetLastRebootReason).WithArgs(&reason); |
| thread_.FlushForTesting(); |
| return reason; |
| } |
| |
| void SetLastReboot(fuchsia::feedback::LastReboot last_reboot) { |
| last_reboot_info_provider_ |
| .AsyncCall(&FakeLastRebootInfoProvider::SetLastReboot) |
| .WithArgs(std::move(last_reboot)); |
| thread_.FlushForTesting(); |
| } |
| |
| bool FdrTriggered() { |
| bool reset_called; |
| factory_reset_service_.AsyncCall(&FakeFactoryReset::reset_called) |
| .WithArgs(&reset_called); |
| thread_.FlushForTesting(); |
| return reset_called; |
| } |
| |
| private: |
| void ServeOutgoingDirectory( |
| fidl::InterfaceRequest<fuchsia::io::Directory> channel) { |
| outgoing_directory_ = std::make_unique<sys::OutgoingDirectory>(); |
| outgoing_directory_->GetOrCreateDirectory("svc")->Serve( |
| fuchsia_io::wire::kPermReadable | fuchsia_io::wire::kPermWritable, |
| fidl::ServerEnd<fuchsia_io::Directory>(channel.TakeChannel())); |
| } |
| |
| const base::test::SingleThreadTaskEnvironment task_environment_; |
| std::unique_ptr<sys::OutgoingDirectory> outgoing_directory_; |
| std::unique_ptr<sys::ServiceDirectory> incoming_directory_; |
| base::SequenceBound<FakeAdmin> admin_; |
| base::SequenceBound<FakeLastRebootInfoProvider> last_reboot_info_provider_; |
| base::SequenceBound<FakeFactoryReset> factory_reset_service_; |
| base::ScopedTempDir dir_; |
| base::FilePath full_path_; |
| |
| protected: |
| base::FilePath GenerateFlagFilePath(std::string_view name) { |
| return full_path_.Append(name); |
| } |
| |
| base::Thread thread_; |
| }; |
| |
| TEST_F(RebootFuchsiaTest, GetLastRebootSourceDefaultsToUnknown) { |
| EXPECT_THAT(RebootUtil::GetLastRebootSource(), |
| Eq(RebootShlib::RebootSource::UNKNOWN)); |
| } |
| |
| TEST_F(RebootFuchsiaTest, GetLastRebootSourceWithoutGranularReason) { |
| fuchsia::feedback::LastReboot last_reboot; |
| last_reboot.set_graceful(true); |
| EXPECT_TRUE(last_reboot.has_graceful()); |
| EXPECT_FALSE(last_reboot.has_reason()); |
| SetLastReboot(std::move(last_reboot)); |
| EXPECT_THAT(RebootUtil::GetLastRebootSource(), |
| Eq(RebootShlib::RebootSource::SW_OTHER)); |
| } |
| |
| fuchsia::feedback::LastReboot GenerateLastReboot(bool graceful, |
| RebootReason reason) { |
| fuchsia::feedback::LastReboot last_reboot; |
| last_reboot.set_graceful(graceful); |
| last_reboot.set_reason(reason); |
| return last_reboot; |
| } |
| |
| // RetrySystemUpdate must be handled separately because it does not work with |
| // the RebootFuchsiaParamTest family of tests. Those tests expect |
| // RebootSource::OTA to map to exactly one StateControlRebootReason, which is |
| // now not the case. |
| TEST_F(RebootFuchsiaTest, RebootReasonRetrySystemUpdateTranslatesFromFuchsia) { |
| SetLastReboot(GenerateLastReboot(true, RebootReason::RETRY_SYSTEM_UPDATE)); |
| EXPECT_THAT(RebootUtil::GetLastRebootSource(), |
| Eq(RebootShlib::RebootSource::OTA)); |
| } |
| |
| TEST_F(RebootFuchsiaTest, RebootReasonZbiSwapTranslatesFromFuchsia) { |
| SetLastReboot(GenerateLastReboot(true, RebootReason::ZBI_SWAP)); |
| EXPECT_THAT(RebootUtil::GetLastRebootSource(), |
| Eq(RebootShlib::RebootSource::OTA)); |
| } |
| |
| TEST_F(RebootFuchsiaTest, RebootNowTriggersFdr) { |
| EXPECT_TRUE(RebootShlib::IsFdrForNextRebootSupported()); |
| |
| RebootShlib::SetFdrForNextReboot(); |
| |
| EXPECT_TRUE(RebootShlib::RebootNow(RebootShlib::RebootSource::API)); |
| EXPECT_TRUE(FdrTriggered()); |
| } |
| |
| class RebootFuchsiaParamTest |
| : public RebootFuchsiaTest, |
| public ::testing::WithParamInterface<RebootReasonParam> { |
| public: |
| RebootFuchsiaParamTest() = default; |
| }; |
| |
| TEST_P(RebootFuchsiaParamTest, RebootNowSendsFidlRebootReason) { |
| EXPECT_TRUE(RebootShlib::RebootNow(GetParam().source)); |
| thread_.FlushForTesting(); |
| EXPECT_THAT(GetLastRebootReason(), Eq(GetParam().state_control_reason)); |
| } |
| |
| TEST_P(RebootFuchsiaParamTest, GetLastRebootSourceTranslatesReasonFromFuchsia) { |
| SetLastReboot(GenerateLastReboot(GetParam().graceful, GetParam().reason)); |
| EXPECT_THAT(RebootUtil::GetLastRebootSource(), Eq(GetParam().source)); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(RebootReasonParamSweep, |
| RebootFuchsiaParamTest, |
| ::testing::ValuesIn(kRebootReasonParams)); |
| |
| class RestartFuchsiaParamTest |
| : public RebootFuchsiaTest, |
| public ::testing::WithParamInterface<RestartReasonParam> { |
| public: |
| RestartFuchsiaParamTest() = default; |
| |
| void SetUp() override { |
| RebootFuchsiaTest::SetUp(); |
| base::WriteFile(GenerateFlagFilePath(GetParam().file), ""); |
| } |
| }; |
| |
| TEST_P(RestartFuchsiaParamTest, GetLastRestartReasons) { |
| fuchsia::feedback::LastReboot last_reboot; |
| last_reboot.set_graceful(true); |
| EXPECT_TRUE(last_reboot.has_graceful()); |
| EXPECT_FALSE(last_reboot.has_reason()); |
| SetLastReboot(std::move(last_reboot)); |
| |
| EXPECT_THAT(RebootUtil::GetLastRebootSource(), Eq(GetParam().source)); |
| |
| EXPECT_TRUE(base::PathExists(GenerateFlagFilePath(kStartedOnce))); |
| EXPECT_FALSE(base::PathExists(GenerateFlagFilePath(kGracefulTeardown))); |
| |
| base::WriteFile(GenerateFlagFilePath(kGracefulTeardown), ""); |
| EXPECT_THAT(RebootUtil::GetLastRebootSource(), Eq(GetParam().source)); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(RestartReasonParamSweep, |
| RestartFuchsiaParamTest, |
| ::testing::ValuesIn(kRestartReasonParams)); |
| |
| TEST_F(RebootFuchsiaTest, ThoroughTestLastRestartReason) { |
| fuchsia::feedback::LastReboot last_reboot; |
| last_reboot.set_graceful(true); |
| EXPECT_TRUE(last_reboot.has_graceful()); |
| EXPECT_FALSE(last_reboot.has_reason()); |
| SetLastReboot(std::move(last_reboot)); |
| |
| EXPECT_FALSE(base::PathExists(GenerateFlagFilePath(kStartedOnce))); |
| EXPECT_FALSE(base::PathExists(GenerateFlagFilePath(kGracefulTeardown))); |
| EXPECT_THAT(RebootUtil::GetLastRebootSource(), |
| Ne(RebootShlib::RebootSource::GRACEFUL_RESTART)); |
| EXPECT_THAT(RebootUtil::GetLastRebootSource(), |
| Ne(RebootShlib::RebootSource::UNGRACEFUL_RESTART)); |
| |
| // Check files are created/deleted as expected |
| const auto once = GenerateFlagFilePath(kStartedOnce); |
| LOG(INFO) << "looking at file " << once << " " << base::PathExists(once); |
| EXPECT_TRUE(base::PathExists(once)); |
| EXPECT_FALSE(base::PathExists(GenerateFlagFilePath(kGracefulTeardown))); |
| |
| // Confirm reboot reason will not change after create files when check again |
| base::WriteFile(GenerateFlagFilePath(kStartedOnce), ""); |
| base::WriteFile(GenerateFlagFilePath(kGracefulTeardown), ""); |
| EXPECT_THAT(RebootUtil::GetLastRebootSource(), |
| Ne(RebootShlib::RebootSource::GRACEFUL_RESTART)); |
| EXPECT_THAT(RebootUtil::GetLastRebootSource(), |
| Ne(RebootShlib::RebootSource::UNGRACEFUL_RESTART)); |
| |
| // Emulate Reboot |
| RebootUtil::Finalize(); |
| InitializeRestartCheck(); |
| EXPECT_THAT(RebootUtil::GetLastRebootSource(), |
| Eq(RebootShlib::RebootSource::GRACEFUL_RESTART)); |
| } |
| |
| } // namespace |
| } // namespace chromecast |