blob: 7034c00ae7e4609ce355164624be422c23856b7f [file] [log] [blame]
// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <errno.h>
#include <sys/types.h>
#include <unistd.h>
#include <memory>
#include <base/bind.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/files/scoped_temp_dir.h>
#include <base/location.h>
#include <base/logging.h>
#include <base/memory/ptr_util.h>
#include <base/memory/ref_counted.h>
#include <base/strings/string_util.h>
#include <brillo/message_loops/fake_message_loop.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "login_manager/browser_job.h"
#include "login_manager/fake_browser_job.h"
#include "login_manager/fake_child_process.h"
#include "login_manager/fake_generator_job.h"
#include "login_manager/mock_device_policy_service.h"
#include "login_manager/mock_file_checker.h"
#include "login_manager/mock_liveness_checker.h"
#include "login_manager/mock_metrics.h"
#include "login_manager/mock_object_proxy.h"
#include "login_manager/mock_session_manager.h"
#include "login_manager/system_utils_impl.h"
#include "power_manager/proto_bindings/suspend.pb.h"
using ::testing::_;
using ::testing::AnyNumber;
using ::testing::AtLeast;
using ::testing::Return;
using ::testing::Sequence;
namespace login_manager {
// Used as a fixture for the tests in this file.
// Gives useful shared functionality.
class SessionManagerProcessTest : public ::testing::Test {
public:
SessionManagerProcessTest()
: manager_(NULL),
liveness_checker_(new MockLivenessChecker),
session_manager_impl_(new MockSessionManager),
must_destroy_mocks_(true) {}
~SessionManagerProcessTest() override {
if (must_destroy_mocks_) {
delete liveness_checker_;
delete session_manager_impl_;
}
}
void SetUp() override {
fake_loop_.SetAsCurrent();
ASSERT_TRUE(tmpdir_.CreateUniqueTempDir());
aborted_browser_pid_path_ = tmpdir_.path().Append("aborted_browser_pid");
}
void TearDown() override {
must_destroy_mocks_ = !manager_.get();
manager_ = NULL;
}
protected:
// kFakeEmail is NOT const so that it can be passed to methods that
// implement dbus calls, which (of necessity) take bare gchar*.
static char kFakeEmail[];
static const pid_t kDummyPid;
static const int kExit;
void MockUtils() { manager_->test_api().set_systemutils(&utils_); }
void ExpectShutdown() {
EXPECT_CALL(*session_manager_impl_, AnnounceSessionStoppingIfNeeded())
.Times(1);
EXPECT_CALL(*session_manager_impl_, AnnounceSessionStopped()).Times(1);
}
void ExpectLivenessChecking() {
EXPECT_CALL(*liveness_checker_, Start()).Times(AtLeast(1));
EXPECT_CALL(*liveness_checker_, Stop()).Times(AtLeast(1));
}
void ExpectOneJobReRun(FakeBrowserJob* job, int exit_status) {
EXPECT_CALL(*job, KillEverything(SIGKILL, _)).Times(AnyNumber());
EXPECT_CALL(*session_manager_impl_, ShouldEndSession())
.WillRepeatedly(Return(false));
EXPECT_CALL(*job, ShouldStop())
.WillOnce(Return(false))
.WillOnce(Return(true));
job->set_fake_child_process(std::make_unique<FakeChildProcess>(
kDummyPid, exit_status, manager_->test_api()));
}
void InitManager(std::unique_ptr<BrowserJobInterface> job) {
manager_ = new SessionManagerService(std::move(job), getuid(), 3, false,
base::TimeDelta(), &metrics_, &utils_);
manager_->test_api().set_liveness_checker(liveness_checker_);
manager_->test_api().set_session_manager(session_manager_impl_);
manager_->test_api().set_aborted_browser_pid_path(
aborted_browser_pid_path_);
}
void SimpleRunManager() {
ExpectShutdown();
manager_->RunBrowser();
fake_loop_.Run();
}
void ForceRunLoop() { fake_loop_.Run(); }
FakeBrowserJob* CreateMockJobAndInitManager(bool schedule_exit) {
FakeBrowserJob* job = new FakeBrowserJob("FakeBrowserJob", schedule_exit);
InitManager(base::WrapUnique(job));
job->set_fake_child_process(
std::make_unique<FakeChildProcess>(kDummyPid, 0, manager_->test_api()));
return job;
}
int PackStatus(int status) { return __W_EXITCODE(status, 0); }
int PackSignal(int signal) { return __W_EXITCODE(0, signal); }
scoped_refptr<SessionManagerService> manager_;
MockMetrics metrics_;
SystemUtilsImpl utils_;
base::FilePath aborted_browser_pid_path_;
// These are bare pointers, not unique_ptrs, because we need to give them
// to a SessionManagerService instance, but also be able to set expectations
// on them after we hand them off.
MockLivenessChecker* liveness_checker_;
MockSessionManager* session_manager_impl_;
private:
bool must_destroy_mocks_;
base::ScopedTempDir tmpdir_;
brillo::FakeMessageLoop fake_loop_{nullptr};
DISALLOW_COPY_AND_ASSIGN(SessionManagerProcessTest);
};
// static
char SessionManagerProcessTest::kFakeEmail[] = "cmasone@whaaat.org";
const pid_t SessionManagerProcessTest::kDummyPid = 4;
const int SessionManagerProcessTest::kExit = 1;
class HandleSuspendReadinessMethodMatcher
: public ::testing::MatcherInterface<dbus::MethodCall*> {
public:
HandleSuspendReadinessMethodMatcher(int delay_id, int suspend_id)
: delay_id_(delay_id), suspend_id_(suspend_id) {}
virtual bool MatchAndExplain(dbus::MethodCall* method_call,
::testing::MatchResultListener* listener) const {
// Make sure we've got the right kind of method call.
if (method_call->GetInterface() != power_manager::kPowerManagerInterface) {
*listener << "interface was " << method_call->GetInterface();
return false;
}
if (method_call->GetMember() !=
power_manager::kHandleSuspendReadinessMethod) {
*listener << "method name was " << method_call->GetMember();
return false;
}
// Check proto for correctness.
power_manager::SuspendReadinessInfo info;
dbus::MessageReader reader(method_call);
reader.PopArrayOfBytesAsProto(&info);
if (info.delay_id() != delay_id_) {
*listener << "delay ID was " << info.delay_id();
return false;
}
if (info.suspend_id() != suspend_id_) {
*listener << "suspend ID was " << info.suspend_id();
return false;
}
return true;
}
virtual void DescribeTo(::std::ostream* os) const {
*os << "HandleSuspendReadiness method call with delay ID " << delay_id_
<< " and suspend ID " << suspend_id_;
}
virtual void DescribeNegationTo(::std::ostream* os) const {
*os << "non-HandleSuspendReadiness method call, or method call "
<< "not with delay ID " << delay_id_ << " and suspend ID "
<< suspend_id_;
}
private:
const int delay_id_;
const int suspend_id_;
};
inline testing::Matcher<dbus::MethodCall*> HandleSuspendReadinessMethod(
int delay_id, int suspend_id) {
return MakeMatcher(
new HandleSuspendReadinessMethodMatcher(delay_id, suspend_id));
}
// Browser processes get correctly terminated.
TEST_F(SessionManagerProcessTest, CleanupBrowser) {
FakeBrowserJob* job = CreateMockJobAndInitManager(false);
EXPECT_CALL(*job, Kill(SIGTERM, _)).Times(1);
EXPECT_CALL(*job, WaitAndAbort(_)).Times(1);
job->RunInBackground();
manager_->test_api().CleanupChildren(3);
}
// Gracefully shut down while the browser is running.
TEST_F(SessionManagerProcessTest, BrowserRunningShutdown) {
FakeBrowserJob* job = CreateMockJobAndInitManager(false);
ExpectLivenessChecking();
ExpectShutdown();
// Expect the job to be killed.
EXPECT_CALL(*job, Kill(SIGTERM, _)).Times(1);
EXPECT_CALL(*job, WaitAndAbort(_)).Times(1);
brillo::MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(&SessionManagerService::RunBrowser, manager_.get()));
brillo::MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(&SessionManagerService::ScheduleShutdown, manager_.get()));
ForceRunLoop();
}
// If the browser exits and asks to stop, the session manager
// should not restart it.
TEST_F(SessionManagerProcessTest, ChildExitFlagFileStop) {
FakeBrowserJob* job = CreateMockJobAndInitManager(true);
manager_->test_api().set_exit_on_child_done(true); // or it'll run forever.
ExpectLivenessChecking();
EXPECT_CALL(*job, KillEverything(SIGKILL, _)).Times(AnyNumber());
EXPECT_CALL(*job, ShouldStop()).WillOnce(Return(false));
job->set_should_run(false);
EXPECT_CALL(*session_manager_impl_, ShouldEndSession())
.WillOnce(Return(false));
SimpleRunManager();
}
// A child that exits with a signal should get re-run.
TEST_F(SessionManagerProcessTest, BadExitChildOnSignal) {
FakeBrowserJob* job = CreateMockJobAndInitManager(true);
ExpectLivenessChecking();
ExpectOneJobReRun(job, PackSignal(SIGILL));
SimpleRunManager();
}
// A child that exits badly should get re-run.
TEST_F(SessionManagerProcessTest, BadExitChild) {
FakeBrowserJob* job = CreateMockJobAndInitManager(true);
ExpectLivenessChecking();
ExpectOneJobReRun(job, PackSignal(kExit));
SimpleRunManager();
}
// A child that exits cleanly should get re-run.
TEST_F(SessionManagerProcessTest, CleanExitChild) {
FakeBrowserJob* job = CreateMockJobAndInitManager(true);
ExpectLivenessChecking();
ExpectOneJobReRun(job, PackSignal(0));
SimpleRunManager();
}
// If the browser exits while the screen is locked, the session manager
// should exit.
TEST_F(SessionManagerProcessTest, LockedExit) {
FakeBrowserJob* job = CreateMockJobAndInitManager(true);
ExpectLivenessChecking();
EXPECT_CALL(*job, KillEverything(SIGKILL, _)).Times(AnyNumber());
EXPECT_CALL(*job, ShouldStop()).Times(0);
EXPECT_CALL(*session_manager_impl_, ShouldEndSession())
.WillOnce(Return(true));
SimpleRunManager();
}
// Liveness checking should be started and stopped along with the browser.
TEST_F(SessionManagerProcessTest, LivenessCheckingStartStop) {
FakeBrowserJob* job = CreateMockJobAndInitManager(true);
{
Sequence start_stop;
EXPECT_CALL(*liveness_checker_, Start()).Times(2);
EXPECT_CALL(*liveness_checker_, Stop()).Times(AtLeast(1));
}
ExpectOneJobReRun(job, PackSignal(0));
SimpleRunManager();
}
// If the child indicates it should be stopped, the session manager must honor.
TEST_F(SessionManagerProcessTest, MustStopChild) {
FakeBrowserJob* job = CreateMockJobAndInitManager(true);
ExpectLivenessChecking();
EXPECT_CALL(*job, KillEverything(SIGKILL, _)).Times(AnyNumber());
EXPECT_CALL(*job, ShouldStop()).WillOnce(Return(true));
EXPECT_CALL(*session_manager_impl_, ShouldEndSession())
.WillRepeatedly(Return(false));
SimpleRunManager();
}
TEST_F(SessionManagerProcessTest, TestWipeOnBadState) {
CreateMockJobAndInitManager(true);
EXPECT_CALL(*session_manager_impl_, Initialize()).WillOnce(Return(false));
// Expect Powerwash to be triggered.
EXPECT_CALL(*session_manager_impl_, InitiateDeviceWipe(_)).Times(1);
EXPECT_CALL(*session_manager_impl_, Finalize()).Times(1);
ASSERT_FALSE(manager_->test_api().InitializeImpl());
ASSERT_EQ(SessionManagerService::MUST_WIPE_DEVICE, manager_->exit_code());
}
// When aborting the browser, the session manager should write the killed pid.
TEST_F(SessionManagerProcessTest, TestAbortedBrowserPidWritten) {
FakeBrowserJob* job = CreateMockJobAndInitManager(false);
EXPECT_CALL(*job, KillEverything(SIGKILL, _)).Times(AnyNumber());
ASSERT_TRUE(job->RunInBackground());
manager_->AbortBrowser(SIGKILL, "");
ASSERT_TRUE(base::PathExists(aborted_browser_pid_path_));
std::string read_pid_str;
ASSERT_TRUE(base::ReadFileToString(aborted_browser_pid_path_, &read_pid_str));
int read_pid = atoi(read_pid_str.c_str());
EXPECT_EQ(kDummyPid, read_pid);
}
} // namespace login_manager