diff options
Diffstat (limited to 'gin')
-rw-r--r-- | gin/converter.cc | 8 | ||||
-rw-r--r-- | gin/converter.h | 2 | ||||
-rw-r--r-- | gin/runner.cc | 5 | ||||
-rw-r--r-- | gin/runner.h | 2 | ||||
-rw-r--r-- | gin/runner_unittest.cc | 2 | ||||
-rw-r--r-- | gin/shell/gin_main.cc | 13 | ||||
-rw-r--r-- | gin/test/expect.js | 289 | ||||
-rw-r--r-- | gin/test/file_runner.cc | 9 | ||||
-rw-r--r-- | gin/test/gtest.cc | 12 | ||||
-rw-r--r-- | gin/try_catch.cc | 32 | ||||
-rw-r--r-- | gin/try_catch.h | 2 |
11 files changed, 353 insertions, 23 deletions
diff --git a/gin/converter.cc b/gin/converter.cc index e60e3a0..614e515 100644 --- a/gin/converter.cc +++ b/gin/converter.cc @@ -171,5 +171,13 @@ v8::Handle<v8::String> StringToSymbol(v8::Isolate* isolate, static_cast<uint32_t>(val.length())); } +std::string V8ToString(v8::Handle<v8::Value> value) { + if (value.IsEmpty()) + return std::string(); + std::string result; + if (!ConvertFromV8(value, &result)) + return std::string(); + return result; +} } // namespace gin diff --git a/gin/converter.h b/gin/converter.h index 2279319..c22c072 100644 --- a/gin/converter.h +++ b/gin/converter.h @@ -162,6 +162,8 @@ bool ConvertFromV8(v8::Handle<v8::Value> input, T* result) { return Converter<T>::FromV8(input, result); } +std::string V8ToString(v8::Handle<v8::Value> value); + } // namespace gin #endif // GIN_CONVERTER_H_ diff --git a/gin/runner.cc b/gin/runner.cc index 0cd7269..37e16a0 100644 --- a/gin/runner.cc +++ b/gin/runner.cc @@ -58,8 +58,9 @@ Runner::Runner(RunnerDelegate* delegate, Isolate* isolate) Runner::~Runner() { } -void Runner::Run(const std::string& script) { - Run(Script::New(StringToV8(isolate(), script))); +void Runner::Run(const std::string& source, const std::string& resource_name) { + Run(Script::New(StringToV8(isolate(), source), + StringToV8(isolate(), resource_name))); } void Runner::Run(v8::Handle<Script> script) { diff --git a/gin/runner.h b/gin/runner.h index 614b60d..21e656f 100644 --- a/gin/runner.h +++ b/gin/runner.h @@ -33,7 +33,7 @@ class Runner : public ContextHolder { Runner(RunnerDelegate* delegate, v8::Isolate* isolate); ~Runner(); - void Run(const std::string& script); + void Run(const std::string& source, const std::string& resource_name); void Run(v8::Handle<v8::Script> script); v8::Handle<v8::Value> Call(v8::Handle<v8::Function> function, diff --git a/gin/runner_unittest.cc b/gin/runner_unittest.cc index 9c3c30e..9493cf1 100644 --- a/gin/runner_unittest.cc +++ b/gin/runner_unittest.cc @@ -25,7 +25,7 @@ TEST(RunnerTest, Run) { Isolate* isolate = instance.isolate(); Runner runner(&delegate, isolate); Runner::Scope scope(&runner); - runner.Run(source); + runner.Run(source, "test_data.js"); std::string result; EXPECT_TRUE(Converter<std::string>::FromV8( diff --git a/gin/shell/gin_main.cc b/gin/shell/gin_main.cc index 9d081a3..24fa6df 100644 --- a/gin/shell/gin_main.cc +++ b/gin/shell/gin_main.cc @@ -24,11 +24,11 @@ std::string Load(const base::FilePath& path) { return source; } -void Run(base::WeakPtr<Runner> runner, const std::string& source) { +void Run(base::WeakPtr<Runner> runner, const base::FilePath& path) { if (!runner) return; Runner::Scope scope(runner.get()); - runner->Run(source); + runner->Run(Load(path), path.AsUTF8Unsafe()); } std::vector<base::FilePath> GetModuleSearchPaths() { @@ -46,7 +46,7 @@ class ShellRunnerDelegate : public ModuleRunnerDelegate { virtual void UnhandledException(Runner* runner, TryCatch& try_catch) OVERRIDE { ModuleRunnerDelegate::UnhandledException(runner, try_catch); - LOG(ERROR) << try_catch.GetPrettyMessage(); + LOG(ERROR) << try_catch.GetStackTrace(); } private: @@ -68,11 +68,16 @@ int main(int argc, char** argv) { gin::ShellRunnerDelegate delegate; gin::Runner runner(&delegate, instance.isolate()); + { + gin::Runner::Scope scope(&runner); + v8::V8::SetCaptureStackTraceForUncaughtExceptions(true); + } + CommandLine::StringVector args = CommandLine::ForCurrentProcess()->GetArgs(); for (CommandLine::StringVector::const_iterator it = args.begin(); it != args.end(); ++it) { base::MessageLoop::current()->PostTask(FROM_HERE, base::Bind( - gin::Run, runner.GetWeakPtr(), gin::Load(base::FilePath(*it)))); + gin::Run, runner.GetWeakPtr(), base::FilePath(*it))); } message_loop.RunUntilIdle(); diff --git a/gin/test/expect.js b/gin/test/expect.js new file mode 100644 index 0000000..4154456 --- /dev/null +++ b/gin/test/expect.js @@ -0,0 +1,289 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +define(["console"], function(console) { + // Equality function based on isEqual in + // Underscore.js 1.5.2 + // http://underscorejs.org + // (c) 2009-2013 Jeremy Ashkenas, + // DocumentCloud, + // and Investigative Reporters & Editors + // Underscore may be freely distributed under the MIT license. + // + function has(obj, key) { + return obj.hasOwnProperty(key); + } + function isFunction(obj) { + return typeof obj === 'function'; + } + function isArrayBufferClass(className) { + return className == '[object ArrayBuffer]' || + className.match(/\[object \w+\d+(Clamped)?Array\]/); + } + // Internal recursive comparison function for `isEqual`. + function eq(a, b, aStack, bStack) { + // Identical objects are equal. `0 === -0`, but they aren't identical. + // See the Harmony `egal` proposal: + // http://wiki.ecmascript.org/doku.php?id=harmony:egal. + if (a === b) + return a !== 0 || 1 / a == 1 / b; + // A strict comparison is necessary because `null == undefined`. + if (a == null || b == null) + return a === b; + // Compare `[[Class]]` names. + var className = toString.call(a); + if (className != toString.call(b)) + return false; + switch (className) { + // Strings, numbers, dates, and booleans are compared by value. + case '[object String]': + // Primitives and their corresponding object wrappers are equivalent; + // thus, `"5"` is equivalent to `new String("5")`. + return a == String(b); + case '[object Number]': + // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is + // performed for other numeric values. + return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b); + case '[object Date]': + case '[object Boolean]': + // Coerce dates and booleans to numeric primitive values. Dates are + // compared by their millisecond representations. Note that invalid + // dates with millisecond representations of `NaN` are not equivalent. + return +a == +b; + // RegExps are compared by their source patterns and flags. + case '[object RegExp]': + return a.source == b.source && + a.global == b.global && + a.multiline == b.multiline && + a.ignoreCase == b.ignoreCase; + } + if (typeof a != 'object' || typeof b != 'object') + return false; + // Assume equality for cyclic structures. The algorithm for detecting + // cyclic structures is adapted from ES 5.1 section 15.12.3, abstract + // operation `JO`. + var length = aStack.length; + while (length--) { + // Linear search. Performance is inversely proportional to the number of + // unique nested structures. + if (aStack[length] == a) + return bStack[length] == b; + } + // Objects with different constructors are not equivalent, but `Object`s + // from different frames are. + var aCtor = a.constructor, bCtor = b.constructor; + if (aCtor !== bCtor && !(isFunction(aCtor) && (aCtor instanceof aCtor) && + isFunction(bCtor) && (bCtor instanceof bCtor)) + && ('constructor' in a && 'constructor' in b)) { + return false; + } + // Add the first object to the stack of traversed objects. + aStack.push(a); + bStack.push(b); + var size = 0, result = true; + // Recursively compare objects and arrays. + if (className == '[object Array]' || isArrayBufferClass(className)) { + // Compare array lengths to determine if a deep comparison is necessary. + size = a.length; + result = size == b.length; + if (result) { + // Deep compare the contents, ignoring non-numeric properties. + while (size--) { + if (!(result = eq(a[size], b[size], aStack, bStack))) + break; + } + } + } else { + // Deep compare objects. + for (var key in a) { + if (has(a, key)) { + // Count the expected number of properties. + size++; + // Deep compare each member. + if (!(result = has(b, key) && eq(a[key], b[key], aStack, bStack))) + break; + } + } + // Ensure that both objects contain the same number of properties. + if (result) { + for (key in b) { + if (has(b, key) && !(size--)) + break; + } + result = !size; + } + } + // Remove the first object from the stack of traversed objects. + aStack.pop(); + bStack.pop(); + return result; + }; + + function describe(subjects) { + var descriptions = []; + Object.getOwnPropertyNames(subjects).forEach(function(name) { + if (name === "Description") + descriptions.push(subjects[name]); + else + descriptions.push(name + ": " + JSON.stringify(subjects[name])); + }); + return descriptions.join(" "); + } + + var predicates = {}; + + predicates.toBe = function(actual, expected) { + return { + "result": actual === expected, + "message": describe({ + "Actual": actual, + "Expected": expected, + }), + }; + }; + + predicates.toEqual = function(actual, expected) { + return { + "result": eq(actual, expected, [], []), + "message": describe({ + "Actual": actual, + "Expected": expected, + }), + }; + }; + + predicates.toBeDefined = function(actual) { + return { + "result": typeof actual !== "undefined", + "message": describe({ + "Actual": actual, + "Description": "Expected a defined value", + }), + }; + }; + + predicates.toBeUndefined = function(actual) { + // Recall: undefined is just a global variable. :) + return { + "result": typeof actual === "undefined", + "message": describe({ + "Actual": actual, + "Description": "Expected an undefined value", + }), + }; + }; + + predicates.toBeNull = function(actual) { + // Recall: typeof null === "object". + return { + "result": actual === null, + "message": describe({ + "Actual": actual, + "Expected": null, + }), + }; + }; + + predicates.toBeTruthy = function(actual) { + return { + "result": !!actual, + "message": describe({ + "Actual": actual, + "Description": "Expected a truthy value", + }), + }; + }; + + predicates.toBeFalsy = function(actual) { + return { + "result": !!!actual, + "message": describe({ + "Actual": actual, + "Description": "Expected a falsy value", + }), + }; + }; + + predicates.toContain = function(actual, element) { + return { + "result": (function () { + for (var i = 0; i < actual.length; ++i) { + if (eq(actual[i], element, [], [])) + return true; + } + return false; + })(), + "message": describe({ + "Actual": actual, + "Element": element, + }), + }; + }; + + predicates.toBeLessThan = function(actual, reference) { + return { + "result": actual < reference, + "message": describe({ + "Actual": actual, + "Reference": reference, + }), + }; + }; + + predicates.toBeGreaterThan = function(actual, reference) { + return { + "result": actual > reference, + "message": describe({ + "Actual": actual, + "Reference": reference, + }), + }; + }; + + predicates.toThrow = function(actual) { + return { + "result": (function () { + if (!isFunction(actual)) + throw new TypeError; + try { + actual(); + } catch (ex) { + return true; + } + return false; + })(), + "message": "Expected function to throw", + }; + } + + function negate(predicate) { + return function() { + var outcome = predicate.apply(null, arguments); + outcome.result = !outcome.result; + return outcome; + } + } + + function check(predicate) { + return function() { + var outcome = predicate.apply(null, arguments); + if (outcome.result) + return; + throw outcome.message; + }; + } + + function Condition(actual) { + this.not = {}; + Object.getOwnPropertyNames(predicates).forEach(function(name) { + var bound = predicates[name].bind(null, actual); + this[name] = check(bound); + this.not[name] = check(negate(bound)); + }, this); + } + + return function(actual) { + return new Condition(actual); + }; +}); diff --git a/gin/test/file_runner.cc b/gin/test/file_runner.cc index 0c60c4b..2bad0b2 100644 --- a/gin/test/file_runner.cc +++ b/gin/test/file_runner.cc @@ -41,7 +41,7 @@ FileRunnerDelegate::~FileRunnerDelegate() { void FileRunnerDelegate::UnhandledException(Runner* runner, TryCatch& try_catch) { ModuleRunnerDelegate::UnhandledException(runner, try_catch); - EXPECT_FALSE(try_catch.HasCaught()) << try_catch.GetPrettyMessage(); + FAIL() << try_catch.GetStackTrace(); } void RunTestFromFile(const base::FilePath& path, FileRunnerDelegate* delegate) { @@ -55,15 +55,14 @@ void RunTestFromFile(const base::FilePath& path, FileRunnerDelegate* delegate) { gin::Runner runner(delegate, instance.isolate()); { gin::Runner::Scope scope(&runner); - runner.Run(source); + v8::V8::SetCaptureStackTraceForUncaughtExceptions(true); + runner.Run(source, path.AsUTF8Unsafe()); message_loop.RunUntilIdle(); v8::Handle<v8::Value> result = runner.context()->Global()->Get( StringToSymbol(runner.isolate(), "result")); - std::string result_string; - ASSERT_TRUE(ConvertFromV8(result, &result_string)); - EXPECT_EQ("PASS", result_string); + EXPECT_EQ("PASS", V8ToString(result)); } } diff --git a/gin/test/gtest.cc b/gin/test/gtest.cc index 422d3ec..254c735 100644 --- a/gin/test/gtest.cc +++ b/gin/test/gtest.cc @@ -18,6 +18,16 @@ namespace gin { namespace { +void Fail(const v8::FunctionCallbackInfo<v8::Value>& info) { + Arguments args(info); + + std::string description; + if (!args.GetNext(&description)) + return args.ThrowError(); + + FAIL() << description; +} + void ExpectTrue(bool condition, const std::string& description) { EXPECT_TRUE(condition) << description; } @@ -48,6 +58,8 @@ v8::Local<v8::ObjectTemplate> GTest::GetTemplate(v8::Isolate* isolate) { data->GetObjectTemplate(&g_wrapper_info); if (templ.IsEmpty()) { templ = v8::ObjectTemplate::New(); + templ->Set(StringToSymbol(isolate, "fail"), + v8::FunctionTemplate::New(Fail)); templ->Set(StringToSymbol(isolate, "expectTrue"), CreateFunctionTempate(isolate, base::Bind(ExpectTrue))); templ->Set(StringToSymbol(isolate, "expectFalse"), diff --git a/gin/try_catch.cc b/gin/try_catch.cc index 302d8bd..89a969f 100644 --- a/gin/try_catch.cc +++ b/gin/try_catch.cc @@ -4,6 +4,9 @@ #include "gin/try_catch.h" +#include <sstream> + +#include "base/logging.h" #include "gin/converter.h" namespace gin { @@ -18,15 +21,26 @@ bool TryCatch::HasCaught() { return try_catch_.HasCaught(); } -std::string TryCatch::GetPrettyMessage() { - std::string info; - ConvertFromV8(try_catch_.Message()->Get(), &info); - - std::string sounce_line; - if (ConvertFromV8(try_catch_.Message()->GetSourceLine(), &sounce_line)) - info += "\n" + sounce_line; - - return info; +std::string TryCatch::GetStackTrace() { + std::stringstream ss; + v8::Handle<v8::Message> message = try_catch_.Message(); + ss << V8ToString(message->Get()) << std::endl + << V8ToString(message->GetSourceLine()) << std::endl; + + v8::Handle<v8::StackTrace> trace = message->GetStackTrace(); + if (trace.IsEmpty()) + return ss.str(); + + int len = trace->GetFrameCount(); + for (int i = 0; i < len; ++i) { + v8::Handle<v8::StackFrame> frame = trace->GetFrame(i); + ss << V8ToString(frame->GetScriptName()) << ":" + << frame->GetLineNumber() << ":" + << frame->GetColumn() << ": " + << V8ToString(frame->GetFunctionName()) + << std::endl; + } + return ss.str(); } } // namespace gin diff --git a/gin/try_catch.h b/gin/try_catch.h index 8026b3b..43f68ac 100644 --- a/gin/try_catch.h +++ b/gin/try_catch.h @@ -18,7 +18,7 @@ class TryCatch { ~TryCatch(); bool HasCaught(); - std::string GetPrettyMessage(); + std::string GetStackTrace(); private: v8::TryCatch try_catch_; |