michael@0: /* Any copyright is dedicated to the Public Domain. michael@0: http://creativecommons.org/publicdomain/zero/1.0/ */ michael@0: michael@0: /* Exercise prefix-based forwarding of packets to other transports. */ michael@0: michael@0: var gMainConnection, gMainTransport; michael@0: var gSubconnection1, gSubconnection2; michael@0: var gClient; michael@0: michael@0: function run_test() michael@0: { michael@0: DebuggerServer.init(); michael@0: michael@0: add_test(createMainConnection); michael@0: add_test(TestNoForwardingYet); michael@0: add_test(createSubconnection1); michael@0: add_test(TestForwardPrefix1OnlyRoot); michael@0: add_test(createSubconnection2); michael@0: add_test(TestForwardPrefix12OnlyRoot); michael@0: add_test(TestForwardPrefix12WithActor1); michael@0: add_test(TestForwardPrefix12WithActor12); michael@0: run_next_test(); michael@0: } michael@0: michael@0: /* michael@0: * Create a pipe connection, and return an object |{ conn, transport }|, michael@0: * where |conn| is the new DebuggerServerConnection instance, and michael@0: * |transport| is the client side of the transport on which it communicates michael@0: * (that is, packets sent on |transport| go to the new connection, and michael@0: * |transport|'s hooks receive replies). michael@0: * michael@0: * |aPrefix| is optional; if present, it's the prefix (minus the ':') for michael@0: * actors in the new connection. michael@0: */ michael@0: function newConnection(aPrefix) michael@0: { michael@0: var conn; michael@0: DebuggerServer.createRootActor = function (aConn) { michael@0: conn = aConn; michael@0: return new DebuggerServer.RootActor(aConn, {}); michael@0: }; michael@0: michael@0: var transport = DebuggerServer.connectPipe(aPrefix); michael@0: michael@0: return { conn: conn, transport: transport }; michael@0: } michael@0: michael@0: /* Create the main connection for these tests. */ michael@0: function createMainConnection() michael@0: { michael@0: ({ conn: gMainConnection, transport: gMainTransport }) = newConnection(); michael@0: gClient = new DebuggerClient(gMainTransport); michael@0: gClient.connect((aType, aTraits) => run_next_test()); michael@0: } michael@0: michael@0: /* michael@0: * Exchange 'echo' messages with five actors: michael@0: * - root michael@0: * - prefix1:root michael@0: * - prefix1:actor michael@0: * - prefix2:root michael@0: * - prefix2:actor michael@0: * michael@0: * Expect proper echos from those named in |aReachables|, and 'noSuchActor' michael@0: * errors from the others. When we've gotten all our replies (errors or michael@0: * otherwise), call |aCompleted|. michael@0: * michael@0: * To avoid deep stacks, we call aCompleted from the next tick. michael@0: */ michael@0: function tryActors(aReachables, aCompleted) { michael@0: let count = 0; michael@0: michael@0: let outerActor; michael@0: for (outerActor of [ 'root', michael@0: 'prefix1:root', 'prefix1:actor', michael@0: 'prefix2:root', 'prefix2:actor' ]) { michael@0: /* michael@0: * Let each callback capture its own iteration's value; outerActor is michael@0: * local to the whole loop, not to a single iteration. michael@0: */ michael@0: let actor = outerActor; michael@0: michael@0: count++; michael@0: michael@0: gClient.request({ to: actor, type: 'echo', value: 'tango'}, // phone home michael@0: (aResponse) => { michael@0: if (aReachables.has(actor)) michael@0: do_check_matches({ from: actor, type: 'echo', value: 'tango' }, aResponse); michael@0: else michael@0: do_check_matches({ from: actor, error: 'noSuchActor' }, aResponse); michael@0: michael@0: if (--count == 0) michael@0: do_execute_soon(aCompleted, "tryActors callback " + aCompleted.name); michael@0: }); michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * With no forwarding established, sending messages to root should work, michael@0: * but sending messages to prefixed actor names, or anyone else, should get michael@0: * an error. michael@0: */ michael@0: function TestNoForwardingYet() michael@0: { michael@0: tryActors(Set(['root']), run_next_test); michael@0: } michael@0: michael@0: /* michael@0: * Create a new pipe connection which forwards its reply packets to michael@0: * gMainConnection's client, and to which gMainConnection forwards packets michael@0: * directed to actors whose names begin with |aPrefix + ':'|, and. michael@0: * michael@0: * Return an object { conn, transport }, as for newConnection. michael@0: */ michael@0: function newSubconnection(aPrefix) michael@0: { michael@0: let { conn, transport } = newConnection(aPrefix); michael@0: transport.hooks = { michael@0: onPacket: (aPacket) => gMainConnection.send(aPacket), michael@0: onClosed: () => {} michael@0: } michael@0: gMainConnection.setForwarding(aPrefix, transport); michael@0: michael@0: return { conn: conn, transport: transport }; michael@0: } michael@0: michael@0: /* Create a second root actor, to which we can forward things. */ michael@0: function createSubconnection1() michael@0: { michael@0: let { conn, transport } = newSubconnection('prefix1'); michael@0: gSubconnection1 = conn; michael@0: transport.ready(); michael@0: gClient.expectReply('prefix1:root', (aReply) => run_next_test()); michael@0: } michael@0: michael@0: // Establish forwarding, but don't put any actors in that server. michael@0: function TestForwardPrefix1OnlyRoot() michael@0: { michael@0: tryActors(Set(['root', 'prefix1:root']), run_next_test); michael@0: } michael@0: michael@0: /* Create a third root actor, to which we can forward things. */ michael@0: function createSubconnection2() michael@0: { michael@0: let { conn, transport } = newSubconnection('prefix2'); michael@0: gSubconnection2 = conn; michael@0: transport.ready(); michael@0: gClient.expectReply('prefix2:root', (aReply) => run_next_test()); michael@0: } michael@0: michael@0: function TestForwardPrefix12OnlyRoot() michael@0: { michael@0: tryActors(Set(['root', 'prefix1:root', 'prefix2:root']), run_next_test); michael@0: } michael@0: michael@0: // A dumb actor that implements 'echo'. michael@0: // michael@0: // It's okay that both subconnections' actors behave identically, because michael@0: // the reply-sending code attaches the replying actor's name to the packet, michael@0: // so simply matching the 'from' field in the reply ensures that we heard michael@0: // from the right actor. michael@0: function EchoActor(aConnection) michael@0: { michael@0: this.conn = aConnection; michael@0: } michael@0: EchoActor.prototype.actorPrefix = "EchoActor"; michael@0: EchoActor.prototype.onEcho = function (aRequest) { michael@0: /* michael@0: * Request packets are frozen. Copy aRequest, so that michael@0: * DebuggerServerConnection.onPacket can attach a 'from' property. michael@0: */ michael@0: return JSON.parse(JSON.stringify(aRequest)); michael@0: }; michael@0: EchoActor.prototype.requestTypes = { michael@0: "echo": EchoActor.prototype.onEcho michael@0: }; michael@0: michael@0: function TestForwardPrefix12WithActor1() michael@0: { michael@0: let actor = new EchoActor(gSubconnection1) michael@0: actor.actorID = 'prefix1:actor'; michael@0: gSubconnection1.addActor(actor); michael@0: michael@0: tryActors(Set(['root', 'prefix1:root', 'prefix1:actor', 'prefix2:root']), run_next_test); michael@0: } michael@0: michael@0: function TestForwardPrefix12WithActor12() michael@0: { michael@0: let actor = new EchoActor(gSubconnection2) michael@0: actor.actorID = 'prefix2:actor'; michael@0: gSubconnection2.addActor(actor); michael@0: michael@0: tryActors(Set(['root', 'prefix1:root', 'prefix1:actor', 'prefix2:root', 'prefix2:actor']), run_next_test); michael@0: }