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
14namespace {
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).
18std::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.
35bool had_errors = false;
36
37// testDone results.
38bool testResult_ok = false;
39
40// Location of test data (currently test/data/webui).
41base::FilePath test_data_directory;
42
43// Location of generated test data (<(PROGRAM_DIR)/test_data).
44base::FilePath gen_test_data_directory;
45
46}  // namespace
47
48V8UnitTest::V8UnitTest()
49    : isolate_(v8::Isolate::GetCurrent()),
50      handle_scope_(isolate_) {
51  InitPathsAndLibraries();
52}
53
54V8UnitTest::~V8UnitTest() {}
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) ? 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
85bool V8UnitTest::RunJavascriptTestF(
86    const std::string& testFixture, 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"),
123    params
124  };
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(PathService::Get(chrome::DIR_GEN_TEST_DATA,
141                               &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::Handle<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate_);
168  v8::Handle<v8::String> logString = v8::String::NewFromUtf8(isolate_, "log");
169  v8::Handle<v8::FunctionTemplate> logFunction =
170      v8::FunctionTemplate::New(&V8UnitTest::Log);
171  global->Set(logString, logFunction);
172
173  // Set up chrome object for chrome.send().
174  v8::Handle<v8::ObjectTemplate> chrome = v8::ObjectTemplate::New(isolate_);
175  global->Set(v8::String::NewFromUtf8(isolate_, "chrome"), chrome);
176  chrome->Set(v8::String::NewFromUtf8(isolate_, "send"),
177              v8::FunctionTemplate::New(isolate_, &V8UnitTest::ChromeSend));
178
179  // Set up console object for console.log(), etc.
180  v8::Handle<v8::ObjectTemplate> console = v8::ObjectTemplate::New(isolate_);
181  global->Set(v8::String::NewFromUtf8(isolate_, "console"), console);
182  console->Set(logString, logFunction);
183  console->Set(v8::String::NewFromUtf8(isolate_, "info"), logFunction);
184  console->Set(v8::String::NewFromUtf8(isolate_, "warn"), logFunction);
185  console->Set(v8::String::NewFromUtf8(isolate_, "error"),
186               v8::FunctionTemplate::New(isolate_, &V8UnitTest::Error));
187
188  context_.Reset(isolate_, v8::Context::New(isolate_, NULL, global));
189}
190
191void V8UnitTest::SetGlobalStringVar(const std::string& var_name,
192                                    const std::string& value) {
193  v8::Local<v8::Context> context =
194      v8::Local<v8::Context>::New(isolate_, context_);
195  v8::Context::Scope context_scope(context);
196  context->Global()->Set(
197      v8::String::NewFromUtf8(isolate_,
198                              var_name.c_str(),
199                              v8::String::kNormalString,
200                              var_name.length()),
201      v8::String::NewFromUtf8(
202          isolate_, value.c_str(), v8::String::kNormalString, value.length()));
203}
204
205void V8UnitTest::ExecuteScriptInContext(const base::StringPiece& script_source,
206                                        const base::StringPiece& script_name) {
207  v8::HandleScope handle_scope(isolate_);
208  v8::Local<v8::Context> context =
209      v8::Local<v8::Context>::New(isolate_, context_);
210  v8::Context::Scope context_scope(context);
211  v8::Handle<v8::String> source =
212      v8::String::NewFromUtf8(isolate_,
213                              script_source.data(),
214                              v8::String::kNormalString,
215                              script_source.size());
216  v8::Handle<v8::String> name =
217      v8::String::NewFromUtf8(isolate_,
218                              script_name.data(),
219                              v8::String::kNormalString,
220                              script_name.size());
221
222  v8::TryCatch try_catch;
223  v8::Handle<v8::Script> script = v8::Script::Compile(source, name);
224  // Ensure the script compiled without errors.
225  if (script.IsEmpty())
226    FAIL() << ExceptionToString(try_catch);
227
228  v8::Handle<v8::Value> result = script->Run();
229  // Ensure the script ran without errors.
230  if (result.IsEmpty())
231    FAIL() << ExceptionToString(try_catch);
232}
233
234std::string V8UnitTest::ExceptionToString(const v8::TryCatch& try_catch) {
235  std::string str;
236  v8::HandleScope handle_scope(v8::Isolate::GetCurrent());
237  v8::String::Utf8Value exception(try_catch.Exception());
238  v8::Local<v8::Message> message(try_catch.Message());
239  if (message.IsEmpty()) {
240    str.append(base::StringPrintf("%s\n", *exception));
241  } else {
242    v8::String::Utf8Value filename(message->GetScriptResourceName());
243    int linenum = message->GetLineNumber();
244    int colnum = message->GetStartColumn();
245    str.append(base::StringPrintf(
246        "%s:%i:%i %s\n", *filename, linenum, colnum, *exception));
247    v8::String::Utf8Value sourceline(message->GetSourceLine());
248    str.append(base::StringPrintf("%s\n", *sourceline));
249  }
250  return str;
251}
252
253void V8UnitTest::TestFunction(const std::string& function_name) {
254  v8::HandleScope handle_scope(isolate_);
255  v8::Local<v8::Context> context =
256      v8::Local<v8::Context>::New(isolate_, context_);
257  v8::Context::Scope context_scope(context);
258
259  v8::Handle<v8::Value> functionProperty = context->Global()->Get(
260      v8::String::NewFromUtf8(isolate_, function_name.c_str()));
261  ASSERT_FALSE(functionProperty.IsEmpty());
262  ASSERT_TRUE(functionProperty->IsFunction());
263  v8::Handle<v8::Function> function =
264      v8::Handle<v8::Function>::Cast(functionProperty);
265
266  v8::TryCatch try_catch;
267  v8::Handle<v8::Value> result = function->Call(context->Global(), 0, NULL);
268  // The test fails if an exception was thrown.
269  if (result.IsEmpty())
270    FAIL() << ExceptionToString(try_catch);
271}
272
273// static
274void V8UnitTest::Log(const v8::FunctionCallbackInfo<v8::Value>& args) {
275  LOG(INFO) << LogArgs2String(args);
276}
277
278void V8UnitTest::Error(const v8::FunctionCallbackInfo<v8::Value>& args) {
279  had_errors = true;
280  LOG(ERROR) << LogArgs2String(args);
281}
282
283void V8UnitTest::ChromeSend(const v8::FunctionCallbackInfo<v8::Value>& args) {
284  v8::HandleScope handle_scope(v8::Isolate::GetCurrent());
285  // We expect to receive 2 args: ("testResult", [ok, message]). However,
286  // chrome.send may pass only one. Therefore we need to ensure we have at least
287  // 1, then ensure that the first is "testResult" before checking again for 2.
288  EXPECT_LE(1, args.Length());
289  if (::testing::Test::HasNonfatalFailure())
290    return;
291  v8::String::Utf8Value message(args[0]);
292  EXPECT_EQ("testResult", std::string(*message, message.length()));
293  if (::testing::Test::HasNonfatalFailure())
294    return;
295  EXPECT_EQ(2, args.Length());
296  if (::testing::Test::HasNonfatalFailure())
297    return;
298  v8::Handle<v8::Array> testResult(args[1].As<v8::Array>());
299  EXPECT_EQ(2U, testResult->Length());
300  if (::testing::Test::HasNonfatalFailure())
301    return;
302  testResult_ok = testResult->Get(0)->BooleanValue();
303  if (!testResult_ok) {
304    v8::String::Utf8Value message(testResult->Get(1));
305    LOG(ERROR) << *message;
306  }
307}
308