Elim cr-checkbox
[chromium-blink-merge.git] / chrome / browser / extensions / api / messaging / native_message_process_host_unittest.cc
blob4f777fd055495178068018f19a825e5415d7c855
1 // Copyright (c) 2012 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.
5 #include "chrome/browser/extensions/api/messaging/native_message_process_host.h"
7 #include "base/bind.h"
8 #include "base/files/file.h"
9 #include "base/files/file_path.h"
10 #include "base/files/file_util.h"
11 #include "base/files/scoped_file.h"
12 #include "base/files/scoped_temp_dir.h"
13 #include "base/json/json_reader.h"
14 #include "base/macros.h"
15 #include "base/memory/scoped_ptr.h"
16 #include "base/message_loop/message_loop.h"
17 #include "base/rand_util.h"
18 #include "base/run_loop.h"
19 #include "base/strings/stringprintf.h"
20 #include "base/test/test_timeouts.h"
21 #include "base/threading/platform_thread.h"
22 #include "base/threading/sequenced_worker_pool.h"
23 #include "base/time/time.h"
24 #include "chrome/browser/extensions/api/messaging/native_messaging_test_util.h"
25 #include "chrome/browser/extensions/api/messaging/native_process_launcher.h"
26 #include "chrome/common/extensions/features/feature_channel.h"
27 #include "components/version_info/version_info.h"
28 #include "content/public/browser/browser_thread.h"
29 #include "content/public/test/test_browser_thread_bundle.h"
30 #include "extensions/common/extension.h"
31 #include "testing/gtest/include/gtest/gtest.h"
33 #if defined(OS_WIN)
34 #include <windows.h>
35 #include "base/win/scoped_handle.h"
36 #else
37 #include <unistd.h>
38 #endif
40 using content::BrowserThread;
42 namespace {
44 const char kTestMessage[] = "{\"text\": \"Hello.\"}";
46 } // namespace
48 namespace extensions {
50 class FakeLauncher : public NativeProcessLauncher {
51 public:
52 FakeLauncher(base::File read_file, base::File write_file)
53 : read_file_(read_file.Pass()),
54 write_file_(write_file.Pass()) {
57 static scoped_ptr<NativeProcessLauncher> Create(base::FilePath read_file,
58 base::FilePath write_file) {
59 int read_flags = base::File::FLAG_OPEN | base::File::FLAG_READ;
60 int write_flags = base::File::FLAG_CREATE | base::File::FLAG_WRITE;
61 #if !defined(OS_POSIX)
62 read_flags |= base::File::FLAG_ASYNC;
63 write_flags |= base::File::FLAG_ASYNC;
64 #endif
65 return scoped_ptr<NativeProcessLauncher>(new FakeLauncher(
66 base::File(read_file, read_flags),
67 base::File(write_file, write_flags)));
70 static scoped_ptr<NativeProcessLauncher> CreateWithPipeInput(
71 base::File read_pipe,
72 base::FilePath write_file) {
73 int write_flags = base::File::FLAG_CREATE | base::File::FLAG_WRITE;
74 #if !defined(OS_POSIX)
75 write_flags |= base::File::FLAG_ASYNC;
76 #endif
78 return scoped_ptr<NativeProcessLauncher>(new FakeLauncher(
79 read_pipe.Pass(),
80 base::File(write_file, write_flags)));
83 void Launch(const GURL& origin,
84 const std::string& native_host_name,
85 const LaunchedCallback& callback) const override {
86 callback.Run(NativeProcessLauncher::RESULT_SUCCESS,
87 base::Process(), read_file_.Pass(), write_file_.Pass());
90 private:
91 mutable base::File read_file_;
92 mutable base::File write_file_;
95 class NativeMessagingTest : public ::testing::Test,
96 public NativeMessageHost::Client,
97 public base::SupportsWeakPtr<NativeMessagingTest> {
98 protected:
99 NativeMessagingTest()
100 : current_channel_(version_info::Channel::DEV),
101 thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP),
102 channel_closed_(false) {}
104 void SetUp() override { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); }
106 void TearDown() override {
107 if (native_message_host_.get()) {
108 BrowserThread::DeleteSoon(
109 BrowserThread::IO, FROM_HERE, native_message_host_.release());
111 base::RunLoop().RunUntilIdle();
114 void PostMessageFromNativeHost(const std::string& message) override {
115 last_message_ = message;
117 // Parse the message.
118 scoped_ptr<base::Value> parsed = base::JSONReader::Read(message);
119 base::DictionaryValue* dict_value;
120 if (parsed.get() && parsed->GetAsDictionary(&dict_value)) {
121 ignore_result(parsed.release());
122 last_message_parsed_.reset(dict_value);
123 } else {
124 LOG(ERROR) << "Failed to parse " << message;
125 last_message_parsed_.reset();
128 if (run_loop_)
129 run_loop_->Quit();
132 void CloseChannel(const std::string& error_message) override {
133 channel_closed_ = true;
134 if (run_loop_)
135 run_loop_->Quit();
138 protected:
139 std::string FormatMessage(const std::string& message) {
140 uint32_t length = message.length();
141 return std::string(reinterpret_cast<char*>(&length), 4).append(message);
144 base::FilePath CreateTempFileWithMessage(const std::string& message) {
145 base::FilePath filename;
146 if (!base::CreateTemporaryFileInDir(temp_dir_.path(), &filename))
147 return base::FilePath();
149 std::string message_with_header = FormatMessage(message);
150 int bytes_written = base::WriteFile(
151 filename, message_with_header.data(), message_with_header.size());
152 if (bytes_written < 0 ||
153 (message_with_header.size() != static_cast<size_t>(bytes_written))) {
154 return base::FilePath();
156 return filename;
159 base::ScopedTempDir temp_dir_;
160 // Force the channel to be dev.
161 ScopedCurrentChannel current_channel_;
162 scoped_ptr<NativeMessageHost> native_message_host_;
163 scoped_ptr<base::RunLoop> run_loop_;
164 content::TestBrowserThreadBundle thread_bundle_;
165 std::string last_message_;
166 scoped_ptr<base::DictionaryValue> last_message_parsed_;
167 bool channel_closed_;
170 // Read a single message from a local file.
171 TEST_F(NativeMessagingTest, SingleSendMessageRead) {
172 base::FilePath temp_output_file = temp_dir_.path().AppendASCII("output");
173 base::FilePath temp_input_file = CreateTempFileWithMessage(kTestMessage);
174 ASSERT_FALSE(temp_input_file.empty());
176 scoped_ptr<NativeProcessLauncher> launcher =
177 FakeLauncher::Create(temp_input_file, temp_output_file).Pass();
178 native_message_host_ = NativeMessageProcessHost::CreateWithLauncher(
179 ScopedTestNativeMessagingHost::kExtensionId,
180 "empty_app.py",
181 launcher.Pass());
182 native_message_host_->Start(this);
183 ASSERT_TRUE(native_message_host_.get());
184 run_loop_.reset(new base::RunLoop());
185 run_loop_->RunUntilIdle();
187 if (last_message_.empty()) {
188 run_loop_.reset(new base::RunLoop());
189 scoped_ptr<NativeMessageProcessHost> native_message_process_host_(
190 static_cast<NativeMessageProcessHost*>(native_message_host_.release()));
191 native_message_process_host_->ReadNowForTesting();
192 run_loop_->Run();
194 EXPECT_EQ(kTestMessage, last_message_);
197 // Tests sending a single message. The message should get written to
198 // |temp_file| and should match the contents of single_message_request.msg.
199 TEST_F(NativeMessagingTest, SingleSendMessageWrite) {
200 base::FilePath temp_output_file = temp_dir_.path().AppendASCII("output");
202 base::File read_file;
203 #if defined(OS_WIN)
204 base::string16 pipe_name = base::StringPrintf(
205 L"\\\\.\\pipe\\chrome.nativeMessaging.out.%llx", base::RandUint64());
206 base::File write_handle = base::File::CreateForAsyncHandle(
207 CreateNamedPipeW(pipe_name.c_str(),
208 PIPE_ACCESS_OUTBOUND | FILE_FLAG_OVERLAPPED |
209 FILE_FLAG_FIRST_PIPE_INSTANCE,
210 PIPE_TYPE_BYTE, 1, 0, 0, 5000, NULL));
211 ASSERT_TRUE(write_handle.IsValid());
212 base::File read_handle = base::File::CreateForAsyncHandle(
213 CreateFileW(pipe_name.c_str(), GENERIC_READ, 0, NULL, OPEN_EXISTING,
214 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL));
215 ASSERT_TRUE(read_handle.IsValid());
217 read_file = read_handle.Pass();
218 #else // defined(OS_WIN)
219 base::PlatformFile pipe_handles[2];
220 ASSERT_EQ(0, pipe(pipe_handles));
221 read_file = base::File(pipe_handles[0]);
222 base::File write_file(pipe_handles[1]);
223 #endif // !defined(OS_WIN)
225 scoped_ptr<NativeProcessLauncher> launcher =
226 FakeLauncher::CreateWithPipeInput(read_file.Pass(),
227 temp_output_file).Pass();
228 native_message_host_ = NativeMessageProcessHost::CreateWithLauncher(
229 ScopedTestNativeMessagingHost::kExtensionId,
230 "empty_app.py",
231 launcher.Pass());
232 native_message_host_->Start(this);
233 ASSERT_TRUE(native_message_host_.get());
234 base::RunLoop().RunUntilIdle();
236 native_message_host_->OnMessage(kTestMessage);
237 base::RunLoop().RunUntilIdle();
239 std::string output;
240 base::TimeTicks start_time = base::TimeTicks::Now();
241 while (base::TimeTicks::Now() - start_time < TestTimeouts::action_timeout()) {
242 ASSERT_TRUE(base::ReadFileToString(temp_output_file, &output));
243 if (!output.empty())
244 break;
245 base::PlatformThread::YieldCurrentThread();
248 EXPECT_EQ(FormatMessage(kTestMessage), output);
251 // Test send message with a real client. The client just echo's back the text
252 // it received.
253 TEST_F(NativeMessagingTest, EchoConnect) {
254 ScopedTestNativeMessagingHost test_host;
255 ASSERT_NO_FATAL_FAILURE(test_host.RegisterTestHost(false));
256 std::string error_message;
257 native_message_host_ = NativeMessageProcessHost::Create(
258 NULL,
259 ScopedTestNativeMessagingHost::kExtensionId,
260 ScopedTestNativeMessagingHost::kHostName,
261 false,
262 &error_message);
263 native_message_host_->Start(this);
264 ASSERT_TRUE(native_message_host_.get());
266 native_message_host_->OnMessage("{\"text\": \"Hello.\"}");
267 run_loop_.reset(new base::RunLoop());
268 run_loop_->Run();
269 ASSERT_FALSE(last_message_.empty());
270 ASSERT_TRUE(last_message_parsed_);
272 std::string expected_url = std::string("chrome-extension://") +
273 ScopedTestNativeMessagingHost::kExtensionId + "/";
274 int id;
275 EXPECT_TRUE(last_message_parsed_->GetInteger("id", &id));
276 EXPECT_EQ(1, id);
277 std::string text;
278 EXPECT_TRUE(last_message_parsed_->GetString("echo.text", &text));
279 EXPECT_EQ("Hello.", text);
280 std::string url;
281 EXPECT_TRUE(last_message_parsed_->GetString("caller_url", &url));
282 EXPECT_EQ(expected_url, url);
284 native_message_host_->OnMessage("{\"foo\": \"bar\"}");
285 run_loop_.reset(new base::RunLoop());
286 run_loop_->Run();
287 EXPECT_TRUE(last_message_parsed_->GetInteger("id", &id));
288 EXPECT_EQ(2, id);
289 EXPECT_TRUE(last_message_parsed_->GetString("echo.foo", &text));
290 EXPECT_EQ("bar", text);
291 EXPECT_TRUE(last_message_parsed_->GetString("caller_url", &url));
292 EXPECT_EQ(expected_url, url);
295 TEST_F(NativeMessagingTest, UserLevel) {
296 ScopedTestNativeMessagingHost test_host;
297 ASSERT_NO_FATAL_FAILURE(test_host.RegisterTestHost(true));
299 std::string error_message;
300 native_message_host_ = NativeMessageProcessHost::Create(
301 NULL,
302 ScopedTestNativeMessagingHost::kExtensionId,
303 ScopedTestNativeMessagingHost::kHostName,
304 true,
305 &error_message);
306 native_message_host_->Start(this);
307 ASSERT_TRUE(native_message_host_.get());
309 native_message_host_->OnMessage("{\"text\": \"Hello.\"}");
310 run_loop_.reset(new base::RunLoop());
311 run_loop_->Run();
312 ASSERT_FALSE(last_message_.empty());
313 ASSERT_TRUE(last_message_parsed_);
316 TEST_F(NativeMessagingTest, DisallowUserLevel) {
317 ScopedTestNativeMessagingHost test_host;
318 ASSERT_NO_FATAL_FAILURE(test_host.RegisterTestHost(true));
320 std::string error_message;
321 native_message_host_ = NativeMessageProcessHost::Create(
322 NULL,
323 ScopedTestNativeMessagingHost::kExtensionId,
324 ScopedTestNativeMessagingHost::kHostName,
325 false,
326 &error_message);
327 native_message_host_->Start(this);
328 ASSERT_TRUE(native_message_host_.get());
329 run_loop_.reset(new base::RunLoop());
330 run_loop_->Run();
332 // The host should fail to start.
333 ASSERT_TRUE(channel_closed_);
336 } // namespace extensions