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
15namespace {
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).
19std::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.
36bool had_errors = false;
37
38// testDone results.
39bool testResult_ok = false;
40
41// Location of test data (currently test/data/webui).
42base::FilePath test_data_directory;
43
44// Location of generated test data (<(PROGRAM_DIR)/test_data).
45base::FilePath gen_test_data_directory;
46
47}  // namespace
48
49V8UnitTest::V8UnitTest() : handle_scope_(blink::mainThreadIsolate()) {
50  InitPathsAndLibraries();
51}
52
53V8UnitTest::~V8UnitTest() {
54}
55
56void V8UnitTest::AddLibrary(const base::FilePath& library_path) {
57  user_libraries_.push_back(library_path);
58}
59
60bool 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
86bool 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
137void 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
166void 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
192void 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
207void 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
237std::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
256void 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
278void V8UnitTest::Log(const v8::FunctionCallbackInfo<v8::Value>& args) {
279  LOG(INFO) << LogArgs2String(args);
280}
281
282void V8UnitTest::Error(const v8::FunctionCallbackInfo<v8::Value>& args) {
283  had_errors = true;
284  LOG(ERROR) << LogArgs2String(args);
285}
286
287void 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