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 23typedef content::TestMessageHandler::MessageResponse MessageResponse; 24 25MessageResponse 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 57MessageResponse StructuredMessageHandler::MissingField( 58 const std::string& type, 59 const std::string& field) { 60 return InternalError(type + " message did not have field: " + field); 61} 62 63MessageResponse StructuredMessageHandler::InternalError( 64 const std::string& reason) { 65 SetError(reason); 66 return DONE; 67} 68 69LoadTestMessageHandler::LoadTestMessageHandler() 70 : test_passed_(false) { 71} 72 73void LoadTestMessageHandler::Log(const std::string& type, 74 const std::string& message) { 75 // TODO(ncbray) better logging. 76 LOG(INFO) << type << " " << message; 77} 78 79MessageResponse 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. 106class 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 126NaClIntegrationMessageHandler::NaClIntegrationMessageHandler() 127 : test_passed_(false) { 128} 129 130void NaClIntegrationMessageHandler::Log(const std::string& message) { 131 // TODO(ncbray) better logging. 132 LOG(INFO) << "|||| " << message; 133} 134 135MessageResponse 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/ 173static 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 182static 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 191static 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 200NaClBrowserTestBase::NaClBrowserTestBase() { 201} 202 203NaClBrowserTestBase::~NaClBrowserTestBase() { 204} 205 206void NaClBrowserTestBase::SetUpCommandLine(base::CommandLine* command_line) { 207 command_line->AppendSwitch(switches::kEnableNaCl); 208} 209 210void 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 219bool NaClBrowserTestBase::GetDocumentRoot(base::FilePath* document_root) { 220 return GetNaClVariantRoot(Variant(), document_root); 221} 222 223bool NaClBrowserTestBase::IsAPnaclTest() { 224 return false; 225} 226 227bool NaClBrowserTestBase::IsPnaclDisabled() { 228 return false; 229} 230 231GURL 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 238bool 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 248void 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 264void 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 283bool 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 295base::FilePath::StringType NaClBrowserTestNewlib::Variant() { 296 return FILE_PATH_LITERAL("newlib"); 297} 298 299base::FilePath::StringType NaClBrowserTestGLibc::Variant() { 300 return FILE_PATH_LITERAL("glibc"); 301} 302 303base::FilePath::StringType NaClBrowserTestPnacl::Variant() { 304 return FILE_PATH_LITERAL("pnacl"); 305} 306 307bool NaClBrowserTestPnacl::IsAPnaclTest() { 308 return true; 309} 310 311base::FilePath::StringType NaClBrowserTestPnaclDisabled::Variant() { 312 return FILE_PATH_LITERAL("pnacl"); 313} 314 315bool NaClBrowserTestPnaclDisabled::IsAPnaclTest() { 316 return true; 317} 318 319bool NaClBrowserTestPnaclDisabled::IsPnaclDisabled() { 320 return true; 321} 322void NaClBrowserTestPnaclDisabled::SetUpCommandLine( 323 base::CommandLine* command_line) { 324 NaClBrowserTestBase::SetUpCommandLine(command_line); 325 command_line->AppendSwitch(switches::kDisablePnacl); 326} 327 328base::FilePath::StringType NaClBrowserTestNonSfiMode::Variant() { 329 return FILE_PATH_LITERAL("libc-free"); 330} 331 332void NaClBrowserTestNonSfiMode::SetUpCommandLine( 333 base::CommandLine* command_line) { 334 NaClBrowserTestBase::SetUpCommandLine(command_line); 335 command_line->AppendSwitch(switches::kEnableNaClNonSfiMode); 336} 337 338base::FilePath::StringType NaClBrowserTestStatic::Variant() { 339 return FILE_PATH_LITERAL("static"); 340} 341 342bool NaClBrowserTestStatic::GetDocumentRoot(base::FilePath* document_root) { 343 *document_root = base::FilePath(FILE_PATH_LITERAL("chrome/test/data/nacl")); 344 return true; 345} 346 347base::FilePath::StringType NaClBrowserTestPnaclNonSfi::Variant() { 348 return FILE_PATH_LITERAL("nonsfi"); 349} 350 351void NaClBrowserTestPnaclNonSfi::SetUpCommandLine( 352 base::CommandLine* command_line) { 353 NaClBrowserTestBase::SetUpCommandLine(command_line); 354 command_line->AppendSwitch(switches::kEnableNaClNonSfiMode); 355} 356 357void 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