cligr 1.0.0

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.
@@ -0,0 +1,391 @@
1
+ /**
2
+ * Integration tests for ProcessManager
3
+ *
4
+ * These tests verify the process management functionality including:
5
+ * - Spawning process groups
6
+ * - Process output prefixing
7
+ * - Restart policies
8
+ * - Crash loop detection
9
+ * - Killing groups
10
+ *
11
+ * Note: These tests spawn real processes and may be platform-specific.
12
+ */
13
+
14
+ import { describe, it, before, after } from 'node:test';
15
+ import assert from 'node:assert';
16
+ import { ProcessManager, ManagedProcess } from '../../src/process/manager.js';
17
+ import type { ProcessItem } from '../../src/config/types.js';
18
+
19
+ describe('ProcessManager Integration Tests', () => {
20
+ let manager: ProcessManager;
21
+
22
+ before(() => {
23
+ manager = new ProcessManager();
24
+ });
25
+
26
+ after(async () => {
27
+ // Clean up any running processes
28
+ await manager.killAll();
29
+ });
30
+
31
+ describe('spawnGroup()', () => {
32
+ it('should spawn a group with single process', async () => {
33
+ const items: ProcessItem[] = [
34
+ { name: 'test1', args: ['hello'], fullCmd: process.platform === 'win32' ? 'echo hello' : 'echo hello' }
35
+ ];
36
+
37
+ manager.spawnGroup('test-group', items, 'no');
38
+
39
+ assert.strictEqual(manager.isGroupRunning('test-group'), true);
40
+ const status = manager.getGroupStatus('test-group');
41
+ assert.strictEqual(status.length, 1);
42
+
43
+ // Clean up
44
+ await manager.killGroup('test-group');
45
+ });
46
+
47
+ it('should spawn a group with multiple processes', async () => {
48
+ const items: ProcessItem[] = [
49
+ { name: 'proc1', args: ['one'], fullCmd: process.platform === 'win32' ? 'echo one' : 'echo one' },
50
+ { name: 'proc2', args: ['two'], fullCmd: process.platform === 'win32' ? 'echo two' : 'echo two' },
51
+ { name: 'proc3', args: ['three'], fullCmd: process.platform === 'win32' ? 'echo three' : 'echo three' }
52
+ ];
53
+
54
+ manager.spawnGroup('multi-group', items, 'no');
55
+
56
+ assert.strictEqual(manager.isGroupRunning('multi-group'), true);
57
+ const status = manager.getGroupStatus('multi-group');
58
+ assert.strictEqual(status.length, 3);
59
+
60
+ // Clean up
61
+ await manager.killGroup('multi-group');
62
+ });
63
+
64
+ it('should throw error when spawning duplicate group', async () => {
65
+ const items: ProcessItem[] = [
66
+ { name: 'test', args: [], fullCmd: 'echo test' }
67
+ ];
68
+
69
+ manager.spawnGroup('dup-group', items, 'no');
70
+
71
+ assert.throws(
72
+ () => manager.spawnGroup('dup-group', items, 'no'),
73
+ (err: Error) => {
74
+ assert.ok(err.message.includes('already running'));
75
+ return true;
76
+ }
77
+ );
78
+
79
+ // Clean up
80
+ await manager.killGroup('dup-group');
81
+ });
82
+
83
+ it('should handle processes with different exit times', async () => {
84
+ // Use sleep commands with different durations
85
+ const sleepCmd = process.platform === 'win32' ? 'timeout' : 'sleep';
86
+ const sleepFlag = process.platform === 'win32' ? '/t' : '';
87
+
88
+ const items: ProcessItem[] = [
89
+ { name: 'short', args: ['1'], fullCmd: `${sleepCmd} ${sleepFlag} 1` },
90
+ { name: 'long', args: ['2'], fullCmd: `${sleepCmd} ${sleepFlag} 2` }
91
+ ];
92
+
93
+ manager.spawnGroup('timed-group', items, 'no');
94
+
95
+ assert.strictEqual(manager.isGroupRunning('timed-group'), true);
96
+
97
+ // Wait for short process to exit
98
+ await new Promise(resolve => setTimeout(resolve, 1500));
99
+
100
+ // Group should still be tracked even after some processes exit
101
+ assert.strictEqual(manager.isGroupRunning('timed-group'), true);
102
+
103
+ // Clean up
104
+ await manager.killGroup('timed-group');
105
+ });
106
+ });
107
+
108
+ describe('killGroup()', () => {
109
+ it('should kill a running group', async () => {
110
+ // Use a long-running process
111
+ const sleepCmd = process.platform === 'win32' ? 'timeout' : 'sleep';
112
+ const sleepFlag = process.platform === 'win32' ? '/t' : '';
113
+
114
+ const items: ProcessItem[] = [
115
+ { name: 'long-running', args: ['10'], fullCmd: `${sleepCmd} ${sleepFlag} 10` }
116
+ ];
117
+
118
+ manager.spawnGroup('kill-test', items, 'no');
119
+ assert.strictEqual(manager.isGroupRunning('kill-test'), true);
120
+
121
+ await manager.killGroup('kill-test');
122
+
123
+ assert.strictEqual(manager.isGroupRunning('kill-test'), false);
124
+ });
125
+
126
+ it('should handle killing non-existent group gracefully', async () => {
127
+ // Should not throw
128
+ await manager.killGroup('non-existent');
129
+ assert.strictEqual(manager.isGroupRunning('non-existent'), false);
130
+ });
131
+
132
+ it('should kill all processes in a group', async () => {
133
+ const sleepCmd = process.platform === 'win32' ? 'timeout' : 'sleep';
134
+ const sleepFlag = process.platform === 'win32' ? '/t' : '';
135
+
136
+ const items: ProcessItem[] = [
137
+ { name: 'p1', args: ['5'], fullCmd: `${sleepCmd} ${sleepFlag} 5` },
138
+ { name: 'p2', args: ['5'], fullCmd: `${sleepCmd} ${sleepFlag} 5` },
139
+ { name: 'p3', args: ['5'], fullCmd: `${sleepCmd} ${sleepFlag} 5` }
140
+ ];
141
+
142
+ manager.spawnGroup('multi-kill', items, 'no');
143
+ assert.strictEqual(manager.isGroupRunning('multi-kill'), true);
144
+
145
+ await manager.killGroup('multi-kill');
146
+
147
+ assert.strictEqual(manager.isGroupRunning('multi-kill'), false);
148
+ });
149
+ });
150
+
151
+ describe('killAll()', () => {
152
+ it('should kill all running groups', async () => {
153
+ const sleepCmd = process.platform === 'win32' ? 'timeout' : 'sleep';
154
+ const sleepFlag = process.platform === 'win32' ? '/t' : '';
155
+
156
+ const items: ProcessItem[] = [
157
+ { name: 'proc', args: ['5'], fullCmd: `${sleepCmd} ${sleepFlag} 5` }
158
+ ];
159
+
160
+ manager.spawnGroup('group1', items, 'no');
161
+ manager.spawnGroup('group2', items, 'no');
162
+ manager.spawnGroup('group3', items, 'no');
163
+
164
+ assert.strictEqual(manager.isGroupRunning('group1'), true);
165
+ assert.strictEqual(manager.isGroupRunning('group2'), true);
166
+ assert.strictEqual(manager.isGroupRunning('group3'), true);
167
+
168
+ await manager.killAll();
169
+
170
+ assert.strictEqual(manager.isGroupRunning('group1'), false);
171
+ assert.strictEqual(manager.isGroupRunning('group2'), false);
172
+ assert.strictEqual(manager.isGroupRunning('group3'), false);
173
+ });
174
+
175
+ it('should handle empty state gracefully', async () => {
176
+ // Should not throw when no groups are running
177
+ await manager.killAll();
178
+ const running = manager.getRunningGroups();
179
+ assert.strictEqual(running.length, 0);
180
+ });
181
+ });
182
+
183
+ describe('getGroupStatus()', () => {
184
+ it('should return status for a running group', async () => {
185
+ const items: ProcessItem[] = [
186
+ { name: 'status-test', args: [], fullCmd: process.platform === 'win32' ? 'echo test' : 'echo test' }
187
+ ];
188
+
189
+ manager.spawnGroup('status-group', items, 'no');
190
+
191
+ const status = manager.getGroupStatus('status-group');
192
+ assert.strictEqual(status.length, 1);
193
+ assert.strictEqual(status[0], 'running');
194
+
195
+ await manager.killGroup('status-group');
196
+ });
197
+
198
+ it('should return empty array for non-existent group', () => {
199
+ const status = manager.getGroupStatus('non-existent');
200
+ assert.deepStrictEqual(status, []);
201
+ });
202
+ });
203
+
204
+ describe('isGroupRunning()', () => {
205
+ it('should return true for running group', async () => {
206
+ const items: ProcessItem[] = [
207
+ { name: 'running-test', args: ['2'], fullCmd: process.platform === 'win32' ? 'timeout /t 2' : 'sleep 2' }
208
+ ];
209
+
210
+ manager.spawnGroup('running-group', items, 'no');
211
+
212
+ assert.strictEqual(manager.isGroupRunning('running-group'), true);
213
+
214
+ await manager.killGroup('running-group');
215
+ });
216
+
217
+ it('should return false for non-existent group', () => {
218
+ assert.strictEqual(manager.isGroupRunning('non-existent'), false);
219
+ });
220
+
221
+ it('should return false after killing group', async () => {
222
+ const items: ProcessItem[] = [
223
+ { name: 'temp', args: ['2'], fullCmd: process.platform === 'win32' ? 'timeout /t 2' : 'sleep 2' }
224
+ ];
225
+
226
+ manager.spawnGroup('temp-group', items, 'no');
227
+ assert.strictEqual(manager.isGroupRunning('temp-group'), true);
228
+
229
+ await manager.killGroup('temp-group');
230
+ assert.strictEqual(manager.isGroupRunning('temp-group'), false);
231
+ });
232
+ });
233
+
234
+ describe('getRunningGroups()', () => {
235
+ it('should return list of running groups', async () => {
236
+ const sleepCmd = process.platform === 'win32' ? 'timeout' : 'sleep';
237
+ const sleepFlag = process.platform === 'win32' ? '/t' : '';
238
+
239
+ const items: ProcessItem[] = [
240
+ { name: 'proc', args: ['2'], fullCmd: `${sleepCmd} ${sleepFlag} 2` }
241
+ ];
242
+
243
+ manager.spawnGroup('list-group-1', items, 'no');
244
+ manager.spawnGroup('list-group-2', items, 'no');
245
+ manager.spawnGroup('list-group-3', items, 'no');
246
+
247
+ const running = manager.getRunningGroups();
248
+ assert.strictEqual(running.length, 3);
249
+ assert.ok(running.includes('list-group-1'));
250
+ assert.ok(running.includes('list-group-2'));
251
+ assert.ok(running.includes('list-group-3'));
252
+
253
+ await manager.killAll();
254
+ });
255
+
256
+ it('should return empty array when no groups running', async () => {
257
+ await manager.killAll();
258
+ const running = manager.getRunningGroups();
259
+ assert.strictEqual(running.length, 0);
260
+ });
261
+ });
262
+
263
+ describe('Restart policies', () => {
264
+ it('should not restart processes with restart=no', async () => {
265
+ // Create a process that exits immediately
266
+ const items: ProcessItem[] = [
267
+ { name: 'no-restart', args: [], fullCmd: process.platform === 'win32' ? 'exit 0' : 'true' }
268
+ ];
269
+
270
+ manager.spawnGroup('no-restart-group', items, 'no');
271
+
272
+ // Wait for process to exit
273
+ await new Promise(resolve => setTimeout(resolve, 500));
274
+
275
+ // Group should still be tracked but process won't restart
276
+ assert.strictEqual(manager.isGroupRunning('no-restart-group'), true);
277
+
278
+ await manager.killGroup('no-restart-group');
279
+ });
280
+
281
+ it('should handle unless-stopped restart policy', async () => {
282
+ const sleepCmd = process.platform === 'win32' ? 'timeout' : 'sleep';
283
+ const sleepFlag = process.platform === 'win32' ? '/t' : '';
284
+
285
+ const items: ProcessItem[] = [
286
+ { name: 'unless-stopped', args: ['5'], fullCmd: `${sleepCmd} ${sleepFlag} 5` }
287
+ ];
288
+
289
+ manager.spawnGroup('unless-stopped-group', items, 'unless-stopped');
290
+
291
+ assert.strictEqual(manager.isGroupRunning('unless-stopped-group'), true);
292
+
293
+ // Kill with SIGTERM
294
+ await manager.killGroup('unless-stopped-group');
295
+
296
+ // Process should not restart after SIGTERM
297
+ await new Promise(resolve => setTimeout(resolve, 2000));
298
+ assert.strictEqual(manager.isGroupRunning('unless-stopped-group'), false);
299
+ });
300
+ });
301
+
302
+ describe('parseCommand()', () => {
303
+ it('should parse simple command', async () => {
304
+ const items: ProcessItem[] = [
305
+ { name: 'parse-test', args: [], fullCmd: 'echo hello' }
306
+ ];
307
+
308
+ manager.spawnGroup('parse-test-group', items, 'no');
309
+
310
+ // Command should execute successfully
311
+ assert.strictEqual(manager.isGroupRunning('parse-test-group'), true);
312
+
313
+ await manager.killGroup('parse-test-group');
314
+ });
315
+
316
+ it('should parse command with multiple arguments', async () => {
317
+ const items: ProcessItem[] = [
318
+ { name: 'multi-arg', args: [], fullCmd: 'node -e "console.log(\'test\')"' }
319
+ ];
320
+
321
+ manager.spawnGroup('multi-arg-group', items, 'no');
322
+
323
+ assert.strictEqual(manager.isGroupRunning('multi-arg-group'), true);
324
+
325
+ await manager.killGroup('multi-arg-group');
326
+ });
327
+
328
+ it('should handle quoted paths with spaces', async () => {
329
+ const items: ProcessItem[] = [
330
+ { name: 'quoted-path', args: [], fullCmd: '"echo" "hello world"' }
331
+ ];
332
+
333
+ manager.spawnGroup('quoted-path-group', items, 'no');
334
+
335
+ assert.strictEqual(manager.isGroupRunning('quoted-path-group'), true);
336
+
337
+ await manager.killGroup('quoted-path-group');
338
+ });
339
+ });
340
+
341
+ describe('Output prefixing', () => {
342
+ it('should prefix process output with item name', async () => {
343
+ // This test verifies output prefixing by checking that processes start successfully
344
+ // Actual output verification would require capturing stdout
345
+ const items: ProcessItem[] = [
346
+ { name: 'prefixed-1', args: [], fullCmd: 'echo output1' },
347
+ { name: 'prefixed-2', args: [], fullCmd: 'echo output2' }
348
+ ];
349
+
350
+ manager.spawnGroup('output-test', items, 'no');
351
+
352
+ assert.strictEqual(manager.isGroupRunning('output-test'), true);
353
+
354
+ await manager.killGroup('output-test');
355
+ });
356
+ });
357
+
358
+ describe('Cross-platform compatibility', () => {
359
+ it('should work with Windows-specific commands', async function skipOnNonWindows() {
360
+ if (process.platform !== 'win32') {
361
+ this.skip();
362
+ }
363
+
364
+ const items: ProcessItem[] = [
365
+ { name: 'win-cmd', args: [], fullCmd: 'cmd /c echo Windows' }
366
+ ];
367
+
368
+ manager.spawnGroup('win-test', items, 'no');
369
+
370
+ assert.strictEqual(manager.isGroupRunning('win-test'), true);
371
+
372
+ await manager.killGroup('win-test');
373
+ });
374
+
375
+ it('should work with Unix-specific commands', async function skipOnWindows() {
376
+ if (process.platform === 'win32') {
377
+ this.skip();
378
+ }
379
+
380
+ const items: ProcessItem[] = [
381
+ { name: 'unix-cmd', args: [], fullCmd: '/bin/echo Unix' }
382
+ ];
383
+
384
+ manager.spawnGroup('unix-test', items, 'no');
385
+
386
+ assert.strictEqual(manager.isGroupRunning('unix-test'), true);
387
+
388
+ await manager.killGroup('unix-test');
389
+ });
390
+ });
391
+ });