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