// Copyright 2009 The Closure Library Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. goog.provide('goog.testing.stacktraceTest'); goog.setTestOnly('goog.testing.stacktraceTest'); goog.require('goog.functions'); goog.require('goog.string'); goog.require('goog.testing.ExpectedFailures'); goog.require('goog.testing.PropertyReplacer'); goog.require('goog.testing.StrictMock'); goog.require('goog.testing.asserts'); goog.require('goog.testing.jsunit'); goog.require('goog.testing.stacktrace'); goog.require('goog.testing.stacktrace.Frame'); goog.require('goog.userAgent'); var stubs = new goog.testing.PropertyReplacer(); var expectedFailures; function setUpPage() { expectedFailures = new goog.testing.ExpectedFailures(); } function setUp() { stubs.set(goog.testing.stacktrace, 'isClosureInspectorActive_', function() { return false; }); } function tearDown() { stubs.reset(); expectedFailures.handleTearDown(); } function testParseStackFrameInV8() { var frameString = ' at Error (unknown source)'; var frame = goog.testing.stacktrace.parseStackFrame_(frameString); var expected = new goog.testing.stacktrace.Frame('', 'Error', '', ''); assertObjectEquals('exception name only', expected, frame); frameString = ' at Object.assert (file:///.../asserts.js:29:10)'; frame = goog.testing.stacktrace.parseStackFrame_(frameString); expected = new goog.testing.stacktrace.Frame( 'Object', 'assert', '', 'file:///.../asserts.js:29:10'); assertObjectEquals('context object + function name + url', expected, frame); frameString = ' at Object.x.y.z (/Users/bob/file.js:564:9)'; frame = goog.testing.stacktrace.parseStackFrame_(frameString); expected = new goog.testing.stacktrace.Frame( 'Object.x.y', 'z', '', '/Users/bob/file.js:564:9'); assertObjectEquals( 'nested context object + function name + url', expected, frame); frameString = ' at http://www.example.com/jsunit.js:117:13'; frame = goog.testing.stacktrace.parseStackFrame_(frameString); expected = new goog.testing.stacktrace.Frame( '', '', '', 'http://www.example.com/jsunit.js:117:13'); assertObjectEquals('url only', expected, frame); frameString = ' at [object Object].exec [as execute] (file:///foo)'; frame = goog.testing.stacktrace.parseStackFrame_(frameString); expected = new goog.testing.stacktrace.Frame( '[object Object]', 'exec', 'execute', 'file:///foo'); assertObjectEquals('function alias', expected, frame); frameString = ' at new Class (file:///foo)'; frame = goog.testing.stacktrace.parseStackFrame_(frameString); expected = new goog.testing.stacktrace.Frame('', 'new Class', '', 'file:///foo'); assertObjectEquals('constructor call', expected, frame); frameString = ' at new (file:///foo)'; frame = goog.testing.stacktrace.parseStackFrame_(frameString); expected = new goog.testing.stacktrace.Frame( '', 'new ', '', 'file:///foo'); assertObjectEquals('anonymous constructor call', expected, frame); frameString = ' at Array.forEach (native)'; frame = goog.testing.stacktrace.parseStackFrame_(frameString); expected = new goog.testing.stacktrace.Frame('Array', 'forEach', '', ''); assertObjectEquals('native function call', expected, frame); frameString = ' at foo (eval at file://bar)'; frame = goog.testing.stacktrace.parseStackFrame_(frameString); expected = new goog.testing.stacktrace.Frame('', 'foo', '', 'eval at file://bar'); assertObjectEquals('eval', expected, frame); frameString = ' at foo.bar (closure/goog/foo.js:11:99)'; frame = goog.testing.stacktrace.parseStackFrame_(frameString); expected = new goog.testing.stacktrace.Frame( 'foo', 'bar', '', 'closure/goog/foo.js:11:99'); assertObjectEquals('Path without schema', expected, frame); // In the Chrome console, execute: console.log(eval('Error().stack')). frameString = ' at eval (eval at (unknown source), :1:1)'; frame = goog.testing.stacktrace.parseStackFrame_(frameString); expected = new goog.testing.stacktrace.Frame( '', 'eval', '', 'eval at (unknown source), :1:1'); assertObjectEquals('nested eval', expected, frame); } function testParseStackFrameInOpera() { var frameString = '@'; var frame = goog.testing.stacktrace.parseStackFrame_(frameString); var expected = new goog.testing.stacktrace.Frame('', '', '', ''); assertObjectEquals('empty frame', expected, frame); frameString = '@javascript:console.log(Error().stack):1'; frame = goog.testing.stacktrace.parseStackFrame_(frameString); expected = new goog.testing.stacktrace.Frame( '', '', '', 'javascript:console.log(Error().stack):1'); assertObjectEquals('javascript path only', expected, frame); frameString = '@file:///foo:42'; frame = goog.testing.stacktrace.parseStackFrame_(frameString); expected = new goog.testing.stacktrace.Frame('', '', '', 'file:///foo:42'); assertObjectEquals('path only', expected, frame); // (function go() { throw Error() })() // var c = go; c() frameString = 'go([arguments not available])@'; frame = goog.testing.stacktrace.parseStackFrame_(frameString); expected = new goog.testing.stacktrace.Frame('', 'go', '', ''); assertObjectEquals('name and empty path', expected, frame); frameString = 'go([arguments not available])@file:///foo:42'; frame = goog.testing.stacktrace.parseStackFrame_(frameString); expected = new goog.testing.stacktrace.Frame('', 'go', '', 'file:///foo:42'); assertObjectEquals('name and path', expected, frame); // (function() { throw Error() })() frameString = '([arguments not available])@file:///foo:42'; frame = goog.testing.stacktrace.parseStackFrame_(frameString); expected = new goog.testing.stacktrace.Frame('', '', '', 'file:///foo:42'); assertObjectEquals('anonymous function', expected, frame); // var b = {foo: function() { throw Error() }} frameString = '()@file:///foo:42'; frame = goog.testing.stacktrace.parseStackFrame_(frameString); expected = new goog.testing.stacktrace.Frame('', 'foo', '', 'file:///foo:42'); assertObjectEquals('object literal function', expected, frame); // var c = {}; c.foo = function() { throw Error() } frameString = '()@file:///foo:42'; frame = goog.testing.stacktrace.parseStackFrame_(frameString); expected = new goog.testing.stacktrace.Frame('c', 'foo', '', 'file:///foo:42'); assertObjectEquals('named object literal function', expected, frame); frameString = '()@'; frame = goog.testing.stacktrace.parseStackFrame_(frameString); expected = new goog.testing.stacktrace.Frame('Foo.prototype', 'bar', '', ''); assertObjectEquals('prototype function', expected, frame); frameString = '()@'; frame = goog.testing.stacktrace.parseStackFrame_(frameString); expected = new goog.testing.stacktrace.Frame('goog.Foo.prototype', 'bar', '', ''); assertObjectEquals('namespaced prototype function', expected, frame); } // All test strings are parsed with the conventional and long // frame algorithms. function testParseStackFrameInFirefox() { var frameString = 'Error("Assertion failed")@:0'; var frame = goog.testing.stacktrace.parseStackFrame_(frameString); var expected = new goog.testing.stacktrace.Frame('', 'Error', '', ''); assertObjectEquals('function name + arguments', expected, frame); frameString = '()@file:///foo:42'; frame = goog.testing.stacktrace.parseStackFrame_(frameString); expected = new goog.testing.stacktrace.Frame('', '', '', 'file:///foo:42'); assertObjectEquals('anonymous function', expected, frame); frameString = '@javascript:alert(0)'; frame = goog.testing.stacktrace.parseStackFrame_(frameString); expected = new goog.testing.stacktrace.Frame('', '', '', 'javascript:alert(0)'); assertObjectEquals('anonymous function', expected, frame); } // All test strings are parsed with the conventional and long // frame algorithms. function testParseStackFrameInFirefoxWithQualifiedName() { var frameString = 'ns.method@http://some.thing/a.js:1:2'; var frame = goog.testing.stacktrace.parseStackFrame_(frameString); var expected = new goog.testing.stacktrace.Frame( '', 'ns.method', '', 'http://some.thing/a.js:1:2'); assertObjectEquals('anonymous function', expected, frame); } function testCanonicalizeFrame() { var frame = new goog.testing.stacktrace.Frame( '', 'foo', 'bar', 'http://x?a=1&b=2:1'); assertEquals( 'canonical stack frame, everything is escaped', '<window>.foo ' + '[as bar] at http://x?a=1&b=2:1', frame.toCanonicalString()); } function testDeobfuscateFunctionName() { goog.testing.stacktrace.setDeobfuscateFunctionName(function(name) { return name.replace(/\$/g, '.'); }); var frame = new goog.testing.stacktrace.Frame('', 'a$b$c', 'd$e', ''); assertEquals( 'deobfuscated function name', 'a.b.c [as d.e]', frame.toCanonicalString()); } function testFramesToString() { var normalFrame = new goog.testing.stacktrace.Frame('', 'foo', '', ''); var anonFrame = new goog.testing.stacktrace.Frame('', '', '', ''); var frames = [normalFrame, anonFrame, null, anonFrame]; var stack = goog.testing.stacktrace.framesToString_(frames); assertEquals('framesToString', '> foo\n> anonymous\n> (unknown)\n', stack); } function testFollowCallChain() { var func = function(var_args) { return goog.testing.stacktrace.followCallChain_(); }; // Created a fake type with a toString method. function LocalType(){}; LocalType.prototype.toString = function() { return 'sg'; }; // Create a mock with no expectations. var mock = new goog.testing.StrictMock(LocalType); mock.$replay(); var frames = func( undefined, null, false, 0, '', {}, goog.nullFunction, mock, new LocalType); // Opera before version 10 doesn't support the caller attribute. In that // browser followCallChain_() returns empty stack trace. expectedFailures.expectFailureFor( goog.userAgent.OPERA && !goog.userAgent.isVersionOrHigher('10')); try { assertTrue('The stack trace consists of >=2 frames', frames.length >= 2); } catch (e) { expectedFailures.handleException(e); } if (frames.length >= 2) { assertEquals('innermost function is anonymous', '', frames[0].getName()); // There are white space differences how browsers convert functions to // strings. assertEquals( 'test function name', 'testFollowCallChain', frames[1].getName()); } mock.$verify(); } // Create a stack trace string with one modest record and one long record, // Verify that all frames are parsed. The length of the long arg is set // to blow Firefox 3x's stack if put through a RegExp. function testParsingLongStackTrace() { var longArg = goog.string.buildString('(', goog.string.repeat('x', 1000000), ')'); var stackTrace = goog.string.buildString( 'shortFrame()@:0\n', 'longFrame', longArg, '@http://google.com/somescript:0\n'); var frames = goog.testing.stacktrace.parse_(stackTrace); assertEquals('number of returned frames', 2, frames.length); var expected = new goog.testing.stacktrace.Frame('', 'shortFrame', '', ''); assertObjectEquals('short frame', expected, frames[0]); assertNull('exception no frame', frames[1]); } function testParseStackFrameInIE10() { var frameString = ' at foo (http://bar:4000/bar.js:150:3)'; var frame = goog.testing.stacktrace.parseStackFrame_(frameString); var expected = new goog.testing.stacktrace.Frame( '', 'foo', '', 'http://bar:4000/bar.js:150:3'); assertObjectEquals('name and path', expected, frame); frameString = ' at Anonymous function (http://bar:4000/bar.js:150:3)'; frame = goog.testing.stacktrace.parseStackFrame_(frameString); expected = new goog.testing.stacktrace.Frame( '', 'Anonymous function', '', 'http://bar:4000/bar.js:150:3'); assertObjectEquals('Anonymous function', expected, frame); frameString = ' at Global code (http://bar:4000/bar.js:150:3)'; frame = goog.testing.stacktrace.parseStackFrame_(frameString); expected = new goog.testing.stacktrace.Frame( '', 'Global code', '', 'http://bar:4000/bar.js:150:3'); assertObjectEquals('Global code', expected, frame); frameString = ' at foo (eval code:150:3)'; frame = goog.testing.stacktrace.parseStackFrame_(frameString); expected = new goog.testing.stacktrace.Frame('', 'foo', '', 'eval code:150:3'); assertObjectEquals('eval code', expected, frame); frameString = ' at eval code (eval code:150:3)'; frame = goog.testing.stacktrace.parseStackFrame_(frameString); expected = new goog.testing.stacktrace.Frame('', 'eval code', '', 'eval code:150:3'); assertObjectEquals('nested eval', expected, frame); } function testParseStackFrameInIE11() { var frameString = ' at a.b.c (Unknown script code:150:3)'; var frame = goog.testing.stacktrace.parseStackFrame_(frameString); var expected = new goog.testing.stacktrace.Frame( '', 'a.b.c', '', 'Unknown script code:150:3'); assertObjectEquals('name and path', expected, frame); } function testParseStackFrameInEdge() { frameString = ' at a.b.c (http://host.com:80/some/file.js:101:2)'; frame = goog.testing.stacktrace.parseStackFrame_(frameString); expected = new goog.testing.stacktrace.Frame( '', 'a.b.c', '', 'http://host.com:80/some/file.js:101:2'); assertObjectEquals(expected, frame); } // Verifies that retrieving the stack trace works when the 'stack' field of an // exception contains an array of CallSites instead of a string. This is the // case when running in a lightweight V8 runtime (for instance, in gjstests), // as opposed to a browser environment. function testGetStackFrameWithV8CallSites() { // A function to create V8 CallSites. Note that CallSite is an extern and thus // cannot be mocked with closure mocks. function createCallSite(functionName, fileName, lineNumber, colNumber) { return { getFunctionName: goog.functions.constant(functionName), getFileName: goog.functions.constant(fileName), getLineNumber: goog.functions.constant(lineNumber), getColumnNumber: goog.functions.constant(colNumber) }; } // Mock the goog.testing.stacktrace.getStack_ function, which normally // triggers an exception for the purpose of reading and returning its stack // trace. Here, pretend that V8 provided an array of CallSites instead of the // string that browsers provide. stubs.set(goog.testing.stacktrace, 'getNativeStack_', function() { return [ createCallSite('fn1', 'file1', 1, 2), createCallSite('fn2', 'file2', 3, 4), createCallSite('fn3', 'file3', 5, 6) ]; }); // Retrieve the stacktrace. This should translate the array of CallSites into // a single multi-line string. var stackTrace = goog.testing.stacktrace.get(); // Make sure the stack trace was translated correctly. var frames = stackTrace.split('\n'); assertEquals(frames[0], '> fn1 at file1:1:2'); assertEquals(frames[1], '> fn2 at file2:3:4'); assertEquals(frames[2], '> fn3 at file3:5:6'); }