Home | History | Annotate | Download | only in base
      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