| // 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 <chromeos/dbus/service_constants.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_.GetPath().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) {} |
| |
| bool MatchAndExplain( |
| dbus::MethodCall* method_call, |
| ::testing::MatchResultListener* listener) const override { |
| // 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; |
| } |
| |
| void DescribeTo(::std::ostream* os) const override { |
| *os << "HandleSuspendReadiness method call with delay ID " << delay_id_ |
| << " and suspend ID " << suspend_id_; |
| } |
| |
| void DescribeNegationTo(::std::ostream* os) const override { |
| *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)); |
| } |
| |
| class StopAllVmsMethodMatcher |
| : public ::testing::MatcherInterface<dbus::MethodCall*> { |
| public: |
| StopAllVmsMethodMatcher() = default; |
| |
| bool MatchAndExplain( |
| dbus::MethodCall* method_call, |
| ::testing::MatchResultListener* listener) const override { |
| // Make sure we've got the right kind of method call. |
| if (method_call->GetInterface() != |
| vm_tools::concierge::kVmConciergeInterface) { |
| *listener << "interface was " << method_call->GetInterface(); |
| return false; |
| } |
| |
| if (method_call->GetMember() != vm_tools::concierge::kStopAllVmsMethod) { |
| *listener << "method name was " << method_call->GetMember(); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void DescribeTo(::std::ostream* os) const override { |
| *os << "StopAllVms method call"; |
| } |
| |
| void DescribeNegationTo(::std::ostream* os) const override { |
| *os << "non-StopAllVms method call"; |
| } |
| }; |
| |
| inline testing::Matcher<dbus::MethodCall*> StopAllVmsMethod() { |
| return MakeMatcher(new StopAllVmsMethodMatcher()); |
| } |
| |
| // 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); |
| } |
| |
| // When the vm_concierge service is running, stop all vms when chrome exits. |
| TEST_F(SessionManagerProcessTest, StopAllVms) { |
| FakeBrowserJob* job = CreateMockJobAndInitManager(true); |
| scoped_refptr<MockObjectProxy> vm_concierge_proxy(new MockObjectProxy()); |
| manager_->test_api().set_vm_concierge_proxy(vm_concierge_proxy.get()); |
| manager_->test_api().set_vm_concierge_available(true); |
| |
| EXPECT_CALL(*vm_concierge_proxy.get(), CallMethod(StopAllVmsMethod(), _, _)) |
| .Times(2); |
| |
| ExpectLivenessChecking(); |
| ExpectOneJobReRun(job, PackSignal(0)); |
| |
| SimpleRunManager(); |
| } |
| |
| } // namespace login_manager |