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