summaryrefslogtreecommitdiffstats
path: root/devtools/server/tests/xpcshell/test_forwardingprefix.js
blob: e917350da58bee2fc7926dfb0aed5268b3745308 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
/* Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/ */

"use strict";

/* Exercise prefix-based forwarding of packets to other transports. */

const { RootActor } = require("resource://devtools/server/actors/root.js");

var gMainConnection, gMainTransport;
var gSubconnection1, gSubconnection2;
var gClient;

function run_test() {
  DevToolsServer.init();

  add_test(createMainConnection);
  add_test(TestNoForwardingYet);
  add_test(createSubconnection1);
  add_test(TestForwardPrefix1OnlyRoot);
  add_test(createSubconnection2);
  add_test(TestForwardPrefix12OnlyRoot);
  add_test(TestForwardPrefix12WithActor1);
  add_test(TestForwardPrefix12WithActor12);
  run_next_test();
}

/*
 * Create a pipe connection, and return an object |{ conn, transport }|,
 * where |conn| is the new DevToolsServerConnection instance, and
 * |transport| is the client side of the transport on which it communicates
 * (that is, packets sent on |transport| go to the new connection, and
 * |transport|'s hooks receive replies).
 *
 * |prefix| is optional; if present, it's the prefix (minus the '/') for
 * actors in the new connection.
 */
function newConnection(prefix) {
  let conn;
  DevToolsServer.createRootActor = function (connection) {
    conn = connection;
    return new RootActor(connection, {});
  };

  const transport = DevToolsServer.connectPipe(prefix);

  return { conn, transport };
}

/* Create the main connection for these tests. */
function createMainConnection() {
  ({ conn: gMainConnection, transport: gMainTransport } = newConnection());
  gClient = new DevToolsClient(gMainTransport);
  gClient.connect().then(([type, traits]) => run_next_test());
}

/*
 * Exchange 'echo' messages with five actors:
 * - root
 * - prefix1/root
 * - prefix1/actor
 * - prefix2/root
 * - prefix2/actor
 *
 * Expect proper echos from those named in |reachables|, and 'noSuchActor'
 * errors from the others. When we've gotten all our replies (errors or
 * otherwise), call |completed|.
 *
 * To avoid deep stacks, we call completed from the next tick.
 */
async function tryActors(reachables, completed) {
  for (const actor of [
    "root",
    "prefix1/root",
    "prefix1/actor",
    "prefix2/root",
    "prefix2/actor",
  ]) {
    let response;
    try {
      if (actor.endsWith("root")) {
        // Root actor doesn't expose any echo method,
        // so fallback on getRoot which returns `{ from: "root" }`.
        // For the top level root actor, we have to use its front.
        if (actor == "root") {
          response = await gClient.mainRoot.getRoot();
        } else {
          response = await gClient.request({ to: actor, type: "getRoot" });
        }
      } else {
        response = await gClient.request({
          to: actor,
          type: "echo",
          value: "tango",
        });
      }
    } catch (e) {
      response = e;
    }
    if (reachables.has(actor)) {
      if (actor.endsWith("root")) {
        // RootActor's getRoot response is almost empty on xpcshell
        Assert.deepEqual({ from: actor }, response);
      } else {
        Assert.deepEqual(
          { from: actor, to: actor, type: "echo", value: "tango" },
          response
        );
      }
    } else {
      Assert.deepEqual(
        {
          from: actor,
          error: "noSuchActor",
          message: "No such actor for ID: " + actor,
        },
        response
      );
    }
  }
  executeSoon(completed, "tryActors callback " + completed.name);
}

/*
 * With no forwarding established, sending messages to root should work,
 * but sending messages to prefixed actor names, or anyone else, should get
 * an error.
 */
function TestNoForwardingYet() {
  tryActors(new Set(["root"]), run_next_test);
}

/*
 * Create a new pipe connection which forwards its reply packets to
 * gMainConnection's client, and to which gMainConnection forwards packets
 * directed to actors whose names begin with |prefix + '/'|, and.
 *
 * Return an object { conn, transport }, as for newConnection.
 */
function newSubconnection(prefix) {
  const { conn, transport } = newConnection(prefix);
  transport.hooks = {
    onPacket: packet => gMainConnection.send(packet),
  };
  gMainConnection.setForwarding(prefix, transport);

  return { conn, transport };
}

/* Create a second root actor, to which we can forward things. */
function createSubconnection1() {
  const { conn, transport } = newSubconnection("prefix1");
  gSubconnection1 = conn;
  transport.ready();
  gClient.expectReply("prefix1/root", reply => run_next_test());
}

// Establish forwarding, but don't put any actors in that server.
function TestForwardPrefix1OnlyRoot() {
  tryActors(new Set(["root", "prefix1/root"]), run_next_test);
}

/* Create a third root actor, to which we can forward things. */
function createSubconnection2() {
  const { conn, transport } = newSubconnection("prefix2");
  gSubconnection2 = conn;
  transport.ready();
  gClient.expectReply("prefix2/root", reply => run_next_test());
}

function TestForwardPrefix12OnlyRoot() {
  tryActors(new Set(["root", "prefix1/root", "prefix2/root"]), run_next_test);
}

// A dumb actor that implements 'echo'.
//
// It's okay that both subconnections' actors behave identically, because
// the reply-sending code attaches the replying actor's name to the packet,
// so simply matching the 'from' field in the reply ensures that we heard
// from the right actor.
const { Actor } = require("resource://devtools/shared/protocol/Actor.js");
class EchoActor extends Actor {
  constructor(conn) {
    super(conn, { typeName: "EchoActor", methods: [] });

    this.requestTypes = {
      echo: EchoActor.prototype.onEcho,
    };
  }

  onEcho(request) {
    /*
     * Request packets are frozen. Copy request, so that
     * DevToolsServerConnection.onPacket can attach a 'from' property.
     */
    return JSON.parse(JSON.stringify(request));
  }
}

function TestForwardPrefix12WithActor1() {
  const actor = new EchoActor(gSubconnection1);
  actor.actorID = "prefix1/actor";
  gSubconnection1.addActor(actor);

  tryActors(
    new Set(["root", "prefix1/root", "prefix1/actor", "prefix2/root"]),
    run_next_test
  );
}

function TestForwardPrefix12WithActor12() {
  const actor = new EchoActor(gSubconnection2);
  actor.actorID = "prefix2/actor";
  gSubconnection2.addActor(actor);

  tryActors(
    new Set([
      "root",
      "prefix1/root",
      "prefix1/actor",
      "prefix2/root",
      "prefix2/actor",
    ]),
    run_next_test
  );
}