// // Copyright (C) 2012 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #include "shill/external_task.h" #include <map> #include <memory> #include <set> #include <string> #include <vector> #include <base/bind.h> #include <base/files/file_path.h> #include <base/memory/weak_ptr.h> #include <base/strings/string_util.h> #include <gmock/gmock.h> #include <gtest/gtest.h> #include "shill/mock_adaptors.h" #include "shill/mock_process_manager.h" #include "shill/nice_mock_control.h" #include "shill/test_event_dispatcher.h" using std::map; using std::set; using std::string; using std::vector; using testing::_; using testing::Matcher; using testing::MatchesRegex; using testing::Mock; using testing::NiceMock; using testing::Return; using testing::SetArgumentPointee; using testing::StrEq; namespace shill { class ExternalTaskTest : public testing::Test, public RPCTaskDelegate { public: ExternalTaskTest() : weak_ptr_factory_(this), death_callback_( base::Bind(&ExternalTaskTest::TaskDiedCallback, weak_ptr_factory_.GetWeakPtr())), external_task_( new ExternalTask(&control_, &process_manager_, weak_ptr_factory_.GetWeakPtr(), death_callback_)), test_rpc_task_destroyed_(false) {} virtual ~ExternalTaskTest() {} virtual void TearDown() { if (!external_task_) { return; } if (external_task_->pid_) { EXPECT_CALL(process_manager_, StopProcess(external_task_->pid_)); } } void set_test_rpc_task_destroyed(bool destroyed) { test_rpc_task_destroyed_ = destroyed; } // Defined out-of-line, due to dependency on TestRPCTask. void FakeUpRunningProcess(unsigned int tag, int pid); void ExpectStop(unsigned int tag, int pid) { EXPECT_CALL(process_manager_, StopProcess(pid)); } void VerifyStop() { if (external_task_) { EXPECT_EQ(0, external_task_->pid_); EXPECT_FALSE(external_task_->rpc_task_); } EXPECT_TRUE(test_rpc_task_destroyed_); // Make sure EXPECTations were met before the fixture's dtor. Mock::VerifyAndClearExpectations(&process_manager_); } protected: // Implements RPCTaskDelegate interface. MOCK_METHOD2(GetLogin, void(string* user, string* password)); MOCK_METHOD2(Notify, void(const string& reason, const map<string, string>& dict)); MOCK_METHOD2(TaskDiedCallback, void(pid_t pid, int exit_status)); NiceMockControl control_; EventDispatcherForTest dispatcher_; MockProcessManager process_manager_; base::WeakPtrFactory<ExternalTaskTest> weak_ptr_factory_; base::Callback<void(pid_t, int)> death_callback_; std::unique_ptr<ExternalTask> external_task_; bool test_rpc_task_destroyed_; }; namespace { class TestRPCTask : public RPCTask { public: TestRPCTask(ControlInterface* control, ExternalTaskTest* test); virtual ~TestRPCTask(); private: ExternalTaskTest* test_; }; TestRPCTask::TestRPCTask(ControlInterface* control, ExternalTaskTest* test) : RPCTask(control, test), test_(test) { test_->set_test_rpc_task_destroyed(false); } TestRPCTask::~TestRPCTask() { test_->set_test_rpc_task_destroyed(true); test_ = nullptr; } } // namespace void ExternalTaskTest::FakeUpRunningProcess(unsigned int tag, int pid) { external_task_->pid_ = pid; external_task_->rpc_task_.reset(new TestRPCTask(&control_, this)); } TEST_F(ExternalTaskTest, Destructor) { const unsigned int kTag = 123; const int kPID = 123456; FakeUpRunningProcess(kTag, kPID); ExpectStop(kTag, kPID); external_task_.reset(); VerifyStop(); } TEST_F(ExternalTaskTest, DestroyLater) { const unsigned int kTag = 123; const int kPID = 123456; FakeUpRunningProcess(kTag, kPID); ExpectStop(kTag, kPID); external_task_.release()->DestroyLater(&dispatcher_); dispatcher_.DispatchPendingEvents(); VerifyStop(); } namespace { // Returns true iff. there is at least one anchored match in |arg|, // for each item in |expected_values|. Order of items does not matter. // // |arg| is a NULL-terminated array of C-strings. // |expected_values| is a container of regular expressions (as strings). MATCHER_P(HasElementsMatching, expected_values, "") { for (const auto& expected_value : expected_values) { auto regex_matcher(MatchesRegex(expected_value).impl()); char** arg_local = arg; while (*arg_local) { if (regex_matcher.MatchAndExplain(*arg_local, result_listener)) { break; } ++arg_local; } if (*arg_local == nullptr) { *result_listener << "missing value " << expected_value << "\n"; arg_local = arg; while (*arg_local) { *result_listener << "received: " << *arg_local << "\n"; ++arg_local; } return false; } } return true; } } // namespace TEST_F(ExternalTaskTest, Start) { const string kCommand = "/run/me"; const vector<string> kCommandOptions{"arg1", "arg2"}; const map<string, string> kCommandEnv{{"env1", "val1"}, {"env2", "val2"}}; map<string, string> expected_env; expected_env.emplace(kRPCTaskServiceVariable, RPCTaskMockAdaptor::kRpcConnId); expected_env.emplace(kRPCTaskPathVariable, RPCTaskMockAdaptor::kRpcId); expected_env.insert(kCommandEnv.begin(), kCommandEnv.end()); const int kPID = 234678; EXPECT_CALL(process_manager_, StartProcess(_, base::FilePath(kCommand), kCommandOptions, expected_env, false, _)) .WillOnce(Return(-1)) .WillOnce(Return(kPID)); Error error; EXPECT_FALSE(external_task_->Start( base::FilePath(kCommand), kCommandOptions, kCommandEnv, false, &error)); EXPECT_EQ(Error::kInternalError, error.type()); EXPECT_FALSE(external_task_->rpc_task_); error.Reset(); EXPECT_TRUE(external_task_->Start( base::FilePath(kCommand), kCommandOptions, kCommandEnv, false, &error)); EXPECT_TRUE(error.IsSuccess()); EXPECT_EQ(kPID, external_task_->pid_); EXPECT_NE(nullptr, external_task_->rpc_task_); } TEST_F(ExternalTaskTest, Stop) { const unsigned int kTag = 123; const int kPID = 123456; FakeUpRunningProcess(kTag, kPID); ExpectStop(kTag, kPID); external_task_->Stop(); ASSERT_NE(nullptr, external_task_); VerifyStop(); } TEST_F(ExternalTaskTest, StopNotStarted) { EXPECT_CALL(process_manager_, StopProcess(_)).Times(0); external_task_->Stop(); EXPECT_FALSE(test_rpc_task_destroyed_); } TEST_F(ExternalTaskTest, GetLogin) { string username; string password; EXPECT_CALL(*this, GetLogin(&username, &password)); EXPECT_CALL(*this, Notify(_, _)).Times(0); external_task_->GetLogin(&username, &password); } TEST_F(ExternalTaskTest, Notify) { const string kReason("you may already have won!"); const map<string, string>& kArgs{ {"arg1", "val1"}, {"arg2", "val2"}}; EXPECT_CALL(*this, GetLogin(_, _)).Times(0); EXPECT_CALL(*this, Notify(kReason, kArgs)); external_task_->Notify(kReason, kArgs); } TEST_F(ExternalTaskTest, OnTaskDied) { const int kPID = 99999; const int kExitStatus = 1; external_task_->pid_ = kPID; EXPECT_CALL(process_manager_, StopProcess(_)).Times(0); EXPECT_CALL(*this, TaskDiedCallback(kPID, kExitStatus)); external_task_->OnTaskDied(kExitStatus); EXPECT_EQ(0, external_task_->pid_); } } // namespace shill