test_forwardingprefix.js (6508B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 /* Exercise prefix-based forwarding of packets to other transports. */ 7 8 const { RootActor } = require("resource://devtools/server/actors/root.js"); 9 10 var gMainConnection, gMainTransport; 11 var gSubconnection1, gSubconnection2; 12 var gClient; 13 14 function run_test() { 15 DevToolsServer.init(); 16 17 add_test(createMainConnection); 18 add_test(TestNoForwardingYet); 19 add_test(createSubconnection1); 20 add_test(TestForwardPrefix1OnlyRoot); 21 add_test(createSubconnection2); 22 add_test(TestForwardPrefix12OnlyRoot); 23 add_test(TestForwardPrefix12WithActor1); 24 add_test(TestForwardPrefix12WithActor12); 25 run_next_test(); 26 } 27 28 /* 29 * Create a pipe connection, and return an object |{ conn, transport }|, 30 * where |conn| is the new DevToolsServerConnection instance, and 31 * |transport| is the client side of the transport on which it communicates 32 * (that is, packets sent on |transport| go to the new connection, and 33 * |transport|'s hooks receive replies). 34 * 35 * |prefix| is optional; if present, it's the prefix (minus the '/') for 36 * actors in the new connection. 37 */ 38 function newConnection(prefix) { 39 let conn; 40 DevToolsServer.createRootActor = function (connection) { 41 conn = connection; 42 return new RootActor(connection, {}); 43 }; 44 45 const transport = DevToolsServer.connectPipe(prefix); 46 47 return { conn, transport }; 48 } 49 50 /* Create the main connection for these tests. */ 51 function createMainConnection() { 52 ({ conn: gMainConnection, transport: gMainTransport } = newConnection()); 53 gClient = new DevToolsClient(gMainTransport); 54 gClient.connect().then(() => run_next_test()); 55 } 56 57 /* 58 * Exchange 'echo' messages with five actors: 59 * - root 60 * - prefix1/root 61 * - prefix1/actor 62 * - prefix2/root 63 * - prefix2/actor 64 * 65 * Expect proper echos from those named in |reachables|, and 'noSuchActor' 66 * errors from the others. When we've gotten all our replies (errors or 67 * otherwise), call |completed|. 68 * 69 * To avoid deep stacks, we call completed from the next tick. 70 */ 71 async function tryActors(reachables, completed) { 72 for (const actor of [ 73 "root", 74 "prefix1/root", 75 "prefix1/actor", 76 "prefix2/root", 77 "prefix2/actor", 78 ]) { 79 let response; 80 try { 81 if (actor.endsWith("root")) { 82 // Root actor doesn't expose any echo method, 83 // so fallback on getRoot which returns `{ from: "root" }`. 84 // For the top level root actor, we have to use its front. 85 if (actor == "root") { 86 response = await gClient.mainRoot.getRoot(); 87 } else { 88 response = await gClient.request({ to: actor, type: "getRoot" }); 89 } 90 } else { 91 response = await gClient.request({ 92 to: actor, 93 type: "echo", 94 value: "tango", 95 }); 96 } 97 } catch (e) { 98 response = e; 99 } 100 if (reachables.has(actor)) { 101 if (actor.endsWith("root")) { 102 // RootActor's getRoot response is almost empty on xpcshell 103 Assert.deepEqual({ from: actor }, response); 104 } else { 105 Assert.deepEqual( 106 { from: actor, to: actor, type: "echo", value: "tango" }, 107 response 108 ); 109 } 110 } else { 111 Assert.deepEqual( 112 { 113 from: actor, 114 error: "noSuchActor", 115 message: "No such actor for ID: " + actor, 116 }, 117 response 118 ); 119 } 120 } 121 executeSoon(completed, "tryActors callback " + completed.name); 122 } 123 124 /* 125 * With no forwarding established, sending messages to root should work, 126 * but sending messages to prefixed actor names, or anyone else, should get 127 * an error. 128 */ 129 function TestNoForwardingYet() { 130 tryActors(new Set(["root"]), run_next_test); 131 } 132 133 /* 134 * Create a new pipe connection which forwards its reply packets to 135 * gMainConnection's client, and to which gMainConnection forwards packets 136 * directed to actors whose names begin with |prefix + '/'|, and. 137 * 138 * Return an object { conn, transport }, as for newConnection. 139 */ 140 function newSubconnection(prefix) { 141 const { conn, transport } = newConnection(prefix); 142 transport.hooks = { 143 onPacket: packet => gMainConnection.send(packet), 144 }; 145 gMainConnection.setForwarding(prefix, transport); 146 147 return { conn, transport }; 148 } 149 150 /* Create a second root actor, to which we can forward things. */ 151 function createSubconnection1() { 152 const { conn, transport } = newSubconnection("prefix1"); 153 gSubconnection1 = conn; 154 transport.ready(); 155 gClient.expectReply("prefix1/root", () => run_next_test()); 156 } 157 158 // Establish forwarding, but don't put any actors in that server. 159 function TestForwardPrefix1OnlyRoot() { 160 tryActors(new Set(["root", "prefix1/root"]), run_next_test); 161 } 162 163 /* Create a third root actor, to which we can forward things. */ 164 function createSubconnection2() { 165 const { conn, transport } = newSubconnection("prefix2"); 166 gSubconnection2 = conn; 167 transport.ready(); 168 gClient.expectReply("prefix2/root", () => run_next_test()); 169 } 170 171 function TestForwardPrefix12OnlyRoot() { 172 tryActors(new Set(["root", "prefix1/root", "prefix2/root"]), run_next_test); 173 } 174 175 // A dumb actor that implements 'echo'. 176 // 177 // It's okay that both subconnections' actors behave identically, because 178 // the reply-sending code attaches the replying actor's name to the packet, 179 // so simply matching the 'from' field in the reply ensures that we heard 180 // from the right actor. 181 const { Actor } = require("resource://devtools/shared/protocol/Actor.js"); 182 class EchoActor extends Actor { 183 constructor(conn) { 184 super(conn, { typeName: "EchoActor", methods: [] }); 185 186 this.requestTypes = { 187 echo: EchoActor.prototype.onEcho, 188 }; 189 } 190 191 onEcho(request) { 192 /* 193 * Request packets are frozen. Copy request, so that 194 * DevToolsServerConnection.onPacket can attach a 'from' property. 195 */ 196 return JSON.parse(JSON.stringify(request)); 197 } 198 } 199 200 function TestForwardPrefix12WithActor1() { 201 const actor = new EchoActor(gSubconnection1); 202 actor.actorID = "prefix1/actor"; 203 gSubconnection1.addActor(actor); 204 205 tryActors( 206 new Set(["root", "prefix1/root", "prefix1/actor", "prefix2/root"]), 207 run_next_test 208 ); 209 } 210 211 function TestForwardPrefix12WithActor12() { 212 const actor = new EchoActor(gSubconnection2); 213 actor.actorID = "prefix2/actor"; 214 gSubconnection2.addActor(actor); 215 216 tryActors( 217 new Set([ 218 "root", 219 "prefix1/root", 220 "prefix1/actor", 221 "prefix2/root", 222 "prefix2/actor", 223 ]), 224 run_next_test 225 ); 226 }