Primiano Tucci | 420e47e | 2017-11-14 11:31:49 +0000 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2017 The Android Open foo Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | #include "ipc/src/host_impl.h" |
| 18 | |
| 19 | #include <memory> |
| 20 | |
Primiano Tucci | 420e47e | 2017-11-14 11:31:49 +0000 | [diff] [blame] | 21 | #include "gmock/gmock.h" |
| 22 | #include "gtest/gtest.h" |
| 23 | #include "ipc/service.h" |
| 24 | #include "ipc/service_descriptor.h" |
| 25 | #include "ipc/src/buffered_frame_deserializer.h" |
| 26 | #include "ipc/src/unix_socket.h" |
Oystein Eftevaag | dd727e4 | 2017-12-05 08:49:55 -0800 | [diff] [blame^] | 27 | #include "perfetto_base/scoped_file.h" |
| 28 | #include "perfetto_base/test/test_task_runner.h" |
Primiano Tucci | 420e47e | 2017-11-14 11:31:49 +0000 | [diff] [blame] | 29 | |
Primiano Tucci | 2ee254a | 2017-11-15 00:38:48 +0000 | [diff] [blame] | 30 | #include "ipc/src/test/client_unittest_messages.pb.h" |
| 31 | #include "ipc/src/wire_protocol.pb.h" |
Primiano Tucci | 420e47e | 2017-11-14 11:31:49 +0000 | [diff] [blame] | 32 | |
| 33 | namespace perfetto { |
| 34 | namespace ipc { |
| 35 | namespace { |
| 36 | |
| 37 | using ::testing::_; |
| 38 | using ::testing::Invoke; |
| 39 | using ::testing::InvokeWithoutArgs; |
| 40 | |
| 41 | constexpr char kSockName[] = "/tmp/perfetto_host_impl_unittest.sock"; |
| 42 | |
| 43 | // RequestProto and ReplyProto are defined in client_unittest_messages.proto. |
| 44 | |
| 45 | class FakeService : public Service { |
| 46 | public: |
| 47 | MOCK_METHOD0(Destroyed, void()); |
| 48 | MOCK_METHOD2(OnFakeMethod1, void(const RequestProto&, DeferredBase*)); |
| 49 | |
| 50 | static void Invoker(Service* service, |
| 51 | const ProtoMessage& req, |
| 52 | DeferredBase deferred_reply) { |
| 53 | static_cast<FakeService*>(service)->OnFakeMethod1( |
| 54 | static_cast<const RequestProto&>(req), &deferred_reply); |
| 55 | } |
| 56 | |
| 57 | static std::unique_ptr<ProtoMessage> RequestDecoder( |
| 58 | const std::string& proto) { |
| 59 | std::unique_ptr<ProtoMessage> reply(new RequestProto()); |
| 60 | EXPECT_TRUE(reply->ParseFromString(proto)); |
| 61 | return reply; |
| 62 | } |
| 63 | |
| 64 | FakeService(const char* service_name) { |
| 65 | descriptor_.service_name = service_name; |
| 66 | descriptor_.methods.push_back( |
| 67 | {"FakeMethod1", &RequestDecoder, nullptr, &Invoker}); |
| 68 | } |
| 69 | |
| 70 | const ServiceDescriptor& GetDescriptor() override { return descriptor_; } |
| 71 | |
| 72 | ServiceDescriptor descriptor_; |
| 73 | }; |
| 74 | |
| 75 | class FakeClient : public UnixSocket::EventListener { |
| 76 | public: |
| 77 | MOCK_METHOD0(OnConnect, void()); |
| 78 | MOCK_METHOD0(OnDisconnect, void()); |
| 79 | MOCK_METHOD1(OnServiceBound, void(const Frame::BindServiceReply&)); |
| 80 | MOCK_METHOD1(OnInvokeMethodReply, void(const Frame::InvokeMethodReply&)); |
Primiano Tucci | f54cae4 | 2017-11-21 19:37:13 +0000 | [diff] [blame] | 81 | MOCK_METHOD1(OnFileDescriptorReceived, void(int)); |
Primiano Tucci | 420e47e | 2017-11-14 11:31:49 +0000 | [diff] [blame] | 82 | MOCK_METHOD0(OnRequestError, void()); |
| 83 | |
| 84 | explicit FakeClient(base::TaskRunner* task_runner) { |
| 85 | sock_ = UnixSocket::Connect(kSockName, this, task_runner); |
| 86 | } |
| 87 | |
| 88 | ~FakeClient() override = default; |
| 89 | |
| 90 | void BindService(const std::string& service_name) { |
| 91 | Frame frame; |
| 92 | uint64_t request_id = requests_.empty() ? 1 : requests_.rbegin()->first + 1; |
| 93 | requests_.emplace(request_id, 0); |
| 94 | frame.set_request_id(request_id); |
| 95 | frame.mutable_msg_bind_service()->set_service_name(service_name); |
| 96 | SendFrame(frame); |
| 97 | } |
| 98 | |
| 99 | void InvokeMethod(ServiceID service_id, |
| 100 | MethodID method_id, |
| 101 | const ProtoMessage& args) { |
| 102 | Frame frame; |
| 103 | uint64_t request_id = requests_.empty() ? 1 : requests_.rbegin()->first + 1; |
| 104 | requests_.emplace(request_id, 0); |
| 105 | frame.set_request_id(request_id); |
| 106 | frame.mutable_msg_invoke_method()->set_service_id(service_id); |
| 107 | frame.mutable_msg_invoke_method()->set_method_id(method_id); |
| 108 | frame.mutable_msg_invoke_method()->set_args_proto(args.SerializeAsString()); |
| 109 | SendFrame(frame); |
| 110 | } |
| 111 | |
| 112 | // UnixSocket::EventListener implementation. |
| 113 | void OnConnect(UnixSocket*, bool success) override { |
| 114 | ASSERT_TRUE(success); |
| 115 | OnConnect(); |
| 116 | } |
| 117 | |
| 118 | void OnDisconnect(UnixSocket*) override { OnDisconnect(); } |
| 119 | |
| 120 | void OnDataAvailable(UnixSocket* sock) override { |
| 121 | ASSERT_EQ(sock_.get(), sock); |
| 122 | auto buf = frame_deserializer_.BeginReceive(); |
Primiano Tucci | f54cae4 | 2017-11-21 19:37:13 +0000 | [diff] [blame] | 123 | base::ScopedFile fd; |
| 124 | size_t rsize = sock->Receive(buf.data, buf.size, &fd); |
Primiano Tucci | 420e47e | 2017-11-14 11:31:49 +0000 | [diff] [blame] | 125 | ASSERT_TRUE(frame_deserializer_.EndReceive(rsize)); |
Primiano Tucci | f54cae4 | 2017-11-21 19:37:13 +0000 | [diff] [blame] | 126 | if (fd) |
| 127 | OnFileDescriptorReceived(*fd); |
Primiano Tucci | 420e47e | 2017-11-14 11:31:49 +0000 | [diff] [blame] | 128 | while (std::unique_ptr<Frame> frame = frame_deserializer_.PopNextFrame()) { |
| 129 | ASSERT_EQ(1u, requests_.count(frame->request_id())); |
| 130 | EXPECT_EQ(0, requests_[frame->request_id()]++); |
| 131 | if (frame->msg_case() == Frame::kMsgBindServiceReply) { |
| 132 | if (frame->msg_bind_service_reply().success()) |
| 133 | last_bound_service_id_ = frame->msg_bind_service_reply().service_id(); |
| 134 | return OnServiceBound(frame->msg_bind_service_reply()); |
| 135 | } |
| 136 | if (frame->msg_case() == Frame::kMsgInvokeMethodReply) |
| 137 | return OnInvokeMethodReply(frame->msg_invoke_method_reply()); |
| 138 | if (frame->msg_case() == Frame::kMsgRequestError) |
| 139 | return OnRequestError(); |
| 140 | FAIL() << "Unexpected frame received from host " << frame->msg_case(); |
| 141 | } |
| 142 | } |
| 143 | |
| 144 | void SendFrame(const Frame& frame) { |
| 145 | std::string buf = BufferedFrameDeserializer::Serialize(frame); |
| 146 | ASSERT_TRUE(sock_->Send(buf.data(), buf.size())); |
| 147 | } |
| 148 | |
| 149 | BufferedFrameDeserializer frame_deserializer_; |
| 150 | std::unique_ptr<UnixSocket> sock_; |
| 151 | std::map<uint64_t /* request_id */, int /* num_replies_received */> requests_; |
| 152 | ServiceID last_bound_service_id_; |
| 153 | }; |
| 154 | |
| 155 | class HostImplTest : public ::testing::Test { |
| 156 | public: |
| 157 | void SetUp() override { |
| 158 | unlink(kSockName); |
| 159 | task_runner_.reset(new base::TestTaskRunner()); |
| 160 | Host* host = Host::CreateInstance(kSockName, task_runner_.get()).release(); |
| 161 | ASSERT_NE(nullptr, host); |
| 162 | host_.reset(static_cast<HostImpl*>(host)); |
| 163 | cli_.reset(new FakeClient(task_runner_.get())); |
| 164 | auto on_connect = task_runner_->CreateCheckpoint("on_connect"); |
| 165 | EXPECT_CALL(*cli_, OnConnect()).WillOnce(Invoke(on_connect)); |
| 166 | task_runner_->RunUntilCheckpoint("on_connect"); |
| 167 | } |
| 168 | |
| 169 | void TearDown() override { |
| 170 | task_runner_->RunUntilIdle(); |
| 171 | cli_.reset(); |
| 172 | host_.reset(); |
| 173 | task_runner_->RunUntilIdle(); |
| 174 | task_runner_.reset(); |
| 175 | unlink(kSockName); |
| 176 | } |
| 177 | |
| 178 | // ::testing::StrictMock<MockEventListener> proxy_events_; |
| 179 | std::unique_ptr<base::TestTaskRunner> task_runner_; |
| 180 | std::unique_ptr<HostImpl> host_; |
| 181 | std::unique_ptr<FakeClient> cli_; |
| 182 | }; |
| 183 | |
| 184 | TEST_F(HostImplTest, BindService) { |
| 185 | // First bind the service when it doesn't exists yet and check that the |
| 186 | // BindService() request fails. |
| 187 | cli_->BindService("FakeService"); // FakeService does not exist yet. |
| 188 | auto on_bind_failure = task_runner_->CreateCheckpoint("on_bind_failure"); |
| 189 | EXPECT_CALL(*cli_, OnServiceBound(_)) |
| 190 | .WillOnce(Invoke([on_bind_failure](const Frame::BindServiceReply& reply) { |
| 191 | ASSERT_FALSE(reply.success()); |
| 192 | on_bind_failure(); |
| 193 | })); |
| 194 | task_runner_->RunUntilCheckpoint("on_bind_failure"); |
| 195 | |
| 196 | // Now expose the service and bind it. |
| 197 | ASSERT_TRUE(host_->ExposeService( |
| 198 | std::unique_ptr<Service>(new FakeService("FakeService")))); |
| 199 | auto on_bind_success = task_runner_->CreateCheckpoint("on_bind_success"); |
| 200 | cli_->BindService("FakeService"); |
| 201 | EXPECT_CALL(*cli_, OnServiceBound(_)) |
| 202 | .WillOnce(Invoke([on_bind_success](const Frame::BindServiceReply& reply) { |
| 203 | ASSERT_TRUE(reply.success()); |
| 204 | on_bind_success(); |
| 205 | })); |
| 206 | task_runner_->RunUntilCheckpoint("on_bind_success"); |
| 207 | } |
| 208 | |
| 209 | TEST_F(HostImplTest, InvokeNonExistingMethod) { |
| 210 | FakeService* fake_service = new FakeService("FakeService"); |
| 211 | ASSERT_TRUE(host_->ExposeService(std::unique_ptr<Service>(fake_service))); |
| 212 | auto on_bind = task_runner_->CreateCheckpoint("on_bind"); |
| 213 | cli_->BindService("FakeService"); |
| 214 | EXPECT_CALL(*cli_, OnServiceBound(_)).WillOnce(InvokeWithoutArgs(on_bind)); |
| 215 | task_runner_->RunUntilCheckpoint("on_bind"); |
| 216 | |
| 217 | auto on_invoke_failure = task_runner_->CreateCheckpoint("on_invoke_failure"); |
| 218 | cli_->InvokeMethod(cli_->last_bound_service_id_, 42, RequestProto()); |
| 219 | EXPECT_CALL(*cli_, OnInvokeMethodReply(_)) |
| 220 | .WillOnce( |
| 221 | Invoke([on_invoke_failure](const Frame::InvokeMethodReply& reply) { |
| 222 | ASSERT_FALSE(reply.success()); |
| 223 | ASSERT_FALSE(reply.has_more()); |
| 224 | on_invoke_failure(); |
| 225 | })); |
| 226 | task_runner_->RunUntilCheckpoint("on_invoke_failure"); |
| 227 | } |
| 228 | |
| 229 | TEST_F(HostImplTest, InvokeMethod) { |
| 230 | FakeService* fake_service = new FakeService("FakeService"); |
| 231 | ASSERT_TRUE(host_->ExposeService(std::unique_ptr<Service>(fake_service))); |
| 232 | auto on_bind = task_runner_->CreateCheckpoint("on_bind"); |
| 233 | cli_->BindService("FakeService"); |
| 234 | EXPECT_CALL(*cli_, OnServiceBound(_)).WillOnce(InvokeWithoutArgs(on_bind)); |
| 235 | task_runner_->RunUntilCheckpoint("on_bind"); |
| 236 | |
| 237 | RequestProto req_args; |
| 238 | req_args.set_data("foo"); |
| 239 | cli_->InvokeMethod(cli_->last_bound_service_id_, 1, req_args); |
| 240 | auto on_reply_sent = task_runner_->CreateCheckpoint("on_reply_sent"); |
| 241 | EXPECT_CALL(*fake_service, OnFakeMethod1(_, _)) |
| 242 | .WillOnce( |
| 243 | Invoke([on_reply_sent](const RequestProto& req, DeferredBase* reply) { |
| 244 | ASSERT_EQ("foo", req.data()); |
| 245 | std::unique_ptr<ReplyProto> reply_args(new ReplyProto()); |
| 246 | reply_args->set_data("bar"); |
| 247 | reply->Resolve(AsyncResult<ProtoMessage>( |
| 248 | std::unique_ptr<ProtoMessage>(reply_args.release()))); |
| 249 | on_reply_sent(); |
| 250 | })); |
| 251 | task_runner_->RunUntilCheckpoint("on_reply_sent"); |
| 252 | |
| 253 | auto on_reply_received = task_runner_->CreateCheckpoint("on_reply_received"); |
| 254 | EXPECT_CALL(*cli_, OnInvokeMethodReply(_)) |
| 255 | .WillOnce( |
| 256 | Invoke([on_reply_received](const Frame::InvokeMethodReply& reply) { |
| 257 | ASSERT_TRUE(reply.success()); |
| 258 | ASSERT_FALSE(reply.has_more()); |
| 259 | ReplyProto reply_args; |
| 260 | reply_args.ParseFromString(reply.reply_proto()); |
| 261 | ASSERT_EQ("bar", reply_args.data()); |
| 262 | on_reply_received(); |
| 263 | })); |
| 264 | task_runner_->RunUntilCheckpoint("on_reply_received"); |
| 265 | } |
| 266 | |
Primiano Tucci | f54cae4 | 2017-11-21 19:37:13 +0000 | [diff] [blame] | 267 | TEST_F(HostImplTest, SendFileDescriptor) { |
| 268 | FakeService* fake_service = new FakeService("FakeService"); |
| 269 | ASSERT_TRUE(host_->ExposeService(std::unique_ptr<Service>(fake_service))); |
| 270 | auto on_bind = task_runner_->CreateCheckpoint("on_bind"); |
| 271 | cli_->BindService("FakeService"); |
| 272 | EXPECT_CALL(*cli_, OnServiceBound(_)).WillOnce(InvokeWithoutArgs(on_bind)); |
| 273 | task_runner_->RunUntilCheckpoint("on_bind"); |
| 274 | |
| 275 | static constexpr char kFileContent[] = "shared file"; |
| 276 | RequestProto req_args; |
| 277 | cli_->InvokeMethod(cli_->last_bound_service_id_, 1, req_args); |
| 278 | auto on_reply_sent = task_runner_->CreateCheckpoint("on_reply_sent"); |
| 279 | FILE* tx_file = tmpfile(); |
| 280 | fwrite(kFileContent, sizeof(kFileContent), 1, tx_file); |
| 281 | fflush(tx_file); |
| 282 | EXPECT_CALL(*fake_service, OnFakeMethod1(_, _)) |
| 283 | .WillOnce(Invoke([on_reply_sent, tx_file](const RequestProto& req, |
| 284 | DeferredBase* reply) { |
| 285 | std::unique_ptr<ReplyProto> reply_args(new ReplyProto()); |
| 286 | auto async_res = AsyncResult<ProtoMessage>( |
| 287 | std::unique_ptr<ProtoMessage>(reply_args.release())); |
| 288 | async_res.set_fd(fileno(tx_file)); |
| 289 | reply->Resolve(std::move(async_res)); |
| 290 | on_reply_sent(); |
| 291 | })); |
| 292 | task_runner_->RunUntilCheckpoint("on_reply_sent"); |
| 293 | fclose(tx_file); |
| 294 | |
| 295 | auto on_fd_received = task_runner_->CreateCheckpoint("on_fd_received"); |
| 296 | EXPECT_CALL(*cli_, OnFileDescriptorReceived(_)) |
| 297 | .WillOnce(Invoke([on_fd_received](int fd) { |
| 298 | char buf[sizeof(kFileContent)] = {}; |
| 299 | ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)); |
Oystein Eftevaag | dd727e4 | 2017-12-05 08:49:55 -0800 | [diff] [blame^] | 300 | ASSERT_EQ(static_cast<int32_t>(sizeof(buf)), |
| 301 | PERFETTO_EINTR(read(fd, buf, sizeof(buf)))); |
Primiano Tucci | f54cae4 | 2017-11-21 19:37:13 +0000 | [diff] [blame] | 302 | ASSERT_STREQ(kFileContent, buf); |
| 303 | on_fd_received(); |
| 304 | })); |
| 305 | EXPECT_CALL(*cli_, OnInvokeMethodReply(_)); |
| 306 | task_runner_->RunUntilCheckpoint("on_fd_received"); |
| 307 | } |
| 308 | |
Primiano Tucci | 420e47e | 2017-11-14 11:31:49 +0000 | [diff] [blame] | 309 | // Invoke a method and immediately after disconnect the client. |
| 310 | TEST_F(HostImplTest, OnClientDisconnect) { |
| 311 | FakeService* fake_service = new FakeService("FakeService"); |
| 312 | ASSERT_TRUE(host_->ExposeService(std::unique_ptr<Service>(fake_service))); |
| 313 | auto on_bind = task_runner_->CreateCheckpoint("on_bind"); |
| 314 | cli_->BindService("FakeService"); |
| 315 | EXPECT_CALL(*cli_, OnServiceBound(_)).WillOnce(InvokeWithoutArgs(on_bind)); |
| 316 | task_runner_->RunUntilCheckpoint("on_bind"); |
| 317 | |
| 318 | RequestProto req_args; |
| 319 | req_args.set_data("foo"); |
| 320 | cli_->InvokeMethod(cli_->last_bound_service_id_, 1, req_args); |
| 321 | EXPECT_CALL(*cli_, OnInvokeMethodReply(_)).Times(0); |
Primiano Tucci | c2025af | 2017-11-20 12:47:49 +0000 | [diff] [blame] | 322 | cli_.reset(); // Disconnect the client. |
Primiano Tucci | 420e47e | 2017-11-14 11:31:49 +0000 | [diff] [blame] | 323 | auto on_host_method = task_runner_->CreateCheckpoint("on_host_method"); |
| 324 | EXPECT_CALL(*fake_service, OnFakeMethod1(_, _)) |
| 325 | .WillOnce(Invoke( |
| 326 | [on_host_method](const RequestProto& req, DeferredBase* reply) { |
| 327 | ASSERT_EQ("foo", req.data()); |
| 328 | on_host_method(); |
| 329 | })); |
| 330 | task_runner_->RunUntilCheckpoint("on_host_method"); |
| 331 | } |
| 332 | |
| 333 | // Like InvokeMethod, but instead of resolving the Deferred reply within the |
| 334 | // call stack, std::move()-s it outside an replies |
| 335 | TEST_F(HostImplTest, MoveReplyObjectAndReplyAsynchronously) { |
| 336 | FakeService* fake_service = new FakeService("FakeService"); |
| 337 | ASSERT_TRUE(host_->ExposeService(std::unique_ptr<Service>(fake_service))); |
| 338 | auto on_bind = task_runner_->CreateCheckpoint("on_bind"); |
| 339 | cli_->BindService("FakeService"); |
| 340 | EXPECT_CALL(*cli_, OnServiceBound(_)).WillOnce(InvokeWithoutArgs(on_bind)); |
| 341 | task_runner_->RunUntilCheckpoint("on_bind"); |
| 342 | |
| 343 | // Invokes the remote method and waits that the FakeService sees it. The reply |
| 344 | // is not resolved but just moved into |moved_reply|. |
| 345 | RequestProto req_args; |
| 346 | cli_->InvokeMethod(cli_->last_bound_service_id_, 1, req_args); |
| 347 | auto on_invoke = task_runner_->CreateCheckpoint("on_invoke"); |
| 348 | DeferredBase moved_reply; |
| 349 | EXPECT_CALL(*fake_service, OnFakeMethod1(_, _)) |
| 350 | .WillOnce(Invoke([on_invoke, &moved_reply](const RequestProto& req, |
| 351 | DeferredBase* reply) { |
| 352 | moved_reply = std::move(*reply); |
| 353 | on_invoke(); |
| 354 | })); |
| 355 | task_runner_->RunUntilCheckpoint("on_invoke"); |
| 356 | |
| 357 | // Check that the FakeClient doesn't see any reply yet. |
| 358 | EXPECT_CALL(*cli_, OnInvokeMethodReply(_)).Times(0); |
| 359 | task_runner_->RunUntilIdle(); |
| 360 | ASSERT_TRUE(::testing::Mock::VerifyAndClearExpectations(cli_.get())); |
| 361 | |
| 362 | // Resolve the reply asynchronously in a deferred task. |
| 363 | task_runner_->PostTask([&moved_reply] { |
| 364 | std::unique_ptr<ReplyProto> reply_args(new ReplyProto()); |
| 365 | reply_args->set_data("bar"); |
| 366 | moved_reply.Resolve(AsyncResult<ProtoMessage>( |
| 367 | std::unique_ptr<ProtoMessage>(reply_args.release()))); |
| 368 | }); |
| 369 | |
| 370 | auto on_reply_received = task_runner_->CreateCheckpoint("on_reply_received"); |
| 371 | EXPECT_CALL(*cli_, OnInvokeMethodReply(_)) |
| 372 | .WillOnce( |
| 373 | Invoke([on_reply_received](const Frame::InvokeMethodReply& reply) { |
| 374 | ASSERT_TRUE(reply.success()); |
| 375 | ASSERT_FALSE(reply.has_more()); |
| 376 | ReplyProto reply_args; |
| 377 | reply_args.ParseFromString(reply.reply_proto()); |
| 378 | ASSERT_EQ("bar", reply_args.data()); |
| 379 | on_reply_received(); |
| 380 | })); |
| 381 | task_runner_->RunUntilCheckpoint("on_reply_received"); |
| 382 | } |
| 383 | |
| 384 | // TODO(primiano): add the tests below in next CLs. |
| 385 | // TEST(HostImplTest, ManyClients) {} |
| 386 | // TEST(HostImplTest, OverlappingRequstsOutOfOrder) {} |
| 387 | // TEST(HostImplTest, StreamingRequest) {} |
| 388 | |
| 389 | } // namespace |
| 390 | } // namespace ipc |
| 391 | } // namespace perfetto |