strong-debugger
Version:
DevTools Remote Debugging Protocol provider for Node.js and io.js
329 lines (296 loc) • 8.47 kB
JavaScript
'use strict';
var Promise = require('./promise');
var tap = require('./tap');
var inspect = require('util').inspect;
var debuglog = require('./debuglog');
var m = require('./matchers');
module.exports = Scenario;
function Scenario() {
this._commands = [];
this._recorder = m.recorder();
this.lastReqId = 0;
this._client = null;
}
Scenario.prototype.run = function(client) {
var self = this;
this._client = client;
return this._commands.reduce(
function(cur, next) {
return cur.then(function() {
debuglog('SCENARIO STEP %s', inspect(next));
return next.run(client);
});
},
Promise.resolve())
.finally(function() {
self._client = null;
});
};
Scenario.prototype.sendRequest = function(req) {
if (req.id) {
if (req.id > this.lastReqId) this.lastReqId = req.id;
} else {
req.id = ++this.lastReqId;
}
this._commands.push({
run: function(client) {
resolveAllRefs(req);
return client.send(req).then(function() {
tap.current().pass('Send request ' + inspect(req));
});
},
inspect: function() {
return '(send request #' + req.id + ' ' + inspect(req) + ')';
}
});
return this;
};
Scenario.prototype.expectResponse = function(id, resultMatcher) {
if (arguments.length === 0) {
resultMatcher = m.isObject();
id = this.lastReqId;
} else if (arguments.length === 1) {
resultMatcher = id;
id = this.lastReqId;
}
this._commands.push({
run: function(client) {
return check();
function check() {
return client.receive().then(function(msg) {
if (!msg.hasOwnProperty('id') && msg.hasOwnProperty('method')) {
debuglog('EXPECT RESPONSE ignored server event');
return check();
}
tap.current().assertThat(
msg,
{ id: id, result: resultMatcher },
'Receive response #' + id + ' that ' + inspect(resultMatcher));
});
}
},
inspect: function() {
return '(expect response #' + id + ' that ' +
inspect(resultMatcher) + ')';
}
});
};
Scenario.prototype.expectMessage = function(matcher) {
this._commands.push({
run: function(client) {
return client.receive().then(function(msg) {
tap.current().assertThat(
msg, matcher, 'Receive message ' + inspect(matcher));
});
},
inspect: function() {
return '(expect message that ' + inspect(matcher) + ')';
}
});
return this;
};
Scenario.prototype.expectEvent = function(method, paramsMatcher) {
if (!paramsMatcher) paramsMatcher = m.isObject();
var eventList = [];
this._commands.push({
run: function(client) {
return check();
function check(timeoutInMs) {
return client.receive(timeoutInMs).then(function(msg) {
eventList.push(msg);
if (msg.method === method && !m.test(msg.params, paramsMatcher))
return check(100);
assertEventInList();
}).catch(Promise.TimeoutError, function(err) {
assertEventInList();
});
}
function assertEventInList() {
tap.current().assertThat(
eventList,
m.hasMember({
method: method,
params: paramsMatcher
}),
'Receive event ' + method + ' with params ' +
inspect(paramsMatcher));
}
},
inspect: function() {
return '(expect event ' + method + ' with params ' +
inspect(paramsMatcher) + ')';
}
});
};
Scenario.prototype.skipEvents = function(method) {
this._commands.push({
run: function(client) {
return skip();
function skip() {
return client.receive(100).then(function(msg) {
if (msg.method === method) {
debuglog('skipped', msg);
return skip();
}
client.undoReceive(msg);
tap.current().pass('Skip all events ' + method);
}).catch(Promise.TimeoutError, function(err) {
debuglog('No more events to skip.');
});
}
},
inspect: function() {
return '(skip all events ' + method + ')';
}
});
};
Scenario.prototype.delay = function(timeInMs) {
this._commands.push({
run: function(client) {
return Promise.delay(timeInMs);
},
inspect: function() {
return '(wait ' + timeInMs + 'ms)';
}
});
};
Scenario.prototype.sendInput = function(text) {
text = String(text);
this._commands.push({
run: function(client) {
client.stdin.write(text);
return Promise.resolve();
},
inspect: function() {
return '(send input ' + JSON.stringify(text) + ')';
}
});
};
Scenario.prototype.waitForStdOut = function(matcher) {
if (!matcher) matcher = m.isString();
var dataList = [];
this._commands.push({
run: function(client) {
return check();
function check(timeoutInMs) {
return client.readStdOut(timeoutInMs).then(function(data) {
data = data.toString();
dataList.push(data);
if (!m.test(data, matcher))
return check(100);
assertDataInList();
}).catch(Promise.TimeoutError, function(err) {
assertDataInList();
});
}
function assertDataInList() {
tap.current().assertThat(
dataList,
m.hasMember(matcher),
'Receive stdout data ' + inspect(matcher));
}
},
inspect: function() {
return '(wait for stdout ' + inspect(matcher) + ')';
}
});
};
Scenario.prototype.saveRef = function(key, expected) {
return this._recorder.save(key, expected);
};
Scenario.prototype.ref = function(key) {
var s = this;
var result = function resolveReference() {
return s._recorder.get(key);
};
result.inspect = function() {
return '$REF(' + key + ') ' + inspect(result());
};
// matcher API
result.test = function(actual) {
return m.test(actual, result());
};
Object.defineProperty(result, 'expectedValue', {
get: function() { return result(); }
});
return result;
};
Scenario.prototype.refScriptIdByName = function(fullPath) {
var s = this;
var result = function resolveReference() {
var id = s._client.findScriptByName(fullPath);
return id !== undefined ? id : 'Unknown script ' + fullPath;
};
result.inspect = function() {
var id = s._client.findScriptByName(fullPath);
var desc = '$SCRIPT_ID(' + fullPath + ')';
if (id !== undefined) desc = id + ' ' + desc;
return desc;
};
// matcher API
result.test = function(actual) {
return m.test(actual, result());
};
Object.defineProperty(result, 'expectedValue', {
get: function() {
return result();
}
});
return result;
};
Scenario.prototype.expectDebuggerPausedAt = function(scriptPath, lineIndex) {
var s = this;
s.skipEvents('Debugger.scriptParsed');
s.expectEvent('Debugger.paused', m.containsProperties({
callFrames: m.startsWith([
m.containsProperties({
location: m.containsProperties({
lineNumber: lineIndex,
scriptId: s.refScriptIdByName(scriptPath)
}),
}),
]),
}));
};
function resolveAllRefs(data) {
if (typeof data !== 'object' || data === null) return;
for (var k in data) {
var value = data[k];
if (typeof value === 'function' && value.name === 'resolveReference') {
data[k] = value();
} else {
resolveAllRefs(value);
}
}
}
Scenario.prototype.enableDebugger = function() {
this.sendRequest({ method: 'Debugger.enable' });
this.expectResponse();
this.skipEvents('Debugger.scriptParsed');
};
Scenario.prototype.resume = function() {
this.sendRequest({ method: 'Debugger.resume' });
this.expectResponse();
this.expectEvent('Debugger.resumed');
};
Scenario.prototype.findGlobalObjectId = function(globalVar, refKey) {
this.sendRequest({ method: 'Runtime.evaluate', params: {
expression: globalVar
}});
this.expectResponse(m.containsProperties({
result: m.containsProperties({
objectId: this.saveRef(refKey, m.isString())
}),
}));
};
Scenario.prototype.findLocalObjectId = function(localVar, refKey) {
this.sendRequest({ method: 'Debugger.evaluateOnCallFrame', params: {
expression: localVar,
frame: 0
}});
this.expectResponse(m.containsProperties({
result: m.containsProperties({
objectId: this.saveRef(refKey, m.isString())
}),
}));
};