Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/chatSessions/copilotcli/vscode-node/test/lockFile.spec.ts
13406 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
7
import * as fs from 'fs/promises';
8
import * as os from 'os';
9
import * as path from 'path';
10
import { TestLogService } from '../../../../../platform/testing/common/testLogService';
11
12
vi.mock('vscode', async (importOriginal) => {
13
const original = await importOriginal<typeof import('vscode')>();
14
return {
15
...original,
16
workspace: {
17
workspaceFolders: [{ uri: { fsPath: '/test/workspace' } }],
18
isTrusted: true,
19
},
20
env: {
21
appName: 'Visual Studio Code',
22
},
23
};
24
});
25
26
import { LockFileHandle, createLockFile, isProcessRunning, cleanupStaleLockFiles } from '../lockFile';
27
28
const logger = new TestLogService();
29
30
describe('LockFileHandle', () => {
31
const testDir = path.join(os.tmpdir(), 'lockfile-test-' + Date.now());
32
const testLockFilePath = path.join(testDir, 'test.lock');
33
const mockServerUri = { path: '/tmp/test.sock', scheme: 'http' } as any;
34
const mockHeaders = { Authorization: 'Bearer test-token' };
35
const testTimestamp = Date.now();
36
37
beforeEach(async () => {
38
await fs.mkdir(testDir, { recursive: true });
39
});
40
41
afterEach(async () => {
42
await fs.rm(testDir, { recursive: true, force: true }).catch(() => { });
43
});
44
45
describe('constructor and path getter', () => {
46
it('should store the lock file path', () => {
47
const handle = new LockFileHandle(testLockFilePath, mockServerUri, mockHeaders, testTimestamp, logger);
48
expect(handle.path).toBe(testLockFilePath);
49
});
50
});
51
52
describe('update', () => {
53
it('should write lock file with correct content', async () => {
54
const handle = new LockFileHandle(testLockFilePath, mockServerUri, mockHeaders, testTimestamp, logger);
55
await handle.update();
56
57
const stat = await fs.stat(testLockFilePath);
58
expect(stat.isFile()).toBe(true);
59
60
const content = JSON.parse(await fs.readFile(testLockFilePath, 'utf-8'));
61
expect(content.socketPath).toBe('/tmp/test.sock');
62
expect(content.scheme).toBe('http');
63
expect(content.headers).toEqual(mockHeaders);
64
expect(content.pid).toBe(process.pid);
65
expect(content.timestamp).toBe(testTimestamp);
66
expect(content.isTrusted).toBe(true);
67
});
68
69
it.skipIf(process.platform === 'win32')('should set restrictive file permissions (0o600)', async () => {
70
const handle = new LockFileHandle(testLockFilePath, mockServerUri, mockHeaders, testTimestamp, logger);
71
await handle.update();
72
73
const stats = await fs.stat(testLockFilePath);
74
const mode = stats.mode & 0o777;
75
expect(mode).toBe(0o600);
76
});
77
});
78
79
describe('remove', () => {
80
it('should delete the lock file if it exists', async () => {
81
await fs.writeFile(testLockFilePath, '{}');
82
const stat = await fs.stat(testLockFilePath);
83
expect(stat.isFile()).toBe(true);
84
85
const handle = new LockFileHandle(testLockFilePath, mockServerUri, mockHeaders, testTimestamp, logger);
86
await handle.remove();
87
88
await expect(fs.stat(testLockFilePath)).rejects.toThrow();
89
});
90
91
it('should not throw if lock file does not exist', async () => {
92
const handle = new LockFileHandle(testLockFilePath, mockServerUri, mockHeaders, testTimestamp, logger);
93
await expect(handle.remove()).resolves.not.toThrow();
94
});
95
});
96
});
97
98
describe('createLockFile', () => {
99
const testDir = path.join(os.tmpdir(), 'lockfile-create-test-' + Date.now());
100
let originalEnv: string | undefined;
101
let createdLockFile: string | null = null;
102
103
beforeEach(() => {
104
originalEnv = process.env.XDG_STATE_HOME;
105
process.env.XDG_STATE_HOME = testDir;
106
});
107
108
afterEach(async () => {
109
if (createdLockFile) {
110
await fs.unlink(createdLockFile).catch(() => { });
111
createdLockFile = null;
112
}
113
if (originalEnv !== undefined) {
114
process.env.XDG_STATE_HOME = originalEnv;
115
} else {
116
delete process.env.XDG_STATE_HOME;
117
}
118
await fs.rm(testDir, { recursive: true, force: true }).catch(() => { });
119
});
120
121
it('should create lock file in .copilot directory', async () => {
122
const mockServerUri = { path: '/tmp/server.sock', scheme: 'http' } as any;
123
const mockHeaders = { 'X-Test': 'value' };
124
125
const handle = await createLockFile(mockServerUri, mockHeaders, logger);
126
createdLockFile = handle.path;
127
128
expect(handle.path).toMatch(/\.copilot[/\\]ide.*\.lock$/);
129
const stat = await fs.stat(handle.path);
130
expect(stat.isFile()).toBe(true);
131
132
const content = JSON.parse(await fs.readFile(handle.path, 'utf-8'));
133
expect(content.socketPath).toBe('/tmp/server.sock');
134
expect(content.scheme).toBe('http');
135
expect(content.headers).toEqual(mockHeaders);
136
expect(content.pid).toBe(process.pid);
137
expect(typeof content.timestamp).toBe('number');
138
expect(content.isTrusted).toBe(true);
139
});
140
141
it('should create .copilot directory if it does not exist', async () => {
142
const mockServerUri = { path: '/tmp/server.sock', scheme: 'http' } as any;
143
const handle = await createLockFile(mockServerUri, {}, logger);
144
createdLockFile = handle.path;
145
146
const copilotDir = path.dirname(handle.path);
147
const stat = await fs.stat(copilotDir);
148
expect(stat.isDirectory()).toBe(true);
149
});
150
151
it('should generate unique lock file names', async () => {
152
const mockServerUri = { path: '/tmp/server.sock', scheme: 'http' } as any;
153
154
const handle1 = await createLockFile(mockServerUri, {}, logger);
155
const handle2 = await createLockFile(mockServerUri, {}, logger);
156
157
expect(handle1.path).not.toBe(handle2.path);
158
159
await handle1.remove();
160
await handle2.remove();
161
});
162
});
163
164
describe('isProcessRunning', () => {
165
it('should return true for current process', () => {
166
expect(isProcessRunning(process.pid)).toBe(true);
167
});
168
169
it('should return false for non-existent process', () => {
170
expect(isProcessRunning(999999999)).toBe(false);
171
});
172
});
173
174
describe('cleanupStaleLockFiles', () => {
175
const testDir = path.join(os.tmpdir(), 'lockfile-cleanup-test-' + Date.now());
176
const copilotDir = path.join(testDir, '.copilot', 'ide');
177
let originalEnv: string | undefined;
178
179
beforeEach(async () => {
180
originalEnv = process.env.XDG_STATE_HOME;
181
process.env.XDG_STATE_HOME = testDir;
182
await fs.mkdir(copilotDir, { recursive: true });
183
});
184
185
afterEach(async () => {
186
if (originalEnv !== undefined) {
187
process.env.XDG_STATE_HOME = originalEnv;
188
} else {
189
delete process.env.XDG_STATE_HOME;
190
}
191
await fs.rm(testDir, { recursive: true, force: true }).catch(() => { });
192
});
193
194
it('should remove lockfiles for non-running processes', async () => {
195
const staleLockFile = path.join(copilotDir, 'stale.lock');
196
const staleLockInfo = {
197
socketPath: '/tmp/test.sock',
198
scheme: 'http',
199
headers: {},
200
pid: 999999999,
201
ideName: 'Test',
202
timestamp: Date.now(),
203
workspaceFolders: [],
204
};
205
await fs.writeFile(staleLockFile, JSON.stringify(staleLockInfo));
206
207
const stat = await fs.stat(staleLockFile);
208
expect(stat.isFile()).toBe(true);
209
const cleaned = await cleanupStaleLockFiles(logger);
210
expect(cleaned).toBe(1);
211
await expect(fs.stat(staleLockFile)).rejects.toThrow();
212
});
213
214
it('should keep lockfiles for running processes', async () => {
215
const activeLockFile = path.join(copilotDir, 'active.lock');
216
const activeLockInfo = {
217
socketPath: '/tmp/test.sock',
218
scheme: 'http',
219
headers: {},
220
pid: process.pid,
221
ideName: 'Test',
222
timestamp: Date.now(),
223
workspaceFolders: [],
224
};
225
await fs.writeFile(activeLockFile, JSON.stringify(activeLockInfo));
226
227
const stat = await fs.stat(activeLockFile);
228
expect(stat.isFile()).toBe(true);
229
const cleaned = await cleanupStaleLockFiles(logger);
230
expect(cleaned).toBe(0);
231
const stat2 = await fs.stat(activeLockFile);
232
expect(stat2.isFile()).toBe(true);
233
});
234
235
it('should return 0 when copilot directory does not exist', async () => {
236
await fs.rm(copilotDir, { recursive: true, force: true });
237
238
const cleaned = await cleanupStaleLockFiles(logger);
239
expect(cleaned).toBe(0);
240
});
241
});
242
243