@theia/task 1.45.0 → 1.46.0-next.72

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. package/README.md +193 -193
  2. package/lib/browser/index.d.ts +6 -6
  3. package/lib/browser/index.js +33 -33
  4. package/lib/browser/process/process-task-contribution.d.ts +6 -6
  5. package/lib/browser/process/process-task-contribution.js +43 -43
  6. package/lib/browser/process/process-task-frontend-module.d.ts +2 -2
  7. package/lib/browser/process/process-task-frontend-module.js +27 -27
  8. package/lib/browser/process/process-task-resolver.d.ts +17 -17
  9. package/lib/browser/process/process-task-resolver.js +101 -101
  10. package/lib/browser/provided-task-configurations.d.ts +43 -43
  11. package/lib/browser/provided-task-configurations.js +213 -213
  12. package/lib/browser/provided-task-configurations.spec.d.ts +1 -1
  13. package/lib/browser/provided-task-configurations.spec.js +43 -43
  14. package/lib/browser/quick-open-task.d.ts +144 -144
  15. package/lib/browser/quick-open-task.d.ts.map +1 -1
  16. package/lib/browser/quick-open-task.js +752 -748
  17. package/lib/browser/quick-open-task.js.map +1 -1
  18. package/lib/browser/task-configuration-manager.d.ts +64 -64
  19. package/lib/browser/task-configuration-manager.js +261 -261
  20. package/lib/browser/task-configuration-model.d.ts +32 -32
  21. package/lib/browser/task-configuration-model.js +79 -79
  22. package/lib/browser/task-configurations.d.ts +127 -127
  23. package/lib/browser/task-configurations.js +486 -486
  24. package/lib/browser/task-contribution.d.ts +182 -182
  25. package/lib/browser/task-contribution.js +206 -206
  26. package/lib/browser/task-definition-registry.d.ts +38 -38
  27. package/lib/browser/task-definition-registry.js +134 -134
  28. package/lib/browser/task-definition-registry.spec.d.ts +1 -1
  29. package/lib/browser/task-definition-registry.spec.js +170 -170
  30. package/lib/browser/task-frontend-contribution.d.ts +56 -56
  31. package/lib/browser/task-frontend-contribution.js +358 -358
  32. package/lib/browser/task-frontend-module.d.ts +5 -5
  33. package/lib/browser/task-frontend-module.js +79 -79
  34. package/lib/browser/task-name-resolver.d.ts +13 -13
  35. package/lib/browser/task-name-resolver.js +67 -67
  36. package/lib/browser/task-node.d.ts +9 -9
  37. package/lib/browser/task-node.js +17 -17
  38. package/lib/browser/task-preferences.d.ts +4 -4
  39. package/lib/browser/task-preferences.js +40 -40
  40. package/lib/browser/task-problem-matcher-registry.d.ts +40 -40
  41. package/lib/browser/task-problem-matcher-registry.js +309 -309
  42. package/lib/browser/task-problem-pattern-registry.d.ts +23 -23
  43. package/lib/browser/task-problem-pattern-registry.js +210 -210
  44. package/lib/browser/task-schema-updater.d.ts +58 -58
  45. package/lib/browser/task-schema-updater.js +688 -688
  46. package/lib/browser/task-service.d.ts +279 -279
  47. package/lib/browser/task-service.js +1119 -1119
  48. package/lib/browser/task-source-resolver.d.ts +9 -9
  49. package/lib/browser/task-source-resolver.js +51 -51
  50. package/lib/browser/task-templates.d.ts +13 -13
  51. package/lib/browser/task-templates.js +161 -161
  52. package/lib/browser/task-terminal-widget-manager.d.ts +44 -44
  53. package/lib/browser/task-terminal-widget-manager.js +228 -228
  54. package/lib/browser/tasks-monaco-contribution.d.ts +1 -1
  55. package/lib/browser/tasks-monaco-contribution.js +27 -27
  56. package/lib/common/index.d.ts +4 -4
  57. package/lib/common/index.js +31 -31
  58. package/lib/common/problem-matcher-protocol.d.ts +124 -124
  59. package/lib/common/problem-matcher-protocol.js +132 -132
  60. package/lib/common/process/task-protocol.d.ts +68 -68
  61. package/lib/common/process/task-protocol.js +33 -33
  62. package/lib/common/task-common-module.d.ts +7 -7
  63. package/lib/common/task-common-module.js +33 -33
  64. package/lib/common/task-protocol.d.ts +192 -192
  65. package/lib/common/task-protocol.js +137 -137
  66. package/lib/common/task-util.d.ts +22 -22
  67. package/lib/common/task-util.js +46 -46
  68. package/lib/common/task-watcher.d.ts +17 -17
  69. package/lib/common/task-watcher.js +86 -86
  70. package/lib/node/custom/custom-task-runner-backend-module.d.ts +2 -2
  71. package/lib/node/custom/custom-task-runner-backend-module.js +36 -36
  72. package/lib/node/custom/custom-task-runner-contribution.d.ts +6 -6
  73. package/lib/node/custom/custom-task-runner-contribution.js +42 -42
  74. package/lib/node/custom/custom-task-runner.d.ts +15 -15
  75. package/lib/node/custom/custom-task-runner.js +69 -69
  76. package/lib/node/custom/custom-task.d.ts +20 -20
  77. package/lib/node/custom/custom-task.js +78 -78
  78. package/lib/node/index.d.ts +3 -3
  79. package/lib/node/index.js +30 -30
  80. package/lib/node/process/process-task-runner-backend-module.d.ts +2 -2
  81. package/lib/node/process/process-task-runner-backend-module.js +36 -36
  82. package/lib/node/process/process-task-runner-contribution.d.ts +6 -6
  83. package/lib/node/process/process-task-runner-contribution.js +43 -43
  84. package/lib/node/process/process-task-runner.d.ts +60 -60
  85. package/lib/node/process/process-task-runner.js +355 -355
  86. package/lib/node/process/process-task.d.ts +26 -26
  87. package/lib/node/process/process-task.js +135 -135
  88. package/lib/node/process/process-task.spec.d.ts +1 -1
  89. package/lib/node/process/process-task.spec.js +29 -29
  90. package/lib/node/task-abstract-line-matcher.d.ts +49 -49
  91. package/lib/node/task-abstract-line-matcher.js +273 -273
  92. package/lib/node/task-backend-application-contribution.d.ts +8 -8
  93. package/lib/node/task-backend-application-contribution.js +48 -48
  94. package/lib/node/task-backend-module.d.ts +3 -3
  95. package/lib/node/task-backend-module.js +50 -50
  96. package/lib/node/task-line-matchers.d.ts +27 -27
  97. package/lib/node/task-line-matchers.js +121 -121
  98. package/lib/node/task-manager.d.ts +55 -55
  99. package/lib/node/task-manager.js +135 -135
  100. package/lib/node/task-problem-collector.d.ts +10 -10
  101. package/lib/node/task-problem-collector.js +57 -57
  102. package/lib/node/task-problem-collector.spec.d.ts +1 -1
  103. package/lib/node/task-problem-collector.spec.js +310 -310
  104. package/lib/node/task-runner-protocol.d.ts +16 -16
  105. package/lib/node/task-runner-protocol.js +19 -19
  106. package/lib/node/task-runner.d.ts +50 -50
  107. package/lib/node/task-runner.js +97 -97
  108. package/lib/node/task-server.d.ts +39 -39
  109. package/lib/node/task-server.js +237 -237
  110. package/lib/node/task-server.slow-spec.d.ts +1 -1
  111. package/lib/node/task-server.slow-spec.js +395 -395
  112. package/lib/node/task.d.ts +50 -50
  113. package/lib/node/task.js +77 -77
  114. package/lib/node/test/task-test-container.d.ts +2 -2
  115. package/lib/node/test/task-test-container.js +58 -58
  116. package/package.json +14 -14
  117. package/src/browser/index.ts +22 -22
  118. package/src/browser/process/process-task-contribution.ts +31 -31
  119. package/src/browser/process/process-task-frontend-module.ts +27 -27
  120. package/src/browser/process/process-task-resolver.ts +89 -89
  121. package/src/browser/provided-task-configurations.spec.ts +46 -46
  122. package/src/browser/provided-task-configurations.ts +213 -213
  123. package/src/browser/quick-open-task.ts +831 -827
  124. package/src/browser/style/index.css +19 -19
  125. package/src/browser/task-configuration-manager.ts +256 -256
  126. package/src/browser/task-configuration-model.ts +101 -101
  127. package/src/browser/task-configurations.ts +508 -508
  128. package/src/browser/task-contribution.ts +266 -266
  129. package/src/browser/task-definition-registry.spec.ts +203 -203
  130. package/src/browser/task-definition-registry.ts +131 -131
  131. package/src/browser/task-frontend-contribution.ts +402 -402
  132. package/src/browser/task-frontend-module.ts +86 -86
  133. package/src/browser/task-name-resolver.ts +55 -55
  134. package/src/browser/task-node.ts +37 -37
  135. package/src/browser/task-preferences.ts +40 -40
  136. package/src/browser/task-problem-matcher-registry.ts +308 -308
  137. package/src/browser/task-problem-pattern-registry.ts +196 -196
  138. package/src/browser/task-schema-updater.ts +701 -701
  139. package/src/browser/task-service.ts +1164 -1164
  140. package/src/browser/task-source-resolver.ts +36 -36
  141. package/src/browser/task-templates.ts +168 -168
  142. package/src/browser/task-terminal-widget-manager.ts +224 -224
  143. package/src/browser/tasks-monaco-contribution.ts +27 -27
  144. package/src/common/index.ts +20 -20
  145. package/src/common/problem-matcher-protocol.ts +234 -234
  146. package/src/common/process/task-protocol.ts +97 -97
  147. package/src/common/task-common-module.ts +34 -34
  148. package/src/common/task-protocol.ts +317 -317
  149. package/src/common/task-util.ts +43 -43
  150. package/src/common/task-watcher.ts +78 -78
  151. package/src/node/custom/custom-task-runner-backend-module.ts +37 -37
  152. package/src/node/custom/custom-task-runner-contribution.ts +30 -30
  153. package/src/node/custom/custom-task-runner.ts +60 -60
  154. package/src/node/custom/custom-task.ts +73 -73
  155. package/src/node/index.ts +19 -19
  156. package/src/node/process/process-task-runner-backend-module.ts +37 -37
  157. package/src/node/process/process-task-runner-contribution.ts +31 -31
  158. package/src/node/process/process-task-runner.ts +371 -371
  159. package/src/node/process/process-task.spec.ts +30 -30
  160. package/src/node/process/process-task.ts +144 -144
  161. package/src/node/task-abstract-line-matcher.ts +312 -312
  162. package/src/node/task-backend-application-contribution.ts +36 -36
  163. package/src/node/task-backend-module.ts +57 -57
  164. package/src/node/task-line-matchers.ts +127 -127
  165. package/src/node/task-manager.ts +129 -129
  166. package/src/node/task-problem-collector.spec.ts +338 -338
  167. package/src/node/task-problem-collector.ts +62 -62
  168. package/src/node/task-runner-protocol.ts +33 -33
  169. package/src/node/task-runner.ts +96 -96
  170. package/src/node/task-server.slow-spec.ts +444 -444
  171. package/src/node/task-server.ts +263 -263
  172. package/src/node/task.ts +103 -103
  173. package/src/node/test/task-test-container.ts +63 -63
