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