Restore breakpoints on debugee restart.

When the debugged application is restarted, node-inspector restores all
breakpoints from the debugging session.
This commit is contained in:
Miroslav Bajtoš 2013-04-26 20:01:28 +02:00 коммит произвёл Miroslav Bajtos
Родитель b541de3cca
Коммит 87c71616ff
6 изменённых файлов: 222 добавлений и 18 удалений

55
lib/Breakpoint.js Normal file
Просмотреть файл

@ -0,0 +1,55 @@
/**
* Creates an immutable representation of a breakpoint.
*
* @param {{
* sourceID: number,
* url: string,
* line: number,
* enabled: boolean,
* condition: string,
* number: number
* }} props
* @returns {Breakpoint}
* @constructor
*/
function Breakpoint(props) {
var sourceID = props.sourceID !== undefined && props.sourceID !== null
? props.sourceID.toString()
: '';
return Object.create(Breakpoint.prototype, {
sourceID: { value: sourceID, enumerable: true },
url: { value: props.url, enumerable: true },
line: { value: props.line, enumerable: true },
enabled: {value: props.enabled, enumerable: true},
condition: {value: props.condition, enumerable: true },
number: {value: props.number, enumerable: true },
key: {
get: function() {
return this.sourceID + ':' + this.line;
}
}
});
}
Breakpoint.prototype = {
createRequest: function() {
return {
arguments: {
type: 'script',
target: this.url,
line: this.line - 1,
enabled: this.enabled,
condition: this.condition
}
};
},
sameAs: function(sourceID, line, condition) {
return this.sourceID === sourceID &&
this.line === line &&
this.condition === condition;
}
};
exports.Breakpoint = Breakpoint;

Просмотреть файл

@ -36,7 +36,6 @@ function getSession(debuggerPort) {
session.on('close', function () {
sessions[debuggerPort] = null;
});
session.attach();
}
return session;
}

Просмотреть файл

