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