Skip to content

Commit fbde477

Browse files
committed
refactor(tests): enhance cleanup logic in useTempDir and useTempDirSuite for better error handling and Windows compatibility
1 parent cbfd7b4 commit fbde477

File tree

1 file changed

+137
-29
lines changed

1 file changed

+137
-29
lines changed

test/test-utils.ts

Lines changed: 137 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,15 @@ export function useTempDir(
8989
closeDatabases: (...dbs) => {
9090
for (const db of dbs) {
9191
try {
92-
if (db && db.isOpen) {
92+
if (db && typeof db.close === "function") {
93+
// Always try to close, regardless of state
9394
db.close();
9495
}
95-
} catch {
96-
// Ignore close errors during cleanup
96+
} catch (err) {
97+
// Log but don't throw during cleanup
98+
if (process.platform === "win32" || process.env.DEBUG_CLEANUP) {
99+
console.log(`Warning: Error closing database: ${err}`);
100+
}
97101
}
98102
}
99103
},
@@ -120,40 +124,140 @@ export function useTempDir(
120124
});
121125

122126
afterEach(async () => {
123-
// Clean up WAL and SHM files if requested
124-
if (options?.cleanupWalFiles && fs.existsSync(context.tempDir)) {
125-
try {
126-
const files = fs.readdirSync(context.tempDir);
127-
for (const file of files) {
128-
if (file.endsWith("-wal") || file.endsWith("-shm")) {
129-
try {
130-
fs.unlinkSync(path.join(context.tempDir, file));
131-
} catch {
132-
// Ignore errors
127+
const startTime = Date.now();
128+
const logDebug = (msg: string) => {
129+
if (process.env.DEBUG_CLEANUP || process.platform === "win32") {
130+
console.log(`[${Date.now() - startTime}ms] ${msg}`);
131+
}
132+
};
133+
134+
logDebug(`afterEach starting for tempDir: ${context.tempDir}`);
135+
136+
// Force a small delay to ensure databases are closed
137+
// This helps with the timing of cleanup vs database closure
138+
await new Promise((resolve) => setTimeout(resolve, 50));
139+
140+
// On Windows, we need to be more careful about cleanup timing
141+
if (process.platform === "win32") {
142+
logDebug("Windows detected - using special cleanup procedure");
143+
144+
// First, give SQLite time to release file handles
145+
logDebug("Initial wait for SQLite cleanup");
146+
await new Promise((resolve) => setTimeout(resolve, 100));
147+
148+
// Try to clean up WAL and SHM files
149+
if (options?.cleanupWalFiles && fs.existsSync(context.tempDir)) {
150+
logDebug("Cleaning up WAL/SHM files");
151+
let retryCount = 0;
152+
const maxRetries = 5;
153+
154+
while (retryCount < maxRetries) {
155+
try {
156+
const files = fs.readdirSync(context.tempDir);
157+
let walShmFound = false;
158+
159+
for (const file of files) {
160+
if (file.endsWith("-wal") || file.endsWith("-shm")) {
161+
walShmFound = true;
162+
try {
163+
fs.unlinkSync(path.join(context.tempDir, file));
164+
logDebug(`Deleted ${file} on attempt ${retryCount + 1}`);
165+
} catch (err: any) {
166+
if (err.code === 'EBUSY' || err.code === 'EPERM') {
167+
logDebug(`File ${file} still in use, will retry`);
168+
} else {
169+
logDebug(`Failed to delete ${file}: ${err}`);
170+
}
171+
}
172+
}
133173
}
174+
175+
if (!walShmFound) {
176+
logDebug("No WAL/SHM files found");
177+
break;
178+
}
179+
180+
// Check if all WAL/SHM files are gone
181+
const remainingFiles = fs.readdirSync(context.tempDir);
182+
const hasWalShm = remainingFiles.some(f => f.endsWith("-wal") || f.endsWith("-shm"));
183+
184+
if (!hasWalShm) {
185+
logDebug("All WAL/SHM files successfully deleted");
186+
break;
187+
}
188+
189+
retryCount++;
190+
if (retryCount < maxRetries) {
191+
logDebug(`Retry ${retryCount}/${maxRetries} - waiting 200ms`);
192+
await new Promise((resolve) => setTimeout(resolve, 200));
193+
}
194+
} catch (err) {
195+
logDebug(`Error during WAL/SHM cleanup: ${err}`);
196+
break;
134197
}
135198
}
136-
} catch {
137-
// Ignore errors
138199
}
139-
}
140-
141-
// Wait a bit for Windows file handles to be released
142-
if (process.platform === "win32") {
143-
await new Promise((resolve) => setTimeout(resolve, 500));
200+
201+
// Additional wait before main cleanup
202+
logDebug("Final wait before directory removal");
203+
await new Promise((resolve) => setTimeout(resolve, 300));
204+
} else {
205+
// Non-Windows cleanup
206+
if (options?.cleanupWalFiles && fs.existsSync(context.tempDir)) {
207+
logDebug("Cleaning up WAL/SHM files");
208+
try {
209+
const files = fs.readdirSync(context.tempDir);
210+
for (const file of files) {
211+
if (file.endsWith("-wal") || file.endsWith("-shm")) {
212+
try {
213+
fs.unlinkSync(path.join(context.tempDir, file));
214+
logDebug(`Deleted ${file}`);
215+
} catch (err) {
216+
logDebug(`Failed to delete ${file}: ${err}`);
217+
}
218+
}
219+
}
220+
} catch (err) {
221+
logDebug(`Error reading directory: ${err}`);
222+
}
223+
}
144224
}
145225

146226
if (fs.existsSync(context.tempDir)) {
147-
await fsp.rm(context.tempDir, {
148-
recursive: true,
149-
force: true,
150-
maxRetries: process.platform === "win32" ? 10 : 3,
151-
retryDelay: process.platform === "win32" ? 1000 : 500,
152-
});
227+
logDebug("Starting fsp.rm of tempDir");
228+
try {
229+
await fsp.rm(context.tempDir, {
230+
recursive: true,
231+
force: true,
232+
maxRetries: process.platform === "win32" ? 3 : 3,
233+
retryDelay: process.platform === "win32" ? 100 : 100,
234+
});
235+
logDebug("Successfully removed tempDir");
236+
} catch (err) {
237+
logDebug(`Error removing tempDir: ${err}`);
238+
// On Windows, if the directory still exists, it's likely in use
239+
// Log but don't fail - the OS will clean it up eventually
240+
if (process.platform === "win32" && fs.existsSync(context.tempDir)) {
241+
logDebug(`Warning: Could not remove ${context.tempDir} - likely still in use`);
242+
243+
// Try one more time with execSync, but don't wait
244+
try {
245+
const { exec } = await import("child_process");
246+
exec(`rmdir /s /q "${context.tempDir}" 2>nul`, (err) => {
247+
if (err) {
248+
logDebug(`Background cleanup also failed: ${err.message}`);
249+
}
250+
});
251+
} catch {
252+
// Ignore
253+
}
254+
}
255+
}
153256
}
154257

155258
// Clear additional files set
156259
additionalFiles.clear();
260+
logDebug("afterEach completed");
157261
}, options?.timeout ?? getTestTimeout()); // Use environment-aware timeout
158262

159263
return context;
@@ -179,11 +283,15 @@ export function useTempDirSuite(
179283
closeDatabases: (...dbs) => {
180284
for (const db of dbs) {
181285
try {
182-
if (db && db.isOpen) {
286+
if (db && typeof db.close === "function") {
287+
// Always try to close, regardless of state
183288
db.close();
184289
}
185-
} catch {
186-
// Ignore close errors during cleanup
290+
} catch (err) {
291+
// Log but don't throw during cleanup
292+
if (process.platform === "win32" || process.env.DEBUG_CLEANUP) {
293+
console.log(`Warning: Error closing database: ${err}`);
294+
}
187295
}
188296
}
189297
},

0 commit comments

Comments
 (0)