Bug 866306 - Add a library to make devtools protocol interaction a bit easier. r=jimb

--HG--
extra : rebase_source : 1b8d8b42ccea6bfea01297a16a561e55f192d554
This commit is contained in:
Dave Camp 2013-06-06 12:29:27 -07:00
parent 14b0f79bbe
commit 26d4bf3695
9 changed files with 2444 additions and 9 deletions

View File

@ -225,6 +225,7 @@ this.DebuggerClient = function DebuggerClient(aTransport)
]);
this.request = this.request.bind(this);
this.localTransport = (this._transport instanceof LocalDebuggerTransport);
/*
* As the first thing on the connection, expect a greeting packet from
@ -601,6 +602,14 @@ DebuggerClient.prototype = {
return;
}
// If we have a registered Front for this actor, let it handle the packet
// and skip all the rest of this unpleasantness.
let front = this.getActor(aPacket.from);
if (front) {
front.onPacket(aPacket);
return;
}
let onResponse;
// See if we have a handler function waiting for a reply from this
// actor. (Don't count unsolicited notifications or pauses as
@ -654,6 +663,36 @@ DebuggerClient.prototype = {
onClosed: function DC_onClosed(aStatus) {
this.notify("closed");
},
/**
* Actor lifetime management, echos the server's actor pools.
*/
__pools: null,
get _pools() {
if (this.__pools) {
return this.__pools;
}
this.__pools = new Set();
return this.__pools;
},
addActorPool: function(pool) {
this._pools.add(pool);
},
removeActorPool: function(pool) {
this._pools.delete(pool);
},
getActor: function(actorID) {
let pool = this.poolFor(actorID);
return pool ? pool.get(actorID) : null;
},
poolFor: function(actorID) {
for (let pool of this._pools) {
if (pool.has(actorID)) return pool;
}
return null;
}
}
eventSource(DebuggerClient.prototype);

View File

@ -0,0 +1,145 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
let {Cu} = require("chrome");
let {DebuggerServer} = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
let promise = require("sdk/core/promise");
let {Class} = require("sdk/core/heritage");
let protocol = require("devtools/server/protocol");
let {method, Arg, Option, RetVal} = protocol;
exports.LongStringActor = protocol.ActorClass({
typeName: "longstractor",
initialize: function(conn, str) {
protocol.Actor.prototype.initialize.call(this, conn);
this.str = str;
this.short = (this.str.length < DebuggerServer.LONG_STRING_LENGTH);
},
destroy: function() {
this.str = null;
protocol.Actor.prototype.destroy.call(this);
},
form: function() {
if (this.short) {
return this.str;
}
return {
type: "longString",
actor: this.actorID,
length: this.str.length,
initial: this.str.substring(0, DebuggerServer.LONG_STRING_INITIAL_LENGTH)
}
},
substring: method(function(start, end) {
return promise.resolve(this.str.substring(start, end));
}, {
request: {
start: Arg(0),
end: Arg(1)
},
response: { substring: RetVal() },
}),
release: method(function() { }, { release: true })
});
/**
* When a LongString on the server is short enough to be passed
* as a full string, the client will get a ShortLongString instead of
* a LongStringFront. Its API should match.
*
* I'm very proud of this name.
*/
exports.ShortLongString = Class({
initialize: function(str) {
this.str = str;
},
get length() { return this.str.length; },
get initial() { return this.str; },
string: function() { return promise.resolve(this.str) },
substring: function(start, end) {
return promise.resolve(this.str.substring(start, end));
},
release: function() {
this.str = null;
return promise.resolve(undefined);
}
})
exports.LongStringFront = protocol.FrontClass(exports.LongStringActor, {
initialize: function(client, form) {
// Don't give the form by default, because we're being tricky and it might just
// be a string.
protocol.Front.prototype.initialize.call(this, client, null);
this.form(form);
},
destroy: function() {
this.initial = null;
this.length = null;
this.strPromise = null;
protocol.Front.prototype.destroy.call(this);
},
form: function(form) {
this.actorID = form.actorID;
this.initial = form.initial;
this.length = form.length;
},
string: function() {
if (!this.strPromise) {
let promiseRest = (thusFar) => {
if (thusFar.length === this.length)
return promise.resolve(thusFar);
else {
return this.substring(thusFar.length,
thusFar.length + DebuggerServer.LONG_STRING_READ_LENGTH)
.then((next) => promiseRest(thusFar + next));
}
}
this.strPromise = promiseRest(this.initial);
}
return this.strPromise;
}
});
// The long string actor needs some custom marshalling, because it is sometimes
// returned as a primitive rather than a complete form.
let stringActorType = protocol.types.getType("longstractor");
protocol.types.addType("longstring", {
_actor: true,
write: (value, context, detail) => {
if (!(context instanceof protocol.Actor)) {
throw Error("Passing a longstring as an argument isn't supported.");
}
if (value.short) {
return value.str;
} else {
return stringActorType.write(value, context, detail);
}
},
read: (value, context, detail) => {
if (context instanceof protocol.Actor) {
throw Error("Passing a longstring as an argument isn't supported.");
}
if (typeof(value) === "string") {
return exports.ShortLongString(value);
}
return stringActorType.read(value, context, detail);
}
});

