blob: 9bb732c549d10e2d86d3d508202ba97988fe2620 [file] [log] [blame] [edit]
// 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