microsoft/vscode-react-native

Public

mirrored fromhttps://github.com/microsoft/vscode-react-nativeAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
cbc7ac5b83bd2f479d8e223b44a3dffd2fe811ab

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

test/debugger/appWorker.test.ts

302lines · modecode

1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the MIT license. See LICENSE file in the project root for details.
3
4import * as assert from "assert";
5import * as WebSocket from "ws";
6import * as path from "path";
7import * as Q from "q";
8import * as sinon from "sinon";
9import * as child_process from "child_process";
10
11import { MultipleLifetimesAppWorker } from "../../src/debugger/appWorker";
12import { ForkedAppWorker } from "../../src/debugger/forkedAppWorker";
13import * as ForkedAppWorkerModule from "../../src/debugger/forkedAppWorker";
14import * as packagerStatus from "../../src/common/packagerStatus";
15import { ScriptImporter, DownloadedScript } from "../../src/debugger/scriptImporter";
16
17suite("appWorker", function () {
18 suite("debuggerContext", function () {
19 const packagerPort = 8081;
20
21 suite("SandboxedAppWorker", function () {
22 const originalSpawn = child_process.spawn;
23 const sourcesStoragePath = path.resolve(__dirname, "assets");
24
25 // Inject 5 sec delay before shutting down to worker to give tests some time to execute
26 const WORKER_DELAY_SHUTDOWN = `setTimeout(() => {console.log("Shutting down")}, 5000)`;
27
28 let testWorker: ForkedAppWorker;
29 let spawnStub: Sinon.SinonStub;
30 let postReplyFunction = sinon.stub();
31
32 function workerWithScript(scriptBody: string): ForkedAppWorker {
33 const wrappedBody = [MultipleLifetimesAppWorker.WORKER_BOOTSTRAP,
34 scriptBody, MultipleLifetimesAppWorker.WORKER_DONE, WORKER_DELAY_SHUTDOWN].join("\n");
35
36 spawnStub = sinon.stub(child_process, "spawn", () =>
37 originalSpawn("node", ["-e", wrappedBody], { stdio: ["pipe", "pipe", "pipe", "ipc"] }));
38
39 testWorker = new ForkedAppWorker("localhost", packagerPort, sourcesStoragePath, "", postReplyFunction);
40 return testWorker;
41 }
42
43 teardown(function () {
44 // Reset everything
45 spawnStub.restore();
46 postReplyFunction.reset();
47 if (testWorker) {
48 testWorker.stop();
49 }
50 });
51
52 test("should execute scripts correctly and be able to invoke the callback", function () {
53 const expectedMessageResult = { success: true };
54 const startScriptContents = `var testResponse = ${JSON.stringify(expectedMessageResult)}; postMessage(testResponse);`;
55
56 return workerWithScript(startScriptContents).start()
57 .then(() =>
58 Q.delay(1000))
59 .then(() =>
60 assert(postReplyFunction.calledWithExactly(expectedMessageResult)));
61 });
62
63 test("should be able to import scripts", function () {
64 // NOTE: we're not able to mock reading script for import since this is performed by a
65 // separate node process and is out of control so we must provide a real script file
66 const scriptImportPath = path.resolve(sourcesStoragePath, "importScriptsTest.js");
67 const startScriptContents = `importScripts("${scriptImportPath}"); postMessage("postImport");`;
68
69 return workerWithScript(startScriptContents).start().then(() => {
70 // We have not yet finished importing the script, we should not have posted a response yet
71 assert(postReplyFunction.notCalled, "postReplyFuncton called before scripts imported");
72 return Q.delay(500);
73 }).then(() => {
74 assert(postReplyFunction.calledWith("postImport"), "postMessage after import not handled");
75 assert(postReplyFunction.calledWith("inImport"), "postMessage not registered from within import");
76 });
77 });
78
79 test("should correctly pass postMessage to the loaded script", function () {
80 const startScriptContents = `onmessage = postMessage;`;
81 const testMessage = { method: "test", success: true };
82
83 const worker = workerWithScript(startScriptContents);
84 return worker.start().then(() => {
85 assert(postReplyFunction.notCalled, "postRepyFunction called before message sent");
86 worker.postMessage(testMessage);
87 return Q.delay(1000);
88 }).then(() => {
89 assert(postReplyFunction.calledWith({ data: testMessage }), "No echo back from app");
90 });
91 });
92
93 test("should be able to require an installed node module via __debug__.require", function () {
94 const expectedMessageResult = { qString: Q.toString() };
95 const startScriptContents = `var Q = __debug__.require('q');
96 var testResponse = { qString: Q.toString() };
97 postMessage(testResponse);`;
98
99 return workerWithScript(startScriptContents).start()
100 .then(() => Q.delay(500))
101 .then(() =>
102 assert(postReplyFunction.calledWithExactly(expectedMessageResult)));
103 });
104
105 test("should download script from remote packager", async () => {
106 class MockAppWorker extends ForkedAppWorker {
107 public workerLoaded = Q.defer<void>();
108 public scriptImporter: ScriptImporter;
109 public debuggeeProcess: any = {
110 send: () => void 0,
111 };
112 }
113 const remotePackagerAddress = "1.2.3.4";
114 const remotePackagerPort = 1337;
115 const worker = new MockAppWorker(remotePackagerAddress, remotePackagerPort, sourcesStoragePath, "", postReplyFunction);
116 const downloadAppScriptStub = sinon.stub(worker.scriptImporter, "downloadAppScript");
117 const fakeDownloadedScript = <DownloadedScript>{ filepath: "/home/test/file" };
118 downloadAppScriptStub.returns(Q.resolve(fakeDownloadedScript));
119 const debuggeeProcessSendStub = sinon.stub(worker.debuggeeProcess, "send");
120 worker.workerLoaded.resolve(void 0);
121 const fakeMessage = {
122 method: "executeApplicationScript",
123 url: "http://localhost:8081/test-url",
124 };
125
126 await worker.postMessage(fakeMessage);
127
128 assert.equal(downloadAppScriptStub.calledOnce, true);
129 assert.equal(downloadAppScriptStub.firstCall.args[0], `http://${remotePackagerAddress}:${remotePackagerPort}/test-url`);
130 assert.equal(debuggeeProcessSendStub.calledOnce, true);
131 assert.deepEqual(debuggeeProcessSendStub.firstCall.args[0], {
132 data: {
133 ...fakeMessage,
134 url: fakeDownloadedScript.filepath,
135 },
136 });
137 });
138 });
139
140 suite("MultipleLifetimesAppWorker", function () {
141 const sourcesStoragePath = path.resolve(__dirname, "assets");
142
143 let multipleLifetimesWorker: MultipleLifetimesAppWorker;
144 let sandboxedAppWorkerStub: Sinon.SinonStub;
145 let appWorkerModuleStub: Sinon.SinonStub;
146 let webSocket: Sinon.SinonStub;
147 let webSocketConstructor: Sinon.SinonStub;
148 let packagerIsRunning: Sinon.SinonStub;
149
150 let sendMessage: (message: string) => void;
151
152 let clock: Sinon.SinonFakeTimers;
153
154 setup(function () {
155 webSocket = sinon.createStubInstance(WebSocket);
156
157 sandboxedAppWorkerStub = sinon.createStubInstance(ForkedAppWorker);
158 appWorkerModuleStub = sinon.stub(ForkedAppWorkerModule, "ForkedAppWorker").returns(sandboxedAppWorkerStub);
159
160 const messageInvocation: Sinon.SinonStub = (<any>webSocket).on.withArgs("message");
161 sendMessage = (message: string) => messageInvocation.callArgWith(1, message);
162
163 webSocketConstructor = sinon.stub();
164 webSocketConstructor.returns(webSocket);
165 packagerIsRunning = sinon.stub(packagerStatus, "ensurePackagerRunning");
166 packagerIsRunning.returns(Q.resolve(true));
167 const attachRequestArguments = {
168 address: "localhost",
169 port: packagerPort,
170 };
171
172 multipleLifetimesWorker = new MultipleLifetimesAppWorker(attachRequestArguments, sourcesStoragePath, "", {
173 webSocketConstructor: webSocketConstructor,
174 });
175
176 sinon.stub(multipleLifetimesWorker, "downloadAndPatchDebuggerWorker").returns(Q.resolve({}));
177 });
178
179 teardown(function () {
180 // Reset everything
181 multipleLifetimesWorker.stop();
182 appWorkerModuleStub.restore();
183 packagerIsRunning.restore();
184
185 if (clock) {
186 clock.restore();
187 }
188 });
189
190 test("with packager running should construct a websocket connection to the correct endpoint and listen for events", function () {
191 return multipleLifetimesWorker.start().then(() => {
192 const websocketRegex = new RegExp("ws://[^:]*:[0-9]*/debugger-proxy\\?role=debugger");
193 assert(webSocketConstructor.calledWithMatch(websocketRegex), "The web socket was not constructed to the correct url: " + webSocketConstructor.args[0][0]);
194
195 const expectedListeners = ["open", "close", "message", "error"];
196 expectedListeners.forEach((event) => {
197 assert((<any>webSocket).on.calledWithMatch(event), `Missing listener for ${event}`);
198 });
199 });
200 });
201
202 test("with packager running should attempt to reconnect after disconnecting", function () {
203 let startWorker = sinon.spy(multipleLifetimesWorker, "start");
204 return multipleLifetimesWorker.start().then(() => {
205 // Forget previous invocations
206 startWorker.reset();
207 packagerIsRunning.returns(Q.resolve(true));
208
209 clock = sinon.useFakeTimers();
210
211 const closeInvocation: Sinon.SinonStub = (<any>webSocket).on.withArgs("close");
212 closeInvocation.callArg(1);
213
214 // Ensure that the retry is 100ms after the disconnection
215 clock.tick(99);
216 assert(startWorker.notCalled, "Attempted to reconnect too quickly");
217
218 clock.tick(1);
219 }).then(() => {
220 assert(startWorker.called);
221 });
222 });
223
224 test("with packager running should respond correctly to prepareJSRuntime messages", function () {
225 return multipleLifetimesWorker.start().then(() => {
226 const messageId = 1;
227 const testMessage = JSON.stringify({ method: "prepareJSRuntime", id: messageId });
228 const expectedReply = JSON.stringify({ replyID: messageId });
229
230 const appWorkerDeferred = Q.defer<void>();
231
232 const appWorkerStart: Sinon.SinonStub = (<any>sandboxedAppWorkerStub).start;
233 const websocketSend: Sinon.SinonStub = (<any>webSocket).send;
234
235 appWorkerStart.returns(appWorkerDeferred.promise);
236
237 sendMessage(testMessage);
238
239 assert(appWorkerStart.called, "SandboxedAppWorker not started in respones to prepareJSRuntime");
240 assert(websocketSend.notCalled, "Response sent prior to configuring sandbox worker");
241
242 appWorkerDeferred.resolve(void 0);
243
244 return Q.delay(1).then(() => {
245 assert(websocketSend.calledWith(expectedReply), "Did not receive the expected response to prepareJSRuntime");
246 });
247 });
248 });
249
250 test("with packager running should pass unknown messages to the sandboxedAppWorker", function () {
251 return multipleLifetimesWorker.start().then(() => {
252 // Start up an app worker
253 const prepareJSMessage = JSON.stringify({ method: "prepareJSRuntime", id: 1 });
254 const appWorkerStart: Sinon.SinonStub = (<any>sandboxedAppWorkerStub).start;
255 appWorkerStart.returns(Q.resolve(void 0));
256
257 sendMessage(prepareJSMessage);
258
259 // Then attempt to message it
260
261 const testMessage = { method: "unknownMethod" };
262 const testMessageString = JSON.stringify(testMessage);
263
264 const postMessageStub: Sinon.SinonStub = (<any>sandboxedAppWorkerStub).postMessage;
265
266 assert(postMessageStub.notCalled, "sandboxedAppWorker.postMessage called prior to any message");
267 sendMessage(testMessageString);
268
269 assert(postMessageStub.calledWith(testMessage), "message was not passed to sandboxedAppWorker");
270 });
271 });
272
273 test("with packager running should close connection if there is another debugger connected to packager", () => {
274 return multipleLifetimesWorker.start().then(() => {
275 // Forget previous invocations
276 webSocketConstructor.reset();
277 clock = sinon.useFakeTimers(new Date().getTime());
278
279 const closeInvocation: Sinon.SinonStub = (<any>webSocket).on.withArgs("close");
280 (<any>webSocket)._closeMessage = "Another debugger is already connected";
281 closeInvocation.callArg(1);
282
283 // Ensure it doesn't try to reconnect
284 clock.tick(100);
285 assert(webSocketConstructor.notCalled, "socket attempted to reconnect");
286 });
287 });
288
289 test("without packager running should not start if there is no packager running", () => {
290 packagerIsRunning.returns(Q.reject(false));
291
292 return multipleLifetimesWorker.start()
293 .done(() => {
294 assert(webSocketConstructor.notCalled, "socket should not be created");
295 }, reason => {
296 assert(reason.message === `Cannot attach to packager. Are you sure there is a packager and it is running in the port ${packagerPort}? If your packager is configured to run in another port make sure to add that to the setting.json.`);
297 });
298 });
299 });
300
301 });
302});
303