• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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()
49     : isolate_(v8::Isolate::GetCurrent()),
50       handle_scope_(isolate_) {
51   InitPathsAndLibraries();
52 }
53 
~V8UnitTest()54 V8UnitTest::~V8UnitTest() {}
55 
AddLibrary(const base::FilePath & library_path)56 void V8UnitTest::AddLibrary(const base::FilePath& library_path) {
57   user_libraries_.push_back(library_path);
58 }
59 
ExecuteJavascriptLibraries()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) ? 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(
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 
InitPathsAndLibraries()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(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 
SetUp()166 void 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 
SetGlobalStringVar(const std::string & var_name,const std::string & value)191 void 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 
ExecuteScriptInContext(const base::StringPiece & script_source,const base::StringPiece & script_name)205 void 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 
ExceptionToString(const v8::TryCatch & try_catch)234 std::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 
TestFunction(const std::string & function_name)253 void 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
Log(const v8::FunctionCallbackInfo<v8::Value> & args)274 void V8UnitTest::Log(const v8::FunctionCallbackInfo<v8::Value>& args) {
275   LOG(INFO) << LogArgs2String(args);
276 }
277 
Error(const v8::FunctionCallbackInfo<v8::Value> & args)278 void V8UnitTest::Error(const v8::FunctionCallbackInfo<v8::Value>& args) {
279   had_errors = true;
280   LOG(ERROR) << LogArgs2String(args);
281 }
282 
ChromeSend(const v8::FunctionCallbackInfo<v8::Value> & args)283 void 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