View File

@ -119,6 +119,7 @@ var DebuggerServer = {
LONG_STRING_LENGTH: 10000,
LONG_STRING_INITIAL_LENGTH: 1000,
LONG_STRING_READ_LENGTH: 1000,
/**
* A handler function that prompts the user to accept or decline the incoming
@ -608,6 +609,13 @@ ActorPool.prototype = {
delete this._cleanups[aActor.actorID];
},
/**
* Match the api expected by the protocol library.
*/
unmanage: function(aActor) {
return this.removeActor(aActor);
},
/**
* Run all actor cleanups.
*/
@ -702,6 +710,13 @@ DebuggerServerConnection.prototype = {
this._actorPool.removeActor(aActor);
},
/**
* Match the api expected by the protocol library.
*/
unmanage: function(aActor) {
return this.removeActor(aActor);
},
/**
* Look up an actor implementation for an actorID. Will search
* all the actor pools registered with the connection.
@ -710,14 +725,9 @@ DebuggerServerConnection.prototype = {
* Actor ID to look up.
*/
getActor: function DSC_getActor(aActorID) {
if (this._actorPool.has(aActorID)) {
return this._actorPool.get(aActorID);
}
for each (let pool in this._extraPools) {
if (pool.has(aActorID)) {
return pool.get(aActorID);
}
let pool = this.poolFor(aActorID);
if (pool) {
return pool.get(aActorID);
}
if (aActorID === "root") {
@ -727,6 +737,19 @@ DebuggerServerConnection.prototype = {
return null;
},
poolFor: function DSC_actorPool(aActorID) {
if (this._actorPool && this._actorPool.has(aActorID)) {
return this._actorPool;
}
for (let pool of this._extraPools) {
if (pool.has(aActorID)) {
return pool;
}
}
return null;
},
_unknownError: function DSC__unknownError(aPrefix, aError) {
let errorString = safeErrorString(aError);
errorString += "\n" + aError.stack;
@ -777,11 +800,14 @@ DebuggerServerConnection.prototype = {
// Dispatch the request to the actor.
if (actor.requestTypes && actor.requestTypes[aPacket.type]) {
try {
ret = actor.requestTypes[aPacket.type].bind(actor)(aPacket);
this.currentPacket = aPacket;
ret = actor.requestTypes[aPacket.type].bind(actor)(aPacket, this);
} catch(e) {
this.transport.send(this._unknownError(
"error occurred while processing '" + aPacket.type,
e));
} finally {
delete this.currentPacket;
}
} else {
ret = { error: "unrecognizedPacketType",

File diff suppressed because it is too large Load Diff

View File

@ -17,6 +17,7 @@ Services.prefs.setBoolPref("devtools.debugger.remote-enabled", true);
Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
Cu.import("resource://gre/modules/devtools/dbg-client.jsm");
Cu.import("resource://gre/modules/devtools/Loader.jsm");
function testExceptionHook(ex) {
try {
@ -233,3 +234,92 @@ function writeFile(aFileName, aContent) {
stream.close();
}
}
function connectPipeTracing() {
return new TracingTransport(DebuggerServer.connectPipe());
}
function TracingTransport(childTransport) {
this.hooks = null;
this.child = childTransport;
this.child.hooks = this;
this.expectations = [];
this.packets = [];
this.checkIndex = 0;
}
function deepEqual(a, b) {
if (a === b)
return true;
if (typeof a != "object" || typeof b != "object")
return false;
if (a === null || b === null)
return false;
if (Object.keys(a).length != Object.keys(b).length)
return false;
for (let k in a) {
if (!deepEqual(a[k], b[k]))
return false;
}
return true;
}
TracingTransport.prototype = {
// Remove actor names
normalize: function(packet) {
return JSON.parse(JSON.stringify(packet, (key, value) => {
if (key === "to" || key === "from" || key === "actor") {
return "<actorid>";
}
return value;
}));
},
send: function(packet) {
this.packets.push({
type: "sent",
packet: this.normalize(packet)
});
return this.child.send(packet);
},
close: function() {
return this.child.close();
},
ready: function() {
return this.child.ready();
},
onPacket: function(packet) {
this.packets.push({
type: "received",
packet: this.normalize(packet)
});
this.hooks.onPacket(packet);
},
onClosed: function() {
this.hooks.onClosed();
},
expectSend: function(expected) {
let packet = this.packets[this.checkIndex++];
do_check_eq(packet.type, "sent");
do_check_true(deepEqual(packet.packet, this.normalize(expected)));
},
expectReceive: function(expected) {
let packet = this.packets[this.checkIndex++];
do_check_eq(packet.type, "received");
do_check_true(deepEqual(packet.packet, this.normalize(expected)));
},
// Write your tests, call dumpLog at the end, inspect the output,
// then sprinkle the calls through the right places in your test.
dumpLog: function() {
for (let entry of this.packets) {
if (entry.type === "sent") {
dump("trace.expectSend(" + entry.packet + ");\n");
} else {
dump("trace.expectReceive(" + entry.packet + ");\n");
}
}
}
};

View File

@ -0,0 +1,434 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test simple requests using the protocol helpers.
*/
let protocol = devtools.require("devtools/server/protocol");
let {method, preEvent, types, Arg, Option, RetVal} = protocol;
let {resolve} = devtools.require("sdk/core/promise");
let events = devtools.require("sdk/event/core");
function simpleHello() {
return {
from: "root",
applicationType: "xpcshell-tests",
traits: [],
}
}
let testTypes = {};
// Predeclaring the actor type so that it can be used in the
// implementation of the child actor.
types.addActorType("childActor");
let ChildActor = protocol.ActorClass({
typeName: "childActor",
// Actors returned by this actor should be owned by the root actor.
marshallPool: function() { return this.parent() },
toString: function() "[ChildActor " + this.childID + "]",
initialize: function(conn, id) {
protocol.Actor.prototype.initialize.call(this, conn);
this.childID = id;
},
destroy: function() {
protocol.Actor.prototype.destroy.call(this);
this.destroyed = true;
},
form: function(detail) {
return {
actor: this.actorID,
childID: this.childID,
detail: detail
};
},
echo: method(function(str) {
return str;
}, {
request: { str: Arg(0) },
response: { str: RetVal("string") },
telemetry: "ECHO"
}),
getDetail1: method(function() {
return this;
}, {
// This also exercises return-value-as-packet.
response: RetVal("childActor#detail1"),
}),
getDetail2: method(function() {
return this;
}, {
// This also exercises return-value-as-packet.
response: RetVal("childActor#detail2"),
}),
getSibling: method(function(id) {
return this.parent().getChild(id);
}, {
request: { id: Arg(0) },
response: { sibling: RetVal("childActor") }
}),
emitEvents: method(function() {
events.emit(this, "event1", 1, 2, 3);
events.emit(this, "named-event", 1, 2, 3);
events.emit(this, "object-event", this);
events.emit(this, "array-object-event", [this]);
}, {
response: { value: "correct response" },
}),
release: method(function() { }, { release: true }),
events: {
"event1" : {
a: Arg(0),
b: Arg(1),
c: Arg(2)
},
"named-event": {
type: "namedEvent",
a: Arg(0),
b: Arg(1),
c: Arg(2)
},
"object-event": {
type: "objectEvent",
detail: Arg(0, "childActor#detail1"),
},
"array-object-event": {
type: "arrayObjectEvent",
detail: Arg(0, "array:childActor#detail2"),
}
}
});
let ChildFront = protocol.FrontClass(ChildActor, {
initialize: function(client, form) {
protocol.Front.prototype.initialize.call(this, client, form);
},
destroy: function() {
this.destroyed = true;
protocol.Front.prototype.destroy.call(this);
},
marshallPool: function() { return this.parent() },
toString: function() "[child front " + this.childID + "]",
form: function(form) {
this.childID = form.childID;
this.detail = form.detail;
},
onEvent1: preEvent("event1", function(a, b, c) {
this.event1arg3 = c;
}),
});
types.addDictType("manyChildrenDict", {
child5: "childActor",
more: "array:childActor",
});
types.addLifetime("temp", "_temporaryHolder");
let rootActor = null;
let RootActor = protocol.ActorClass({
typeName: "root",
toString: function() "[root actor]",
initialize: function(conn) {
rootActor = this;
this.actorID = "root";
this._children = {};
protocol.Actor.prototype.initialize.call(this, conn);
// Root actor owns itself.
this.manage(this);
},
toString: function() "root actor",
sayHello: simpleHello,
getChild: method(function(id) {
if (id in this._children) {
return this._children[id];
}
let child = new ChildActor(this.conn, id);
this._children[id] = child;
return child;
}, {
request: { str: Arg(0) },
response: { actor: RetVal("childActor") },
}),
getChildren: method(function(ids) {
return [this.getChild(id) for (id of ids)];
}, {
request: { ids: Arg(0, "array:string") },
response: { children: RetVal("array:childActor") },
}),
getManyChildren: method(function() {
return {
foo: "bar", // note that this isn't in the specialization array.
child5: this.getChild("child5"),
more: [ this.getChild("child6"), this.getChild("child7") ]
}
}, {
response: RetVal("manyChildrenDict")
}),
// This should remind you of a pause actor.
getTemporaryChild: method(function(id) {
if (!this._temporaryHolder) {
this._temporaryHolder = this.manage(new protocol.Actor(this.conn));
}
return new ChildActor(this.conn, id);
}, {
request: { id: Arg(0) },
response: { child: RetVal("temp:childActor") }
}),
clearTemporaryChildren: method(function(id) {
if (!this._temporaryHolder) {
return;
}
this._temporaryHolder.destroy();
delete this._temporaryHolder;
})
});
let RootFront = protocol.FrontClass(RootActor, {
toString: function() "[root front]",
initialize: function(client) {
this.actorID = "root";
protocol.Front.prototype.initialize.call(this, client);
// Root actor owns itself.
this.manage(this);
},
getTemporaryChild: protocol.custom(function(id) {
if (!this._temporaryHolder) {
this._temporaryHolder = this.manage(new protocol.Front(this.conn, {actor: this.actorID + "_temp"}));
}
return this._getTemporaryChild(id);
},{
impl: "_getTemporaryChild"
}),
clearTemporaryChildren: protocol.custom(function() {
if (!this._temporaryHolder) {
return;
}
this._temporaryHolder.destroy();
delete this._temporaryHolder;
return this._clearTemporaryChildren();
}, {
impl: "_clearTemporaryChildren"
})
});
function run_test()
{
DebuggerServer.createRootActor = (conn => {
return RootActor(conn);
});
DebuggerServer.init(() => true);
let trace = connectPipeTracing();
let client = new DebuggerClient(trace);
client.connect((applicationType, traits) => {
trace.expectReceive({"from":"<actorid>","applicationType":"xpcshell-tests","traits":[]})
do_check_eq(applicationType, "xpcshell-tests");
let rootFront = RootFront(client);
let childFront = null;
let expectRootChildren = size => {
do_check_eq(rootActor._poolMap.size, size + 1);
do_check_eq(rootFront._poolMap.size, size + 1);
if (childFront) {
do_check_eq(childFront._poolMap.size, 0);
}
};
rootFront.getChild("child1").then(ret => {
trace.expectSend({"type":"getChild","str":"child1","to":"<actorid>"})
trace.expectReceive({"actor":"<actorid>","from":"<actorid>"})
childFront = ret;
do_check_true(childFront instanceof ChildFront);
do_check_eq(childFront.childID, "child1");
expectRootChildren(1);
}).then(() => {
// Request the child again, make sure the same is returned.
return rootFront.getChild("child1");
}).then(ret => {
trace.expectSend({"type":"getChild","str":"child1","to":"<actorid>"})
trace.expectReceive({"actor":"<actorid>","from":"<actorid>"})
expectRootChildren(1);
do_check_true(ret === childFront);
}).then(() => {
return childFront.echo("hello");
}).then(ret => {
trace.expectSend({"type":"echo","str":"hello","to":"<actorid>"})
trace.expectReceive({"str":"hello","from":"<actorid>"})
do_check_eq(ret, "hello");
}).then(() => {
return childFront.getDetail1();
}).then(ret => {
trace.expectSend({"type":"getDetail1","to":"<actorid>"});
trace.expectReceive({"actor":"<actorid>","childID":"child1","detail":"detail1","from":"<actorid>"});
do_check_true(ret === childFront);
do_check_eq(childFront.detail, "detail1");
}).then(() => {
return childFront.getDetail2();
}).then(ret => {
trace.expectSend({"type":"getDetail2","to":"<actorid>"});
trace.expectReceive({"actor":"<actorid>","childID":"child1","detail":"detail2","from":"<actorid>"});
do_check_true(ret === childFront);
do_check_eq(childFront.detail, "detail2");
}).then(() => {
return childFront.getSibling("siblingID");
}).then(ret => {
trace.expectSend({"type":"getSibling","id":"siblingID","to":"<actorid>"});
trace.expectReceive({"sibling":{"actor":"<actorid>","childID":"siblingID"},"from":"<actorid>"});
expectRootChildren(2);
}).then(ret => {
return rootFront.getTemporaryChild("temp1").then(temp1 => {
trace.expectSend({"type":"getTemporaryChild","id":"temp1","to":"<actorid>"});
trace.expectReceive({"child":{"actor":"<actorid>","childID":"temp1"},"from":"<actorid>"});
// At this point we expect two direct children, plus the temporary holder
// which should hold 1 itself.
do_check_eq(rootActor._temporaryHolder.__poolMap.size, 1);
do_check_eq(rootFront._temporaryHolder.__poolMap.size, 1);
expectRootChildren(3);
return rootFront.getTemporaryChild("temp2").then(temp2 => {
trace.expectSend({"type":"getTemporaryChild","id":"temp2","to":"<actorid>"});
trace.expectReceive({"child":{"actor":"<actorid>","childID":"temp2"},"from":"<actorid>"});
// Same amount of direct children, and an extra in the temporary holder.
expectRootChildren(3);
do_check_eq(rootActor._temporaryHolder.__poolMap.size, 2);
do_check_eq(rootFront._temporaryHolder.__poolMap.size, 2);
// Get the children of the temporary holder...
let checkActors = [entry[1] for (entry of rootActor._temporaryHolder.__poolMap)];
let checkFronts = [entry[1] for (entry of rootFront._temporaryHolder.__poolMap)];
// Now release the temporary holders and expect them to drop again.
return rootFront.clearTemporaryChildren().then(() => {
trace.expectSend({"type":"clearTemporaryChildren","to":"<actorid>"});
trace.expectReceive({"from":"<actorid>"});
expectRootChildren(2);
do_check_false(!!rootActor._temporaryHolder);
do_check_false(!!rootFront._temporaryHolder);
for (let checkActor of checkActors) {
do_check_true(checkActor.destroyed);
do_check_true(checkActor.destroyed);
}
});
});
})
}).then(ret => {
return rootFront.getChildren(["child1", "child2"]);
}).then(ret => {
trace.expectSend({"type":"getChildren","ids":["child1","child2"],"to":"<actorid>"});
trace.expectReceive({"children":[{"actor":"<actorid>","childID":"child1"},{"actor":"<actorid>","childID":"child2"}],"from":"<actorid>"});
expectRootChildren(3);
do_check_true(ret[0] === childFront);
do_check_true(ret[1] !== childFront);
do_check_true(ret[1] instanceof ChildFront);
// On both children, listen to events. We're only
// going to trigger events on the first child, so an event
// triggered on the second should cause immediate failures.
let set = new Set(["event1", "named-event", "object-event", "array-object-event"]);
childFront.on("event1", (a, b, c) => {
do_check_eq(a, 1);
do_check_eq(b, 2);
do_check_eq(c, 3);
// Verify that the pre-event handler was called.
do_check_eq(childFront.event1arg3, 3);
set.delete("event1");
});
childFront.on("named-event", (a, b, c) => {
do_check_eq(a, 1);
do_check_eq(b, 2);
do_check_eq(c, 3);
set.delete("named-event");
});
childFront.on("object-event", (obj) => {
do_check_true(obj === childFront);
do_check_eq(childFront.detail, "detail1");
set.delete("object-event");
});
childFront.on("array-object-event", (array) => {
do_check_true(array[0] === childFront);
do_check_eq(childFront.detail, "detail2");
set.delete("array-object-event");
});
let fail = function() {
do_throw("Unexpected event");
}
ret[1].on("event1", fail);
ret[1].on("named-event", fail);
ret[1].on("object-event", fail);
ret[1].on("array-object-event", fail);
return childFront.emitEvents().then(() => {
trace.expectSend({"type":"emitEvents","to":"<actorid>"});
trace.expectReceive({"type":"event1","a":1,"b":2,"c":3,"from":"<actorid>"});
trace.expectReceive({"type":"namedEvent","a":1,"b":2,"c":3,"from":"<actorid>"});
trace.expectReceive({"type":"objectEvent","detail":{"actor":"<actorid>","childID":"child1","detail":"detail1"},"from":"<actorid>"});
trace.expectReceive({"type":"arrayObjectEvent","detail":[{"actor":"<actorid>","childID":"child1","detail":"detail2"}],"from":"<actorid>"});
trace.expectReceive({"value":"correct response","from":"<actorid>"});
do_check_eq(set.size, 0);
});
}).then(ret => {
return rootFront.getManyChildren();
}).then(ret => {
trace.expectSend({"type":"getManyChildren","to":"<actorid>"});
trace.expectReceive({"foo":"bar","child5":{"actor":"<actorid>","childID":"child5"},"more":[{"actor":"<actorid>","childID":"child6"},{"actor":"<actorid>","childID":"child7"}],"from":"<actorid>"});
// Check all the crazy stuff we did in getManyChildren
do_check_eq(ret.foo, "bar");
do_check_eq(ret.child5.childID, "child5");
do_check_eq(ret.more[0].childID, "child6");
do_check_eq(ret.more[1].childID, "child7");
}).then(() => {
client.close(() => {
do_test_finished();
});
}).then(null, err => {
do_report_unexpected_exception(err, "Failure executing test");
});
});
do_test_pending();
}

View File

@ -0,0 +1,204 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test simple requests using the protocol helpers.
*/
let protocol = devtools.require("devtools/server/protocol");
let {method, RetVal, Arg, Option} = protocol;
let {defer, resolve} = devtools.require("sdk/core/promise");
let events = devtools.require("sdk/event/core");
let {LongStringActor} = devtools.require("devtools/server/actors/string");
function simpleHello() {
return {
from: "root",
applicationType: "xpcshell-tests",
traits: [],
}
}
DebuggerServer.LONG_STRING_LENGTH = DebuggerServer.LONG_STRING_INITIAL_LENGTH = DebuggerServer.LONG_STRING_READ_LENGTH = 5;
let SHORT_STR = "abc";
let LONG_STR = "abcdefghijklmnop";
let rootActor = null;
let RootActor = protocol.ActorClass({
typeName: "root",
initialize: function(conn) {
rootActor = this;
protocol.Actor.prototype.initialize.call(this, conn);
// Root actor owns itself.
this.manage(this);
this.actorID = "root";
},
sayHello: simpleHello,
shortString: method(function() {
return new LongStringActor(this.conn, SHORT_STR);
}, {
response: { value: RetVal("longstring") },
}),
longString: method(function() {
return new LongStringActor(this.conn, LONG_STR);
}, {
response: { value: RetVal("longstring") },
}),
emitShortString: method(function() {
events.emit(this, "string-event", new LongStringActor(this.conn, SHORT_STR));
}, {
oneway: true,
}),
emitLongString: method(function() {
events.emit(this, "string-event", new LongStringActor(this.conn, LONG_STR));
}, {
oneway: true,
}),
events: {
"string-event": {
str: Arg(0, "longstring")
}
}
});
let RootFront = protocol.FrontClass(RootActor, {
initialize: function(client) {
this.actorID = "root";
protocol.Front.prototype.initialize.call(this, client);
// Root owns itself.
this.manage(this);
}
});
function run_test()
{
DebuggerServer.createRootActor = (conn => {
return RootActor(conn);
});
DebuggerServer.init(() => true);
let trace = connectPipeTracing();
let client = new DebuggerClient(trace);
let rootClient = RootFront(client);
let strfront = null;
let expectRootChildren = function(size) {
do_check_eq(rootActor.__poolMap.size, size + 1);
do_check_eq(rootClient.__poolMap.size, size + 1);
}
// Root actor has no children yet.
expectRootChildren(0);
client.connect((applicationType, traits) => {
trace.expectReceive({"from":"<actorid>","applicationType":"xpcshell-tests","traits":[]});
do_check_eq(applicationType, "xpcshell-tests");
rootClient.shortString().then(ret => {
trace.expectSend({"type":"shortString","to":"<actorid>"});
trace.expectReceive({"value":"abc","from":"<actorid>"});
// Should only own the one reference (itself) at this point.
expectRootChildren(0);
strfront = ret;
}).then(() => {
return strfront.string();
}).then(ret => {
do_check_eq(ret, SHORT_STR);
}).then(() => {
return rootClient.longString();
}).then(ret => {
trace.expectSend({"type":"longString","to":"<actorid>"});
trace.expectReceive({"value":{"type":"longString","actor":"<actorid>","length":16,"initial":"abcde"},"from":"<actorid>"});
strfront = ret;
// Should own a reference to itself and an extra string now.
expectRootChildren(1);
}).then(() => {
return strfront.string();
}).then(ret => {
trace.expectSend({"type":"substring","start":5,"end":10,"to":"<actorid>"});
trace.expectReceive({"substring":"fghij","from":"<actorid>"});
trace.expectSend({"type":"substring","start":10,"end":15,"to":"<actorid>"});
trace.expectReceive({"substring":"klmno","from":"<actorid>"});
trace.expectSend({"type":"substring","start":15,"end":20,"to":"<actorid>"});
trace.expectReceive({"substring":"p","from":"<actorid>"});
do_check_eq(ret, LONG_STR);
}).then(() => {
return strfront.release();
}).then(() => {
trace.expectSend({"type":"release","to":"<actorid>"});
trace.expectReceive({"from":"<actorid>"});
// That reference should be removed now.
expectRootChildren(0);
}).then(() => {
let deferred = defer();
rootClient.once("string-event", (str) => {
trace.expectSend({"type":"emitShortString","to":"<actorid>"});
trace.expectReceive({"type":"string-event","str":"abc","from":"<actorid>"});
do_check_true(!!str);
strfront = str;
// Shouldn't generate any new references
expectRootChildren(0);
// will generate no packets.
strfront.string().then((value) => { deferred.resolve(value) });
});
rootClient.emitShortString();
return deferred.promise;
}).then(value => {
do_check_eq(value, SHORT_STR);
}).then(() => {
// Will generate no packets
return strfront.release();
}).then(() => {
let deferred = defer();
rootClient.once("string-event", (str) => {
trace.expectSend({"type":"emitLongString","to":"<actorid>"});
trace.expectReceive({"type":"string-event","str":{"type":"longString","actor":"<actorid>","length":16,"initial":"abcde"},"from":"<actorid>"});
do_check_true(!!str);
// Should generate one new reference
expectRootChildren(1);
strfront = str;
strfront.string().then((value) => {
trace.expectSend({"type":"substring","start":5,"end":10,"to":"<actorid>"});
trace.expectReceive({"substring":"fghij","from":"<actorid>"});
trace.expectSend({"type":"substring","start":10,"end":15,"to":"<actorid>"});
trace.expectReceive({"substring":"klmno","from":"<actorid>"});
trace.expectSend({"type":"substring","start":15,"end":20,"to":"<actorid>"});
trace.expectReceive({"substring":"p","from":"<actorid>"});
deferred.resolve(value);
});
});
rootClient.emitLongString();
return deferred.promise;
}).then(value => {
do_check_eq(value, LONG_STR);
}).then(() => {
return strfront.release();
}).then(() => {
trace.expectSend({"type":"release","to":"<actorid>"});
trace.expectReceive({"from":"<actorid>"});
expectRootChildren(0);
}).then(() => {
client.close(() => {
do_test_finished();
});
}).then(null, err => {
do_report_unexpected_exception(err, "Failure executing test");
});
});
do_test_pending();
}

View File

@ -0,0 +1,275 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test simple requests using the protocol helpers.
*/
let protocol = devtools.require("devtools/server/protocol");
let {method, Arg, Option, RetVal} = protocol;
let {defer, resolve} = devtools.require("sdk/core/promise");
let events = devtools.require("sdk/event/core");
function simpleHello() {
return {
from: "root",
applicationType: "xpcshell-tests",
traits: [],
}
}
let RootActor = protocol.ActorClass({
typeName: "root",
initialize: function(conn) {
protocol.Actor.prototype.initialize.call(this, conn);
// Root actor owns itself.
this.manage(this);
this.actorID = "root";
},
sayHello: simpleHello,
simpleReturn: method(function() {
return 1;
}, {
response: { value: RetVal() },
}),
promiseReturn: method(function() {
return resolve(1);
}, {
response: { value: RetVal("number") },
}),
simpleArgs: method(function(a, b) {
return { firstResponse: a + 1, secondResponse: b + 1 };
}, {
request: {
firstArg: Arg(0),
secondArg: Arg(1),
},
response: RetVal()
}),
nestedArgs: method(function(a, b, c) {
return { a: a, b: b, c: c };
}, {
request: {
firstArg: Arg(0),
nest: {
secondArg: Arg(1),
nest: {
thirdArg: Arg(2)
}
}
},
response: RetVal()
}),
optionArgs: method(function(options) {
return { option1: options.option1, option2: options.option2 };
}, {
request: {
option1: Option(0),
option2: Option(0)
},
response: RetVal()
}),
optionalArgs: method(function(a, b=200) {
return b;
}, {
request: {
a: Arg(0),
b: Arg(1, "number", { optional: true })
},
response: {
value: RetVal("number")
},
}),
arrayArgs: method(function(a) {
return a;
}, {
request: {
a: Arg(0, "array:number")
},
response: {
arrayReturn: RetVal("array:number")
},
}),
nestedArrayArgs: method(function(a) {
return a;
}, {
request: { a: Arg(0, "array:array:number") },
response: { value: RetVal("array:array:number") },
}),
/**
* Test that the 'type' part of the request packet works
* correctly when the type isn't the same as the method name
*/
renamedEcho: method(function(a) {
if (this.conn.currentPacket.type != "echo") {
return "goodbye";
}
return a;
}, {
request: {
type: "echo",
a: Arg(0),
},
response: {
value: RetVal("string")
},
}),
testOneWay: method(function(a) {
// Emit to show that we got this message, because there won't be a response.
events.emit(this, "oneway", a);
}, {
request: { a: Arg(0) },
oneway: true
}),
events: {
"oneway": { a: Arg(0) }
}
});
let RootFront = protocol.FrontClass(RootActor, {
initialize: function(client) {
this.actorID = "root";
protocol.Front.prototype.initialize.call(this, client);
// Root owns itself.
this.manage(this);
}
});
function run_test()
{
DebuggerServer.createRootActor = (conn => {
return RootActor(conn);
});
DebuggerServer.init(() => true);
check_except(() => {
let badActor = ActorClass({
missing: preEvent("missing-event", function() {
})
})
});
protocol.types.getType("array:array:array:number");
protocol.types.getType("array:array:array:number");
check_except(() => protocol.types.getType("unknown"));
check_except(() => protocol.types.getType("array:unknown"));
check_except(() => protocol.types.getType("unknown:number"));
let trace = connectPipeTracing();
let client = new DebuggerClient(trace);
let rootClient = RootFront(client);
client.connect((applicationType, traits) => {
trace.expectReceive({"from":"<actorid>","applicationType":"xpcshell-tests","traits":[]});
do_check_eq(applicationType, "xpcshell-tests");
rootClient.simpleReturn().then(ret => {
trace.expectSend({"type":"simpleReturn","to":"<actorid>"});
trace.expectReceive({"value":1,"from":"<actorid>"});
do_check_eq(ret, 1);
}).then(() => {
return rootClient.promiseReturn();
}).then(ret => {
trace.expectSend({"type":"promiseReturn","to":"<actorid>"});
trace.expectReceive({"value":1,"from":"<actorid>"});
do_check_eq(ret, 1);
}).then(() => {
return rootClient.simpleArgs(5, 10)
}).then(ret => {
trace.expectSend({"type":"simpleArgs","firstArg":5,"secondArg":10,"to":"<actorid>"});
trace.expectReceive({"firstResponse":6,"secondResponse":11,"from":"<actorid>"});
do_check_eq(ret.firstResponse, 6);
do_check_eq(ret.secondResponse, 11);
}).then(() => {
return rootClient.nestedArgs(1, 2, 3);
}).then(ret => {
trace.expectSend({"type":"nestedArgs","firstArg":1,"nest":{"secondArg":2,"nest":{"thirdArg":3}},"to":"<actorid>"});
trace.expectReceive({"a":1,"b":2,"c":3,"from":"<actorid>"});
do_check_eq(ret.a, 1);
do_check_eq(ret.b, 2);
do_check_eq(ret.c, 3);
}).then(() => {
return rootClient.optionArgs({
"option1": 5,
"option2": 10
});
}).then(ret => {
trace.expectSend({"type":"optionArgs","option1":5,"option2":10,"to":"<actorid>"});
trace.expectReceive({"option1":5,"option2":10,"from":"<actorid>"});
do_check_eq(ret.option1, 5);
do_check_eq(ret.option2, 10);
}).then(() => {
return rootClient.optionArgs({});
}).then(ret => {
trace.expectSend({"type":"optionArgs","to":"<actorid>"});
trace.expectReceive({"from":"<actorid>"});
do_check_true(typeof(ret.option1) === "undefined");
do_check_true(typeof(ret.option2) === "undefined");
}).then(ret => {
// Explicitly call an optional argument...
return rootClient.optionalArgs(5, 10);
}).then(ret => {
trace.expectSend({"type":"optionalArgs","a":5,"b":10,"to":"<actorid>"});
trace.expectReceive({"value":10,"from":"<actorid>"});
do_check_eq(ret, 10);
}).then(() => {
// Now don't pass the optional argument, expect the default.
return rootClient.optionalArgs(5);
}).then(ret => {
trace.expectSend({"type":"optionalArgs","a":5,"to":"<actorid>"});
trace.expectReceive({"value":200,"from":"<actorid>"});
do_check_eq(ret, 200);
}).then(ret => {
return rootClient.arrayArgs([0, 1, 2, 3, 4, 5]);
}).then(ret => {
trace.expectSend({"type":"arrayArgs","a":[0,1,2,3,4,5],"to":"<actorid>"});
trace.expectReceive({"arrayReturn":[0,1,2,3,4,5],"from":"<actorid>"});
do_check_eq(ret[0], 0);
do_check_eq(ret[5], 5);
}).then(() => {
return rootClient.arrayArgs([[5]]);
}).then(ret => {
trace.expectSend({"type":"arrayArgs","a":[[5]],"to":"<actorid>"});
trace.expectReceive({"arrayReturn":[[5]],"from":"<actorid>"});
do_check_eq(ret[0][0], 5);
}).then(() => {
return rootClient.renamedEcho("hello");
}).then(str => {
trace.expectSend({"type":"echo","a":"hello","to":"<actorid>"});
trace.expectReceive({"value":"hello","from":"<actorid>"});
do_check_eq(str, "hello");
let deferred = defer();
rootClient.on("oneway", (response) => {
trace.expectSend({"type":"testOneWay","a":"hello","to":"<actorid>"});
trace.expectReceive({"type":"oneway","a":"hello","from":"<actorid>"});
do_check_eq(response, "hello");
deferred.resolve();
});
do_check_true(typeof(rootClient.testOneWay("hello")) === "undefined");
return deferred.promise;
}).then(() => {
client.close(() => {
do_test_finished();
});
}).then(null, err => {
do_report_unexpected_exception(err, "Failure executing test");
});
});
do_test_pending();
}

View File

@ -41,6 +41,9 @@ reason = bug 821285
[test_eval-03.js]
[test_eval-04.js]
[test_eval-05.js]
[test_protocol_simple.js]
[test_protocol_longstring.js]
[test_protocol_children.js]
[test_breakpoint-01.js]
[test_register_actor.js]
skip-if = toolkit == "gonk"