Skip to content

Commit

Permalink
Implement the Debug Zone
Browse files Browse the repository at this point in the history
This fixes #73

Adds a "Debug Zone" that provides debugging information for when a Task
fails to complete. it is used in conjunction with the Timeout Zone.
  • Loading branch information
matthewp committed Apr 8, 2016
1 parent cf315a6 commit 858fa4c
Show file tree
Hide file tree
Showing 4 changed files with 210 additions and 0 deletions.
1 change: 1 addition & 0 deletions debug.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require("./lib/zones/debug");
104 changes: 104 additions & 0 deletions lib/zones/debug.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
var Zone = require("../zone");
var timeoutZone = require("./timeout");

var EOL = "\n";
if(typeof process === "object" && {}.toString.call(process) === "[object process]") {
var r = require;
EOL = r("os").EOL;
}

module.exports = function(data){

var taskFns = {};
var queue = [];

return {
created: function(){
eachTask(function(name, value, tasks){
taskFns[name] = value;
tasks[name] = function(){
var fn = value.apply(this, arguments);
return function(){
var e = new Error();
var waitFor = Zone.prototype.waitFor;
Zone.prototype.waitFor = function(){
var waitFn = waitFor.apply(this, arguments);
var wrapped = function(){
var idx = queue.indexOf(wrapped);
queue.splice(idx, 1);
return waitFn.apply(this, arguments);
};
wrapped.__debugInfo = {
e: e,
task: getTaskName(name)
};
queue.push(wrapped);
return wrapped;
};
var res = fn.apply(this, arguments);
Zone.prototype.waitFor = waitFor;
return res;
};
};
});
},

ended: function(){
eachTask(function(name, value, tasks){
tasks[name] = taskFns[name];
});
},

beforeTimeout: function(){
var infos = queue.map(function(fn){
var info = fn.__debugInfo;
return {
task: info.task,
// Deleting the first two lines because it has our own
// function calls
stack: deleteLines(info.e.stack, [1,2])
};
});
data.debugInfo = infos;
queue = [];
},

plugins: [timeoutZone]
};
};

var taskNameMap = {
then: "Promise"
};
function getTaskName(name) {
return taskNameMap[name] || name;
}

function eachTask(callback){
var tasks = Zone.tasks;
for(var t in tasks) {
callback(t, tasks[t], tasks);
}
}

function deleteLines(str, lines){
var parts = str.split(EOL);
// v8 Includes an "Error" line by itself but Firefox does not
// This determines where we start deleting lines from.
var hasPrecedingMsg = parts[0] !== "Error";
var haveCut = false;

while(lines.length) {
var line = lines.shift();
if(haveCut) {
line--;
}
if(hasPrecedingMsg) {
line--;
}
parts.splice(line, 1);
haveCut = true;
}

return parts.join(EOL);
}
104 changes: 104 additions & 0 deletions test/debug_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
var assert = require("assert");
var debugZone = require("../debug");

describe("Debug Zone", function(){
it("Depends on the timeoutZone", function(){
var zone = new Zone(debugZone);
assert.equal(typeof zone.timeout, "function",
"timeoutZone is being used");
});

describe("Wrapping tasks", function(){
beforeEach(function(){
this.taskSetTimeout = Zone.tasks.setTimeout;
});

it("Cleans up after itself", function(done){
var zone = new Zone(debugZone);
var test = this;

zone.run(function(){}).then(function(){
assert.equal(Zone.tasks.setTimeout, test.taskSetTimeout,
"was reset");
done();
});

});

});

it("Gives you a stack trace", function(done){
var zone = new Zone(debugZone);
var timeout = zone.timeout(30);

zone.run(function(){
function someFunc(){
setTimeout(function(){}, 50);
}
someFunc();
});

timeout.then(function(data){
var info = data.debugInfo;

assert.ok(info, "Zone has a debugInfo");
assert.equal(info.length, 1, "has one info");
assert.equal(info[0].task, "setTimeout", "has info on a setTimeout");
assert.ok(/someFunc/.test(info[0].stack), "has someFunc in the stack");
}).then(done, done);
});

it("Doesn't include debug info when a task does complete", function(done){
var zone = new Zone(debugZone);
var timeout = zone.timeout(10);

zone.run(function(){
setTimeout(function(){});
setTimeout(function(){}, 30);
});

timeout.then(function(data){
var info = data.debugInfo;

assert.equal(info.length, 1, "There is only 1 item in the debug info");
}).then(done, done);
});

it("Includes debug info for the tasks that did not complete", function(done){
var zone = new Zone(debugZone);
var timeout = zone.timeout(20);

zone.run(function(){
setTimeout(function(){}, 10);

function someFunc(){
setTimeout(function(){}, 30);
}
someFunc();

function makePromise(){
var p = new Promise(function(){});
return p.then(function(){
Zone.current.data.failed = true;
});
}
makePromise();
});

timeout.then(function(data){
var info = data.debugInfo;

assert.equal(info.length, 2, "There were two timed out tasks");

var stInfo = info[0];
assert.equal(stInfo.task, "setTimeout", "this is for the setTimeout");
assert.ok(/someFunc/.test(stInfo.stack), "contains right function call");

var pInfo = info[1];
assert.equal(pInfo.task, "Promise", "this is for the Promise");
assert.ok(/makePromise/.test(pInfo.stack), "contains the right function call");
}).then(done, done);

});
});

1 change: 1 addition & 0 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -807,3 +807,4 @@ describe("Zone.ignore", function(){
// Require other tests
require("./xhr");
require("./timeout_test");
require("./debug_test");

0 comments on commit 858fa4c

Please sign in to comment.