• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include <utility>
6 
7 #include "mojo/public/cpp/system/invitation.h"
8 #include "base/base_paths.h"
9 #include "base/bind.h"
10 #include "base/callback.h"
11 #include "base/command_line.h"
12 #include "base/logging.h"
13 #include "base/macros.h"
14 #include "base/optional.h"
15 #include "base/path_service.h"
16 #include "base/run_loop.h"
17 #include "base/strings/string_piece.h"
18 #include "base/test/bind_test_util.h"
19 #include "base/test/multiprocess_test.h"
20 #include "base/test/scoped_task_environment.h"
21 #include "base/test/test_timeouts.h"
22 #include "build/build_config.h"
23 #include "mojo/public/cpp/platform/named_platform_channel.h"
24 #include "mojo/public/cpp/platform/platform_channel.h"
25 #include "mojo/public/cpp/system/message_pipe.h"
26 #include "mojo/public/cpp/system/platform_handle.h"
27 #include "mojo/public/cpp/system/wait.h"
28 #include "testing/gtest/include/gtest/gtest.h"
29 #include "testing/multiprocess_func_list.h"
30 
31 namespace mojo {
32 namespace {
33 
34 enum class InvitationType {
35   kNormal,
36   kIsolated,
37 };
38 
39 enum class TransportType {
40   kChannel,
41   kChannelServer,
42 };
43 
44 // Switches and values to tell clients of parameterized test runs what mode they
45 // should be testing against.
46 const char kTransportTypeSwitch[] = "test-transport-type";
47 const char kTransportTypeChannel[] = "channel";
48 const char kTransportTypeChannelServer[] = "channel-server";
49 
50 class InvitationCppTest : public testing::Test,
51                           public testing::WithParamInterface<TransportType> {
52  public:
53   InvitationCppTest() = default;
54   ~InvitationCppTest() override = default;
55 
56  protected:
LaunchChildTestClient(const std::string & test_client_name,ScopedMessagePipeHandle * primordial_pipes,size_t num_primordial_pipes,InvitationType invitation_type,TransportType transport_type,const ProcessErrorCallback & error_callback={})57   void LaunchChildTestClient(const std::string& test_client_name,
58                              ScopedMessagePipeHandle* primordial_pipes,
59                              size_t num_primordial_pipes,
60                              InvitationType invitation_type,
61                              TransportType transport_type,
62                              const ProcessErrorCallback& error_callback = {}) {
63     base::CommandLine command_line(
64         base::GetMultiProcessTestChildBaseCommandLine());
65 
66     base::LaunchOptions launch_options;
67     base::Optional<PlatformChannel> channel;
68     PlatformChannelEndpoint channel_endpoint;
69     PlatformChannelServerEndpoint server_endpoint;
70     if (transport_type == TransportType::kChannel) {
71       command_line.AppendSwitchASCII(kTransportTypeSwitch,
72                                      kTransportTypeChannel);
73       channel.emplace();
74       channel->PrepareToPassRemoteEndpoint(&launch_options, &command_line);
75 #if defined(OS_WIN)
76       launch_options.start_hidden = true;
77 #endif
78       channel_endpoint = channel->TakeLocalEndpoint();
79     } else if (transport_type == TransportType::kChannelServer) {
80       command_line.AppendSwitchASCII(kTransportTypeSwitch,
81                                      kTransportTypeChannelServer);
82 #if defined(OS_FUCHSIA)
83       NOTREACHED() << "Named pipe support does not exist for Mojo on Fuchsia.";
84 #else
85       NamedPlatformChannel::Options named_channel_options;
86 #if !defined(OS_WIN)
87       CHECK(base::PathService::Get(base::DIR_TEMP,
88                                    &named_channel_options.socket_dir));
89 #endif
90       NamedPlatformChannel named_channel(named_channel_options);
91       named_channel.PassServerNameOnCommandLine(&command_line);
92       server_endpoint = named_channel.TakeServerEndpoint();
93 #endif
94     }
95 
96     child_process_ = base::SpawnMultiProcessTestChild(
97         test_client_name, command_line, launch_options);
98     if (channel)
99       channel->RemoteProcessLaunchAttempted();
100 
101     OutgoingInvitation invitation;
102     if (invitation_type == InvitationType::kNormal) {
103       for (uint64_t name = 0; name < num_primordial_pipes; ++name)
104         primordial_pipes[name] = invitation.AttachMessagePipe(name);
105     }
106 
107     if (transport_type == TransportType::kChannel) {
108       DCHECK(channel_endpoint.is_valid());
109       if (invitation_type == InvitationType::kNormal) {
110         OutgoingInvitation::Send(std::move(invitation), child_process_.Handle(),
111                                  std::move(channel_endpoint), error_callback);
112       } else {
113         DCHECK(primordial_pipes);
114         DCHECK_EQ(num_primordial_pipes, 1u);
115         primordial_pipes[0] =
116             OutgoingInvitation::SendIsolated(std::move(channel_endpoint));
117       }
118     } else if (transport_type == TransportType::kChannelServer) {
119       DCHECK(server_endpoint.is_valid());
120       if (invitation_type == InvitationType::kNormal) {
121         OutgoingInvitation::Send(std::move(invitation), child_process_.Handle(),
122                                  std::move(server_endpoint), error_callback);
123       } else {
124         DCHECK(primordial_pipes);
125         DCHECK_EQ(num_primordial_pipes, 1u);
126         primordial_pipes[0] =
127             OutgoingInvitation::SendIsolated(std::move(server_endpoint));
128       }
129     }
130   }
131 
WaitForChildExit()132   void WaitForChildExit() {
133     int wait_result = -1;
134     base::WaitForMultiprocessTestChildExit(
135         child_process_, TestTimeouts::action_timeout(), &wait_result);
136     child_process_.Close();
137     EXPECT_EQ(0, wait_result);
138   }
139 
WriteMessage(const ScopedMessagePipeHandle & pipe,base::StringPiece message)140   static void WriteMessage(const ScopedMessagePipeHandle& pipe,
141                            base::StringPiece message) {
142     CHECK_EQ(MOJO_RESULT_OK,
143              WriteMessageRaw(pipe.get(), message.data(), message.size(),
144                              nullptr, 0, MOJO_WRITE_MESSAGE_FLAG_NONE));
145   }
146 
ReadMessage(const ScopedMessagePipeHandle & pipe)147   static std::string ReadMessage(const ScopedMessagePipeHandle& pipe) {
148     CHECK_EQ(MOJO_RESULT_OK, Wait(pipe.get(), MOJO_HANDLE_SIGNAL_READABLE));
149 
150     std::vector<uint8_t> payload;
151     std::vector<ScopedHandle> handles;
152     CHECK_EQ(MOJO_RESULT_OK, ReadMessageRaw(pipe.get(), &payload, &handles,
153                                             MOJO_READ_MESSAGE_FLAG_NONE));
154     return std::string(payload.begin(), payload.end());
155   }
156 
157  private:
158   base::test::ScopedTaskEnvironment task_environment_;
159   base::Process child_process_;
160 
161   DISALLOW_COPY_AND_ASSIGN(InvitationCppTest);
162 };
163 
164 class TestClientBase : public InvitationCppTest {
165  public:
RecoverEndpointFromCommandLine()166   static PlatformChannelEndpoint RecoverEndpointFromCommandLine() {
167     const auto& command_line = *base::CommandLine::ForCurrentProcess();
168     std::string transport_type_string =
169         command_line.GetSwitchValueASCII(kTransportTypeSwitch);
170     CHECK(!transport_type_string.empty());
171     if (transport_type_string == kTransportTypeChannel) {
172       return PlatformChannel::RecoverPassedEndpointFromCommandLine(
173           command_line);
174     } else {
175       return NamedPlatformChannel::ConnectToServer(command_line);
176     }
177   }
178 
AcceptInvitation()179   static IncomingInvitation AcceptInvitation() {
180     return IncomingInvitation::Accept(RecoverEndpointFromCommandLine());
181   }
182 
AcceptIsolatedInvitation()183   static ScopedMessagePipeHandle AcceptIsolatedInvitation() {
184     return IncomingInvitation::AcceptIsolated(RecoverEndpointFromCommandLine());
185   }
186 
187  private:
188   DISALLOW_COPY_AND_ASSIGN(TestClientBase);
189 };
190 
191 #define DEFINE_TEST_CLIENT(name)             \
192   class name##Impl : public TestClientBase { \
193    public:                                   \
194     static void Run();                       \
195   };                                         \
196   MULTIPROCESS_TEST_MAIN(name) {             \
197     name##Impl::Run();                       \
198     return 0;                                \
199   }                                          \
200   void name##Impl::Run()
201 
202 const char kTestMessage1[] = "hello";
203 const char kTestMessage2[] = "hello";
204 
TEST_P(InvitationCppTest,Send)205 TEST_P(InvitationCppTest, Send) {
206   ScopedMessagePipeHandle pipe;
207   LaunchChildTestClient("CppSendClient", &pipe, 1, InvitationType::kNormal,
208                         GetParam());
209   WriteMessage(pipe, kTestMessage1);
210   WaitForChildExit();
211 }
212 
DEFINE_TEST_CLIENT(CppSendClient)213 DEFINE_TEST_CLIENT(CppSendClient) {
214   auto invitation = AcceptInvitation();
215   auto pipe = invitation.ExtractMessagePipe(0);
216   CHECK_EQ(kTestMessage1, ReadMessage(pipe));
217 }
218 
TEST_P(InvitationCppTest,SendIsolated)219 TEST_P(InvitationCppTest, SendIsolated) {
220   ScopedMessagePipeHandle pipe;
221   LaunchChildTestClient("CppSendIsolatedClient", &pipe, 1,
222                         InvitationType::kIsolated, GetParam());
223   WriteMessage(pipe, kTestMessage1);
224   WaitForChildExit();
225 }
226 
DEFINE_TEST_CLIENT(CppSendIsolatedClient)227 DEFINE_TEST_CLIENT(CppSendIsolatedClient) {
228   auto pipe = AcceptIsolatedInvitation();
229   CHECK_EQ(kTestMessage1, ReadMessage(pipe));
230 }
231 
TEST_P(InvitationCppTest,SendWithMultiplePipes)232 TEST_P(InvitationCppTest, SendWithMultiplePipes) {
233   ScopedMessagePipeHandle pipes[2];
234   LaunchChildTestClient("CppSendWithMultiplePipesClient", pipes, 2,
235                         InvitationType::kNormal, GetParam());
236   WriteMessage(pipes[0], kTestMessage1);
237   WriteMessage(pipes[1], kTestMessage2);
238   WaitForChildExit();
239 }
240 
DEFINE_TEST_CLIENT(CppSendWithMultiplePipesClient)241 DEFINE_TEST_CLIENT(CppSendWithMultiplePipesClient) {
242   auto invitation = AcceptInvitation();
243   auto pipe0 = invitation.ExtractMessagePipe(0);
244   auto pipe1 = invitation.ExtractMessagePipe(1);
245   CHECK_EQ(kTestMessage1, ReadMessage(pipe0));
246   CHECK_EQ(kTestMessage2, ReadMessage(pipe1));
247 }
248 
TEST(InvitationCppTest_NoParam,SendIsolatedInvitationWithDuplicateName)249 TEST(InvitationCppTest_NoParam, SendIsolatedInvitationWithDuplicateName) {
250   base::test::ScopedTaskEnvironment task_environment;
251   PlatformChannel channel1;
252   PlatformChannel channel2;
253   const char kConnectionName[] = "foo";
254   ScopedMessagePipeHandle pipe0 = OutgoingInvitation::SendIsolated(
255       channel1.TakeLocalEndpoint(), kConnectionName);
256   ScopedMessagePipeHandle pipe1 = OutgoingInvitation::SendIsolated(
257       channel2.TakeLocalEndpoint(), kConnectionName);
258   Wait(pipe0.get(), MOJO_HANDLE_SIGNAL_PEER_CLOSED);
259 }
260 
261 const char kErrorMessage[] = "ur bad :{{";
262 const char kDisconnectMessage[] = "go away plz";
263 
TEST_P(InvitationCppTest,ProcessErrors)264 TEST_P(InvitationCppTest, ProcessErrors) {
265   ProcessErrorCallback actual_error_callback;
266 
267   ScopedMessagePipeHandle pipe;
268   LaunchChildTestClient(
269       "CppProcessErrorsClient", &pipe, 1, InvitationType::kNormal, GetParam(),
270       base::BindLambdaForTesting([&](const std::string& error_message) {
271         ASSERT_TRUE(actual_error_callback);
272         actual_error_callback.Run(error_message);
273       }));
274 
275   MojoMessageHandle message;
276   Wait(pipe.get(), MOJO_HANDLE_SIGNAL_READABLE);
277   EXPECT_EQ(MOJO_RESULT_OK,
278             MojoReadMessage(pipe.get().value(), nullptr, &message));
279 
280   // Report the message as bad and expect to be notified through the process
281   // error callback.
282   base::RunLoop error_loop;
283   actual_error_callback =
284       base::BindLambdaForTesting([&](const std::string& error_message) {
285         EXPECT_NE(error_message.find(kErrorMessage), std::string::npos);
286         error_loop.Quit();
287       });
288   EXPECT_EQ(MOJO_RESULT_OK,
289             MojoNotifyBadMessage(message, kErrorMessage, sizeof(kErrorMessage),
290                                  nullptr));
291   error_loop.Run();
292   EXPECT_EQ(MOJO_RESULT_OK, MojoDestroyMessage(message));
293 
294   // TODO(https://crbug.com/846833): Once we can rework the C++ invitation API
295   // to also notify on disconnect, this test should cover that too. For now we
296   // just tell the process to exit and wait for it to do.
297   WriteMessage(pipe, kDisconnectMessage);
298   WaitForChildExit();
299 }
300 
DEFINE_TEST_CLIENT(CppProcessErrorsClient)301 DEFINE_TEST_CLIENT(CppProcessErrorsClient) {
302   auto invitation = AcceptInvitation();
303   auto pipe = invitation.ExtractMessagePipe(0);
304   WriteMessage(pipe, "ignored");
305   EXPECT_EQ(kDisconnectMessage, ReadMessage(pipe));
306 }
307 
308 INSTANTIATE_TEST_CASE_P(,
309                         InvitationCppTest,
310                         testing::Values(TransportType::kChannel
311 #if !defined(OS_FUCHSIA)
312                                         ,
313                                         TransportType::kChannelServer
314 #endif
315                                         ));
316 
317 }  // namespace
318 }  // namespace mojo
319