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