diff options
Diffstat (limited to 'devtools/server/tests/unit/test_protocol_children.js')
-rw-r--r-- | devtools/server/tests/unit/test_protocol_children.js | 559 |
1 files changed, 559 insertions, 0 deletions
diff --git a/devtools/server/tests/unit/test_protocol_children.js b/devtools/server/tests/unit/test_protocol_children.js new file mode 100644 index 000000000..67773ebef --- /dev/null +++ b/devtools/server/tests/unit/test_protocol_children.js @@ -0,0 +1,559 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test simple requests using the protocol helpers. + */ +var protocol = require("devtools/shared/protocol"); +var {preEvent, types, Arg, Option, RetVal} = protocol; + +var events = require("sdk/event/core"); + +function simpleHello() { + return { + from: "root", + applicationType: "xpcshell-tests", + traits: [], + }; +} + +var testTypes = {}; + +// Predeclaring the actor type so that it can be used in the +// implementation of the child actor. +types.addActorType("childActor"); + +const childSpec = protocol.generateActorSpec({ + typeName: "childActor", + + events: { + "event1" : { + a: Arg(0), + b: Arg(1), + c: Arg(2) + }, + "event2" : { + 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"), + } + }, + + methods: { + echo: { + request: { str: Arg(0) }, + response: { str: RetVal("string") }, + }, + getDetail1: { + // This also exercises return-value-as-packet. + response: RetVal("childActor#detail1"), + }, + getDetail2: { + // This also exercises return-value-as-packet. + response: RetVal("childActor#detail2"), + }, + getIDDetail: { + response: { + idDetail: RetVal("childActor#actorid") + } + }, + getIntArray: { + request: { inputArray: Arg(0, "array:number") }, + response: RetVal("array:number") + }, + getSibling: { + request: { id: Arg(0) }, + response: { sibling: RetVal("childActor") } + }, + emitEvents: { + response: { value: "correct response" }, + }, + release: { + release: true + } + } +}); + +var ChildActor = protocol.ActorClassWithSpec(childSpec, { + // Actors returned by this actor should be owned by the root actor. + marshallPool: function () { return this.parent(); }, + + toString: function () { return "[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: function (str) { + return str; + }, + + getDetail1: function () { + return this; + }, + + getDetail2: function () { + return this; + }, + + getIDDetail: function () { + return this; + }, + + getIntArray: function (inputArray) { + // Test that protocol.js converts an iterator to an array. + let f = function* () { + for (let i of inputArray) { + yield 2 * i; + } + }; + return f(); + }, + + getSibling: function (id) { + return this.parent().getChild(id); + }, + + emitEvents: function () { + events.emit(this, "event1", 1, 2, 3); + events.emit(this, "event2", 4, 5, 6); + events.emit(this, "named-event", 1, 2, 3); + events.emit(this, "object-event", this); + events.emit(this, "array-object-event", [this]); + }, + + release: function () { }, +}); + +var ChildFront = protocol.FrontClassWithSpec(childSpec, { + 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 () { return "[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; + }), + + onEvent2a: preEvent("event2", function (a, b, c) { + return promise.resolve().then(() => this.event2arg3 = c); + }), + + onEvent2b: preEvent("event2", function (a, b, c) { + this.event2arg2 = b; + }), +}); + +types.addDictType("manyChildrenDict", { + child5: "childActor", + more: "array:childActor", +}); + +types.addLifetime("temp", "_temporaryHolder"); + +const rootSpec = protocol.generateActorSpec({ + typeName: "root", + + methods: { + getChild: { + request: { str: Arg(0) }, + response: { actor: RetVal("childActor") }, + }, + getChildren: { + request: { ids: Arg(0, "array:string") }, + response: { children: RetVal("array:childActor") }, + }, + getChildren2: { + request: { ids: Arg(0, "array:childActor") }, + response: { children: RetVal("array:childActor") }, + }, + getManyChildren: { + response: RetVal("manyChildrenDict") + }, + getTemporaryChild: { + request: { id: Arg(0) }, + response: { child: RetVal("temp:childActor") } + }, + clearTemporaryChildren: {} + } +}); + +var rootActor = null; +var RootActor = protocol.ActorClassWithSpec(rootSpec, { + toString: function () { return "[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: function (id) { + if (id in this._children) { + return this._children[id]; + } + let child = new ChildActor(this.conn, id); + this._children[id] = child; + return child; + }, + + getChildren: function (ids) { + return ids.map(id => this.getChild(id)); + }, + + getChildren2: function (ids) { + let f = function* () { + for (let c of ids) { + yield c; + } + }; + return f(); + }, + + getManyChildren: function () { + return { + foo: "bar", // note that this isn't in the specialization array. + child5: this.getChild("child5"), + more: [ this.getChild("child6"), this.getChild("child7") ] + }; + }, + + // This should remind you of a pause actor. + getTemporaryChild: function (id) { + if (!this._temporaryHolder) { + this._temporaryHolder = this.manage(new protocol.Actor(this.conn)); + } + return new ChildActor(this.conn, id); + }, + + clearTemporaryChildren: function (id) { + if (!this._temporaryHolder) { + return; + } + this._temporaryHolder.destroy(); + delete this._temporaryHolder; + } +}); + +var RootFront = protocol.FrontClassWithSpec(rootSpec, { + toString: function () { return "[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 = protocol.Front(this.conn); + this._temporaryHolder.actorID = this.actorID + "_temp"; + this._temporaryHolder = this.manage(this._temporaryHolder); + } + 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(); + + let trace = connectPipeTracing(); + let client = new DebuggerClient(trace); + client.connect().then(([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.getIDDetail(); + }).then(ret => { + trace.expectSend({"type":"getIDDetail", "to":"<actorid>"}); + trace.expectReceive({"idDetail": childFront.actorID, "from":"<actorid>"}); + do_check_true(ret === childFront); + }).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 = rootActor._temporaryHolder.__poolMap.values(); + let checkFronts = rootFront._temporaryHolder.__poolMap.values(); + + // 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", "event2", "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("event2", (a, b, c) => { + do_check_eq(a, 4); + do_check_eq(b, 5); + do_check_eq(c, 6); + // Verify that the async pre-event handler was called, + // setting the property before this handler was called. + do_check_eq(childFront.event2arg3, 6); + // And check that the sync preEvent with the same name is also + // executed + do_check_eq(childFront.event2arg2, 5); + set.delete("event2"); + }); + 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("event2", 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":"event2", "a":4, "b":5, "c":6, "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(() => { + // Test accepting a generator. + let f = function* () { + for (let i of [1, 2, 3, 4, 5]) { + yield i; + } + }; + return childFront.getIntArray(f()); + }).then((ret) => { + do_check_eq(ret.length, 5); + let expected = [2, 4, 6, 8, 10]; + for (let i = 0; i < 5; ++i) { + do_check_eq(ret[i], expected[i]); + } + }).then(() => { + return rootFront.getChildren(["child1", "child2"]); + }).then(ids => { + let f = function* () { + for (let id of ids) { + yield id; + } + }; + return rootFront.getChildren2(f()); + }).then(ret => { + do_check_eq(ret.length, 2); + do_check_true(ret[0] === childFront); + do_check_true(ret[1] !== childFront); + do_check_true(ret[1] instanceof ChildFront); + }).then(() => { + client.close().then(() => { + do_test_finished(); + }); + }).then(null, err => { + do_report_unexpected_exception(err, "Failure executing test"); + }); + }); + do_test_pending(); +} |