fix: resolve test hang issues in CI
- Fixed MSW event listener memory leaks - Added proper database connection cleanup - Fixed MSW server lifecycle management - Reduced global test timeout to 30s for faster failure detection - Added resource cleanup in all integration tests This should resolve the GitHub Actions test hanging issue
This commit is contained in:
BIN
data/nodes.db
BIN
data/nodes.db
Binary file not shown.
@@ -138,9 +138,13 @@ describe('Database Connection Management', () => {
|
|||||||
|
|
||||||
// Test concurrent reads
|
// Test concurrent reads
|
||||||
const promises = connections.map((conn, index) => {
|
const promises = connections.map((conn, index) => {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve, reject) => {
|
||||||
const result = conn.prepare('SELECT ? as id').get(index);
|
try {
|
||||||
resolve(result);
|
const result = conn.prepare('SELECT ? as id').get(index);
|
||||||
|
resolve(result);
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -148,12 +152,32 @@ describe('Database Connection Management', () => {
|
|||||||
expect(results).toHaveLength(connectionCount);
|
expect(results).toHaveLength(connectionCount);
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
// Cleanup connections
|
// Cleanup connections - ensure all are closed even if some fail
|
||||||
connections.forEach(conn => conn.close());
|
await Promise.all(
|
||||||
if (fs.existsSync(dbPath)) {
|
connections.map(async (conn) => {
|
||||||
fs.unlinkSync(dbPath);
|
try {
|
||||||
fs.unlinkSync(`${dbPath}-wal`);
|
if (conn.open) {
|
||||||
fs.unlinkSync(`${dbPath}-shm`);
|
conn.close();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Ignore close errors
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// Clean up files with error handling
|
||||||
|
try {
|
||||||
|
if (fs.existsSync(dbPath)) {
|
||||||
|
fs.unlinkSync(dbPath);
|
||||||
|
}
|
||||||
|
if (fs.existsSync(`${dbPath}-wal`)) {
|
||||||
|
fs.unlinkSync(`${dbPath}-wal`);
|
||||||
|
}
|
||||||
|
if (fs.existsSync(`${dbPath}-shm`)) {
|
||||||
|
fs.unlinkSync(`${dbPath}-shm`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Ignore cleanup errors
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -285,7 +309,7 @@ describe('Database Connection Management', () => {
|
|||||||
db.exec('ROLLBACK');
|
db.exec('ROLLBACK');
|
||||||
conn2.close();
|
conn2.close();
|
||||||
}
|
}
|
||||||
});
|
}, { timeout: 5000 }); // Add explicit timeout
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Database Configuration', () => {
|
describe('Database Configuration', () => {
|
||||||
|
|||||||
@@ -50,13 +50,21 @@ describe('MSW Setup Verification', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('Integration Test Server', () => {
|
describe('Integration Test Server', () => {
|
||||||
|
let serverStarted = false;
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
// Start a separate MSW instance for more control
|
// Only start if not already running
|
||||||
mswTestServer.start({ onUnhandledRequest: 'error' });
|
if (!serverStarted) {
|
||||||
|
mswTestServer.start({ onUnhandledRequest: 'error' });
|
||||||
|
serverStarted = true;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
mswTestServer.stop();
|
if (serverStarted) {
|
||||||
|
mswTestServer.stop();
|
||||||
|
serverStarted = false;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle workflow creation with custom response', async () => {
|
it('should handle workflow creation with custom response', async () => {
|
||||||
@@ -163,7 +171,7 @@ describe('MSW Setup Verification', () => {
|
|||||||
expect(requests).toHaveLength(2);
|
expect(requests).toHaveLength(2);
|
||||||
expect(requests[0].url).toContain('/api/v1/workflows');
|
expect(requests[0].url).toContain('/api/v1/workflows');
|
||||||
expect(requests[1].url).toContain('/api/v1/executions');
|
expect(requests[1].url).toContain('/api/v1/executions');
|
||||||
});
|
}, { timeout: 10000 }); // Increase timeout for this specific test
|
||||||
|
|
||||||
it('should work with scoped handlers', async () => {
|
it('should work with scoped handlers', async () => {
|
||||||
const result = await mswTestServer.withScope(
|
const result = await mswTestServer.withScope(
|
||||||
|
|||||||
@@ -66,17 +66,34 @@ export const mswTestServer = {
|
|||||||
waitForRequests: (count: number, timeout = 5000): Promise<Request[]> => {
|
waitForRequests: (count: number, timeout = 5000): Promise<Request[]> => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const requests: Request[] = [];
|
const requests: Request[] = [];
|
||||||
const timeoutId = setTimeout(() => {
|
let timeoutId: NodeJS.Timeout | null = null;
|
||||||
reject(new Error(`Timeout waiting for ${count} requests. Got ${requests.length}`));
|
|
||||||
}, timeout);
|
// Event handler function to allow cleanup
|
||||||
|
const handleRequest = ({ request }: { request: Request }) => {
|
||||||
integrationTestServer.events.on('request:match', ({ request }) => {
|
|
||||||
requests.push(request);
|
requests.push(request);
|
||||||
if (requests.length === count) {
|
if (requests.length === count) {
|
||||||
clearTimeout(timeoutId);
|
cleanup();
|
||||||
resolve(requests);
|
resolve(requests);
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
|
// Cleanup function to remove listener and clear timeout
|
||||||
|
const cleanup = () => {
|
||||||
|
if (timeoutId) {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
timeoutId = null;
|
||||||
|
}
|
||||||
|
integrationTestServer.events.removeListener('request:match', handleRequest);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set timeout
|
||||||
|
timeoutId = setTimeout(() => {
|
||||||
|
cleanup();
|
||||||
|
reject(new Error(`Timeout waiting for ${count} requests. Got ${requests.length}`));
|
||||||
|
}, timeout);
|
||||||
|
|
||||||
|
// Add event listener
|
||||||
|
integrationTestServer.events.on('request:match', handleRequest);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -86,14 +103,28 @@ export const mswTestServer = {
|
|||||||
verifyNoUnhandledRequests: (): Promise<void> => {
|
verifyNoUnhandledRequests: (): Promise<void> => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let hasUnhandled = false;
|
let hasUnhandled = false;
|
||||||
|
let timeoutId: NodeJS.Timeout | null = null;
|
||||||
|
|
||||||
integrationTestServer.events.on('request:unhandled', ({ request }) => {
|
const handleUnhandled = ({ request }: { request: Request }) => {
|
||||||
hasUnhandled = true;
|
hasUnhandled = true;
|
||||||
|
cleanup();
|
||||||
reject(new Error(`Unhandled request: ${request.method} ${request.url}`));
|
reject(new Error(`Unhandled request: ${request.method} ${request.url}`));
|
||||||
});
|
};
|
||||||
|
|
||||||
|
const cleanup = () => {
|
||||||
|
if (timeoutId) {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
timeoutId = null;
|
||||||
|
}
|
||||||
|
integrationTestServer.events.removeListener('request:unhandled', handleUnhandled);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add event listener
|
||||||
|
integrationTestServer.events.on('request:unhandled', handleUnhandled);
|
||||||
|
|
||||||
// Give a small delay to allow any pending requests
|
// Give a small delay to allow any pending requests
|
||||||
setTimeout(() => {
|
timeoutId = setTimeout(() => {
|
||||||
|
cleanup();
|
||||||
if (!hasUnhandled) {
|
if (!hasUnhandled) {
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ function setTestDefaults(): void {
|
|||||||
TEST_TIMEOUT_UNIT: '5000',
|
TEST_TIMEOUT_UNIT: '5000',
|
||||||
TEST_TIMEOUT_INTEGRATION: '15000',
|
TEST_TIMEOUT_INTEGRATION: '15000',
|
||||||
TEST_TIMEOUT_E2E: '30000',
|
TEST_TIMEOUT_E2E: '30000',
|
||||||
TEST_TIMEOUT_GLOBAL: '60000',
|
TEST_TIMEOUT_GLOBAL: '30000', // Reduced from 60s to 30s to catch hangs faster
|
||||||
|
|
||||||
// Test execution
|
// Test execution
|
||||||
TEST_RETRY_ATTEMPTS: '2',
|
TEST_RETRY_ATTEMPTS: '2',
|
||||||
|
|||||||
Reference in New Issue
Block a user