Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/git/src/test/askpassManager.test.ts
5221 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 'mocha';
7
import * as assert from 'assert';
8
import * as fs from 'fs';
9
import * as path from 'path';
10
import * as os from 'os';
11
import { ensureAskpassScripts } from '../askpassManager';
12
import { Event, EventEmitter, LogLevel, LogOutputChannel } from 'vscode';
13
14
class MockLogOutputChannel implements LogOutputChannel {
15
logLevel: LogLevel = LogLevel.Info;
16
onDidChangeLogLevel: Event<LogLevel> = new EventEmitter<LogLevel>().event;
17
private logs: { level: string; message: string }[] = [];
18
19
trace(message: string, ..._args: any[]): void {
20
this.logs.push({ level: 'trace', message });
21
}
22
debug(message: string, ..._args: any[]): void {
23
this.logs.push({ level: 'debug', message });
24
}
25
info(message: string, ..._args: any[]): void {
26
this.logs.push({ level: 'info', message });
27
}
28
warn(message: string, ..._args: any[]): void {
29
this.logs.push({ level: 'warn', message });
30
}
31
error(error: string | Error, ..._args: any[]): void {
32
this.logs.push({ level: 'error', message: error.toString() });
33
}
34
35
name: string = 'MockLogOutputChannel';
36
append(_value: string): void { }
37
appendLine(_value: string): void { }
38
replace(_value: string): void { }
39
clear(): void { }
40
show(_column?: unknown, _preserveFocus?: unknown): void { }
41
hide(): void { }
42
dispose(): void { }
43
44
getLogs(): { level: string; message: string }[] {
45
return this.logs;
46
}
47
48
hasLog(level: string, messageSubstring: string): boolean {
49
return this.logs.some(log => log.level === level && log.message.includes(messageSubstring));
50
}
51
}
52
53
// Helper to set mtime on a directory
54
async function setDirectoryMtime(dirPath: string, mtime: Date): Promise<void> {
55
await fs.promises.utimes(dirPath, mtime, mtime);
56
}
57
58
suite('askpassManager', () => {
59
let tempDir: string;
60
let sourceDir: string;
61
62
setup(async () => {
63
// Create a temporary directory for testing
64
tempDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'askpass-test-'));
65
66
// Create source directory with dummy askpass files
67
sourceDir = path.join(tempDir, 'source');
68
await fs.promises.mkdir(sourceDir, { recursive: true });
69
70
const askpassFiles = ['askpass.sh', 'askpass-main.js', 'ssh-askpass.sh', 'askpass-empty.sh', 'ssh-askpass-empty.sh'];
71
for (const file of askpassFiles) {
72
await fs.promises.writeFile(path.join(sourceDir, file), `#!/bin/sh\n# ${file}\n`);
73
}
74
});
75
76
teardown(async () => {
77
// Clean up temporary directory
78
try {
79
await fs.promises.rm(tempDir, { recursive: true, force: true });
80
} catch {
81
// Ignore errors during cleanup
82
}
83
});
84
85
test('garbage collection removes old directories', async function () {
86
const storageDir = path.join(tempDir, 'storage');
87
const askpassBaseDir = path.join(storageDir, 'askpass');
88
const logger = new MockLogOutputChannel();
89
90
// Create old directories with old mtimes (8 days ago)
91
const oldDate = new Date(Date.now() - (8 * 24 * 60 * 60 * 1000));
92
const oldDirs = ['oldhash1', 'oldhash2'];
93
94
for (const dirName of oldDirs) {
95
const dirPath = path.join(askpassBaseDir, dirName);
96
await fs.promises.mkdir(dirPath, { recursive: true });
97
await fs.promises.writeFile(path.join(dirPath, 'test.txt'), 'old');
98
await setDirectoryMtime(dirPath, oldDate);
99
}
100
101
// Create a recent directory (1 day ago)
102
const recentDate = new Date(Date.now() - (1 * 24 * 60 * 60 * 1000));
103
const recentDir = path.join(askpassBaseDir, 'recenthash');
104
await fs.promises.mkdir(recentDir, { recursive: true });
105
await fs.promises.writeFile(path.join(recentDir, 'test.txt'), 'recent');
106
await setDirectoryMtime(recentDir, recentDate);
107
108
// Call ensureAskpassScripts which should trigger garbage collection when creating a new directory
109
await ensureAskpassScripts(sourceDir, storageDir, logger);
110
111
// Check that old directories were removed
112
for (const dirName of oldDirs) {
113
const dirPath = path.join(askpassBaseDir, dirName);
114
const exists = await fs.promises.access(dirPath).then(() => true).catch(() => false);
115
assert.strictEqual(exists, false, `Old directory ${dirName} should have been removed`);
116
}
117
118
// Check that recent directory still exists
119
const recentExists = await fs.promises.access(recentDir).then(() => true).catch(() => false);
120
assert.strictEqual(recentExists, true, 'Recent directory should still exist');
121
122
// Check logs
123
assert.ok(logger.hasLog('info', 'Removing old askpass directory'), 'Should log removal of old directories');
124
});
125
126
test('garbage collection skips non-directory entries', async function () {
127
const storageDir = path.join(tempDir, 'storage');
128
const askpassBaseDir = path.join(storageDir, 'askpass');
129
const logger = new MockLogOutputChannel();
130
131
// Create a file in the askpass directory (not a directory)
132
await fs.promises.mkdir(askpassBaseDir, { recursive: true });
133
const filePath = path.join(askpassBaseDir, 'somefile.txt');
134
await fs.promises.writeFile(filePath, 'test');
135
136
// Set old mtime
137
const oldDate = new Date(Date.now() - (8 * 24 * 60 * 60 * 1000));
138
await fs.promises.utimes(filePath, oldDate, oldDate);
139
140
// Call ensureAskpassScripts which should trigger garbage collection
141
await ensureAskpassScripts(sourceDir, storageDir, logger);
142
143
// Check that file still exists (should not be removed)
144
const exists = await fs.promises.access(filePath).then(() => true).catch(() => false);
145
assert.strictEqual(exists, true, 'Non-directory file should not be removed');
146
});
147
148
test('mtime is updated on existing directory', async function () {
149
const storageDir = path.join(tempDir, 'storage');
150
const logger = new MockLogOutputChannel();
151
152
// Call ensureAskpassScripts to create the directory
153
const paths1 = await ensureAskpassScripts(sourceDir, storageDir, logger);
154
155
// Get the directory path and its initial mtime
156
const askpassDir = path.dirname(paths1.askpass);
157
const stat1 = await fs.promises.stat(askpassDir);
158
const mtime1 = stat1.mtime.getTime();
159
160
// Wait a bit to ensure time difference
161
await new Promise(resolve => setTimeout(resolve, 100));
162
163
// Call again (should update mtime)
164
await ensureAskpassScripts(sourceDir, storageDir, logger);
165
166
// Check that mtime was updated
167
const stat2 = await fs.promises.stat(askpassDir);
168
const mtime2 = stat2.mtime.getTime();
169
170
assert.ok(mtime2 > mtime1, 'Mtime should be updated on subsequent calls');
171
});
172
173
test('garbage collection handles empty askpass directory', async function () {
174
const storageDir = path.join(tempDir, 'storage');
175
const logger = new MockLogOutputChannel();
176
177
// Don't create any askpass directories, just call ensureAskpassScripts
178
await ensureAskpassScripts(sourceDir, storageDir, logger);
179
180
// Should complete without errors
181
assert.ok(true, 'Should handle empty or non-existent askpass directory gracefully');
182
});
183
184
test('current content-addressed directory is not removed', async function () {
185
const storageDir = path.join(tempDir, 'storage');
186
const logger = new MockLogOutputChannel();
187
188
// Create the current content-addressed directory
189
const paths = await ensureAskpassScripts(sourceDir, storageDir, logger);
190
const currentDir = path.dirname(paths.askpass);
191
192
// Set its mtime to 8 days ago (would normally be removed)
193
const oldDate = new Date(Date.now() - (8 * 24 * 60 * 60 * 1000));
194
await setDirectoryMtime(currentDir, oldDate);
195
196
// Call again which should trigger GC
197
await ensureAskpassScripts(sourceDir, storageDir, logger);
198
199
// Current directory should still exist
200
const exists = await fs.promises.access(currentDir).then(() => true).catch(() => false);
201
assert.strictEqual(exists, true, 'Current content-addressed directory should not be removed');
202
});
203
});
204
205