Home | History | Annotate | Download | only in nacl
      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 "chrome/test/nacl/nacl_browsertest_util.h"
      6 
      7 #include <stdlib.h>
      8 #include "base/command_line.h"
      9 #include "base/json/json_reader.h"
     10 #include "base/path_service.h"
     11 #include "base/values.h"
     12 #include "chrome/browser/ui/browser.h"
     13 #include "chrome/browser/ui/tabs/tab_strip_model.h"
     14 #include "chrome/common/chrome_paths.h"
     15 #include "chrome/common/chrome_switches.h"
     16 #include "chrome/test/base/ui_test_utils.h"
     17 #include "components/nacl/common/nacl_switches.h"
     18 #include "content/public/browser/plugin_service.h"
     19 #include "content/public/browser/web_contents.h"
     20 #include "content/public/common/webplugininfo.h"
     21 #include "net/base/net_util.h"
     22 
     23 typedef content::TestMessageHandler::MessageResponse MessageResponse;
     24 
     25 MessageResponse StructuredMessageHandler::HandleMessage(
     26     const std::string& json) {
     27   scoped_ptr<base::Value> value;
     28   base::JSONReader reader(base::JSON_ALLOW_TRAILING_COMMAS);
     29   // Automation messages are stringified before they are sent because the
     30   // automation channel cannot handle arbitrary objects.  This means we
     31   // need to decode the json twice to get the original message.
     32   value.reset(reader.ReadToValue(json));
     33   if (!value.get())
     34     return InternalError("Could parse automation JSON: " + json +
     35                          " because " + reader.GetErrorMessage());
     36 
     37   std::string temp;
     38   if (!value->GetAsString(&temp))
     39     return InternalError("Message was not a string: " + json);
     40 
     41   value.reset(reader.ReadToValue(temp));
     42   if (!value.get())
     43     return InternalError("Could not parse message JSON: " + temp +
     44                          " because " + reader.GetErrorMessage());
     45 
     46   base::DictionaryValue* msg;
     47   if (!value->GetAsDictionary(&msg))
     48     return InternalError("Message was not an object: " + temp);
     49 
     50   std::string type;
     51   if (!msg->GetString("type", &type))
     52     return MissingField("unknown", "type");
     53 
     54   return HandleStructuredMessage(type, msg);
     55 }
     56 
     57 MessageResponse StructuredMessageHandler::MissingField(
     58     const std::string& type,
     59     const std::string& field) {
     60   return InternalError(type + " message did not have field: " + field);
     61 }
     62 
     63 MessageResponse StructuredMessageHandler::InternalError(
     64     const std::string& reason) {
     65   SetError(reason);
     66   return DONE;
     67 }
     68 
     69 LoadTestMessageHandler::LoadTestMessageHandler()
     70     : test_passed_(false) {
     71 }
     72 
     73 void LoadTestMessageHandler::Log(const std::string& type,
     74                                  const std::string& message) {
     75   // TODO(ncbray) better logging.
     76   LOG(INFO) << type << " " << message;
     77 }
     78 
     79 MessageResponse LoadTestMessageHandler::HandleStructuredMessage(
     80    const std::string& type,
     81    base::DictionaryValue* msg) {
     82   if (type == "Log") {
     83     std::string message;
     84     if (!msg->GetString("message", &message))
     85       return MissingField(type, "message");
     86     Log("LOG", message);
     87     return CONTINUE;
     88   } else if (type == "Shutdown") {
     89     std::string message;
     90     if (!msg->GetString("message", &message))
     91       return MissingField(type, "message");
     92     if (!msg->GetBoolean("passed", &test_passed_))
     93       return MissingField(type, "passed");
     94     Log("SHUTDOWN", message);
     95     return DONE;
     96   } else {
     97     return InternalError("Unknown message type: " + type);
     98   }
     99 }
    100 
    101 // A message handler for nacl_integration tests ported to be browser_tests.
    102 // nacl_integration tests report to their test jig using a series of RPC calls
    103 // that are encoded as URL requests. When these tests run as browser_tests,
    104 // they make the same RPC requests, but use the automation channel instead of
    105 // URL requests. This message handler decodes and responds to these requests.
    106 class NaClIntegrationMessageHandler : public StructuredMessageHandler {
    107  public:
    108   NaClIntegrationMessageHandler();
    109 
    110   void Log(const std::string& message);
    111 
    112   virtual MessageResponse HandleStructuredMessage(
    113       const std::string& type,
    114       base::DictionaryValue* msg) OVERRIDE;
    115 
    116   bool test_passed() const {
    117     return test_passed_;
    118   }
    119 
    120  private:
    121   bool test_passed_;
    122 
    123   DISALLOW_COPY_AND_ASSIGN(NaClIntegrationMessageHandler);
    124 };
    125 
    126 NaClIntegrationMessageHandler::NaClIntegrationMessageHandler()
    127     : test_passed_(false) {
    128 }
    129 
    130 void NaClIntegrationMessageHandler::Log(const std::string& message) {
    131   // TODO(ncbray) better logging.
    132   LOG(INFO) << "|||| " << message;
    133 }
    134 
    135 MessageResponse NaClIntegrationMessageHandler::HandleStructuredMessage(
    136     const std::string& type,
    137     base::DictionaryValue* msg) {
    138   if (type == "TestLog") {
    139     std::string message;
    140     if (!msg->GetString("message", &message))
    141       return MissingField(type, "message");
    142     Log(message);
    143     return CONTINUE;
    144   } else if (type == "Shutdown") {
    145     std::string message;
    146     if (!msg->GetString("message", &message))
    147       return MissingField(type, "message");
    148     if (!msg->GetBoolean("passed", &test_passed_))
    149       return MissingField(type, "passed");
    150     Log(message);
    151     return DONE;
    152   } else if (type == "Ping") {
    153     return CONTINUE;
    154   } else if (type == "JavaScriptIsAlive") {
    155     return CONTINUE;
    156   } else {
    157     return InternalError("Unknown message type: " + type);
    158   }
    159 }
    160 
    161 // NaCl browser tests serve files out of the build directory because nexes and
    162 // pexes are artifacts of the build.  To keep things tidy, all test data is kept
    163 // in a subdirectory.  Several variants of a test may be run, for example when
    164 // linked against newlib and when linked against glibc.  These variants are kept
    165 // in different subdirectories.  For example, the build directory will look
    166 // something like this on Linux:
    167 // out/
    168 //     Release/
    169 //             nacl_test_data/
    170 //                            newlib/
    171 //                            glibc/
    172 //                            pnacl/
    173 static bool GetNaClVariantRoot(const base::FilePath::StringType& variant,
    174                                base::FilePath* document_root) {
    175   if (!ui_test_utils::GetRelativeBuildDirectory(document_root))
    176     return false;
    177   *document_root = document_root->Append(FILE_PATH_LITERAL("nacl_test_data"));
    178   *document_root = document_root->Append(variant);
    179   return true;
    180 }
    181 
    182 static void AddPnaclParm(const base::FilePath::StringType& url,
    183                          base::FilePath::StringType* url_with_parm) {
    184   if (url.find(FILE_PATH_LITERAL("?")) == base::FilePath::StringType::npos) {
    185     *url_with_parm = url + FILE_PATH_LITERAL("?pnacl=1");
    186   } else {
    187     *url_with_parm = url + FILE_PATH_LITERAL("&pnacl=1");
    188   }
    189 }
    190 
    191 static void AddPnaclDisabledParm(const base::FilePath::StringType& url,
    192                                  base::FilePath::StringType* url_with_parm) {
    193   if (url.find(FILE_PATH_LITERAL("?")) == base::FilePath::StringType::npos) {
    194     *url_with_parm = url + FILE_PATH_LITERAL("?pnacl_disabled=1");
    195   } else {
    196     *url_with_parm = url + FILE_PATH_LITERAL("&pnacl_disabled=1");
    197   }
    198 }
    199 
    200 NaClBrowserTestBase::NaClBrowserTestBase() {
    201 }
    202 
    203 NaClBrowserTestBase::~NaClBrowserTestBase() {
    204 }
    205 
    206 void NaClBrowserTestBase::SetUpCommandLine(base::CommandLine* command_line) {
    207   command_line->AppendSwitch(switches::kEnableNaCl);
    208 }
    209 
    210 void NaClBrowserTestBase::SetUpOnMainThread() {
    211   // Sanity check.
    212   base::FilePath plugin_lib;
    213   ASSERT_TRUE(PathService::Get(chrome::FILE_NACL_PLUGIN, &plugin_lib));
    214   ASSERT_TRUE(base::PathExists(plugin_lib)) << plugin_lib.value();
    215 
    216   ASSERT_TRUE(StartTestServer()) << "Cannot start test server.";
    217 }
    218 
    219 bool NaClBrowserTestBase::GetDocumentRoot(base::FilePath* document_root) {
    220   return GetNaClVariantRoot(Variant(), document_root);
    221 }
    222 
    223 bool NaClBrowserTestBase::IsAPnaclTest() {
    224   return false;
    225 }
    226 
    227 bool NaClBrowserTestBase::IsPnaclDisabled() {
    228   return false;
    229 }
    230 
    231 GURL NaClBrowserTestBase::TestURL(
    232     const base::FilePath::StringType& url_fragment) {
    233   base::FilePath expanded_url = base::FilePath(FILE_PATH_LITERAL("files"));
    234   expanded_url = expanded_url.Append(url_fragment);
    235   return test_server_->GetURL(expanded_url.MaybeAsASCII());
    236 }
    237 
    238 bool NaClBrowserTestBase::RunJavascriptTest(
    239     const GURL& url,
    240     content::TestMessageHandler* handler) {
    241   content::JavascriptTestObserver observer(
    242       browser()->tab_strip_model()->GetActiveWebContents(),
    243       handler);
    244   ui_test_utils::NavigateToURL(browser(), url);
    245   return observer.Run();
    246 }
    247 
    248 void NaClBrowserTestBase::RunLoadTest(
    249     const base::FilePath::StringType& test_file) {
    250   LoadTestMessageHandler handler;
    251   base::FilePath::StringType test_file_with_pnacl = test_file;
    252   if (IsAPnaclTest()) {
    253     AddPnaclParm(test_file, &test_file_with_pnacl);
    254   }
    255   base::FilePath::StringType test_file_with_both = test_file_with_pnacl;
    256   if (IsPnaclDisabled()) {
    257     AddPnaclDisabledParm(test_file_with_pnacl, &test_file_with_both);
    258   }
    259   bool ok = RunJavascriptTest(TestURL(test_file_with_both), &handler);
    260   ASSERT_TRUE(ok) << handler.error_message();
    261   ASSERT_TRUE(handler.test_passed()) << "Test failed.";
    262 }
    263 
    264 void NaClBrowserTestBase::RunNaClIntegrationTest(
    265     const base::FilePath::StringType& url_fragment, bool full_url) {
    266   NaClIntegrationMessageHandler handler;
    267   base::FilePath::StringType url_fragment_with_pnacl = url_fragment;
    268   if (IsAPnaclTest()) {
    269     AddPnaclParm(url_fragment, &url_fragment_with_pnacl);
    270   }
    271   base::FilePath::StringType url_fragment_with_both = url_fragment_with_pnacl;
    272   if (IsPnaclDisabled()) {
    273     AddPnaclDisabledParm(url_fragment_with_pnacl, &url_fragment_with_both);
    274   }
    275   bool ok = RunJavascriptTest(full_url
    276                               ? GURL(url_fragment_with_both)
    277                               : TestURL(url_fragment_with_both),
    278                               &handler);
    279   ASSERT_TRUE(ok) << handler.error_message();
    280   ASSERT_TRUE(handler.test_passed()) << "Test failed.";
    281 }
    282 
    283 bool NaClBrowserTestBase::StartTestServer() {
    284   // Launch the web server.
    285   base::FilePath document_root;
    286   if (!GetDocumentRoot(&document_root))
    287     return false;
    288   test_server_.reset(new net::SpawnedTestServer(
    289                          net::SpawnedTestServer::TYPE_HTTP,
    290                          net::SpawnedTestServer::kLocalhost,
    291                          document_root));
    292   return test_server_->Start();
    293 }
    294 
    295 base::FilePath::StringType NaClBrowserTestNewlib::Variant() {
    296   return FILE_PATH_LITERAL("newlib");
    297 }
    298 
    299 base::FilePath::StringType NaClBrowserTestGLibc::Variant() {
    300   return FILE_PATH_LITERAL("glibc");
    301 }
    302 
    303 base::FilePath::StringType NaClBrowserTestPnacl::Variant() {
    304   return FILE_PATH_LITERAL("pnacl");
    305 }
    306 
    307 bool NaClBrowserTestPnacl::IsAPnaclTest() {
    308   return true;
    309 }
    310 
    311 base::FilePath::StringType NaClBrowserTestPnaclDisabled::Variant() {
    312   return FILE_PATH_LITERAL("pnacl");
    313 }
    314 
    315 bool NaClBrowserTestPnaclDisabled::IsAPnaclTest() {
    316   return true;
    317 }
    318 
    319 bool NaClBrowserTestPnaclDisabled::IsPnaclDisabled() {
    320   return true;
    321 }
    322 void NaClBrowserTestPnaclDisabled::SetUpCommandLine(
    323     base::CommandLine* command_line) {
    324   NaClBrowserTestBase::SetUpCommandLine(command_line);
    325   command_line->AppendSwitch(switches::kDisablePnacl);
    326 }
    327 
    328 base::FilePath::StringType NaClBrowserTestNonSfiMode::Variant() {
    329   return FILE_PATH_LITERAL("libc-free");
    330 }
    331 
    332 void NaClBrowserTestNonSfiMode::SetUpCommandLine(
    333     base::CommandLine* command_line) {
    334   NaClBrowserTestBase::SetUpCommandLine(command_line);
    335   command_line->AppendSwitch(switches::kEnableNaClNonSfiMode);
    336 }
    337 
    338 base::FilePath::StringType NaClBrowserTestStatic::Variant() {
    339   return FILE_PATH_LITERAL("static");
    340 }
    341 
    342 bool NaClBrowserTestStatic::GetDocumentRoot(base::FilePath* document_root) {
    343   *document_root = base::FilePath(FILE_PATH_LITERAL("chrome/test/data/nacl"));
    344   return true;
    345 }
    346 
    347 base::FilePath::StringType NaClBrowserTestPnaclNonSfi::Variant() {
    348   return FILE_PATH_LITERAL("nonsfi");
    349 }
    350 
    351 void NaClBrowserTestPnaclNonSfi::SetUpCommandLine(
    352     base::CommandLine* command_line) {
    353   NaClBrowserTestBase::SetUpCommandLine(command_line);
    354   command_line->AppendSwitch(switches::kEnableNaClNonSfiMode);
    355 }
    356 
    357 void NaClBrowserTestNewlibExtension::SetUpCommandLine(
    358     CommandLine* command_line) {
    359   NaClBrowserTestBase::SetUpCommandLine(command_line);
    360   base::FilePath src_root;
    361   ASSERT_TRUE(PathService::Get(base::DIR_SOURCE_ROOT, &src_root));
    362 
    363   // Extension-based tests should specialize the GetDocumentRoot() / Variant()
    364   // to point at the isolated the test extension directory.
    365   // Otherwise, multiple NaCl extensions tests will end up sharing the
    366   // same directory when loading the extension files.
    367   base::FilePath document_root;
    368   ASSERT_TRUE(GetDocumentRoot(&document_root));
    369 
    370   // Document root is relative to source root, and source root may not be CWD.
    371   command_line->AppendSwitchPath(switches::kLoadExtension,
    372                                  src_root.Append(document_root));
    373 }
    374