Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 85 additions & 53 deletions packages/toolbox-adk/test/e2e/jest.globalSetup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,102 +57,134 @@ export default async function globalSetup(): Promise<void> {

const localToolboxPath = path.resolve(__dirname, TOOLBOX_BINARY_NAME);

const bucketName = 'mcp-toolbox-for-databases';
console.log(
`Downloading toolbox binary from gs://mcp-toolbox-for-databases/${toolboxGcsPath} to ${localToolboxPath}...`,
);
await downloadBlob(
'mcp-toolbox-for-databases',
toolboxGcsPath,
localToolboxPath,
`Downloading toolbox binary from gs://${bucketName}/${toolboxGcsPath} to ${localToolboxPath}...`,
);
await downloadBlob(bucketName, toolboxGcsPath, localToolboxPath);
console.log('Toolbox binary downloaded successfully.');

// Make toolbox executable
await fs.chmod(localToolboxPath, 0o700);

// Start toolbox server
console.log('Starting toolbox server process...');
const serverProcess = spawn(
// Start toolbox servers
console.log('Starting toolbox server processes...');
const serverProcess1 = spawn(
localToolboxPath,
['--tools-file', toolsFilePath],
['--port', '5000', '--tools-file', toolsFilePath],
{
stdio: ['ignore', 'pipe', 'pipe'], // ignore stdin, pipe stdout/stderr
stdio: ['ignore', 'pipe', 'pipe'],
},
);
const serverProcess2 = spawn(
localToolboxPath,
['--port', '5001', '--tools-file', toolsFilePath, '--enable-draft-specs'],
{
stdio: ['ignore', 'pipe', 'pipe'],
},
);

(globalThis as CustomGlobal).__TOOLBOX_SERVER_PROCESS__ = serverProcess;
(globalThis as CustomGlobal).__TOOLBOX_SERVER_PROCESS__ = serverProcess1;
(globalThis as CustomGlobal).__TOOLBOX_SERVER_PROCESS_2__ = serverProcess2;

serverProcess.stdout?.on('data', (data: Buffer) => {
console.log(`[ToolboxServer STDOUT]: ${data.toString().trim()}`);
serverProcess1.stdout?.on('data', (data: Buffer) => {
console.log(`[ToolboxServer1 STDOUT]: ${data.toString().trim()}`);
});

serverProcess.stderr?.on('data', (data: Buffer) => {
console.error(`[ToolboxServer STDERR]: ${data.toString().trim()}`);
serverProcess1.stderr?.on('data', (data: Buffer) => {
console.error(`[ToolboxServer1 STDERR]: ${data.toString().trim()}`);
});

serverProcess.on('error', err => {
console.error('Toolbox server process error:', err);
throw new Error('Failed to start toolbox server process.');
serverProcess1.on('error', err => {
console.error('Toolbox server 1 process error:', err);
throw new Error('Failed to start toolbox server 1 process.');
});

serverProcess.on('exit', (code, signal) => {
serverProcess1.on('exit', (code, signal) => {
console.log(
`Toolbox server process exited with code ${code}, signal ${signal}.`,
`Toolbox server 1 process exited with code ${code}, signal ${signal}.`,
);
if (
(globalThis as CustomGlobal).__TOOLBOX_SERVER_PROCESS__ &&
!(globalThis as CustomGlobal).__SERVER_TEARDOWN_INITIATED__
) {
console.error('Toolbox server exited prematurely during setup.');
console.error('Toolbox server 1 exited prematurely during setup.');
}
});

serverProcess2.stdout?.on('data', (data: Buffer) => {
console.log(`[ToolboxServer2 STDOUT]: ${data.toString().trim()}`);
});
serverProcess2.stderr?.on('data', (data: Buffer) => {
console.error(`[ToolboxServer2 STDERR]: ${data.toString().trim()}`);
});
serverProcess2.on('error', err => {
console.error('Toolbox server 2 process error:', err);
throw new Error('Failed to start toolbox server 2 process.');
});
serverProcess2.on('exit', (code, signal) => {
console.log(
`Toolbox server 2 process exited with code ${code}, signal ${signal}.`,
);
if (
(globalThis as CustomGlobal).__TOOLBOX_SERVER_PROCESS_2__ &&
!(globalThis as CustomGlobal).__SERVER_TEARDOWN_INITIATED__
) {
console.error('Toolbox server 2 exited prematurely during setup.');
}
});

// Wait for server to start (basic poll check)
let started = false;
// Wait for servers to start (basic poll check)
let started1 = false;
let started2 = false;
const startTime = Date.now();
while (Date.now() - startTime < SERVER_READY_TIMEOUT_MS) {
if (
serverProcess.pid &&
!serverProcess.killed &&
serverProcess.exitCode === null
serverProcess1.pid &&
!serverProcess1.killed &&
serverProcess1.exitCode === null
) {
started1 = true;
}
if (
serverProcess2.pid &&
!serverProcess2.killed &&
serverProcess2.exitCode === null
) {
started2 = true;
}
if (started1 && started2) {
console.log(
'Toolbox server process appears to be running. Polling for stability...',
'Both Toolbox servers started successfully (processes are active).',
);
await delay(SERVER_READY_POLL_INTERVAL_MS * 2);
if (serverProcess.exitCode === null) {
console.log(
'Toolbox server started successfully (process is active).',
);
started = true;
break;
} else {
console.log('Toolbox server process exited after initial start.');
break;
}
break;
}
await delay(SERVER_READY_POLL_INTERVAL_MS);
console.log('Checking if toolbox server is started...');
console.log('Checking if toolbox servers are started...');
}

if (!started) {
if (serverProcess && !serverProcess.killed) {
serverProcess.kill('SIGTERM');
}
if (!started1 || !started2) {
if (serverProcess1 && !serverProcess1.killed)
serverProcess1.kill('SIGTERM');
if (serverProcess2 && !serverProcess2.killed)
serverProcess2.kill('SIGTERM');
throw new Error(
`Toolbox server failed to start within ${SERVER_READY_TIMEOUT_MS / 1000} seconds.`,
`Toolbox servers failed to start within ${SERVER_READY_TIMEOUT_MS / 1000} seconds.`,
);
}

console.log('Jest Global Setup: Completed successfully.');
} catch (error) {
console.error('Jest Global Setup Failed:', error);
// Attempt to kill server if it started partially
const serverProcess = (globalThis as CustomGlobal)
// Attempt to kill servers if they started partially
const serverProcess1 = (globalThis as CustomGlobal)
.__TOOLBOX_SERVER_PROCESS__;
if (serverProcess && !serverProcess.killed) {
console.log('Attempting to terminate partially started server...');
serverProcess.kill('SIGKILL');
const serverProcess2 = (globalThis as CustomGlobal)
.__TOOLBOX_SERVER_PROCESS_2__;
if (serverProcess1 && !serverProcess1.killed) {
console.log('Attempting to terminate partially started server 1...');
serverProcess1.kill('SIGKILL');
}
if (serverProcess2 && !serverProcess2.killed) {
console.log('Attempting to terminate partially started server 2...');
serverProcess2.kill('SIGKILL');
}
// Clean up temp file if created
const toolsFilePath = (globalThis as CustomGlobal).__TOOLS_FILE_PATH__;
Expand Down
91 changes: 55 additions & 36 deletions packages/toolbox-adk/test/e2e/jest.globalTeardown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
// limitations under the License.

import * as fs from 'fs-extra';
import {ChildProcess} from 'child_process';
import {CustomGlobal} from './types.js';

const SERVER_TERMINATE_TIMEOUT_MS = 10000; // 10 seconds
Expand All @@ -22,50 +23,67 @@ export default async function globalTeardown(): Promise<void> {
(globalThis as CustomGlobal).__SERVER_TEARDOWN_INITIATED__ = true;

const customGlobal = globalThis as CustomGlobal;
const serverProcess = customGlobal.__TOOLBOX_SERVER_PROCESS__;
const serverProcess1 = customGlobal.__TOOLBOX_SERVER_PROCESS__;
const serverProcess2 = customGlobal.__TOOLBOX_SERVER_PROCESS_2__;
const toolsFilePath = customGlobal.__TOOLS_FILE_PATH__;

if (serverProcess && !serverProcess.killed) {
console.log('Stopping toolbox server process...');
serverProcess.kill('SIGTERM'); // Graceful termination
const killServer = async (
serverProcess: ChildProcess | undefined,
name: string,
) => {
if (serverProcess && !serverProcess.killed) {
console.log(`Stopping toolbox server ${name} process...`);
serverProcess.kill('SIGTERM'); // Graceful termination

// Wait for the process to exit
const stopPromise = new Promise<void>((resolve, reject) => {
const timeout = setTimeout(() => {
if (!serverProcess.killed) {
console.warn(
'Toolbox server did not terminate gracefully, sending SIGKILL.',
);
serverProcess.kill('SIGKILL');
}
// Resolve even if SIGKILL is needed, as we want teardown to finish
resolve();
}, SERVER_TERMINATE_TIMEOUT_MS);
// Wait for the process to exit
const stopPromise = new Promise<void>((resolve, reject) => {
const timeout = setTimeout(() => {
if (!serverProcess.killed) {
console.warn(
`Toolbox server ${name} did not terminate gracefully, sending SIGKILL.`,
);
serverProcess.kill('SIGKILL');
}
// Resolve even if SIGKILL is needed, as we want teardown to finish
resolve();
}, SERVER_TERMINATE_TIMEOUT_MS);

serverProcess.on('exit', (code, signal) => {
clearTimeout(timeout);
console.log(
`Toolbox server process exited with code ${code}, signal ${signal} during teardown.`,
serverProcess.on(
'exit',
(code: number | null, signal: NodeJS.Signals | null) => {
clearTimeout(timeout);
console.log(
`Toolbox server ${name} process exited with code ${code}, signal ${signal} during teardown.`,
);
resolve();
},
);
resolve();
});
serverProcess.on('error', err => {
// Should not happen if already running
clearTimeout(timeout);
console.error('Error during server process termination:', err);
reject(err);
serverProcess.on('error', (err: Error) => {
// Should not happen if already running
clearTimeout(timeout);
console.error(
`Error during server ${name} process termination:`,
err,
);
reject(err);
});
});
});

try {
await stopPromise;
} catch (error) {
console.error('Error while waiting for server to stop:', error);
if (!serverProcess.killed) serverProcess.kill('SIGKILL'); // Ensure it's killed
try {
await stopPromise;
} catch (error) {
console.error(`Error while waiting for server ${name} to stop:`, error);
if (!serverProcess.killed) serverProcess.kill('SIGKILL'); // Ensure it's killed
}
} else {
console.log(
`Toolbox server ${name} process was not running or already handled.`,
);
}
} else {
console.log('Toolbox server process was not running or already handled.');
}
};

await killServer(serverProcess1, '1');
await killServer(serverProcess2, '2');

if (toolsFilePath) {
try {
Expand All @@ -79,6 +97,7 @@ export default async function globalTeardown(): Promise<void> {
}
}
customGlobal.__TOOLBOX_SERVER_PROCESS__ = undefined;
customGlobal.__TOOLBOX_SERVER_PROCESS_2__ = undefined;
customGlobal.__TOOLS_FILE_PATH__ = undefined;
customGlobal.__GOOGLE_CLOUD_PROJECT__ = undefined;

Expand Down
Loading