v8/test/inspector/protocol-test.js
kozyatinskiy b98dd0af92 [inspector] added creation frame for async call chains for promises
With creation frame we can show additional information with description of each async stack trace, which could help user to understand where promises were chained.
At least in case of Promise.resolve().then(foo1).then(foo2) we would be able to show following stack trace for break in foo2 callback:

foo2 (test.js:14:2)
-- Promise.resolve (test.js:29:14)--
-- Promise.resolve (test.js:28:14)--
promiseThen (test.js:30:2)

More details: https://docs.google.com/document/d/1u19N45f1gSF7M39mGsycJEK3IPyJgIXCBnWyiPeuJFE

BUG=v8:5738
R=dgozman@chromium.org,gsathya@chromium.org

Review-Url: https://codereview.chromium.org/2648873002
Cr-Commit-Position: refs/heads/master@{#42682}
2017-01-26 09:32:37 +00:00

239 lines
7.6 KiB
JavaScript

// Copyright 2016 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
InspectorTest = {};
InspectorTest._dispatchTable = new Map();
InspectorTest._requestId = 0;
InspectorTest._dumpInspectorProtocolMessages = false;
InspectorTest._eventHandler = {};
Protocol = new Proxy({}, {
get: function(target, agentName, receiver) {
return new Proxy({}, {
get: function(target, methodName, receiver) {
const eventPattern = /^on(ce)?([A-Z][A-Za-z0-9]+)/;
var match = eventPattern.exec(methodName);
if (!match) {
return (args) => InspectorTest._sendCommandPromise(`${agentName}.${methodName}`, args || {});
} else {
var eventName = match[2];
eventName = eventName.charAt(0).toLowerCase() + eventName.slice(1);
if (match[1])
return (args) => InspectorTest._waitForEventPromise(`${agentName}.${eventName}`, args || {});
else
return (listener) => { InspectorTest._eventHandler[`${agentName}.${eventName}`] = listener };
}
}
});
}
});
InspectorTest.log = print.bind(null);
InspectorTest.logMessage = function(originalMessage)
{
var message = JSON.parse(JSON.stringify(originalMessage));
if (message.id)
message.id = "<messageId>";
const nonStableFields = new Set(["objectId", "scriptId", "exceptionId", "timestamp", "executionContextId", "callFrameId", "breakpointId"]);
var objects = [ message ];
while (objects.length) {
var object = objects.shift();
for (var key in object) {
if (nonStableFields.has(key))
object[key] = `<${key}>`;
else if (typeof object[key] === "object")
objects.push(object[key]);
}
}
InspectorTest.logObject(message);
return originalMessage;
}
InspectorTest.logObject = function(object, title)
{
var lines = [];
function dumpValue(value, prefix, prefixWithName)
{
if (typeof value === "object" && value !== null) {
if (value instanceof Array)
dumpItems(value, prefix, prefixWithName);
else
dumpProperties(value, prefix, prefixWithName);
} else {
lines.push(prefixWithName + String(value).replace(/\n/g, " "));
}
}
function dumpProperties(object, prefix, firstLinePrefix)
{
prefix = prefix || "";
firstLinePrefix = firstLinePrefix || prefix;
lines.push(firstLinePrefix + "{");
var propertyNames = Object.keys(object);
propertyNames.sort();
for (var i = 0; i < propertyNames.length; ++i) {
var name = propertyNames[i];
if (!object.hasOwnProperty(name))
continue;
var prefixWithName = " " + prefix + name + " : ";
dumpValue(object[name], " " + prefix, prefixWithName);
}
lines.push(prefix + "}");
}
function dumpItems(object, prefix, firstLinePrefix)
{
prefix = prefix || "";
firstLinePrefix = firstLinePrefix || prefix;
lines.push(firstLinePrefix + "[");
for (var i = 0; i < object.length; ++i)
dumpValue(object[i], " " + prefix, " " + prefix + "[" + i + "] : ");
lines.push(prefix + "]");
}
dumpValue(object, "", title || "");
InspectorTest.log(lines.join("\n"));
}
InspectorTest.logCallFrames = function(callFrames)
{
for (var frame of callFrames) {
var functionName = frame.functionName || '(anonymous)';
var url = frame.url ? frame.url : InspectorTest._scriptMap.get(frame.location.scriptId).url;
var lineNumber = frame.location ? frame.location.lineNumber : frame.lineNumber;
var columnNumber = frame.location ? frame.location.columnNumber : frame.columnNumber;
InspectorTest.log(`${functionName} (${url}:${lineNumber}:${columnNumber})`);
}
}
InspectorTest.logAsyncStackTrace = function(asyncStackTrace)
{
while (asyncStackTrace) {
if (asyncStackTrace.promiseCreationFrame) {
var frame = asyncStackTrace.promiseCreationFrame;
InspectorTest.log(`-- ${asyncStackTrace.description} (${frame.url
}:${frame.lineNumber}:${frame.columnNumber})--`);
} else {
InspectorTest.log(`-- ${asyncStackTrace.description} --`);
}
InspectorTest.logCallFrames(asyncStackTrace.callFrames);
asyncStackTrace = asyncStackTrace.parent;
}
}
InspectorTest.completeTest = function()
{
Protocol.Debugger.disable().then(() => quit());
}
InspectorTest.completeTestAfterPendingTimeouts = function()
{
Protocol.Runtime.evaluate({
expression: "new Promise(resolve => setTimeout(resolve, 0))",
awaitPromise: true }).then(InspectorTest.completeTest);
}
InspectorTest.addScript = (string, lineOffset, columnOffset) => compileAndRunWithOrigin(string, "", lineOffset || 0, columnOffset || 0);
InspectorTest.addScriptWithUrl = (string, url) => compileAndRunWithOrigin(string, url, 0, 0);
InspectorTest.startDumpingProtocolMessages = function()
{
InspectorTest._dumpInspectorProtocolMessages = true;
}
InspectorTest.sendRawCommand = function(requestId, command, handler)
{
if (InspectorTest._dumpInspectorProtocolMessages)
print("frontend: " + command);
InspectorTest._dispatchTable.set(requestId, handler);
sendMessageToBackend(command);
}
InspectorTest.checkExpectation = function(fail, name, messageObject)
{
if (fail === !!messageObject.error) {
InspectorTest.log("PASS: " + name);
return true;
}
InspectorTest.log("FAIL: " + name + ": " + JSON.stringify(messageObject));
InspectorTest.completeTest();
return false;
}
InspectorTest.expectedSuccess = InspectorTest.checkExpectation.bind(null, false);
InspectorTest.expectedError = InspectorTest.checkExpectation.bind(null, true);
InspectorTest.setupScriptMap = function() {
if (InspectorTest._scriptMap)
return;
InspectorTest._scriptMap = new Map();
}
InspectorTest.runTestSuite = function(testSuite)
{
function nextTest()
{
if (!testSuite.length) {
InspectorTest.completeTest();
return;
}
var fun = testSuite.shift();
InspectorTest.log("\nRunning test: " + fun.name);
fun(nextTest);
}
nextTest();
}
InspectorTest._sendCommandPromise = function(method, params)
{
var requestId = ++InspectorTest._requestId;
var messageObject = { "id": requestId, "method": method, "params": params };
var fulfillCallback;
var promise = new Promise(fulfill => fulfillCallback = fulfill);
InspectorTest.sendRawCommand(requestId, JSON.stringify(messageObject), fulfillCallback);
return promise;
}
InspectorTest._waitForEventPromise = function(eventName)
{
return new Promise(fulfill => InspectorTest._eventHandler[eventName] = fullfillAndClearListener.bind(null, fulfill));
function fullfillAndClearListener(fulfill, result)
{
delete InspectorTest._eventHandler[eventName];
fulfill(result);
}
}
InspectorTest._dispatchMessage = function(messageObject)
{
if (InspectorTest._dumpInspectorProtocolMessages)
print("backend: " + JSON.stringify(messageObject));
try {
var messageId = messageObject["id"];
if (typeof messageId === "number") {
var handler = InspectorTest._dispatchTable.get(messageId);
if (handler) {
handler(messageObject);
InspectorTest._dispatchTable.delete(messageId);
}
} else {
var eventName = messageObject["method"];
var eventHandler = InspectorTest._eventHandler[eventName];
if (InspectorTest._scriptMap && eventName === "Debugger.scriptParsed")
InspectorTest._scriptMap.set(messageObject.params.scriptId, JSON.parse(JSON.stringify(messageObject.params)));
if (eventHandler)
eventHandler(messageObject);
}
} catch (e) {
InspectorTest.log("Exception when dispatching message: " + e + "\n" + e.stack + "\n message = " + JSON.stringify(messageObject, null, 2));
InspectorTest.completeTest();
}
}