1 // Copyright (c) 2011 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/base/v8_unit_test.h" 6 7 #include "base/files/file_util.h" 8 #include "base/logging.h" 9 #include "base/path_service.h" 10 #include "base/strings/string_piece.h" 11 #include "base/strings/stringprintf.h" 12 #include "chrome/common/chrome_paths.h" 13 #include "third_party/WebKit/public/web/WebKit.h" 14 15 namespace { 16 17 // |args| are passed through the various JavaScript logging functions such as 18 // console.log. Returns a string appropriate for logging with LOG(severity). 19 std::string LogArgs2String(const v8::FunctionCallbackInfo<v8::Value>& args) { 20 std::string message; 21 bool first = true; 22 for (int i = 0; i < args.Length(); i++) { 23 v8::HandleScope handle_scope(v8::Isolate::GetCurrent()); 24 if (first) 25 first = false; 26 else 27 message += " "; 28 29 v8::String::Utf8Value str(args[i]); 30 message += *str; 31 } 32 return message; 33 } 34 35 // Whether errors were seen. 36 bool had_errors = false; 37 38 // testDone results. 39 bool testResult_ok = false; 40 41 // Location of test data (currently test/data/webui). 42 base::FilePath test_data_directory; 43 44 // Location of generated test data (<(PROGRAM_DIR)/test_data). 45 base::FilePath gen_test_data_directory; 46 47 } // namespace 48 49 V8UnitTest::V8UnitTest() : handle_scope_(blink::mainThreadIsolate()) { 50 InitPathsAndLibraries(); 51 } 52 53 V8UnitTest::~V8UnitTest() { 54 } 55 56 void V8UnitTest::AddLibrary(const base::FilePath& library_path) { 57 user_libraries_.push_back(library_path); 58 } 59 60 bool V8UnitTest::ExecuteJavascriptLibraries() { 61 std::string utf8_content; 62 for (std::vector<base::FilePath>::iterator user_libraries_iterator = 63 user_libraries_.begin(); 64 user_libraries_iterator != user_libraries_.end(); 65 ++user_libraries_iterator) { 66 std::string library_content; 67 base::FilePath library_file(*user_libraries_iterator); 68 if (!user_libraries_iterator->IsAbsolute()) { 69 base::FilePath gen_file = gen_test_data_directory.Append(library_file); 70 library_file = base::PathExists(gen_file) 71 ? gen_file 72 : test_data_directory.Append(*user_libraries_iterator); 73 } 74 library_file = base::MakeAbsoluteFilePath(library_file); 75 if (!base::ReadFileToString(library_file, &library_content)) { 76 ADD_FAILURE() << library_file.value(); 77 return false; 78 } 79 ExecuteScriptInContext(library_content, library_file.MaybeAsASCII()); 80 if (::testing::Test::HasFatalFailure()) 81 return false; 82 } 83 return true; 84 } 85 86 bool V8UnitTest::RunJavascriptTestF(const std::string& testFixture, 87 const std::string& testName) { 88 had_errors = false; 89 testResult_ok = false; 90 std::string test_js; 91 if (!ExecuteJavascriptLibraries()) 92 return false; 93 94 v8::Isolate* isolate = blink::mainThreadIsolate(); 95 v8::HandleScope handle_scope(isolate); 96 v8::Local<v8::Context> context = 97 v8::Local<v8::Context>::New(isolate, context_); 98 v8::Context::Scope context_scope(context); 99 100 v8::Handle<v8::Value> functionProperty = 101 context->Global()->Get(v8::String::NewFromUtf8(isolate, "runTest")); 102 EXPECT_FALSE(functionProperty.IsEmpty()); 103 if (::testing::Test::HasNonfatalFailure()) 104 return false; 105 EXPECT_TRUE(functionProperty->IsFunction()); 106 if (::testing::Test::HasNonfatalFailure()) 107 return false; 108 v8::Handle<v8::Function> function = 109 v8::Handle<v8::Function>::Cast(functionProperty); 110 111 v8::Local<v8::Array> params = v8::Array::New(isolate); 112 params->Set(0, 113 v8::String::NewFromUtf8(isolate, 114 testFixture.data(), 115 v8::String::kNormalString, 116 testFixture.size())); 117 params->Set(1, 118 v8::String::NewFromUtf8(isolate, 119 testName.data(), 120 v8::String::kNormalString, 121 testName.size())); 122 v8::Handle<v8::Value> args[] = { 123 v8::Boolean::New(isolate, false), 124 v8::String::NewFromUtf8(isolate, "RUN_TEST_F"), params}; 125 126 v8::TryCatch try_catch; 127 v8::Handle<v8::Value> result = function->Call(context->Global(), 3, args); 128 // The test fails if an exception was thrown. 129 EXPECT_FALSE(result.IsEmpty()); 130 if (::testing::Test::HasNonfatalFailure()) 131 return false; 132 133 // Ok if ran successfully, passed tests, and didn't have console errors. 134 return result->BooleanValue() && testResult_ok && !had_errors; 135 } 136 137 void V8UnitTest::InitPathsAndLibraries() { 138 ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &test_data_directory)); 139 test_data_directory = test_data_directory.AppendASCII("webui"); 140 ASSERT_TRUE( 141 PathService::Get(chrome::DIR_GEN_TEST_DATA, &gen_test_data_directory)); 142 143 base::FilePath mockPath; 144 ASSERT_TRUE(PathService::Get(base::DIR_SOURCE_ROOT, &mockPath)); 145 mockPath = mockPath.AppendASCII("chrome"); 146 mockPath = mockPath.AppendASCII("third_party"); 147 mockPath = mockPath.AppendASCII("mock4js"); 148 mockPath = mockPath.AppendASCII("mock4js.js"); 149 AddLibrary(mockPath); 150 151 base::FilePath accessibilityAuditPath; 152 ASSERT_TRUE(PathService::Get(base::DIR_SOURCE_ROOT, &accessibilityAuditPath)); 153 accessibilityAuditPath = accessibilityAuditPath.AppendASCII("third_party"); 154 accessibilityAuditPath = 155 accessibilityAuditPath.AppendASCII("accessibility-audit"); 156 accessibilityAuditPath = accessibilityAuditPath.AppendASCII("axs_testing.js"); 157 AddLibrary(accessibilityAuditPath); 158 159 base::FilePath testApiPath; 160 ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &testApiPath)); 161 testApiPath = testApiPath.AppendASCII("webui"); 162 testApiPath = testApiPath.AppendASCII("test_api.js"); 163 AddLibrary(testApiPath); 164 } 165 166 void V8UnitTest::SetUp() { 167 v8::Isolate* isolate = blink::mainThreadIsolate(); 168 v8::Handle<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate); 169 v8::Handle<v8::String> logString = v8::String::NewFromUtf8(isolate, "log"); 170 v8::Handle<v8::FunctionTemplate> logFunction = 171 v8::FunctionTemplate::New(isolate, &V8UnitTest::Log); 172 global->Set(logString, logFunction); 173 174 // Set up chrome object for chrome.send(). 175 v8::Handle<v8::ObjectTemplate> chrome = v8::ObjectTemplate::New(isolate); 176 global->Set(v8::String::NewFromUtf8(isolate, "chrome"), chrome); 177 chrome->Set(v8::String::NewFromUtf8(isolate, "send"), 178 v8::FunctionTemplate::New(isolate, &V8UnitTest::ChromeSend)); 179 180 // Set up console object for console.log(), etc. 181 v8::Handle<v8::ObjectTemplate> console = v8::ObjectTemplate::New(isolate); 182 global->Set(v8::String::NewFromUtf8(isolate, "console"), console); 183 console->Set(logString, logFunction); 184 console->Set(v8::String::NewFromUtf8(isolate, "info"), logFunction); 185 console->Set(v8::String::NewFromUtf8(isolate, "warn"), logFunction); 186 console->Set(v8::String::NewFromUtf8(isolate, "error"), 187 v8::FunctionTemplate::New(isolate, &V8UnitTest::Error)); 188 189 context_.Reset(isolate, v8::Context::New(isolate, NULL, global)); 190 } 191 192 void V8UnitTest::SetGlobalStringVar(const std::string& var_name, 193 const std::string& value) { 194 v8::Isolate* isolate = blink::mainThreadIsolate(); 195 v8::Local<v8::Context> context = 196 v8::Local<v8::Context>::New(isolate, context_); 197 v8::Context::Scope context_scope(context); 198 context->Global()->Set( 199 v8::String::NewFromUtf8(isolate, 200 var_name.c_str(), 201 v8::String::kNormalString, 202 var_name.length()), 203 v8::String::NewFromUtf8( 204 isolate, value.c_str(), v8::String::kNormalString, value.length())); 205 } 206 207 void V8UnitTest::ExecuteScriptInContext(const base::StringPiece& script_source, 208 const base::StringPiece& script_name) { 209 v8::Isolate* isolate = blink::mainThreadIsolate(); 210 v8::HandleScope handle_scope(isolate); 211 v8::Local<v8::Context> context = 212 v8::Local<v8::Context>::New(isolate, context_); 213 v8::Context::Scope context_scope(context); 214 v8::Handle<v8::String> source = 215 v8::String::NewFromUtf8(isolate, 216 script_source.data(), 217 v8::String::kNormalString, 218 script_source.size()); 219 v8::Handle<v8::String> name = 220 v8::String::NewFromUtf8(isolate, 221 script_name.data(), 222 v8::String::kNormalString, 223 script_name.size()); 224 225 v8::TryCatch try_catch; 226 v8::Handle<v8::Script> script = v8::Script::Compile(source, name); 227 // Ensure the script compiled without errors. 228 if (script.IsEmpty()) 229 FAIL() << ExceptionToString(try_catch); 230 231 v8::Handle<v8::Value> result = script->Run(); 232 // Ensure the script ran without errors. 233 if (result.IsEmpty()) 234 FAIL() << ExceptionToString(try_catch); 235 } 236 237 std::string V8UnitTest::ExceptionToString(const v8::TryCatch& try_catch) { 238 std::string str; 239 v8::HandleScope handle_scope(v8::Isolate::GetCurrent()); 240 v8::String::Utf8Value exception(try_catch.Exception()); 241 v8::Local<v8::Message> message(try_catch.Message()); 242 if (message.IsEmpty()) { 243 str.append(base::StringPrintf("%s\n", *exception)); 244 } else { 245 v8::String::Utf8Value filename(message->GetScriptOrigin().ResourceName()); 246 int linenum = message->GetLineNumber(); 247 int colnum = message->GetStartColumn(); 248 str.append(base::StringPrintf( 249 "%s:%i:%i %s\n", *filename, linenum, colnum, *exception)); 250 v8::String::Utf8Value sourceline(message->GetSourceLine()); 251 str.append(base::StringPrintf("%s\n", *sourceline)); 252 } 253 return str; 254 } 255 256 void V8UnitTest::TestFunction(const std::string& function_name) { 257 v8::Isolate* isolate = blink::mainThreadIsolate(); 258 v8::HandleScope handle_scope(isolate); 259 v8::Local<v8::Context> context = 260 v8::Local<v8::Context>::New(isolate, context_); 261 v8::Context::Scope context_scope(context); 262 263 v8::Handle<v8::Value> functionProperty = context->Global()->Get( 264 v8::String::NewFromUtf8(isolate, function_name.c_str())); 265 ASSERT_FALSE(functionProperty.IsEmpty()); 266 ASSERT_TRUE(functionProperty->IsFunction()); 267 v8::Handle<v8::Function> function = 268 v8::Handle<v8::Function>::Cast(functionProperty); 269 270 v8::TryCatch try_catch; 271 v8::Handle<v8::Value> result = function->Call(context->Global(), 0, NULL); 272 // The test fails if an exception was thrown. 273 if (result.IsEmpty()) 274 FAIL() << ExceptionToString(try_catch); 275 } 276 277 // static 278 void V8UnitTest::Log(const v8::FunctionCallbackInfo<v8::Value>& args) { 279 LOG(INFO) << LogArgs2String(args); 280 } 281 282 void V8UnitTest::Error(const v8::FunctionCallbackInfo<v8::Value>& args) { 283 had_errors = true; 284 LOG(ERROR) << LogArgs2String(args); 285 } 286 287 void V8UnitTest::ChromeSend(const v8::FunctionCallbackInfo<v8::Value>& args) { 288 v8::HandleScope handle_scope(v8::Isolate::GetCurrent()); 289 // We expect to receive 2 args: ("testResult", [ok, message]). However, 290 // chrome.send may pass only one. Therefore we need to ensure we have at least 291 // 1, then ensure that the first is "testResult" before checking again for 2. 292 EXPECT_LE(1, args.Length()); 293 if (::testing::Test::HasNonfatalFailure()) 294 return; 295 v8::String::Utf8Value message(args[0]); 296 EXPECT_EQ("testResult", std::string(*message, message.length())); 297 if (::testing::Test::HasNonfatalFailure()) 298 return; 299 EXPECT_EQ(2, args.Length()); 300 if (::testing::Test::HasNonfatalFailure()) 301 return; 302 v8::Handle<v8::Array> testResult(args[1].As<v8::Array>()); 303 EXPECT_EQ(2U, testResult->Length()); 304 if (::testing::Test::HasNonfatalFailure()) 305 return; 306 testResult_ok = testResult->Get(0)->BooleanValue(); 307 if (!testResult_ok) { 308 v8::String::Utf8Value message(testResult->Get(1)); 309 LOG(ERROR) << *message; 310 } 311 } 312