summaryrefslogtreecommitdiffstats
path: root/gin
diff options
context:
space:
mode:
Diffstat (limited to 'gin')
-rw-r--r--gin/converter.cc8
-rw-r--r--gin/converter.h2
-rw-r--r--gin/runner.cc5
-rw-r--r--gin/runner.h2
-rw-r--r--gin/runner_unittest.cc2
-rw-r--r--gin/shell/gin_main.cc13
-rw-r--r--gin/test/expect.js289
-rw-r--r--gin/test/file_runner.cc9
-rw-r--r--gin/test/gtest.cc12
-rw-r--r--gin/try_catch.cc32
-rw-r--r--gin/try_catch.h2
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_;