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/environment.h" 10 #include "base/json/json_reader.h" 11 #include "base/path_service.h" 12 #include "base/values.h" 13 #include "chrome/browser/ui/browser.h" 14 #include "chrome/browser/ui/tabs/tab_strip_model.h" 15 #include "chrome/common/chrome_paths.h" 16 #include "chrome/common/chrome_switches.h" 17 #include "chrome/test/base/ui_test_utils.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 TestMessageHandler::MessageResponse MessageResponse; 24 25 MessageResponse StructuredMessageHandler::HandleMessage( 26 const std::string& json) { 27 scoped_ptr<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 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 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 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 static bool GetNaClVariantRoot(const base::FilePath::StringType& variant, 173 base::FilePath* document_root) { 174 if (!ui_test_utils::GetRelativeBuildDirectory(document_root)) 175 return false; 176 *document_root = document_root->Append(FILE_PATH_LITERAL("nacl_test_data")); 177 *document_root = document_root->Append(variant); 178 return true; 179 } 180 181 static void AddPnaclParm(const base::FilePath::StringType& url, 182 base::FilePath::StringType* url_with_parm) { 183 if (url.find(FILE_PATH_LITERAL("?")) == base::FilePath::StringType::npos) { 184 *url_with_parm = url + FILE_PATH_LITERAL("?pnacl=1"); 185 } else { 186 *url_with_parm = url + FILE_PATH_LITERAL("&pnacl=1"); 187 } 188 } 189 190 NaClBrowserTestBase::NaClBrowserTestBase() { 191 } 192 193 NaClBrowserTestBase::~NaClBrowserTestBase() { 194 } 195 196 void NaClBrowserTestBase::SetUpCommandLine(CommandLine* command_line) { 197 command_line->AppendSwitch(switches::kEnableNaCl); 198 } 199 200 void NaClBrowserTestBase::SetUpInProcessBrowserTestFixture() { 201 // Sanity check. 202 base::FilePath plugin_lib; 203 ASSERT_TRUE(PathService::Get(chrome::FILE_NACL_PLUGIN, &plugin_lib)); 204 ASSERT_TRUE(base::PathExists(plugin_lib)) << plugin_lib.value(); 205 206 ASSERT_TRUE(StartTestServer()) << "Cannot start test server."; 207 } 208 209 bool NaClBrowserTestBase::GetDocumentRoot(base::FilePath* document_root) { 210 return GetNaClVariantRoot(Variant(), document_root); 211 } 212 213 bool NaClBrowserTestBase::IsPnacl() { 214 return false; 215 } 216 217 GURL NaClBrowserTestBase::TestURL( 218 const base::FilePath::StringType& url_fragment) { 219 base::FilePath expanded_url = base::FilePath(FILE_PATH_LITERAL("files")); 220 expanded_url = expanded_url.Append(url_fragment); 221 return test_server_->GetURL(expanded_url.MaybeAsASCII()); 222 } 223 224 bool NaClBrowserTestBase::RunJavascriptTest(const GURL& url, 225 TestMessageHandler* handler) { 226 JavascriptTestObserver observer( 227 browser()->tab_strip_model()->GetActiveWebContents()->GetRenderViewHost(), 228 handler); 229 ui_test_utils::NavigateToURL(browser(), url); 230 return observer.Run(); 231 } 232 233 void NaClBrowserTestBase::RunLoadTest( 234 const base::FilePath::StringType& test_file) { 235 LoadTestMessageHandler handler; 236 base::FilePath::StringType test_file_with_parm = test_file; 237 if (IsPnacl()) { 238 AddPnaclParm(test_file, &test_file_with_parm); 239 } 240 bool ok = RunJavascriptTest(TestURL(test_file_with_parm), &handler); 241 ASSERT_TRUE(ok) << handler.error_message(); 242 ASSERT_TRUE(handler.test_passed()) << "Test failed."; 243 } 244 245 void NaClBrowserTestBase::RunNaClIntegrationTest( 246 const base::FilePath::StringType& url_fragment) { 247 NaClIntegrationMessageHandler handler; 248 base::FilePath::StringType url_fragment_with_parm = url_fragment; 249 if (IsPnacl()) { 250 AddPnaclParm(url_fragment, &url_fragment_with_parm); 251 } 252 bool ok = RunJavascriptTest(TestURL(url_fragment_with_parm), &handler); 253 ASSERT_TRUE(ok) << handler.error_message(); 254 ASSERT_TRUE(handler.test_passed()) << "Test failed."; 255 } 256 257 bool NaClBrowserTestBase::StartTestServer() { 258 // Launch the web server. 259 base::FilePath document_root; 260 if (!GetDocumentRoot(&document_root)) 261 return false; 262 test_server_.reset(new net::SpawnedTestServer( 263 net::SpawnedTestServer::TYPE_HTTP, 264 net::SpawnedTestServer::kLocalhost, 265 document_root)); 266 return test_server_->Start(); 267 } 268 269 base::FilePath::StringType NaClBrowserTestNewlib::Variant() { 270 return FILE_PATH_LITERAL("newlib"); 271 } 272 273 base::FilePath::StringType NaClBrowserTestGLibc::Variant() { 274 return FILE_PATH_LITERAL("glibc"); 275 } 276 277 base::FilePath::StringType NaClBrowserTestPnacl::Variant() { 278 return FILE_PATH_LITERAL("pnacl"); 279 } 280 281 bool NaClBrowserTestPnacl::IsPnacl() { 282 return true; 283 } 284 285 void NaClBrowserTestPnacl::SetUpCommandLine(CommandLine* command_line) { 286 NaClBrowserTestBase::SetUpCommandLine(command_line); 287 command_line->AppendSwitch(switches::kEnablePnacl); 288 } 289 290 NaClBrowserTestPnaclWithOldCache::NaClBrowserTestPnaclWithOldCache() { 291 scoped_ptr<base::Environment> env(base::Environment::Create()); 292 env->SetVar("PNACL_USE_OLD_CACHE", "true"); 293 } 294 295 base::FilePath::StringType NaClBrowserTestStatic::Variant() { 296 return FILE_PATH_LITERAL("static"); 297 } 298 299 bool NaClBrowserTestStatic::GetDocumentRoot(base::FilePath* document_root) { 300 *document_root = base::FilePath(FILE_PATH_LITERAL("chrome/test/data/nacl")); 301 return true; 302 } 303