@ -1,5 +1,6 @@
var events = require('events'),
debugr = require('./debugger');
debugr = require('./debugger'),
Breakpoint = require('./Breakpoint').Breakpoint;
///////////////////////////////////////////////////////////
// exports
@ -7,6 +8,7 @@ var events = require('events'),
exports.create = function(debuggerPort, config) {
var debug = null,
conn = null,
attachedToDebugger = false,
//map from sourceID:lineNumber to breakpoint
breakpoints = {},
//map from sourceID to filename
@ -250,18 +252,28 @@ exports.create = function(debuggerPort, config) {
debug.request('listbreakpoints', {},
function(msg) {
msg.body.breakpoints.forEach(function(bp) {
var data;
var data = {
sourceID: null,
url: null,
line: bp.line + 1,
enabled: bp.active,
condition: bp.condition,
number: bp.number
};
if (bp.type === 'scriptId') {
data = {
sourceID: bp.script_id,
url: sourceIDs[bp.script_id].url,
line: bp.line + 1,
enabled: bp.active,
condition: bp.condition,
number: bp.number
};
breakpoints[bp.script_id + ':' + (bp.line + 1)] = data;
sendEvent('restoredBreakpoint', data);
data.sourceID = bp.script_id;
data.url = sourceIDs[bp.script_id].url;
} else if (bp.type == 'scriptName') {
data.url = bp.script_name;
if (bp.actual_locations && bp.actual_locations.length > 0)
data.sourceID = bp.actual_locations[0].script_id;
}
if (data.sourceID !== null || data.url !== null) {
var bpObj = new Breakpoint(data);
breakpoints[bpObj.key] = bpObj;
sendEvent('restoredBreakpoint', bpObj);
}
});
if (!msg.running) {
@ -271,6 +283,47 @@ exports.create = function(debuggerPort, config) {
});
}
function listBreakpointsToRestore(breakpointsAlreadySet) {
var breakpointArray = Object.keys(breakpoints).map(function(k) {
return breakpoints[k];
});
return breakpointArray.filter(function(breakpoint) {
function sameAsV8Breakpoint(v8data) {
return breakpoint.sameAs(
v8data.script_id,
v8data.line + 1,
v8data.condition);
}
return !breakpointsAlreadySet.some(sameAsV8Breakpoint);
});
}
function restoreSessionBreakpoints(callback) {
if (Object.keys(breakpoints).length < 1) {
callback();
return;
}
debug.request('listbreakpoints', {}, function(msg) {
var breakpointsToRestore = listBreakpointsToRestore(msg.body.breakpoints);
function restoreNext() {
if (breakpointsToRestore.length < 1) {
callback();
return;
}
var bp = breakpointsToRestore.shift();
var req = bp.createRequest();
debug.request('setbreakpoint', req, restoreNext);
}
restoreNext();
});
}
return Object.create(events.EventEmitter.prototype, {
attach: {
value: function()
@ -286,10 +339,12 @@ exports.create = function(debuggerPort, config) {
}
};
sendEvent('debuggerWasDisabled');
self.close();
// Do not close the session - keep it for debugee restart
// self.close();
attachedToDebugger = false;
});
debug.on('connect', function() {
browserConnected();
restoreSessionBreakpoints(browserConnected);
});
debug.on('exception', function(msg) {
breakEvent(msg);
@ -314,6 +369,8 @@ exports.create = function(debuggerPort, config) {
};
sendEvent('addConsoleMessage', data);
});
attachedToDebugger = true;
}
},
close: {
@ -567,15 +624,15 @@ exports.create = function(debuggerPort, config) {
handleResponse = function(msg) {
if (msg.success) {
var b = msg.body;
breakpoints[b.script_id + ':' + (b.line + 1)] = {
var breakpoint = new Breakpoint({
sourceID: b.script_id,
url: sourceIDs[b.script_id].url,
line: b.line + 1,
enabled: enabled,
condition: condition,
number: b.breakpoint
};
b.breakpoint;
});
breakpoints[breakpoint.key] = breakpoint;
var data = { success: true, actualLineNumber: b.line + 1 };
sendResponse(seq, true, data);
}
@ -945,6 +1002,8 @@ exports.create = function(debuggerPort, config) {
},
join: {
value: function(ws_connection) {
if (!attachedToDebugger)
this.attach();
var self = this;
conn = ws_connection;
conn.on('message', function(data) {

Просмотреть файл

@ -14,5 +14,12 @@
"dependencies": {
"socket.io": "~0.8.2",
"paperboy": "~0.0.2"
},
"devDependencies": {
"mocha": "latest",
"chai": "latest"
},
"scripts": {
"test": "mocha"
}
}

83
test/Breakpoint.js Normal file
Просмотреть файл

@ -0,0 +1,83 @@
var expect = require('chai').expect,
Breakpoint = require('../lib/Breakpoint').Breakpoint;
describe('Breakpoint', function() {
describe('key', function() {
it('should return scriptID:line', function() {
var bp = aBreakpoint({sourceID: 10, line: 20});
expect(bp.key).to.equal('10:20');
})
});
describe('createRequest()', function() {
it('should create "script" type request', function() {
var bp = aBreakpoint({
url: '/script/url',
line: 1,
enabled: true,
condition: 'a-condition'
});
var request = bp.createRequest();
expect(request.arguments).to.deep.equal({
type: 'script',
target: '/script/url',
line: 0, // V8 use zero-based line numbers
enabled: true,
condition: 'a-condition'
});
});
});
describe('sameAs()', function() {
var aLineNumber = 1;
var anotherLineNumber = 2;
var bp;
beforeEach(function() {
bp = aBreakpoint({
sourceID: 'a-source-id',
line: aLineNumber,
condition: 'a-condition'
});
})
it('returns true for matching breakpoint', function() {
var result = bp.sameAs('a-source-id', aLineNumber, 'a-condition');
expect(result).to.equal(true);
})
it('returns false for different source id', function() {
var result = bp.sameAs('another-source-id', aLineNumber, 'a-condition')
expect(result).to.equal(false);
})
it('returns false for different line', function() {
var result = bp.sameAs('a-source-id', anotherLineNumber, 'a-condition')
expect(result).to.equal(false);
})
it('returns false for different condition', function() {
var result = bp.sameAs('a-source-id', aLineNumber, 'another-condition')
expect(result).to.equal(false);
})
});
})
;
function aBreakpoint(props) {
var val = {
sourceID: 100, // arbitrary number
url: 'http://some/script/url',
line: 10, // arbitrary number
enabled: true,
condition: null,
number: 1 // arbitrary number
};
for (var p in props) {
val[p] = props[p];
}
return new Breakpoint(props);
}

1
test/mocha.opts Normal file
Просмотреть файл

@ -0,0 +1 @@
--require chai