Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 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/command_line.h" | |
| 6 #include "base/json/json_reader.h" | |
| 7 #include "base/path_service.h" | |
| 8 #include "base/timer.h" | |
| 9 #include "chrome/browser/ui/browser_tabstrip.h" | |
| 10 #include "chrome/common/chrome_paths.h" | |
| 11 #include "chrome/common/chrome_switches.h" | |
| 12 #include "chrome/test/base/in_process_browser_test.h" | |
| 13 #include "chrome/test/base/ui_test_utils.h" | |
| 14 #include "content/public/browser/dom_operation_notification_details.h" | |
| 15 #include "content/public/browser/notification_observer.h" | |
| 16 #include "content/public/browser/notification_registrar.h" | |
| 17 #include "content/public/browser/notification_types.h" | |
| 18 #include "content/public/browser/plugin_service.h" | |
| 19 #include "content/public/browser/render_view_host.h" | |
| 20 #include "content/public/browser/web_contents.h" | |
| 21 #include "net/base/net_util.h" | |
| 22 #include "webkit/plugins/webplugininfo.h" | |
| 23 | |
| 24 namespace { | |
| 25 | |
| 26 const int kTimeoutMs = 90000; | |
| 27 | |
| 28 // Base class for handling a stream of automation messages produced by a | |
| 29 // JavascriptTestObserver. | |
| 30 class TestMessageHandler { | |
| 31 public: | |
| 32 enum MessageResponse { | |
| 33 // Reset the timeout and keep running. | |
| 34 CONTINUE, | |
| 35 // Stop runnning. | |
| 36 DONE | |
| 37 }; | |
| 38 | |
| 39 TestMessageHandler() : ok_(true) {} | |
|
sky
2012/08/13 21:29:57
Add a virtual destructor.
Nick Bray (chromium)
2012/08/13 23:16:26
Done.
| |
| 40 | |
| 41 virtual MessageResponse HandleMessage(const std::string& json) = 0; | |
| 42 | |
| 43 void SetError(const std::string& message) { | |
| 44 ok_ = false; | |
| 45 error_message_ = message; | |
| 46 } | |
| 47 | |
| 48 bool ok() const { | |
| 49 return ok_; | |
| 50 } | |
| 51 | |
| 52 const std::string& error_message() const { | |
| 53 return error_message_; | |
| 54 } | |
| 55 | |
| 56 private: | |
| 57 bool ok_; | |
| 58 std::string error_message_; | |
| 59 }; | |
| 60 | |
| 61 // A helper base class that decodes structured automation messages of the form: | |
| 62 // {"type": type_name, ...} | |
| 63 class StructuredMessageHandler : public TestMessageHandler { | |
| 64 public: | |
| 65 MessageResponse HandleMessage(const std::string& json) { | |
|
sky
2012/08/13 21:29:57
virtual OVERRIDE
Nick Bray (chromium)
2012/08/13 23:16:26
Done.
| |
| 66 scoped_ptr<Value> value; | |
| 67 base::JSONReader reader(base::JSON_ALLOW_TRAILING_COMMAS); | |
| 68 // Automation messages are stringified before they are sent because the | |
| 69 // automation channel cannot handle arbitrary objects. This means we | |
| 70 // need to decode the json twice to get the original message. | |
| 71 value.reset(reader.ReadToValue(json)); | |
| 72 if (!value.get()) | |
| 73 return InternalError("Could parse automation JSON: " + json + | |
| 74 " because " + reader.GetErrorMessage()); | |
| 75 | |
| 76 std::string temp; | |
| 77 if (!value->GetAsString(&temp)) | |
| 78 return InternalError("Message was not a string: " + json); | |
| 79 | |
| 80 value.reset(reader.ReadToValue(temp)); | |
| 81 if (!value.get()) | |
| 82 return InternalError("Could not parse message JSON: " + temp + | |
| 83 " because " + reader.GetErrorMessage()); | |
| 84 | |
| 85 DictionaryValue *msg; | |
|
sky
2012/08/13 21:29:57
* on the other side, eg DictionaryValue* msg. Sear
Nick Bray (chromium)
2012/08/13 23:16:26
I fixed the "&" issues, but forgot about the "*" i
| |
| 86 if (!value->GetAsDictionary(&msg)) | |
| 87 return InternalError("Message was not an object: " + temp); | |
| 88 | |
| 89 std::string type; | |
| 90 if (!msg->GetString("type", &type)) | |
| 91 return MissingField("unknown", "type"); | |
| 92 | |
| 93 return HandleStructuredMessage(type, msg); | |
| 94 } | |
| 95 | |
| 96 virtual MessageResponse HandleStructuredMessage(const std::string& type, | |
| 97 DictionaryValue *msg) = 0; | |
| 98 | |
| 99 MessageResponse MissingField(const std::string& type, | |
| 100 const std::string& field) WARN_UNUSED_RESULT { | |
| 101 return InternalError(type + " message did not have field: " + field); | |
| 102 } | |
| 103 | |
| 104 MessageResponse InternalError(const std::string& reason) WARN_UNUSED_RESULT { | |
| 105 SetError(reason); | |
| 106 return DONE; | |
| 107 } | |
| 108 }; | |
|
sky
2012/08/13 21:29:57
DISALLOW_...
Nick Bray (chromium)
2012/08/13 23:16:26
Abstract class.
| |
| 109 | |
| 110 // A simple structured message handler for tests that load nexes. | |
| 111 class LoadTestMessageHandler : public StructuredMessageHandler { | |
| 112 public: | |
| 113 LoadTestMessageHandler() : test_passed_(false) {} | |
| 114 | |
| 115 void Log(const std::string& type, const std::string& message) { | |
| 116 // TODO(ncbray) better logging. | |
| 117 LOG(INFO) << type << " " << message; | |
| 118 } | |
| 119 | |
| 120 virtual MessageResponse HandleStructuredMessage( | |
| 121 const std::string& type, | |
| 122 DictionaryValue* msg) OVERRIDE { | |
| 123 if (type == "Log") { | |
| 124 std::string message; | |
| 125 if (!msg->GetString("message", &message)) | |
| 126 return MissingField(type, "message"); | |
| 127 Log("LOG", message); | |
| 128 return CONTINUE; | |
| 129 } else if (type == "Shutdown") { | |
| 130 std::string message; | |
| 131 if (!msg->GetString("message", &message)) | |
| 132 return MissingField(type, "message"); | |
| 133 if (!msg->GetBoolean("passed", &test_passed_)) | |
| 134 return MissingField(type, "passed"); | |
| 135 Log("SHUTDOWN", message); | |
| 136 return DONE; | |
| 137 } else { | |
| 138 return InternalError("Unknown message type: " + type); | |
| 139 } | |
| 140 } | |
| 141 | |
| 142 bool test_passed() const { | |
| 143 return test_passed_; | |
| 144 } | |
| 145 | |
| 146 private: | |
| 147 bool test_passed_; | |
| 148 | |
| 149 DISALLOW_COPY_AND_ASSIGN(LoadTestMessageHandler); | |
| 150 }; | |
| 151 | |
| 152 // This class captures a stream of automation messages coming from a Javascript | |
| 153 // test and dispatches them to a message handler. | |
| 154 // TODO(ncbray) factor out and share with PPAPI tests. | |
| 155 class JavascriptTestObserver : public content::NotificationObserver { | |
| 156 public: | |
| 157 JavascriptTestObserver( | |
| 158 content::RenderViewHost* render_view_host, | |
| 159 TestMessageHandler *handler, | |
| 160 base::TimeDelta timeout) | |
| 161 : handler_(handler), | |
| 162 running_(false) { | |
| 163 registrar_.Add(this, content::NOTIFICATION_DOM_OPERATION_RESPONSE, | |
| 164 content::Source<content::RenderViewHost>(render_view_host)); | |
| 165 timer_.Start(FROM_HERE, timeout, this, &JavascriptTestObserver::TimeOut); | |
| 166 } | |
| 167 | |
| 168 // Pump the message loop until the message handler indicates the Javascript | |
| 169 // test is done running. Return true if the test jig functioned correctly and | |
| 170 // nothing timed out. | |
| 171 bool Run() { | |
| 172 running_ = true; | |
| 173 content::RunMessageLoop(); | |
| 174 running_ = false; | |
| 175 return handler_->ok(); | |
| 176 } | |
| 177 | |
| 178 virtual void Observe( | |
| 179 int type, | |
| 180 const content::NotificationSource& source, | |
| 181 const content::NotificationDetails& details) OVERRIDE { | |
| 182 DCHECK(type == content::NOTIFICATION_DOM_OPERATION_RESPONSE); | |
| 183 content::Details<content::DomOperationNotificationDetails> dom_op_details( | |
| 184 details); | |
| 185 // We might receive responses for other script execution, but we only | |
| 186 // care about the test finished message. | |
| 187 TestMessageHandler::MessageResponse response = | |
| 188 handler_->HandleMessage(dom_op_details->json); | |
| 189 | |
| 190 if (response == TestMessageHandler::DONE) { | |
| 191 EndTest(); | |
| 192 } else { | |
| 193 Continue(); | |
| 194 } | |
| 195 } | |
| 196 | |
| 197 private: | |
| 198 void Continue() { | |
| 199 timer_.Reset(); | |
| 200 } | |
| 201 | |
| 202 void TimeOut() { | |
| 203 Error("The test timed out."); | |
| 204 } | |
| 205 | |
| 206 void Error(const std::string& message) { | |
| 207 handler_->SetError(message); | |
| 208 EndTest(); | |
| 209 } | |
| 210 | |
| 211 void EndTest() { | |
| 212 if (running_) { | |
| 213 timer_.Stop(); | |
| 214 MessageLoopForUI::current()->Quit(); | |
| 215 } | |
| 216 } | |
| 217 | |
| 218 TestMessageHandler* handler_; | |
| 219 bool running_; | |
| 220 content::NotificationRegistrar registrar_; | |
| 221 base::OneShotTimer<JavascriptTestObserver> timer_; | |
| 222 | |
| 223 DISALLOW_COPY_AND_ASSIGN(JavascriptTestObserver); | |
| 224 }; | |
| 225 | |
| 226 // NaCl browser tests serve files out of the build directory because nexes and | |
| 227 // pexes are artifacts of the build. To keep things tidy, all test data is kept | |
| 228 // in a subdirectory. Several variants of a test may be run, for example when | |
| 229 // linked against newlib and when linked against glibc. These variants are kept | |
| 230 // in different subdirectories. For example, the build directory will look | |
| 231 // something like this on Linux: | |
| 232 // out/ | |
| 233 // Release/ | |
| 234 // nacl_test_data/ | |
| 235 // newlib/ | |
| 236 // glibc/ | |
| 237 bool GetNaClVariantRoot(const FilePath::StringType& variant, | |
| 238 FilePath* document_root) { | |
| 239 if (!ui_test_utils::GetRelativeBuildDirectory(document_root)) | |
| 240 return false; | |
| 241 *document_root = document_root->Append(FILE_PATH_LITERAL("nacl_test_data")); | |
| 242 *document_root = document_root->Append(variant); | |
| 243 return true; | |
| 244 } | |
| 245 | |
| 246 class NaClBrowserTestBase : public InProcessBrowserTest { | |
| 247 public: | |
| 248 NaClBrowserTestBase() {} | |
| 249 | |
| 250 virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { | |
| 251 command_line->AppendSwitch(switches::kNoFirstRun); | |
| 252 command_line->AppendSwitch(switches::kEnableNaCl); | |
| 253 } | |
| 254 | |
| 255 virtual void SetUpInProcessBrowserTestFixture() OVERRIDE { | |
| 256 // Sanity check. | |
| 257 FilePath plugin_lib; | |
| 258 ASSERT_TRUE(PathService::Get(chrome::FILE_NACL_PLUGIN, &plugin_lib)); | |
| 259 ASSERT_TRUE(file_util::PathExists(plugin_lib)) << plugin_lib.value(); | |
| 260 | |
| 261 ASSERT_TRUE(StartTestServer()) << "Cannot start test server."; | |
| 262 } | |
| 263 | |
| 264 virtual FilePath::StringType Variant() = 0; | |
| 265 | |
| 266 GURL TestURL(const FilePath::StringType& test_file) { | |
| 267 FilePath real_path = test_server_->document_root().Append(test_file); | |
| 268 EXPECT_TRUE(file_util::PathExists(real_path)) << real_path.value(); | |
| 269 | |
| 270 FilePath url_path = FilePath(FILE_PATH_LITERAL("files")); | |
| 271 url_path = url_path.Append(test_file); | |
| 272 return test_server_->GetURL(url_path.MaybeAsASCII()); | |
| 273 } | |
| 274 | |
| 275 bool RunJavascriptTest(const GURL& url, TestMessageHandler *handler) { | |
| 276 JavascriptTestObserver observer( | |
| 277 chrome::GetActiveWebContents(browser())->GetRenderViewHost(), | |
| 278 handler, | |
| 279 base::TimeDelta::FromMilliseconds(kTimeoutMs)); | |
| 280 ui_test_utils::NavigateToURL(browser(), url); | |
| 281 return observer.Run(); | |
| 282 } | |
| 283 | |
| 284 void RunLoadTest(const FilePath::StringType& test_file) { | |
| 285 LoadTestMessageHandler handler; | |
| 286 bool ok = RunJavascriptTest(TestURL(test_file), &handler); | |
| 287 ASSERT_TRUE(ok) << handler.error_message(); | |
| 288 ASSERT_TRUE(handler.test_passed()) << "Test failed."; | |
| 289 } | |
| 290 | |
| 291 private: | |
| 292 bool StartTestServer() { | |
| 293 // Launch the web server. | |
| 294 FilePath document_root; | |
| 295 if (!GetNaClVariantRoot(Variant(), &document_root)) | |
| 296 return false; | |
| 297 test_server_.reset(new net::TestServer(net::TestServer::TYPE_HTTP, | |
| 298 net::TestServer::kLocalhost, | |
| 299 document_root)); | |
| 300 return test_server_->Start(); | |
| 301 } | |
| 302 | |
| 303 scoped_ptr<net::TestServer> test_server_; | |
| 304 }; | |
| 305 | |
| 306 class NaClBrowserTestNewlib : public NaClBrowserTestBase { | |
| 307 virtual FilePath::StringType Variant() { | |
| 308 return FILE_PATH_LITERAL("newlib"); | |
| 309 } | |
| 310 }; | |
| 311 | |
| 312 class NaClBrowserTestGLibc : public NaClBrowserTestBase { | |
| 313 virtual FilePath::StringType Variant() { | |
| 314 return FILE_PATH_LITERAL("glibc"); | |
| 315 } | |
| 316 }; | |
| 317 | |
| 318 IN_PROC_BROWSER_TEST_F(NaClBrowserTestNewlib, SimpleLoadTest) { | |
| 319 RunLoadTest(FILE_PATH_LITERAL("nacl_load_test.html")); | |
| 320 } | |
| 321 | |
| 322 IN_PROC_BROWSER_TEST_F(NaClBrowserTestGLibc, SimpleLoadTest) { | |
| 323 RunLoadTest(FILE_PATH_LITERAL("nacl_load_test.html")); | |
| 324 } | |
| 325 | |
| 326 } // namespace anonymous | |
| OLD | NEW |