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 "ppapi/tests/testing_instance.h" 6 7 #include <algorithm> 8 #include <cstring> 9 #include <iomanip> 10 #include <sstream> 11 #include <vector> 12 13 #include "ppapi/cpp/core.h" 14 #include "ppapi/cpp/module.h" 15 #include "ppapi/cpp/var.h" 16 #include "ppapi/cpp/view.h" 17 #include "ppapi/tests/test_case.h" 18 19 TestCaseFactory* TestCaseFactory::head_ = NULL; 20 21 // Cookie value we use to signal "we're still working." See the comment above 22 // the class declaration for how this works. 23 static const char kProgressSignal[] = "..."; 24 25 // Returns a new heap-allocated test case for the given test, or NULL on 26 // failure. 27 TestingInstance::TestingInstance(PP_Instance instance) 28 #if (defined __native_client__) 29 : pp::Instance(instance), 30 #else 31 : pp::InstancePrivate(instance), 32 #endif 33 current_case_(NULL), 34 executed_tests_(false), 35 number_tests_executed_(0), 36 nacl_mode_(false), 37 ssl_server_port_(-1), 38 websocket_port_(-1), 39 remove_plugin_(true) { 40 callback_factory_.Initialize(this); 41 } 42 43 TestingInstance::~TestingInstance() { 44 if (current_case_) 45 delete current_case_; 46 } 47 48 bool TestingInstance::Init(uint32_t argc, 49 const char* argn[], 50 const char* argv[]) { 51 for (uint32_t i = 0; i < argc; i++) { 52 if (std::strcmp(argn[i], "mode") == 0) { 53 if (std::strcmp(argv[i], "nacl") == 0) 54 nacl_mode_ = true; 55 } else if (std::strcmp(argn[i], "protocol") == 0) { 56 protocol_ = argv[i]; 57 } else if (std::strcmp(argn[i], "websocket_host") == 0) { 58 websocket_host_ = argv[i]; 59 } else if (std::strcmp(argn[i], "websocket_port") == 0) { 60 websocket_port_ = atoi(argv[i]); 61 } else if (std::strcmp(argn[i], "ssl_server_port") == 0) { 62 ssl_server_port_ = atoi(argv[i]); 63 } 64 } 65 // Create the proper test case from the argument. 66 for (uint32_t i = 0; i < argc; i++) { 67 if (std::strcmp(argn[i], "testcase") == 0) { 68 if (argv[i][0] == '\0') 69 break; 70 current_case_ = CaseForTestName(argv[i]); 71 test_filter_ = argv[i]; 72 if (!current_case_) 73 errors_.append(std::string("Unknown test case ") + argv[i]); 74 else if (!current_case_->Init()) 75 errors_.append(" Test case could not initialize."); 76 return true; 77 } 78 } 79 80 // In DidChangeView, we'll dump out a list of all available tests. 81 return true; 82 } 83 84 #if !(defined __native_client__) 85 pp::Var TestingInstance::GetInstanceObject() { 86 if (current_case_) 87 return current_case_->GetTestObject(); 88 89 return pp::VarPrivate(); 90 } 91 #endif 92 93 void TestingInstance::HandleMessage(const pp::Var& message_data) { 94 if (current_case_) 95 current_case_->HandleMessage(message_data); 96 } 97 98 void TestingInstance::DidChangeView(const pp::View& view) { 99 if (!executed_tests_) { 100 executed_tests_ = true; 101 pp::Module::Get()->core()->CallOnMainThread( 102 0, 103 callback_factory_.NewCallback(&TestingInstance::ExecuteTests)); 104 } 105 if (current_case_) 106 current_case_->DidChangeView(view); 107 } 108 109 bool TestingInstance::HandleInputEvent(const pp::InputEvent& event) { 110 if (current_case_) 111 return current_case_->HandleInputEvent(event); 112 return false; 113 } 114 115 void TestingInstance::EvalScript(const std::string& script) { 116 SendTestCommand("EvalScript", script); 117 } 118 119 void TestingInstance::SetCookie(const std::string& name, 120 const std::string& value) { 121 SendTestCommand("SetCookie", name + "=" + value); 122 } 123 124 void TestingInstance::LogTest(const std::string& test_name, 125 const std::string& error_message, 126 PP_TimeTicks start_time) { 127 // Compute the time to run the test and save it in a string for logging: 128 PP_TimeTicks end_time(pp::Module::Get()->core()->GetTimeTicks()); 129 std::ostringstream number_stream; 130 PP_TimeTicks elapsed_time(end_time - start_time); 131 number_stream << std::fixed << std::setprecision(3) << elapsed_time; 132 std::string time_string(number_stream.str()); 133 134 // Tell the browser we're still working. 135 ReportProgress(kProgressSignal); 136 137 number_tests_executed_++; 138 139 std::string html; 140 html.append("<div class=\"test_line\"><span class=\"test_name\">"); 141 html.append(test_name); 142 html.append("</span> "); 143 if (error_message.empty()) { 144 html.append("<span class=\"pass\">PASS</span>"); 145 } else { 146 html.append("<span class=\"fail\">FAIL</span>: <span class=\"err_msg\">"); 147 html.append(error_message); 148 html.append("</span>"); 149 150 if (!errors_.empty()) 151 errors_.append(", "); // Separator for different error messages. 152 errors_.append(test_name + " FAIL: " + error_message); 153 } 154 html.append(" <span class=\"time\">("); 155 html.append(time_string); 156 html.append("s)</span>"); 157 158 html.append("</div>"); 159 LogHTML(html); 160 161 std::string test_time; 162 test_time.append(test_name); 163 test_time.append(" finished in "); 164 test_time.append(time_string); 165 test_time.append(" seconds."); 166 LogTestTime(test_time); 167 } 168 169 void TestingInstance::AppendError(const std::string& message) { 170 if (!errors_.empty()) 171 errors_.append(", "); 172 errors_.append(message); 173 } 174 175 void TestingInstance::ExecuteTests(int32_t unused) { 176 ReportProgress(kProgressSignal); 177 178 // Clear the console. 179 SendTestCommand("ClearConsole"); 180 181 if (!errors_.empty()) { 182 // Catch initialization errors and output the current error string to 183 // the console. 184 LogError("Plugin initialization failed: " + errors_); 185 } else if (!current_case_) { 186 LogAvailableTests(); 187 errors_.append("FAIL: Only listed tests"); 188 } else { 189 current_case_->RunTests(test_filter_); 190 191 if (number_tests_executed_ == 0) { 192 errors_.append("No tests executed. The test filter might be too " 193 "restrictive: '" + test_filter_ + "'."); 194 LogError(errors_); 195 } 196 if (current_case_->skipped_tests().size()) { 197 // TODO(dmichael): Convert all TestCases to run all tests in one fixture, 198 // and enable this check. Currently, a lot of our tests 199 // run 1 test per fixture, which is slow. 200 /* 201 errors_.append("Some tests were not listed and thus were not run. Make " 202 "sure all tests are passed in the test_case URL (even if " 203 "they are marked DISABLED_). Forgotten tests: "); 204 std::set<std::string>::const_iterator iter = 205 current_case_->skipped_tests().begin(); 206 for (; iter != current_case_->skipped_tests().end(); ++iter) { 207 errors_.append(*iter); 208 errors_.append(" "); 209 } 210 LogError(errors_); 211 */ 212 } 213 if (current_case_->remaining_tests().size()) { 214 errors_.append("Some listed tests were not found in the TestCase. Check " 215 "the test names that were passed to make sure they match " 216 "tests in the TestCase. Unknown tests: "); 217 std::map<std::string, bool>::const_iterator iter = 218 current_case_->remaining_tests().begin(); 219 for (; iter != current_case_->remaining_tests().end(); ++iter) { 220 errors_.append(iter->first); 221 errors_.append(" "); 222 } 223 LogError(errors_); 224 } 225 } 226 227 if (remove_plugin_) 228 SendTestCommand("RemovePluginWhenFinished"); 229 std::string result(errors_); 230 if (result.empty()) 231 result = "PASS"; 232 SendTestCommand("DidExecuteTests", result); 233 // Note, DidExecuteTests may unload the plugin. We can't really do anything 234 // after this point. 235 } 236 237 TestCase* TestingInstance::CaseForTestName(const std::string& name) { 238 std::string case_name = name.substr(0, name.find_first_of('_')); 239 TestCaseFactory* iter = TestCaseFactory::head_; 240 while (iter != NULL) { 241 if (case_name == iter->name_) 242 return iter->method_(this); 243 iter = iter->next_; 244 } 245 return NULL; 246 } 247 248 void TestingInstance::SendTestCommand(const std::string& command) { 249 std::string msg("TESTING_MESSAGE:"); 250 msg += command; 251 PostMessage(pp::Var(msg)); 252 } 253 254 void TestingInstance::SendTestCommand(const std::string& command, 255 const std::string& params) { 256 SendTestCommand(command + ":" + params); 257 } 258 259 260 void TestingInstance::LogAvailableTests() { 261 // Print out a listing of all tests. 262 std::vector<std::string> test_cases; 263 TestCaseFactory* iter = TestCaseFactory::head_; 264 while (iter != NULL) { 265 test_cases.push_back(iter->name_); 266 iter = iter->next_; 267 } 268 std::sort(test_cases.begin(), test_cases.end()); 269 270 std::string html; 271 html.append("Available test cases: <dl>"); 272 for (size_t i = 0; i < test_cases.size(); ++i) { 273 html.append("<dd><a href='?testcase="); 274 html.append(test_cases[i]); 275 if (nacl_mode_) 276 html.append("&mode=nacl"); 277 html.append("'>"); 278 html.append(test_cases[i]); 279 html.append("</a></dd>"); 280 } 281 html.append("</dl>"); 282 html.append("<button onclick='RunAll()'>Run All Tests</button>"); 283 284 LogHTML(html); 285 } 286 287 void TestingInstance::LogError(const std::string& text) { 288 std::string html; 289 html.append("<span class=\"fail\">FAIL</span>: <span class=\"err_msg\">"); 290 html.append(text); 291 html.append("</span>"); 292 LogHTML(html); 293 } 294 295 void TestingInstance::LogHTML(const std::string& html) { 296 SendTestCommand("LogHTML", html); 297 } 298 299 void TestingInstance::ReportProgress(const std::string& progress_value) { 300 SendTestCommand("ReportProgress", progress_value); 301 } 302 303 void TestingInstance::AddPostCondition(const std::string& script) { 304 SendTestCommand("AddPostCondition", script); 305 } 306 307 void TestingInstance::LogTestTime(const std::string& test_time) { 308 SendTestCommand("LogTestTime", test_time); 309 } 310 311 class Module : public pp::Module { 312 public: 313 Module() : pp::Module() {} 314 virtual ~Module() {} 315 316 virtual pp::Instance* CreateInstance(PP_Instance instance) { 317 return new TestingInstance(instance); 318 } 319 }; 320 321 namespace pp { 322 323 Module* CreateModule() { 324 return new ::Module(); 325 } 326 327 } // namespace pp 328