diff -r 000000000000 -r 6474c204b198 toolkit/devtools/server/tests/unit/test_protocol_children.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/toolkit/devtools/server/tests/unit/test_protocol_children.js Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,451 @@ +/* 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 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) { + if (detail === "actorid") { + return this.actorID; + } + 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"), + }), + + getIDDetail: method(function() { + return this; + }, { + response: { + idDetail: RetVal("childActor#actorid") + } + }), + + 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, detail) { + if (detail === "actorid") { + return; + } + 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); + }, + + 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 promise.resolve(undefined); + } + 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":"","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":""}) + trace.expectReceive({"actor":"","from":""}) + + 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":""}) + trace.expectReceive({"actor":"","from":""}) + + expectRootChildren(1); + do_check_true(ret === childFront); + }).then(() => { + return childFront.echo("hello"); + }).then(ret => { + trace.expectSend({"type":"echo","str":"hello","to":""}) + trace.expectReceive({"str":"hello","from":""}) + + do_check_eq(ret, "hello"); + }).then(() => { + return childFront.getDetail1(); + }).then(ret => { + trace.expectSend({"type":"getDetail1","to":""}); + trace.expectReceive({"actor":"","childID":"child1","detail":"detail1","from":""}); + do_check_true(ret === childFront); + do_check_eq(childFront.detail, "detail1"); + }).then(() => { + return childFront.getDetail2(); + }).then(ret => { + trace.expectSend({"type":"getDetail2","to":""}); + trace.expectReceive({"actor":"","childID":"child1","detail":"detail2","from":""}); + do_check_true(ret === childFront); + do_check_eq(childFront.detail, "detail2"); + }).then(() => { + return childFront.getIDDetail(); + }).then(ret => { + trace.expectSend({"type":"getIDDetail","to":""}); + trace.expectReceive({"idDetail": childFront.actorID,"from":""}); + do_check_true(ret === childFront); + }).then(() => { + return childFront.getSibling("siblingID"); + }).then(ret => { + trace.expectSend({"type":"getSibling","id":"siblingID","to":""}); + trace.expectReceive({"sibling":{"actor":"","childID":"siblingID"},"from":""}); + + expectRootChildren(2); + }).then(ret => { + return rootFront.getTemporaryChild("temp1").then(temp1 => { + trace.expectSend({"type":"getTemporaryChild","id":"temp1","to":""}); + trace.expectReceive({"child":{"actor":"","childID":"temp1"},"from":""}); + + // 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":""}); + trace.expectReceive({"child":{"actor":"","childID":"temp2"},"from":""}); + + // 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":""}); + trace.expectReceive({"from":""}); + + 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":""}); + trace.expectReceive({"children":[{"actor":"","childID":"child1"},{"actor":"","childID":"child2"}],"from":""}); + + 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":""}); + trace.expectReceive({"type":"event1","a":1,"b":2,"c":3,"from":""}); + trace.expectReceive({"type":"namedEvent","a":1,"b":2,"c":3,"from":""}); + trace.expectReceive({"type":"objectEvent","detail":{"actor":"","childID":"child1","detail":"detail1"},"from":""}); + trace.expectReceive({"type":"arrayObjectEvent","detail":[{"actor":"","childID":"child1","detail":"detail2"}],"from":""}); + trace.expectReceive({"value":"correct response","from":""}); + + + do_check_eq(set.size, 0); + }); + }).then(ret => { + return rootFront.getManyChildren(); + }).then(ret => { + trace.expectSend({"type":"getManyChildren","to":""}); + trace.expectReceive({"foo":"bar","child5":{"actor":"","childID":"child5"},"more":[{"actor":"","childID":"child6"},{"actor":"","childID":"child7"}],"from":""}); + + // 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(); +}