microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
488dfb95e5dc78ff6ac49af7223d606b8ca319d2

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/test/debugger/appWorker.test.ts

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