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,255 @@
1
+ /**
2
+ * Integration tests for blocking/long-running processes
3
+ *
4
+ * These tests verify the ProcessManager's ability to handle
5
+ * processes that run indefinitely or for extended periods.
6
+ */
7
+
8
+ import { describe, it, before, after } from 'node:test';
9
+ import assert from 'node:assert';
10
+ import { ProcessManager } from '../../src/process/manager.js';
11
+ import type { ProcessItem } from '../../src/config/types.js';
12
+ import fs from 'node:fs';
13
+ import path from 'node:path';
14
+ import os from 'node:os';
15
+
16
+ describe('Blocking Processes Integration Tests', () => {
17
+ let manager: ProcessManager;
18
+ let testScriptsDir: string;
19
+
20
+ before(() => {
21
+ manager = new ProcessManager();
22
+ testScriptsDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cligr-blocking-test-'));
23
+ });
24
+
25
+ after(() => {
26
+ // Clean up any running processes
27
+ manager.killAll();
28
+
29
+ // Clean up test scripts directory
30
+ if (fs.existsSync(testScriptsDir)) {
31
+ fs.rmSync(testScriptsDir, { recursive: true, force: true });
32
+ }
33
+ });
34
+
35
+ function createInfiniteLoopScript(scriptName: string, delayMs: number = 1000): string {
36
+ const scriptPath = path.join(testScriptsDir, scriptName);
37
+ const scriptContent = `
38
+ // Infinite loop script - simulates a long-running process
39
+ let counter = 0;
40
+ const interval = setInterval(() => {
41
+ counter++;
42
+ console.log(\`${scriptName}: \${counter}\`);
43
+ }, ${delayMs});
44
+
45
+ // Handle graceful shutdown
46
+ process.on('SIGTERM', () => {
47
+ clearInterval(interval);
48
+ process.exit(0);
49
+ });
50
+
51
+ process.on('SIGINT', () => {
52
+ clearInterval(interval);
53
+ process.exit(0);
54
+ });
55
+ `;
56
+ fs.writeFileSync(scriptPath, scriptContent);
57
+ return scriptPath;
58
+ }
59
+
60
+ function createServerScript(scriptName: string, port: number): string {
61
+ const scriptPath = path.join(testScriptsDir, scriptName);
62
+ const scriptContent = `
63
+ // HTTP server script - simulates a service that stays running
64
+ import http from 'http';
65
+
66
+ const server = http.createServer((req, res) => {
67
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
68
+ res.end('OK\\n');
69
+ });
70
+
71
+ server.listen(${port}, () => {
72
+ console.log(\`${scriptName}: listening on port \${port}\`);
73
+ });
74
+
75
+ process.on('SIGTERM', () => {
76
+ server.close(() => process.exit(0));
77
+ });
78
+
79
+ process.on('SIGINT', () => {
80
+ server.close(() => process.exit(0));
81
+ });
82
+ `;
83
+ fs.writeFileSync(scriptPath, scriptContent);
84
+ return scriptPath;
85
+ }
86
+
87
+ describe('Multiple processes spawn in parallel', () => {
88
+ it('should spawn all processes concurrently', { timeout: 10000 }, async () => {
89
+ const scriptPath = createInfiniteLoopScript('concurrent.js', 500);
90
+
91
+ const items: ProcessItem[] = [
92
+ { name: 'proc1', args: [scriptPath], fullCmd: `node ${scriptPath}` },
93
+ { name: 'proc2', args: [scriptPath], fullCmd: `node ${scriptPath}` },
94
+ { name: 'proc3', args: [scriptPath], fullCmd: `node ${scriptPath}` },
95
+ { name: 'proc4', args: [scriptPath], fullCmd: `node ${scriptPath}` },
96
+ { name: 'proc5', args: [scriptPath], fullCmd: `node ${scriptPath}` }
97
+ ];
98
+
99
+ const start = Date.now();
100
+ manager.spawnGroup('concurrent-group', items, 'no');
101
+ const elapsed = Date.now() - start;
102
+
103
+ // spawnGroup should return quickly (all processes started in parallel)
104
+ assert.ok(elapsed < 1000, `spawnGroup took ${elapsed}ms, expected < 1000ms`);
105
+
106
+ // Verify all processes are tracked
107
+ assert.strictEqual(manager.isGroupRunning('concurrent-group'), true);
108
+
109
+ // Wait for processes to start and run a bit
110
+ await new Promise(resolve => setTimeout(resolve, 2000));
111
+
112
+ // Kill the group
113
+ manager.killGroup('concurrent-group');
114
+
115
+ // Verify processes were killed
116
+ assert.strictEqual(manager.isGroupRunning('concurrent-group'), false);
117
+ });
118
+
119
+ it('should handle many concurrent processes', { timeout: 15000 }, async () => {
120
+ const scriptPath = createInfiniteLoopScript('many.js', 1000);
121
+ const items: ProcessItem[] = [];
122
+
123
+ // Create 20 items
124
+ for (let i = 0; i < 20; i++) {
125
+ items.push({
126
+ name: `proc${i}`,
127
+ args: [scriptPath],
128
+ fullCmd: `node ${scriptPath}`
129
+ });
130
+ }
131
+
132
+ const start = Date.now();
133
+ manager.spawnGroup('many-group', items, 'no');
134
+ const elapsed = Date.now() - start;
135
+
136
+ // Should spawn 20 processes quickly
137
+ assert.ok(elapsed < 2000, `spawnGroup took ${elapsed}ms, expected < 2000ms`);
138
+
139
+ assert.strictEqual(manager.isGroupRunning('many-group'), true);
140
+
141
+ // Let them run
142
+ await new Promise(resolve => setTimeout(resolve, 2000));
143
+
144
+ manager.killGroup('many-group');
145
+ assert.strictEqual(manager.isGroupRunning('many-group'), false);
146
+ });
147
+ });
148
+
149
+ describe('Server processes', () => {
150
+ it('should manage multiple server processes on different ports', { timeout: 10000 }, async () => {
151
+ const ports = [18100, 18101, 18102];
152
+ const items: ProcessItem[] = [];
153
+
154
+ for (const port of ports) {
155
+ const scriptPath = createServerScript(`server-${port}.js`, port);
156
+ items.push({
157
+ name: `server-${port}`,
158
+ args: [scriptPath],
159
+ fullCmd: `node ${scriptPath}`
160
+ });
161
+ }
162
+
163
+ manager.spawnGroup('server-group', items, 'no');
164
+
165
+ assert.strictEqual(manager.isGroupRunning('server-group'), true);
166
+
167
+ // Wait for servers to start
168
+ await new Promise(resolve => setTimeout(resolve, 1500));
169
+
170
+ manager.killGroup('server-group');
171
+ assert.strictEqual(manager.isGroupRunning('server-group'), false);
172
+ });
173
+ });
174
+
175
+ describe('Long-running processes', () => {
176
+ it('should manage processes with long execution time', { timeout: 10000 }, async () => {
177
+ // Use sleep command for long-running processes
178
+ const sleepCmd = process.platform === 'win32' ? 'timeout' : 'sleep';
179
+ const sleepArg = process.platform === 'win32' ? '/t' : '';
180
+
181
+ const items: ProcessItem[] = [
182
+ { name: 'long1', args: [sleepArg, '5'], fullCmd: `${sleepCmd} ${sleepArg} 5` },
183
+ { name: 'long2', args: [sleepArg, '5'], fullCmd: `${sleepCmd} ${sleepArg} 5` },
184
+ { name: 'long3', args: [sleepArg, '5'], fullCmd: `${sleepCmd} ${sleepArg} 5` }
185
+ ];
186
+
187
+ manager.spawnGroup('long-group', items, 'no');
188
+
189
+ assert.strictEqual(manager.isGroupRunning('long-group'), true);
190
+
191
+ // Wait a bit then kill before they complete
192
+ await new Promise(resolve => setTimeout(resolve, 2000));
193
+
194
+ manager.killGroup('long-group');
195
+ assert.strictEqual(manager.isGroupRunning('long-group'), false);
196
+ });
197
+ });
198
+
199
+ describe('Process lifecycle verification', () => {
200
+ it('should track process status correctly', { timeout: 10000 }, async () => {
201
+ const scriptPath = createInfiniteLoopScript('status.js', 500);
202
+
203
+ const items: ProcessItem[] = [
204
+ { name: 'status1', args: [scriptPath], fullCmd: `node ${scriptPath}` },
205
+ { name: 'status2', args: [scriptPath], fullCmd: `node ${scriptPath}` }
206
+ ];
207
+
208
+ manager.spawnGroup('status-group', items, 'no');
209
+
210
+ // Check status
211
+ const status = manager.getGroupStatus('status-group');
212
+ assert.strictEqual(status.length, 2);
213
+ assert.strictEqual(status[0], 'running');
214
+ assert.strictEqual(status[1], 'running');
215
+
216
+ // Check running groups
217
+ const runningGroups = manager.getRunningGroups();
218
+ assert.ok(runningGroups.includes('status-group'));
219
+
220
+ await new Promise(resolve => setTimeout(resolve, 1000));
221
+
222
+ manager.killGroup('status-group');
223
+
224
+ // After killing, group should not be running
225
+ assert.strictEqual(manager.isGroupRunning('status-group'), false);
226
+ });
227
+ });
228
+
229
+ describe('Cleanup verification', () => {
230
+ it('should properly clean up all processes', { timeout: 10000 }, async () => {
231
+ const scriptPath = createInfiniteLoopScript('cleanup.js', 500);
232
+
233
+ // Create multiple groups
234
+ for (let i = 0; i < 3; i++) {
235
+ const items: ProcessItem[] = [
236
+ { name: `cleanup${i}-1`, args: [scriptPath], fullCmd: `node ${scriptPath}` },
237
+ { name: `cleanup${i}-2`, args: [scriptPath], fullCmd: `node ${scriptPath}` }
238
+ ];
239
+
240
+ manager.spawnGroup(`cleanup-group-${i}`, items, 'no');
241
+ }
242
+
243
+ // All groups should be running
244
+ assert.strictEqual(manager.getRunningGroups().length, 3);
245
+
246
+ await new Promise(resolve => setTimeout(resolve, 1000));
247
+
248
+ // Kill all at once
249
+ manager.killAll();
250
+
251
+ // All groups should be cleaned up
252
+ assert.strictEqual(manager.getRunningGroups().length, 0);
253
+ });
254
+ });
255
+ });