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