native_message_process_host_unittest.cc revision 868fa2fe829687343ffae624259930155e16dbd8
16eabb9b770a7c60cb92aa2e22f360754f32f39f8Wu-cheng Li// Copyright (c) 2012 The Chromium Authors. All rights reserved.
26eabb9b770a7c60cb92aa2e22f360754f32f39f8Wu-cheng Li// Use of this source code is governed by a BSD-style license that can be
36eabb9b770a7c60cb92aa2e22f360754f32f39f8Wu-cheng Li// found in the LICENSE file.
46eabb9b770a7c60cb92aa2e22f360754f32f39f8Wu-cheng Li
56eabb9b770a7c60cb92aa2e22f360754f32f39f8Wu-cheng Li#include "base/bind.h"
66eabb9b770a7c60cb92aa2e22f360754f32f39f8Wu-cheng Li#include "base/command_line.h"
76eabb9b770a7c60cb92aa2e22f360754f32f39f8Wu-cheng Li#include "base/file_util.h"
86eabb9b770a7c60cb92aa2e22f360754f32f39f8Wu-cheng Li#include "base/files/file_path.h"
96eabb9b770a7c60cb92aa2e22f360754f32f39f8Wu-cheng Li#include "base/files/scoped_temp_dir.h"
106eabb9b770a7c60cb92aa2e22f360754f32f39f8Wu-cheng Li#include "base/json/json_reader.h"
116eabb9b770a7c60cb92aa2e22f360754f32f39f8Wu-cheng Li#include "base/memory/scoped_ptr.h"
126eabb9b770a7c60cb92aa2e22f360754f32f39f8Wu-cheng Li#include "base/memory/weak_ptr.h"
136eabb9b770a7c60cb92aa2e22f360754f32f39f8Wu-cheng Li#include "base/message_loop.h"
146eabb9b770a7c60cb92aa2e22f360754f32f39f8Wu-cheng Li#include "base/path_service.h"
156eabb9b770a7c60cb92aa2e22f360754f32f39f8Wu-cheng Li#include "base/platform_file.h"
166eabb9b770a7c60cb92aa2e22f360754f32f39f8Wu-cheng Li#include "base/process_util.h"
176eabb9b770a7c60cb92aa2e22f360754f32f39f8Wu-cheng Li#include "base/run_loop.h"
186eabb9b770a7c60cb92aa2e22f360754f32f39f8Wu-cheng Li#include "base/strings/stringprintf.h"
196eabb9b770a7c60cb92aa2e22f360754f32f39f8Wu-cheng Li#include "base/test/test_timeouts.h"
206eabb9b770a7c60cb92aa2e22f360754f32f39f8Wu-cheng Li#include "base/threading/platform_thread.h"
216eabb9b770a7c60cb92aa2e22f360754f32f39f8Wu-cheng Li#include "base/threading/sequenced_worker_pool.h"
226eabb9b770a7c60cb92aa2e22f360754f32f39f8Wu-cheng Li#include "base/time.h"
236eabb9b770a7c60cb92aa2e22f360754f32f39f8Wu-cheng Li#include "chrome/browser/extensions/api/messaging/native_message_process_host.h"
246eabb9b770a7c60cb92aa2e22f360754f32f39f8Wu-cheng Li#include "chrome/browser/extensions/api/messaging/native_messaging_test_util.h"
25913f3784d368a5e11fee5d5db2c355ef832685daWu-cheng Li#include "chrome/browser/extensions/api/messaging/native_process_launcher.h"
266eabb9b770a7c60cb92aa2e22f360754f32f39f8Wu-cheng Li#include "chrome/common/chrome_paths.h"
276eabb9b770a7c60cb92aa2e22f360754f32f39f8Wu-cheng Li#include "chrome/common/chrome_switches.h"
286eabb9b770a7c60cb92aa2e22f360754f32f39f8Wu-cheng Li#include "chrome/common/chrome_version_info.h"
296eabb9b770a7c60cb92aa2e22f360754f32f39f8Wu-cheng Li#include "chrome/common/extensions/extension.h"
306eabb9b770a7c60cb92aa2e22f360754f32f39f8Wu-cheng Li#include "chrome/common/extensions/features/feature.h"
316eabb9b770a7c60cb92aa2e22f360754f32f39f8Wu-cheng Li#include "content/public/browser/browser_thread.h"
326eabb9b770a7c60cb92aa2e22f360754f32f39f8Wu-cheng Li#include "content/public/test/test_browser_thread_bundle.h"
336eabb9b770a7c60cb92aa2e22f360754f32f39f8Wu-cheng Li#include "testing/gtest/include/gtest/gtest.h"
346eabb9b770a7c60cb92aa2e22f360754f32f39f8Wu-cheng Li
356eabb9b770a7c60cb92aa2e22f360754f32f39f8Wu-cheng Liusing content::BrowserThread;
36
37namespace {
38
39const char kTestMessage[] = "{\"text\": \"Hello.\"}";
40
41base::FilePath GetTestDir() {
42  base::FilePath test_dir;
43  PathService::Get(chrome::DIR_TEST_DATA, &test_dir);
44  test_dir = test_dir.AppendASCII("native_messaging");
45  return test_dir;
46}
47
48}  // namespace
49
50namespace extensions {
51
52class FakeLauncher : public NativeProcessLauncher {
53 public:
54  FakeLauncher(base::FilePath read_file, base::FilePath write_file) {
55    read_file_ = base::CreatePlatformFile(
56        read_file,
57        base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ |
58            base::PLATFORM_FILE_ASYNC,
59        NULL, NULL);
60    write_file_ = base::CreatePlatformFile(
61        write_file,
62        base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE |
63            base::PLATFORM_FILE_ASYNC,
64        NULL, NULL);
65  }
66
67  virtual void Launch(const GURL& origin,
68                      const std::string& native_host_name,
69                      LaunchedCallback callback) const OVERRIDE {
70    callback.Run(NativeProcessLauncher::RESULT_SUCCESS,
71                 read_file_, write_file_);
72  }
73
74 private:
75  base::PlatformFile read_file_;
76  base::PlatformFile write_file_;
77};
78
79class NativeMessagingTest : public ::testing::Test,
80                            public NativeMessageProcessHost::Client,
81                            public base::SupportsWeakPtr<NativeMessagingTest> {
82 protected:
83  NativeMessagingTest()
84      : current_channel_(chrome::VersionInfo::CHANNEL_DEV),
85        native_message_process_host_(NULL),
86        thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP) {
87  }
88
89  virtual void SetUp() OVERRIDE {
90    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
91    // Change the user data dir so native apps will be looked for in the test
92    // directory.
93    ASSERT_TRUE(PathService::Get(chrome::DIR_USER_DATA, &user_data_dir_));
94    ASSERT_TRUE(PathService::Override(chrome::DIR_USER_DATA, GetTestDir()));
95  }
96
97  virtual void TearDown() OVERRIDE {
98    // Change the user data dir back for other tests.
99    ASSERT_TRUE(PathService::Override(chrome::DIR_USER_DATA, user_data_dir_));
100    if (native_message_process_host_.get()) {
101      BrowserThread::DeleteSoon(BrowserThread::IO, FROM_HERE,
102                                native_message_process_host_.release());
103    }
104    base::RunLoop().RunUntilIdle();
105  }
106
107  virtual void PostMessageFromNativeProcess(
108      int port_id,
109      scoped_ptr<base::ListValue> message_as_list) OVERRIDE  {
110    // |message_as_list| should contain a single DictionaryValue. Extract it
111    // into |last_message_|.
112    ASSERT_EQ(1u, message_as_list->GetSize());
113    base::Value* last_message_value = NULL;
114    message_as_list->Remove(0, &last_message_value);
115    ASSERT_EQ(base::Value::TYPE_DICTIONARY, last_message_value->GetType());
116    last_message_.reset(
117        static_cast<base::DictionaryValue*>(last_message_value));
118
119    if (read_message_run_loop_)
120      read_message_run_loop_->Quit();
121  }
122
123  virtual void CloseChannel(int port_id,
124                            const std::string& error_message) OVERRIDE {
125  }
126
127 protected:
128  std::string FormatMessage(const std::string& message) {
129    Pickle pickle;
130    pickle.WriteString(message);
131    return std::string(const_cast<const Pickle*>(&pickle)->payload(),
132                       pickle.payload_size());
133  }
134
135  base::FilePath CreateTempFileWithMessage(const std::string& message) {
136    base::FilePath filename = temp_dir_.path().AppendASCII("input");
137    file_util::CreateTemporaryFile(&filename);
138    std::string message_with_header = FormatMessage(message);
139    EXPECT_TRUE(file_util::WriteFile(
140        filename, message_with_header.data(), message_with_header.size()));
141    return filename;
142  }
143
144  // Force the channel to be dev.
145  base::ScopedTempDir temp_dir_;
146  Feature::ScopedCurrentChannel current_channel_;
147  scoped_ptr<NativeMessageProcessHost> native_message_process_host_;
148  base::FilePath user_data_dir_;
149  scoped_ptr<base::RunLoop> read_message_run_loop_;
150  content::TestBrowserThreadBundle thread_bundle_;
151  scoped_ptr<DictionaryValue> last_message_;
152};
153
154// Read a single message from a local file.
155TEST_F(NativeMessagingTest, SingleSendMessageRead) {
156  base::FilePath temp_output_file = temp_dir_.path().AppendASCII("output");
157  base::FilePath temp_input_file = CreateTempFileWithMessage(kTestMessage);
158
159  scoped_ptr<NativeProcessLauncher> launcher(
160      new FakeLauncher(temp_input_file, temp_output_file));
161  native_message_process_host_ = NativeMessageProcessHost::CreateWithLauncher(
162      AsWeakPtr(), kTestNativeMessagingExtensionId, "empty_app.py",
163      0, launcher.Pass());
164  ASSERT_TRUE(native_message_process_host_.get());
165  read_message_run_loop_.reset(new base::RunLoop());
166  read_message_run_loop_->RunUntilIdle();
167
168  if (!last_message_) {
169    read_message_run_loop_.reset(new base::RunLoop());
170    native_message_process_host_->ReadNowForTesting();
171    read_message_run_loop_->Run();
172  }
173  ASSERT_TRUE(last_message_);
174
175  scoped_ptr<base::Value> kTestMessageAsValue(
176      base::JSONReader::Read(kTestMessage));
177  ASSERT_TRUE(kTestMessageAsValue);
178  EXPECT_TRUE(base::Value::Equals(kTestMessageAsValue.get(),
179                                  last_message_.get()))
180      << "Expected " << *kTestMessageAsValue << " got " << *last_message_;
181}
182
183// Tests sending a single message. The message should get written to
184// |temp_file| and should match the contents of single_message_request.msg.
185TEST_F(NativeMessagingTest, SingleSendMessageWrite) {
186  base::FilePath temp_output_file = temp_dir_.path().AppendASCII("output");
187  base::FilePath temp_input_file = CreateTempFileWithMessage("{}");
188
189  scoped_ptr<NativeProcessLauncher> launcher(
190      new FakeLauncher(temp_input_file, temp_output_file));
191  native_message_process_host_ = NativeMessageProcessHost::CreateWithLauncher(
192      AsWeakPtr(), kTestNativeMessagingExtensionId, "empty_app.py",
193      0, launcher.Pass());
194  ASSERT_TRUE(native_message_process_host_.get());
195  base::RunLoop().RunUntilIdle();
196
197  native_message_process_host_->Send(kTestMessage);
198  base::RunLoop().RunUntilIdle();
199
200  std::string output;
201  base::TimeTicks start_time = base::TimeTicks::Now();
202  while (base::TimeTicks::Now() - start_time < TestTimeouts::action_timeout()) {
203    ASSERT_TRUE(file_util::ReadFileToString(temp_output_file, &output));
204    if (!output.empty())
205      break;
206    base::PlatformThread::YieldCurrentThread();
207  }
208
209  EXPECT_EQ(FormatMessage(kTestMessage), output);
210}
211
212// Test send message with a real client. The client just echo's back the text
213// it received.
214TEST_F(NativeMessagingTest, EchoConnect) {
215  base::FilePath manifest_path = temp_dir_.path().AppendASCII(
216      std::string(kTestNativeMessagingHostName) + ".json");
217  ASSERT_NO_FATAL_FAILURE(CreateTestNativeHostManifest(manifest_path));
218
219  std::string hosts_option = base::StringPrintf(
220      "%s=%s", extensions::kTestNativeMessagingHostName,
221      manifest_path.AsUTF8Unsafe().c_str());
222  CommandLine::ForCurrentProcess()->AppendSwitchASCII(
223      switches::kNativeMessagingHosts, hosts_option);
224
225  native_message_process_host_ = NativeMessageProcessHost::Create(
226      AsWeakPtr(), kTestNativeMessagingExtensionId,
227      kTestNativeMessagingHostName, 0);
228  ASSERT_TRUE(native_message_process_host_.get());
229
230  native_message_process_host_->Send("{\"text\": \"Hello.\"}");
231  read_message_run_loop_.reset(new base::RunLoop());
232  read_message_run_loop_->Run();
233  ASSERT_TRUE(last_message_);
234
235  std::string expected_url = std::string("chrome-extension://") +
236      kTestNativeMessagingExtensionId + "/";
237  int id;
238  EXPECT_TRUE(last_message_->GetInteger("id", &id));
239  EXPECT_EQ(1, id);
240  std::string text;
241  EXPECT_TRUE(last_message_->GetString("echo.text", &text));
242  EXPECT_EQ("Hello.", text);
243  std::string url;
244  EXPECT_TRUE(last_message_->GetString("caller_url", &url));
245  EXPECT_EQ(expected_url, url);
246
247
248  native_message_process_host_->Send("{\"foo\": \"bar\"}");
249  read_message_run_loop_.reset(new base::RunLoop());
250  read_message_run_loop_->Run();
251  EXPECT_TRUE(last_message_->GetInteger("id", &id));
252  EXPECT_EQ(2, id);
253  EXPECT_TRUE(last_message_->GetString("echo.foo", &text));
254  EXPECT_EQ("bar", text);
255  EXPECT_TRUE(last_message_->GetString("caller_url", &url));
256  EXPECT_EQ(expected_url, url);
257}
258
259}  // namespace extensions
260