@@ -1,444 +1,444 @@
1
- // *****************************************************************************
2
- // Copyright (C) 2017-2019 Ericsson and others.
3
- //
4
- // This program and the accompanying materials are made available under the
5
- // terms of the Eclipse Public License v. 2.0 which is available at
6
- // http://www.eclipse.org/legal/epl-2.0.
7
- //
8
- // This Source Code may also be made available under the following Secondary
9
- // Licenses when the conditions for such availability set forth in the Eclipse
10
- // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
- // with the GNU Classpath Exception which is available at
12
- // https://www.gnu.org/software/classpath/license.html.
13
- //
14
- // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
- // *****************************************************************************
16
-
17
- // tslint:disable-next-line:no-implicit-dependencies
18
- import 'reflect-metadata';
19
- import { createTaskTestContainer } from './test/task-test-container';
20
- import { BackendApplication } from '@theia/core/lib/node/backend-application';
21
- import { TaskExitedEvent, TaskInfo, TaskServer, TaskWatcher, TaskConfiguration } from '../common';
22
- import { ProcessType, ProcessTaskConfiguration } from '../common/process/task-protocol';
23
- import * as http from 'http';
24
- import * as https from 'https';
25
- import { isWindows, isOSX } from '@theia/core/lib/common/os';
26
- import { FileUri } from '@theia/core/lib/node';
27
- import { terminalsPath } from '@theia/terminal/lib/common/terminal-protocol';
28
- import { expectThrowsAsync } from '@theia/core/lib/common/test/expect';
29
- import { TestWebSocketChannelSetup } from '@theia/core/lib/node/messaging/test/test-web-socket-channel';
30
- import { expect } from 'chai';
31
- import URI from '@theia/core/lib/common/uri';
32
- import { StringBufferingStream } from '@theia/terminal/lib/node/buffering-stream';
33
-
34
- // test scripts that we bundle with tasks
35
- const commandShortRunning = './task';
36
- const commandShortRunningOsx = './task-osx';
37
- const commandShortRunningWindows = '.\\task.bat';
38
-
39
- const commandLongRunning = './task-long-running';
40
- const commandLongRunningOsx = './task-long-running-osx';
41
- const commandLongRunningWindows = '.\\task-long-running.bat';
42
-
43
- const bogusCommand = 'thisisnotavalidcommand';
44
-
45
- const commandUnixNoop = 'true';
46
- const commandWindowsNoop = 'rundll32.exe';
47
-
48
- /** Expects argv to be ['a', 'b', 'c'] */
49
- const script0 = './test-arguments-0.js';
50
- /** Expects argv to be ['a', 'b', ' c'] */
51
- const script1 = './test-arguments-1.js';
52
- /** Expects argv to be ['a', 'b', 'c"'] */
53
- const script2 = './test-arguments-2.js';
54
-
55
- // we use test-resources subfolder ('<theia>/packages/task/test-resources/'),
56
- // as workspace root, for these tests
57
- const wsRootUri: URI = FileUri.create(__dirname).resolve('../../test-resources');
58
- const wsRoot: string = FileUri.fsPath(wsRootUri);
59
-
60
- describe('Task server / back-end', function (): void {
61
- this.timeout(10000);
62
-
63
- let backend: BackendApplication;
64
- let server: http.Server | https.Server;
65
- let taskServer: TaskServer;
66
- let taskWatcher: TaskWatcher;
67
-
68
- beforeEach(async () => {
69
- delete process.env['THEIA_TASK_TEST_DEBUG'];
70
- const testContainer = createTaskTestContainer();
71
- taskWatcher = testContainer.get(TaskWatcher);
72
- taskServer = testContainer.get(TaskServer);
73
- taskServer.setClient(taskWatcher.getTaskClient());
74
- backend = testContainer.get(BackendApplication);
75
- server = await backend.start(3000, 'localhost');
76
- });
77
-
78
- afterEach(async () => {
79
- const _backend = backend;
80
- const _server = server;
81
- backend = undefined!;
82
- taskServer = undefined!;
83
- taskWatcher = undefined!;
84
- server = undefined!;
85
- _backend['onStop']();
86
- _server.close();
87
- });
88
-
89
- it('task running in terminal - expected data is received from the terminal ws server', async function (): Promise<void> {
90
- const someString = 'someSingleWordString';
91
-
92
- // create task using terminal process
93
- const command = isWindows ? commandShortRunningWindows : (isOSX ? commandShortRunningOsx : commandShortRunning);
94
- const taskInfo: TaskInfo = await taskServer.run(createProcessTaskConfig('shell', `${command} ${someString}`), wsRoot);
95
- const terminalId = taskInfo.terminalId;
96
-
97
- const messagesToWaitFor = 10;
98
- const messages: string[] = [];
99
-
100
- // check output of task on terminal is what we expect
101
- const expected = `${isOSX ? 'tasking osx' : 'tasking'}... ${someString}`;
102
-
103
- // hook-up to terminal's ws and confirm that it outputs expected tasks' output
104
- await new Promise<void>((resolve, reject) => {
105
- const setup = new TestWebSocketChannelSetup({ server, path: `${terminalsPath}/${terminalId}` });
106
- const stringBuffer = new StringBufferingStream();
107
- setup.connectionProvider.listen(`${terminalsPath}/${terminalId}`, (path, channel) => {
108
- channel.onMessage(e => stringBuffer.push(e().readString()));
109
- channel.onError(reject);
110
- channel.onClose(() => reject(new Error('Channel has been closed')));
111
- }, false);
112
- stringBuffer.onData(currentMessage => {
113
- // Instead of waiting for one message from the terminal, we wait for several ones as the very first message can be something unexpected.
114
- // For instance: `nvm is not compatible with the \"PREFIX\" environment variable: currently set to \"/usr/local\"\r\n`
115
- messages.unshift(currentMessage);
116
- if (currentMessage.includes(expected)) {
117
- resolve();
118
- } else if (messages.length >= messagesToWaitFor) {
119
- reject(new Error(`expected sub-string not found in terminal output. Expected: "${expected}" vs Actual messages: ${JSON.stringify(messages)}`));
120
- }
121
- });
122
- });
123
- });
124
-
125
- it('task using raw process - task server success response shall not contain a terminal id', async function (): Promise<void> {
126
- const someString = 'someSingleWordString';
127
- const command = isWindows ? commandShortRunningWindows : (isOSX ? commandShortRunningOsx : commandShortRunning);
128
- const executable = FileUri.fsPath(wsRootUri.resolve(command));
129
-
130
- // create task using raw process
131
- const taskInfo: TaskInfo = await taskServer.run(createProcessTaskConfig('process', executable, [someString]), wsRoot);
132
-
133
- await new Promise<void>((resolve, reject) => {
134
- const toDispose = taskWatcher.onTaskExit((event: TaskExitedEvent) => {
135
- if (event.taskId === taskInfo.taskId && event.code === 0) {
136
- if (typeof taskInfo.terminalId === 'number') {
137
- resolve();
138
- } else {
139
- reject(new Error(`terminal id was expected to be a number, got: ${typeof taskInfo.terminalId}`));
140
- }
141
- toDispose.dispose();
142
- }
143
- });
144
- });
145
- });
146
-
147
- it('task is executed successfully with cwd as a file URI', async function (): Promise<void> {
148
- const command = isWindows ? commandShortRunningWindows : (isOSX ? commandShortRunningOsx : commandShortRunning);
149
- const config = createProcessTaskConfig('shell', command, undefined, FileUri.create(wsRoot).toString());
150
- const taskInfo: TaskInfo = await taskServer.run(config, wsRoot);
151
- await checkSuccessfulProcessExit(taskInfo, taskWatcher);
152
- });
153
-
154
- it('task is executed successfully using terminal process', async function (): Promise<void> {
155
- const command = isWindows ? commandShortRunningWindows : (isOSX ? commandShortRunningOsx : commandShortRunning);
156
- const taskInfo: TaskInfo = await taskServer.run(createProcessTaskConfig('shell', command, undefined), wsRoot);
157
- await checkSuccessfulProcessExit(taskInfo, taskWatcher);
158
- });
159
-
160
- it('task is executed successfully using raw process', async function (): Promise<void> {
161
- const command = isWindows ? commandShortRunningWindows : (isOSX ? commandShortRunningOsx : commandShortRunning);
162
- const executable = FileUri.fsPath(wsRootUri.resolve(command));
163
- const taskInfo: TaskInfo = await taskServer.run(createProcessTaskConfig('process', executable, []));
164
- await checkSuccessfulProcessExit(taskInfo, taskWatcher);
165
- });
166
-
167
- it('task without a specific runner is executed successfully using as a process', async function (): Promise<void> {
168
- const command = isWindows ? commandWindowsNoop : commandUnixNoop;
169
-
170
- // there's no runner registered for the 'npm' task type
171
- const taskConfig: TaskConfiguration = createTaskConfig('npm', command, []);
172
- const taskInfo: TaskInfo = await taskServer.run(taskConfig, wsRoot);
173
- await checkSuccessfulProcessExit(taskInfo, taskWatcher);
174
- });
175
-
176
- it('task can successfully execute command found in system path using a terminal process', async function (): Promise<void> {
177
- const command = isWindows ? commandWindowsNoop : commandUnixNoop;
178
- const opts: TaskConfiguration = createProcessTaskConfig('shell', command, []);
179
- const taskInfo: TaskInfo = await taskServer.run(opts, wsRoot);
180
- await checkSuccessfulProcessExit(taskInfo, taskWatcher);
181
- });
182
-
183
- it('task can successfully execute command found in system path using a raw process', async function (): Promise<void> {
184
- const command = isWindows ? commandWindowsNoop : commandUnixNoop;
185
- const taskInfo: TaskInfo = await taskServer.run(createProcessTaskConfig('process', command, []), wsRoot);
186
- await checkSuccessfulProcessExit(taskInfo, taskWatcher);
187
- });
188
-
189
- it('task using type "shell" can be killed', async function (): Promise<void> {
190
- const taskInfo: TaskInfo = await taskServer.run(createTaskConfigTaskLongRunning('shell'), wsRoot);
191
-
192
- const exitStatusPromise = getExitStatus(taskInfo, taskWatcher);
193
- taskServer.kill(taskInfo.taskId);
194
- const exitStatus = await exitStatusPromise;
195
-
196
- // node-pty reports different things on Linux/macOS vs Windows when
197
- // killing a process. This is not ideal, but that's how things are
198
- // currently. Ideally, its behavior should be aligned as much as
199
- // possible on what node's child_process module does.
200
- if (isWindows) {
201
- // On Windows, node-pty just reports an exit code of 0.
202
- expect(exitStatus).equals(0);
203
- } else {
204
- // On Linux/macOS, node-pty sends SIGHUP by default, for some reason.
205
- expect(exitStatus).equals('SIGHUP');
206
- }
207
- });
208
-
209
- it('task using type "process" can be killed', async function (): Promise<void> {
210
- const taskInfo: TaskInfo = await taskServer.run(createTaskConfigTaskLongRunning('process'), wsRoot);
211
-
212
- const exitStatusPromise = getExitStatus(taskInfo, taskWatcher);
213
- taskServer.kill(taskInfo.taskId);
214
- const exitStatus = await exitStatusPromise;
215
-
216
- // node-pty reports different things on Linux/macOS vs Windows when
217
- // killing a process. This is not ideal, but that's how things are
218
- // currently. Ideally, its behavior should be aligned as much as
219
- // possible on what node's child_process module does.
220
- if (isWindows) {
221
- // On Windows, node-pty just reports an exit code of 0.
222
- expect(exitStatus).equals(0);
223
- } else {
224
- // On Linux/macOS, node-pty sends SIGHUP by default, for some reason.
225
- expect(exitStatus).equals('SIGHUP');
226
- }
227
- });
228
-
229
- /**
230
- * TODO: Figure out how to debug a process that correctly starts but exits with a return code > 0
231
- */
232
- it('task using terminal process can handle command that does not exist', async function (): Promise<void> {
233
- const taskInfo: TaskInfo = await taskServer.run(createProcessTaskConfig2('shell', bogusCommand, []), wsRoot);
234
- const code = await new Promise<number>((resolve, reject) => {
235
- taskWatcher.onTaskExit((event: TaskExitedEvent) => {
236
- if (event.taskId !== taskInfo.taskId || event.code === undefined) {
237
- reject(new Error(JSON.stringify(event)));
238
- } else {
239
- resolve(event.code);
240
- }
241
- });
242
- });
243
- // node-pty reports different things on Linux/macOS vs Windows when
244
- // killing a process. This is not ideal, but that's how things are
245
- // currently. Ideally, its behavior should be aligned as much as
246
- // possible on what node's child_process module does.
247
- if (isWindows) {
248
- expect(code).equals(1);
249
- } else {
250
- expect(code).equals(127);
251
- }
252
- });
253
-
254
- it('task using raw process can handle command that does not exist', async function (): Promise<void> {
255
- const p = taskServer.run(createProcessTaskConfig2('process', bogusCommand, []), wsRoot);
256
- await expectThrowsAsync(p, 'ENOENT');
257
- });
258
-
259
- it('getTasks(ctx) returns tasks according to created context', async function (): Promise<void> {
260
- const context1 = 'aContext';
261
- const context2 = 'anotherContext';
262
-
263
- // create some tasks: 4 for context1, 2 for context2
264
- const task1 = await taskServer.run(createTaskConfigTaskLongRunning('shell'), context1);
265
- const task2 = await taskServer.run(createTaskConfigTaskLongRunning('process'), context2);
266
- const task3 = await taskServer.run(createTaskConfigTaskLongRunning('shell'), context1);
267
- const task4 = await taskServer.run(createTaskConfigTaskLongRunning('process'), context2);
268
- const task5 = await taskServer.run(createTaskConfigTaskLongRunning('shell'), context1);
269
- const task6 = await taskServer.run(createTaskConfigTaskLongRunning('process'), context1);
270
-
271
- const runningTasksCtx1 = await taskServer.getTasks(context1); // should return 4 tasks
272
- const runningTasksCtx2 = await taskServer.getTasks(context2); // should return 2 tasks
273
- const runningTasksAll = await taskServer.getTasks(); // should return 6 tasks
274
-
275
- if (runningTasksCtx1.length !== 4) {
276
- throw new Error(`Error: unexpected number of running tasks for context 1: expected: 4, actual: ${runningTasksCtx1.length}`);
277
- } if (runningTasksCtx2.length !== 2) {
278
- throw new Error(`Error: unexpected number of running tasks for context 2: expected: 2, actual: ${runningTasksCtx1.length}`);
279
- } if (runningTasksAll.length !== 6) {
280
- throw new Error(`Error: unexpected total number of running tasks for all contexts: expected: 6, actual: ${runningTasksCtx1.length}`);
281
- }
282
-
283
- // cleanup
284
- await taskServer.kill(task1.taskId);
285
- await taskServer.kill(task2.taskId);
286
- await taskServer.kill(task3.taskId);
287
- await taskServer.kill(task4.taskId);
288
- await taskServer.kill(task5.taskId);
289
- await taskServer.kill(task6.taskId);
290
- });
291
-
292
- it('creating and killing a bunch of tasks works as expected', async function (): Promise<void> {
293
- // const command = isWindows ? command_absolute_path_long_running_windows : command_absolute_path_long_running;
294
- const numTasks = 20;
295
- const taskInfo: TaskInfo[] = [];
296
-
297
- // create a mix of terminal and raw processes
298
- for (let i = 0; i < numTasks; i++) {
299
- if (i % 2 === 0) {
300
- taskInfo.push(await taskServer.run(createTaskConfigTaskLongRunning('shell')));
301
- } else {
302
- taskInfo.push(await taskServer.run(createTaskConfigTaskLongRunning('process')));
303
- }
304
- }
305
-
306
- const numRunningTasksAfterCreated = await taskServer.getTasks();
307
-
308
- for (let i = 0; i < taskInfo.length; i++) {
309
- await taskServer.kill(taskInfo[i].taskId);
310
- }
311
- const numRunningTasksAfterKilled = await taskServer.getTasks();
312
-
313
- if (numRunningTasksAfterCreated.length !== numTasks) {
314
- throw new Error(`Error: unexpected number of running tasks: expected: ${numTasks}, actual: ${numRunningTasksAfterCreated.length}`);
315
- } if (numRunningTasksAfterKilled.length !== 0) {
316
- throw new Error(`Error: remaining running tasks, after all killed: expected: 0, actual: ${numRunningTasksAfterKilled.length}`);
317
- }
318
-
319
- });
320
-
321
- it('shell task should execute the command as a whole if not arguments are specified', async function (): Promise<void> {
322
- const taskInfo = await taskServer.run(createProcessTaskConfig2('shell', `node ${script0} debug-hint:0a a b c`));
323
- const exitStatus = await getExitStatus(taskInfo, taskWatcher);
324
- expect(exitStatus).eq(0);
325
- });
326
-
327
- it('shell task should fail if user defines a full command line and arguments', async function (): Promise<void> {
328
- const taskInfo = await taskServer.run(createProcessTaskConfig2('shell', `node ${script0} debug-hint:0b a b c`, []));
329
- const exitStatus = await getExitStatus(taskInfo, taskWatcher);
330
- expect(exitStatus).not.eq(0);
331
- });
332
-
333
- it('shell task should be able to exec using simple arguments', async function (): Promise<void> {
334
- const taskInfo = await taskServer.run(createProcessTaskConfig2('shell', 'node', [script0, 'debug-hint:0c', 'a', 'b', 'c']));
335
- const exitStatus = await getExitStatus(taskInfo, taskWatcher);
336
- expect(exitStatus).eq(0);
337
- });
338
-
339
- it('shell task should be able to run using arguments containing whitespace', async function (): Promise<void> {
340
- const taskInfo = await taskServer.run(createProcessTaskConfig2('shell', 'node', [script1, 'debug-hint:1', 'a', 'b', ' c']));
341
- const exitStatus = await getExitStatus(taskInfo, taskWatcher);
342
- expect(exitStatus).eq(0);
343
- });
344
-
345
- it('shell task will fail if user specify problematic arguments', async function (): Promise<void> {
346
- const taskInfo = await taskServer.run(createProcessTaskConfig2('shell', 'node', [script2, 'debug-hint:2a', 'a', 'b', 'c"']));
347
- const exitStatus = await getExitStatus(taskInfo, taskWatcher);
348
- expect(exitStatus).not.eq(0);
349
- });
350
-
351
- it('shell task should be able to run using arguments specifying which quoting method to use', async function (): Promise<void> {
352
- const taskInfo = await taskServer.run(createProcessTaskConfig2('shell', 'node', [script2, 'debug-hint:2b', 'a', 'b', { value: 'c"', quoting: 'escape' }]));
353
- const exitStatus = await getExitStatus(taskInfo, taskWatcher);
354
- expect(exitStatus).eq(0);
355
- });
356
-
357
- it('shell task should be able to run using arguments with forbidden characters but no whitespace', async function (): Promise<void> {
358
- const taskInfo = await taskServer.run(createProcessTaskConfig2('shell', 'node', ['-e', 'setTimeout(console.log,1000,1+2)']));
359
- const exitStatus = await getExitStatus(taskInfo, taskWatcher);
360
- expect(exitStatus).eq(0);
361
- });
362
-
363
- });
364
-
365
- function createTaskConfig(taskType: string, command: string, args: string[]): TaskConfiguration {
366
- const options: TaskConfiguration = {
367
- label: 'test task',
368
- type: taskType,
369
- _source: '/source/folder',
370
- _scope: '/source/folder',
371
- command,
372
- args,
373
- options: { cwd: wsRoot }
374
- };
375
- return options;
376
- }
377
-
378
- function createProcessTaskConfig(processType: ProcessType, command: string, args?: string[], cwd: string = wsRoot): TaskConfiguration {
379
- return <ProcessTaskConfiguration>{
380
- label: 'test task',
381
- type: processType,
382
- _source: '/source/folder',
383
- _scope: '/source/folder',
384
- command,
385
- args,
386
- options: { cwd },
387
- };
388
- }
389
-
390
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
391
- function createProcessTaskConfig2(processType: ProcessType, command: string, args?: any[]): TaskConfiguration {
392
- return <ProcessTaskConfiguration>{
393
- label: 'test task',
394
- type: processType,
395
- command,
396
- args,
397
- options: { cwd: wsRoot },
398
- };
399
- }
400
-
401
- function createTaskConfigTaskLongRunning(processType: ProcessType): TaskConfiguration {
402
- return <ProcessTaskConfiguration>{
403
- label: '[Task] long running test task (~300s)',
404
- type: processType,
405
- _source: '/source/folder',
406
- _scope: '/source/folder',
407
- options: { cwd: wsRoot },
408
- command: commandLongRunning,
409
- windows: {
410
- command: FileUri.fsPath(wsRootUri.resolve(commandLongRunningWindows)),
411
- options: { cwd: wsRoot }
412
- },
413
- osx: {
414
- command: FileUri.fsPath(wsRootUri.resolve(commandLongRunningOsx))
415
- }
416
- };
417
- }
418
-
419
- function checkSuccessfulProcessExit(taskInfo: TaskInfo, taskWatcher: TaskWatcher): Promise<void> {
420
- return new Promise<void>((resolve, reject) => {
421
- const toDispose = taskWatcher.onTaskExit((event: TaskExitedEvent) => {
422
- if (event.taskId === taskInfo.taskId && event.code === 0) {
423
- toDispose.dispose();
424
- resolve();
425
- }
426
- });
427
- });
428
- }
429
-
430
- function getExitStatus(taskInfo: TaskInfo, taskWatcher: TaskWatcher): Promise<string | number> {
431
- return new Promise<string | number>((resolve, reject) => {
432
- taskWatcher.onTaskExit((event: TaskExitedEvent) => {
433
- if (event.taskId === taskInfo.taskId) {
434
- if (typeof event.signal === 'string') {
435
- resolve(event.signal);
436
- } else if (typeof event.code === 'number') {
437
- resolve(event.code);
438
- } else {
439
- reject(new Error('no code nor signal'));
440
- }
441
- }
442
- });
443
- });
444
- }
1
+ // *****************************************************************************
2
+ // Copyright (C) 2017-2019 Ericsson and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ // tslint:disable-next-line:no-implicit-dependencies
18
+ import 'reflect-metadata';
19
+ import { createTaskTestContainer } from './test/task-test-container';
20
+ import { BackendApplication } from '@theia/core/lib/node/backend-application';
21
+ import { TaskExitedEvent, TaskInfo, TaskServer, TaskWatcher, TaskConfiguration } from '../common';
22
+ import { ProcessType, ProcessTaskConfiguration } from '../common/process/task-protocol';
23
+ import * as http from 'http';
24
+ import * as https from 'https';
25
+ import { isWindows, isOSX } from '@theia/core/lib/common/os';
26
+ import { FileUri } from '@theia/core/lib/node';
27
+ import { terminalsPath } from '@theia/terminal/lib/common/terminal-protocol';
28
+ import { expectThrowsAsync } from '@theia/core/lib/common/test/expect';
29
+ import { TestWebSocketChannelSetup } from '@theia/core/lib/node/messaging/test/test-web-socket-channel';
30
+ import { expect } from 'chai';
31
+ import URI from '@theia/core/lib/common/uri';
32
+ import { StringBufferingStream } from '@theia/terminal/lib/node/buffering-stream';
33
+
34
+ // test scripts that we bundle with tasks
35
+ const commandShortRunning = './task';
36
+ const commandShortRunningOsx = './task-osx';
37
+ const commandShortRunningWindows = '.\\task.bat';
38
+
39
+ const commandLongRunning = './task-long-running';
40
+ const commandLongRunningOsx = './task-long-running-osx';
41
+ const commandLongRunningWindows = '.\\task-long-running.bat';
42
+
43
+ const bogusCommand = 'thisisnotavalidcommand';
44
+
45
+ const commandUnixNoop = 'true';
46
+ const commandWindowsNoop = 'rundll32.exe';
47
+
48
+ /** Expects argv to be ['a', 'b', 'c'] */
49
+ const script0 = './test-arguments-0.js';
50
+ /** Expects argv to be ['a', 'b', ' c'] */
51
+ const script1 = './test-arguments-1.js';
52
+ /** Expects argv to be ['a', 'b', 'c"'] */
53
+ const script2 = './test-arguments-2.js';
54
+
55
+ // we use test-resources subfolder ('<theia>/packages/task/test-resources/'),
56
+ // as workspace root, for these tests
57
+ const wsRootUri: URI = FileUri.create(__dirname).resolve('../../test-resources');
58
+ const wsRoot: string = FileUri.fsPath(wsRootUri);
59
+
60
+ describe('Task server / back-end', function (): void {
61
+ this.timeout(10000);
62
+
63
+ let backend: BackendApplication;
64
+ let server: http.Server | https.Server;
65
+ let taskServer: TaskServer;
66
+ let taskWatcher: TaskWatcher;
67
+
68
+ beforeEach(async () => {
69
+ delete process.env['THEIA_TASK_TEST_DEBUG'];
70
+ const testContainer = createTaskTestContainer();
71
+ taskWatcher = testContainer.get(TaskWatcher);
72
+ taskServer = testContainer.get(TaskServer);
73
+ taskServer.setClient(taskWatcher.getTaskClient());
74
+ backend = testContainer.get(BackendApplication);
75
+ server = await backend.start(3000, 'localhost');
76
+ });
77
+
78
+ afterEach(async () => {
79
+ const _backend = backend;
80
+ const _server = server;
81
+ backend = undefined!;
82
+ taskServer = undefined!;
83
+ taskWatcher = undefined!;
84
+ server = undefined!;
85
+ _backend['onStop']();
86
+ _server.close();
87
+ });
88
+
89
+ it('task running in terminal - expected data is received from the terminal ws server', async function (): Promise<void> {
90
+ const someString = 'someSingleWordString';
91
+
92
+ // create task using terminal process
93
+ const command = isWindows ? commandShortRunningWindows : (isOSX ? commandShortRunningOsx : commandShortRunning);
94
+ const taskInfo: TaskInfo = await taskServer.run(createProcessTaskConfig('shell', `${command} ${someString}`), wsRoot);
95
+ const terminalId = taskInfo.terminalId;
96
+
97
+ const messagesToWaitFor = 10;
98
+ const messages: string[] = [];
99
+
100
+ // check output of task on terminal is what we expect
101
+ const expected = `${isOSX ? 'tasking osx' : 'tasking'}... ${someString}`;
102
+
103
+ // hook-up to terminal's ws and confirm that it outputs expected tasks' output
104
+ await new Promise<void>((resolve, reject) => {
105
+ const setup = new TestWebSocketChannelSetup({ server, path: `${terminalsPath}/${terminalId}` });
106
+ const stringBuffer = new StringBufferingStream();
107
+ setup.connectionProvider.listen(`${terminalsPath}/${terminalId}`, (path, channel) => {
108
+ channel.onMessage(e => stringBuffer.push(e().readString()));
109
+ channel.onError(reject);
110
+ channel.onClose(() => reject(new Error('Channel has been closed')));
111
+ }, false);
112
+ stringBuffer.onData(currentMessage => {
113
+ // Instead of waiting for one message from the terminal, we wait for several ones as the very first message can be something unexpected.
114
+ // For instance: `nvm is not compatible with the \"PREFIX\" environment variable: currently set to \"/usr/local\"\r\n`
115
+ messages.unshift(currentMessage);
116
+ if (currentMessage.includes(expected)) {
117
+ resolve();
118
+ } else if (messages.length >= messagesToWaitFor) {
119
+ reject(new Error(`expected sub-string not found in terminal output. Expected: "${expected}" vs Actual messages: ${JSON.stringify(messages)}`));
120
+ }
121
+ });
122
+ });
123
+ });
124
+
125
+ it('task using raw process - task server success response shall not contain a terminal id', async function (): Promise<void> {
126
+ const someString = 'someSingleWordString';
127
+ const command = isWindows ? commandShortRunningWindows : (isOSX ? commandShortRunningOsx : commandShortRunning);
128
+ const executable = FileUri.fsPath(wsRootUri.resolve(command));
129
+
130
+ // create task using raw process
131
+ const taskInfo: TaskInfo = await taskServer.run(createProcessTaskConfig('process', executable, [someString]), wsRoot);
132
+
133
+ await new Promise<void>((resolve, reject) => {
134
+ const toDispose = taskWatcher.onTaskExit((event: TaskExitedEvent) => {
135
+ if (event.taskId === taskInfo.taskId && event.code === 0) {
136
+ if (typeof taskInfo.terminalId === 'number') {
137
+ resolve();
138
+ } else {
139
+ reject(new Error(`terminal id was expected to be a number, got: ${typeof taskInfo.terminalId}`));
140
+ }
141
+ toDispose.dispose();
142
+ }
143
+ });
144
+ });
145
+ });
146
+
147
+ it('task is executed successfully with cwd as a file URI', async function (): Promise<void> {
148
+ const command = isWindows ? commandShortRunningWindows : (isOSX ? commandShortRunningOsx : commandShortRunning);
149
+ const config = createProcessTaskConfig('shell', command, undefined, FileUri.create(wsRoot).toString());
150
+ const taskInfo: TaskInfo = await taskServer.run(config, wsRoot);
151
+ await checkSuccessfulProcessExit(taskInfo, taskWatcher);
152
+ });
153
+
154
+ it('task is executed successfully using terminal process', async function (): Promise<void> {
155
+ const command = isWindows ? commandShortRunningWindows : (isOSX ? commandShortRunningOsx : commandShortRunning);
156
+ const taskInfo: TaskInfo = await taskServer.run(createProcessTaskConfig('shell', command, undefined), wsRoot);
157
+ await checkSuccessfulProcessExit(taskInfo, taskWatcher);
158
+ });
159
+
160
+ it('task is executed successfully using raw process', async function (): Promise<void> {
161
+ const command = isWindows ? commandShortRunningWindows : (isOSX ? commandShortRunningOsx : commandShortRunning);
162
+ const executable = FileUri.fsPath(wsRootUri.resolve(command));
163
+ const taskInfo: TaskInfo = await taskServer.run(createProcessTaskConfig('process', executable, []));
164
+ await checkSuccessfulProcessExit(taskInfo, taskWatcher);
165
+ });
166
+
167
+ it('task without a specific runner is executed successfully using as a process', async function (): Promise<void> {
168
+ const command = isWindows ? commandWindowsNoop : commandUnixNoop;
169
+
170
+ // there's no runner registered for the 'npm' task type
171
+ const taskConfig: TaskConfiguration = createTaskConfig('npm', command, []);
172
+ const taskInfo: TaskInfo = await taskServer.run(taskConfig, wsRoot);
173
+ await checkSuccessfulProcessExit(taskInfo, taskWatcher);
174
+ });
175
+
176
+ it('task can successfully execute command found in system path using a terminal process', async function (): Promise<void> {
177
+ const command = isWindows ? commandWindowsNoop : commandUnixNoop;
178
+ const opts: TaskConfiguration = createProcessTaskConfig('shell', command, []);
179
+ const taskInfo: TaskInfo = await taskServer.run(opts, wsRoot);
180
+ await checkSuccessfulProcessExit(taskInfo, taskWatcher);
181
+ });
182
+
183
+ it('task can successfully execute command found in system path using a raw process', async function (): Promise<void> {
184
+ const command = isWindows ? commandWindowsNoop : commandUnixNoop;
185
+ const taskInfo: TaskInfo = await taskServer.run(createProcessTaskConfig('process', command, []), wsRoot);
186
+ await checkSuccessfulProcessExit(taskInfo, taskWatcher);
187
+ });
188
+
189
+ it('task using type "shell" can be killed', async function (): Promise<void> {
190
+ const taskInfo: TaskInfo = await taskServer.run(createTaskConfigTaskLongRunning('shell'), wsRoot);
191
+
192
+ const exitStatusPromise = getExitStatus(taskInfo, taskWatcher);
193
+ taskServer.kill(taskInfo.taskId);
194
+ const exitStatus = await exitStatusPromise;
195
+
196
+ // node-pty reports different things on Linux/macOS vs Windows when
197
+ // killing a process. This is not ideal, but that's how things are
198
+ // currently. Ideally, its behavior should be aligned as much as
199
+ // possible on what node's child_process module does.
200
+ if (isWindows) {
201
+ // On Windows, node-pty just reports an exit code of 0.
202
+ expect(exitStatus).equals(0);
203
+ } else {
204
+ // On Linux/macOS, node-pty sends SIGHUP by default, for some reason.
205
+ expect(exitStatus).equals('SIGHUP');
206
+ }
207
+ });
208
+
209
+ it('task using type "process" can be killed', async function (): Promise<void> {
210
+ const taskInfo: TaskInfo = await taskServer.run(createTaskConfigTaskLongRunning('process'), wsRoot);
211
+
212
+ const exitStatusPromise = getExitStatus(taskInfo, taskWatcher);
213
+ taskServer.kill(taskInfo.taskId);
214
+ const exitStatus = await exitStatusPromise;
215
+
216
+ // node-pty reports different things on Linux/macOS vs Windows when
217
+ // killing a process. This is not ideal, but that's how things are
218
+ // currently. Ideally, its behavior should be aligned as much as
219
+ // possible on what node's child_process module does.
220
+ if (isWindows) {
221
+ // On Windows, node-pty just reports an exit code of 0.
222
+ expect(exitStatus).equals(0);
223
+ } else {
224
+ // On Linux/macOS, node-pty sends SIGHUP by default, for some reason.
225
+ expect(exitStatus).equals('SIGHUP');
226
+ }
227
+ });
228
+
229
+ /**
230
+ * TODO: Figure out how to debug a process that correctly starts but exits with a return code > 0
231
+ */
232
+ it('task using terminal process can handle command that does not exist', async function (): Promise<void> {
233
+ const taskInfo: TaskInfo = await taskServer.run(createProcessTaskConfig2('shell', bogusCommand, []), wsRoot);
234
+ const code = await new Promise<number>((resolve, reject) => {
235
+ taskWatcher.onTaskExit((event: TaskExitedEvent) => {
236
+ if (event.taskId !== taskInfo.taskId || event.code === undefined) {
237
+ reject(new Error(JSON.stringify(event)));
238
+ } else {
239
+ resolve(event.code);
240
+ }
241
+ });
242
+ });
243
+ // node-pty reports different things on Linux/macOS vs Windows when
244
+ // killing a process. This is not ideal, but that's how things are
245
+ // currently. Ideally, its behavior should be aligned as much as
246
+ // possible on what node's child_process module does.
247
+ if (isWindows) {
248
+ expect(code).equals(1);
249
+ } else {
250
+ expect(code).equals(127);
251
+ }
252
+ });
253
+
254
+ it('task using raw process can handle command that does not exist', async function (): Promise<void> {
255
+ const p = taskServer.run(createProcessTaskConfig2('process', bogusCommand, []), wsRoot);
256
+ await expectThrowsAsync(p, 'ENOENT');
257
+ });
258
+
259
+ it('getTasks(ctx) returns tasks according to created context', async function (): Promise<void> {
260
+ const context1 = 'aContext';
261
+ const context2 = 'anotherContext';
262
+
263
+ // create some tasks: 4 for context1, 2 for context2
264
+ const task1 = await taskServer.run(createTaskConfigTaskLongRunning('shell'), context1);
265
+ const task2 = await taskServer.run(createTaskConfigTaskLongRunning('process'), context2);
266
+ const task3 = await taskServer.run(createTaskConfigTaskLongRunning('shell'), context1);
267
+ const task4 = await taskServer.run(createTaskConfigTaskLongRunning('process'), context2);
268
+ const task5 = await taskServer.run(createTaskConfigTaskLongRunning('shell'), context1);
269
+ const task6 = await taskServer.run(createTaskConfigTaskLongRunning('process'), context1);
270
+
271
+ const runningTasksCtx1 = await taskServer.getTasks(context1); // should return 4 tasks
272
+ const runningTasksCtx2 = await taskServer.getTasks(context2); // should return 2 tasks
273
+ const runningTasksAll = await taskServer.getTasks(); // should return 6 tasks
274
+
275
+ if (runningTasksCtx1.length !== 4) {
276
+ throw new Error(`Error: unexpected number of running tasks for context 1: expected: 4, actual: ${runningTasksCtx1.length}`);
277
+ } if (runningTasksCtx2.length !== 2) {
278
+ throw new Error(`Error: unexpected number of running tasks for context 2: expected: 2, actual: ${runningTasksCtx1.length}`);
279
+ } if (runningTasksAll.length !== 6) {
280
+ throw new Error(`Error: unexpected total number of running tasks for all contexts: expected: 6, actual: ${runningTasksCtx1.length}`);
281
+ }
282
+
283
+ // cleanup
284
+ await taskServer.kill(task1.taskId);
285
+ await taskServer.kill(task2.taskId);
286
+ await taskServer.kill(task3.taskId);
287
+ await taskServer.kill(task4.taskId);
288
+ await taskServer.kill(task5.taskId);
289
+ await taskServer.kill(task6.taskId);
290
+ });
291
+
292
+ it('creating and killing a bunch of tasks works as expected', async function (): Promise<void> {
293
+ // const command = isWindows ? command_absolute_path_long_running_windows : command_absolute_path_long_running;
294
+ const numTasks = 20;
295
+ const taskInfo: TaskInfo[] = [];
296
+
297
+ // create a mix of terminal and raw processes
298
+ for (let i = 0; i < numTasks; i++) {
299
+ if (i % 2 === 0) {
300
+ taskInfo.push(await taskServer.run(createTaskConfigTaskLongRunning('shell')));
301
+ } else {
302
+ taskInfo.push(await taskServer.run(createTaskConfigTaskLongRunning('process')));
303
+ }
304
+ }
305
+
306
+ const numRunningTasksAfterCreated = await taskServer.getTasks();
307
+
308
+ for (let i = 0; i < taskInfo.length; i++) {
309
+ await taskServer.kill(taskInfo[i].taskId);
310
+ }
311
+ const numRunningTasksAfterKilled = await taskServer.getTasks();
312
+
313
+ if (numRunningTasksAfterCreated.length !== numTasks) {
314
+ throw new Error(`Error: unexpected number of running tasks: expected: ${numTasks}, actual: ${numRunningTasksAfterCreated.length}`);
315
+ } if (numRunningTasksAfterKilled.length !== 0) {
316
+ throw new Error(`Error: remaining running tasks, after all killed: expected: 0, actual: ${numRunningTasksAfterKilled.length}`);
317
+ }
318
+
319
+ });
320
+
321
+ it('shell task should execute the command as a whole if not arguments are specified', async function (): Promise<void> {
322
+ const taskInfo = await taskServer.run(createProcessTaskConfig2('shell', `node ${script0} debug-hint:0a a b c`));
323
+ const exitStatus = await getExitStatus(taskInfo, taskWatcher);
324
+ expect(exitStatus).eq(0);
325
+ });
326
+
327
+ it('shell task should fail if user defines a full command line and arguments', async function (): Promise<void> {
328
+ const taskInfo = await taskServer.run(createProcessTaskConfig2('shell', `node ${script0} debug-hint:0b a b c`, []));
329
+ const exitStatus = await getExitStatus(taskInfo, taskWatcher);
330
+ expect(exitStatus).not.eq(0);
331
+ });
332
+
333
+ it('shell task should be able to exec using simple arguments', async function (): Promise<void> {
334
+ const taskInfo = await taskServer.run(createProcessTaskConfig2('shell', 'node', [script0, 'debug-hint:0c', 'a', 'b', 'c']));
335
+ const exitStatus = await getExitStatus(taskInfo, taskWatcher);
336
+ expect(exitStatus).eq(0);
337
+ });
338
+
339
+ it('shell task should be able to run using arguments containing whitespace', async function (): Promise<void> {
340
+ const taskInfo = await taskServer.run(createProcessTaskConfig2('shell', 'node', [script1, 'debug-hint:1', 'a', 'b', ' c']));
341
+ const exitStatus = await getExitStatus(taskInfo, taskWatcher);
342
+ expect(exitStatus).eq(0);
343
+ });
344
+
345
+ it('shell task will fail if user specify problematic arguments', async function (): Promise<void> {
346
+ const taskInfo = await taskServer.run(createProcessTaskConfig2('shell', 'node', [script2, 'debug-hint:2a', 'a', 'b', 'c"']));
347
+ const exitStatus = await getExitStatus(taskInfo, taskWatcher);
348
+ expect(exitStatus).not.eq(0);
349
+ });
350
+
351
+ it('shell task should be able to run using arguments specifying which quoting method to use', async function (): Promise<void> {
352
+ const taskInfo = await taskServer.run(createProcessTaskConfig2('shell', 'node', [script2, 'debug-hint:2b', 'a', 'b', { value: 'c"', quoting: 'escape' }]));
353
+ const exitStatus = await getExitStatus(taskInfo, taskWatcher);
354
+ expect(exitStatus).eq(0);
355
+ });
356
+
357
+ it('shell task should be able to run using arguments with forbidden characters but no whitespace', async function (): Promise<void> {
358
+ const taskInfo = await taskServer.run(createProcessTaskConfig2('shell', 'node', ['-e', 'setTimeout(console.log,1000,1+2)']));
359
+ const exitStatus = await getExitStatus(taskInfo, taskWatcher);
360
+ expect(exitStatus).eq(0);
361
+ });
362
+
363
+ });
364
+
365
+ function createTaskConfig(taskType: string, command: string, args: string[]): TaskConfiguration {
366
+ const options: TaskConfiguration = {
367
+ label: 'test task',
368
+ type: taskType,
369
+ _source: '/source/folder',
370
+ _scope: '/source/folder',
371
+ command,
372
+ args,
373
+ options: { cwd: wsRoot }
374
+ };
375
+ return options;
376
+ }
377
+
378
+ function createProcessTaskConfig(processType: ProcessType, command: string, args?: string[], cwd: string = wsRoot): TaskConfiguration {
379
+ return <ProcessTaskConfiguration>{
380
+ label: 'test task',
381
+ type: processType,
382
+ _source: '/source/folder',
383
+ _scope: '/source/folder',
384
+ command,
385
+ args,
386
+ options: { cwd },
387
+ };
388
+ }
389
+
390
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
391
+ function createProcessTaskConfig2(processType: ProcessType, command: string, args?: any[]): TaskConfiguration {
392
+ return <ProcessTaskConfiguration>{
393
+ label: 'test task',
394
+ type: processType,
395
+ command,
396
+ args,
397
+ options: { cwd: wsRoot },
398
+ };
399
+ }
400
+
401
+ function createTaskConfigTaskLongRunning(processType: ProcessType): TaskConfiguration {
402
+ return <ProcessTaskConfiguration>{
403
+ label: '[Task] long running test task (~300s)',
404
+ type: processType,
405
+ _source: '/source/folder',
406
+ _scope: '/source/folder',
407
+ options: { cwd: wsRoot },
408
+ command: commandLongRunning,
409
+ windows: {
410
+ command: FileUri.fsPath(wsRootUri.resolve(commandLongRunningWindows)),
411
+ options: { cwd: wsRoot }
412
+ },
413
+ osx: {
414
+ command: FileUri.fsPath(wsRootUri.resolve(commandLongRunningOsx))
415
+ }
416
+ };
417
+ }
418
+
419
+ function checkSuccessfulProcessExit(taskInfo: TaskInfo, taskWatcher: TaskWatcher): Promise<void> {
420
+ return new Promise<void>((resolve, reject) => {
421
+ const toDispose = taskWatcher.onTaskExit((event: TaskExitedEvent) => {
422
+ if (event.taskId === taskInfo.taskId && event.code === 0) {
423
+ toDispose.dispose();
424
+ resolve();
425
+ }
426
+ });
427
+ });
428
+ }
429
+
430
+ function getExitStatus(taskInfo: TaskInfo, taskWatcher: TaskWatcher): Promise<string | number> {
431
+ return new Promise<string | number>((resolve, reject) => {
432
+ taskWatcher.onTaskExit((event: TaskExitedEvent) => {
433
+ if (event.taskId === taskInfo.taskId) {
434
+ if (typeof event.signal === 'string') {
435
+ resolve(event.signal);
436
+ } else if (typeof event.code === 'number') {
437
+ resolve(event.code);
438
+ } else {
439
+ reject(new Error('no code nor signal'));
440
+ }
441
+ }
442
+ });
443
+ });
444
+ }