microsoft/vscode-react-native

Public

mirrored from https://github.com/microsoft/vscode-react-nativeAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
0.4.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/test/debugger/appWorker.test.ts

270lines · modeblame

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