native_message_process_host_unittest.cc revision a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7
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.
4
5#include "base/bind.h"
6#include "base/command_line.h"
7#include "base/file_util.h"
8#include "base/files/file_path.h"
9#include "base/files/scoped_temp_dir.h"
10#include "base/json/json_reader.h"
11#include "base/memory/scoped_ptr.h"
12#include "base/memory/weak_ptr.h"
13#include "base/message_loop/message_loop.h"
14#include "base/path_service.h"
15#include "base/platform_file.h"
16#include "base/rand_util.h"
17#include "base/run_loop.h"
18#include "base/strings/stringprintf.h"
19#include "base/test/test_timeouts.h"
20#include "base/threading/platform_thread.h"
21#include "base/threading/sequenced_worker_pool.h"
22#include "base/time/time.h"
23#include "chrome/browser/extensions/api/messaging/native_message_process_host.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/chrome_paths.h"
27#include "chrome/common/chrome_switches.h"
28#include "chrome/common/chrome_version_info.h"
29#include "chrome/common/extensions/features/feature_channel.h"
30#include "content/public/browser/browser_thread.h"
31#include "content/public/test/test_browser_thread_bundle.h"
32#include "extensions/common/extension.h"
33#include "testing/gtest/include/gtest/gtest.h"
34
35#if defined(OS_WIN)
36#include <windows.h>
37#include "base/win/scoped_handle.h"
38#else
39#include <unistd.h>
40#endif
41
42using content::BrowserThread;
43
44namespace {
45
46const char kTestMessage[] = "{\"text\": \"Hello.\"}";
47
48base::FilePath GetTestDir() {
49  base::FilePath test_dir;
50  PathService::Get(chrome::DIR_TEST_DATA, &test_dir);
51  test_dir = test_dir.AppendASCII("native_messaging");
52  return test_dir;
53}
54
55}  // namespace
56
57namespace extensions {
58
59class FakeLauncher : public NativeProcessLauncher {
60 public:
61  FakeLauncher(base::PlatformFile read_file, base::PlatformFile write_file)
62    : read_file_(read_file),
63      write_file_(write_file) {
64  }
65
66  static scoped_ptr<NativeProcessLauncher> Create(base::FilePath read_file,
67                                         base::FilePath write_file) {
68    int read_flags = base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ |
69                     base::PLATFORM_FILE_ASYNC;
70    int write_flags = base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE |
71                      base::PLATFORM_FILE_ASYNC;
72    return scoped_ptr<NativeProcessLauncher>(new FakeLauncher(
73        base::CreatePlatformFile(read_file, read_flags, NULL, NULL),
74        base::CreatePlatformFile(write_file, write_flags, NULL, NULL)));
75  }
76
77  static scoped_ptr<NativeProcessLauncher> CreateWithPipeInput(
78      base::PlatformFile read_pipe,
79      base::FilePath write_file) {
80    int write_flags = base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE |
81                      base::PLATFORM_FILE_ASYNC;
82    return scoped_ptr<NativeProcessLauncher>(new FakeLauncher(
83        read_pipe,
84        base::CreatePlatformFile(write_file, write_flags, NULL, NULL)));
85  }
86
87  virtual void Launch(const GURL& origin,
88                      const std::string& native_host_name,
89                      LaunchedCallback callback) const OVERRIDE {
90    callback.Run(NativeProcessLauncher::RESULT_SUCCESS,
91                 base::kNullProcessHandle, read_file_, write_file_);
92  }
93
94 private:
95  base::PlatformFile read_file_;
96  base::PlatformFile write_file_;
97};
98
99class NativeMessagingTest : public ::testing::Test,
100                            public NativeMessageProcessHost::Client,
101                            public base::SupportsWeakPtr<NativeMessagingTest> {
102 protected:
103  NativeMessagingTest()
104      : current_channel_(chrome::VersionInfo::CHANNEL_DEV),
105        thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP) {}
106
107  virtual void SetUp() OVERRIDE {
108    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
109    // Change the user data dir so native apps will be looked for in the test
110    // directory.
111    ASSERT_TRUE(PathService::Get(chrome::DIR_USER_DATA, &user_data_dir_));
112    ASSERT_TRUE(PathService::Override(chrome::DIR_USER_DATA, GetTestDir()));
113  }
114
115  virtual void TearDown() OVERRIDE {
116    // Change the user data dir back for other tests.
117    ASSERT_TRUE(PathService::Override(chrome::DIR_USER_DATA, user_data_dir_));
118    if (native_message_process_host_.get()) {
119      BrowserThread::DeleteSoon(BrowserThread::IO, FROM_HERE,
120                                native_message_process_host_.release());
121    }
122    base::RunLoop().RunUntilIdle();
123  }
124
125  virtual void PostMessageFromNativeProcess(
126      int port_id,
127      const std::string& message) OVERRIDE  {
128    last_message_ = message;
129
130    // Parse the message.
131    base::Value* parsed = base::JSONReader::Read(message);
132    base::DictionaryValue* dict_value;
133    if (parsed && parsed->GetAsDictionary(&dict_value)) {
134      last_message_parsed_.reset(dict_value);
135    } else {
136      LOG(ERROR) << "Failed to parse " << message;
137      last_message_parsed_.reset();
138      delete parsed;
139    }
140
141    if (read_message_run_loop_)
142      read_message_run_loop_->Quit();
143  }
144
145  virtual void CloseChannel(int port_id,
146                            const std::string& error_message) OVERRIDE {
147  }
148
149 protected:
150  std::string FormatMessage(const std::string& message) {
151    Pickle pickle;
152    pickle.WriteString(message);
153    return std::string(const_cast<const Pickle*>(&pickle)->payload(),
154                       pickle.payload_size());
155  }
156
157  base::FilePath CreateTempFileWithMessage(const std::string& message) {
158    base::FilePath filename = temp_dir_.path().AppendASCII("input");
159    base::CreateTemporaryFile(&filename);
160    std::string message_with_header = FormatMessage(message);
161    EXPECT_TRUE(file_util::WriteFile(
162        filename, message_with_header.data(), message_with_header.size()));
163    return filename;
164  }
165
166  // Force the channel to be dev.
167  base::ScopedTempDir temp_dir_;
168  ScopedCurrentChannel current_channel_;
169  scoped_ptr<NativeMessageProcessHost> native_message_process_host_;
170  base::FilePath user_data_dir_;
171  scoped_ptr<base::RunLoop> read_message_run_loop_;
172  content::TestBrowserThreadBundle thread_bundle_;
173  std::string last_message_;
174  scoped_ptr<base::DictionaryValue> last_message_parsed_;
175};
176
177// Read a single message from a local file.
178TEST_F(NativeMessagingTest, SingleSendMessageRead) {
179  base::FilePath temp_output_file = temp_dir_.path().AppendASCII("output");
180  base::FilePath temp_input_file = CreateTempFileWithMessage(kTestMessage);
181
182  scoped_ptr<NativeProcessLauncher> launcher =
183      FakeLauncher::Create(temp_input_file, temp_output_file).Pass();
184  native_message_process_host_ = NativeMessageProcessHost::CreateWithLauncher(
185      AsWeakPtr(), kTestNativeMessagingExtensionId, "empty_app.py",
186      0, launcher.Pass());
187  ASSERT_TRUE(native_message_process_host_.get());
188  read_message_run_loop_.reset(new base::RunLoop());
189  read_message_run_loop_->RunUntilIdle();
190
191  if (last_message_.empty()) {
192    read_message_run_loop_.reset(new base::RunLoop());
193    native_message_process_host_->ReadNowForTesting();
194    read_message_run_loop_->Run();
195  }
196  EXPECT_EQ(kTestMessage, last_message_);
197}
198
199// Tests sending a single message. The message should get written to
200// |temp_file| and should match the contents of single_message_request.msg.
201TEST_F(NativeMessagingTest, SingleSendMessageWrite) {
202  base::FilePath temp_output_file = temp_dir_.path().AppendASCII("output");
203
204  base::PlatformFile read_file;
205#if defined(OS_WIN)
206  base::string16 pipe_name = base::StringPrintf(
207      L"\\\\.\\pipe\\chrome.nativeMessaging.out.%llx", base::RandUint64());
208  base::win::ScopedHandle write_handle(
209      CreateNamedPipeW(pipe_name.c_str(),
210                       PIPE_ACCESS_OUTBOUND | FILE_FLAG_OVERLAPPED |
211                           FILE_FLAG_FIRST_PIPE_INSTANCE,
212                       PIPE_TYPE_BYTE, 1, 0, 0, 5000, NULL));
213  ASSERT_TRUE(write_handle);
214  base::win::ScopedHandle read_handle(
215      CreateFileW(pipe_name.c_str(), GENERIC_READ, 0, NULL, OPEN_EXISTING,
216                  FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL));
217  ASSERT_TRUE(read_handle);
218
219  read_file = read_handle.Get();
220#else  // defined(OS_WIN)
221  base::PlatformFile pipe_handles[2];
222  ASSERT_EQ(0, pipe(pipe_handles));
223  file_util::ScopedFD read_fd(pipe_handles);
224  file_util::ScopedFD write_fd(pipe_handles + 1);
225
226  read_file = pipe_handles[0];
227#endif  // !defined(OS_WIN)
228
229  scoped_ptr<NativeProcessLauncher> launcher =
230      FakeLauncher::CreateWithPipeInput(read_file, temp_output_file).Pass();
231  native_message_process_host_ = NativeMessageProcessHost::CreateWithLauncher(
232      AsWeakPtr(), kTestNativeMessagingExtensionId, "empty_app.py",
233      0, launcher.Pass());
234  ASSERT_TRUE(native_message_process_host_.get());
235  base::RunLoop().RunUntilIdle();
236
237  native_message_process_host_->Send(kTestMessage);
238  base::RunLoop().RunUntilIdle();
239
240  std::string output;
241  base::TimeTicks start_time = base::TimeTicks::Now();
242  while (base::TimeTicks::Now() - start_time < TestTimeouts::action_timeout()) {
243    ASSERT_TRUE(base::ReadFileToString(temp_output_file, &output));
244    if (!output.empty())
245      break;
246    base::PlatformThread::YieldCurrentThread();
247  }
248
249  EXPECT_EQ(FormatMessage(kTestMessage), output);
250}
251
252// Test send message with a real client. The client just echo's back the text
253// it received.
254TEST_F(NativeMessagingTest, EchoConnect) {
255  base::FilePath manifest_path = temp_dir_.path().AppendASCII(
256      std::string(kTestNativeMessagingHostName) + ".json");
257  ASSERT_NO_FATAL_FAILURE(CreateTestNativeHostManifest(manifest_path));
258
259  std::string hosts_option = base::StringPrintf(
260      "%s=%s", extensions::kTestNativeMessagingHostName,
261      manifest_path.AsUTF8Unsafe().c_str());
262  CommandLine::ForCurrentProcess()->AppendSwitchASCII(
263      switches::kNativeMessagingHosts, hosts_option);
264
265  native_message_process_host_ = NativeMessageProcessHost::Create(
266      gfx::NativeView(), AsWeakPtr(), kTestNativeMessagingExtensionId,
267      kTestNativeMessagingHostName, 0);
268  ASSERT_TRUE(native_message_process_host_.get());
269
270  native_message_process_host_->Send("{\"text\": \"Hello.\"}");
271  read_message_run_loop_.reset(new base::RunLoop());
272  read_message_run_loop_->Run();
273  ASSERT_FALSE(last_message_.empty());
274  ASSERT_TRUE(last_message_parsed_);
275
276  std::string expected_url = std::string("chrome-extension://") +
277      kTestNativeMessagingExtensionId + "/";
278  int id;
279  EXPECT_TRUE(last_message_parsed_->GetInteger("id", &id));
280  EXPECT_EQ(1, id);
281  std::string text;
282  EXPECT_TRUE(last_message_parsed_->GetString("echo.text", &text));
283  EXPECT_EQ("Hello.", text);
284  std::string url;
285  EXPECT_TRUE(last_message_parsed_->GetString("caller_url", &url));
286  EXPECT_EQ(expected_url, url);
287
288
289  native_message_process_host_->Send("{\"foo\": \"bar\"}");
290  read_message_run_loop_.reset(new base::RunLoop());
291  read_message_run_loop_->Run();
292  EXPECT_TRUE(last_message_parsed_->GetInteger("id", &id));
293  EXPECT_EQ(2, id);
294  EXPECT_TRUE(last_message_parsed_->GetString("echo.foo", &text));
295  EXPECT_EQ("bar", text);
296  EXPECT_TRUE(last_message_parsed_->GetString("caller_url", &url));
297  EXPECT_EQ(expected_url, url);
298}
299
300}  // namespace extensions
301