From 5142182c1d559e47130feafd18ee5c2c2c70bb45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Wed, 6 Aug 2025 20:45:13 +0200 Subject: [PATCH] refactor test helpers for Meteor modern test suite --- tools/modern-tests/helpers.js | 143 ++++++++++++++++++++++++++ tools/modern-tests/rspack-run.test.js | 132 ++---------------------- 2 files changed, 151 insertions(+), 124 deletions(-) create mode 100644 tools/modern-tests/helpers.js diff --git a/tools/modern-tests/helpers.js b/tools/modern-tests/helpers.js new file mode 100644 index 0000000000..1c8ac887ca --- /dev/null +++ b/tools/modern-tests/helpers.js @@ -0,0 +1,143 @@ +const execa = require('execa'); +const waitOn = require('wait-on'); +const path = require('path'); +const fs = require('fs-extra'); +const os = require('os'); +const rimraf = require('rimraf'); + +// Get the absolute path to the meteor executable +const REPO_ROOT = path.resolve(__dirname, '../..'); +const METEOR_EXECUTABLE = path.join(REPO_ROOT, 'meteor'); + +/** + * Helper function to set up a Meteor app in a temporary directory + * Copies the app and runs npm install + * @param {string} appName - Name of the app in the apps directory + * @returns {string} - Path to the temporary directory containing the app + */ +export async function setupMeteorApp(appName) { + // Create a unique temporary directory + const randomSuffix = Math.random().toString(36).substring(2, 10); + const tempDir = path.join(os.tmpdir(), `${appName}-${randomSuffix}`); + + // Source app directory + const sourceAppDir = path.join(__dirname, 'apps', appName); + console.log(`Source app directory: ${sourceAppDir}`); + console.log(`Temporary directory: ${tempDir}`); + + try { + // Create the destination directory if it doesn't exist + if (!fs.existsSync(tempDir)) { + await fs.mkdir(tempDir, { recursive: true }); + } + + // Use fs-extra's copy method with recursive option + await fs.copy(sourceAppDir, tempDir, { + dereference: true, + preserveTimestamps: true, + overwrite: true + }); + console.log(`Copied app to temporary directory: ${tempDir}`); + } catch (err) { + console.error('Error during copy:', err); + } + + // Run npm install in the temporary directory + console.log('Running npm install...'); + await execa.command('npm install', { + cwd: tempDir, + stdio: 'inherit', + shell: true, + }); + + return { tempDir }; +} + +/** + * Helper function to run a Meteor app + * @param {string} tempDir - Path to the directory containing the app + * @param {number} port - Port to run the app on + * @returns {Object} - The meteor process + */ +export async function runMeteorApp(tempDir, port) { + // Start Meteor CLI in dev mode + console.log(`Starting Meteor app on port ${port}...`); + const meteorProcess = execa(METEOR_EXECUTABLE, ['run', '--port', port.toString()], { + cwd: tempDir, + stdio: 'inherit', + }); + + // Wait for server to be up + console.log(`Waiting for app to be available on port ${port}...`); + await waitOn({ + resources: [`http-get://localhost:${port}`], + timeout: 60000 + }); + + return { meteorProcess }; +} + +/** + * Helper function to kill a Meteor process + * @param {Object} meteorProcess - The Meteor process to kill + * @returns {Promise} + */ +export async function killMeteorProcess(meteorProcess) { + if (meteorProcess) { + try { + await meteorProcess.kill('SIGKILL'); + console.log('Successfully killed meteor process'); + } catch (err) { + console.log(`Error killing meteor process: ${err.message}`); + } + } +} + +/** + * Kills any process running on the specified port + * @param {number} port - The port to kill processes on + * @returns {Promise} + */ +export async function killProcessByPort(port) { + try { + // Different commands based on OS + const command = process.platform === 'win32' + ? `FOR /F "tokens=5" %a in ('netstat -ano ^| find "LISTENING" ^| find ":${port}"') do taskkill /F /PID %a` + : `lsof -i :${port} -t | xargs -r kill -9`; + + console.log(`Killing process on port ${port}...`); + try { + // Use { reject: false } to prevent execa from throwing on non-zero exit codes + const result = await execa.command(command, { shell: true, reject: false }); + if (result.failed) { + // It's okay if this fails because there might not be a process on that port + console.log(`No process found on port ${port} or command returned non-zero exit code`); + } else { + console.log(`Successfully killed process on port ${port}`); + } + } catch (err) { + // This catch block will only be reached for operational errors, not for command failures + console.log(`Error executing kill command: ${err.message}`); + } + console.log(`Successfully ensured no process is running on port ${port}`); + } catch (error) { + // This should never be reached with the inner try/catch, but keeping as a safety net + console.error(`Error killing process on port ${port}:`, error); + } +} + +/** + * Helper function to clean up a temporary directory + * @param {string} tempDir - Path to the temporary directory to clean up + * @returns {Promise} + */ +export async function cleanupTempDir(tempDir) { + if (tempDir) { + try { + rimraf.sync(tempDir, { disableGlob: true, maxRetries: 5, retryDelay: 500 }); + console.log(`Removed temporary directory: ${tempDir}`); + } catch (err) { + console.log(`Sync removal failed, trying async removal: ${err}`); + } + } +} diff --git a/tools/modern-tests/rspack-run.test.js b/tools/modern-tests/rspack-run.test.js index 8e3148d091..0ff7aeb5e6 100644 --- a/tools/modern-tests/rspack-run.test.js +++ b/tools/modern-tests/rspack-run.test.js @@ -1,107 +1,4 @@ -const execa = require('execa'); -const waitOn = require('wait-on'); -const path = require('path'); -const fs = require('fs-extra'); -const os = require('os'); -const rimraf = require('rimraf'); - -/** - * Kills any process running on the specified port - * @param {number} port - The port to kill processes on - * @returns {Promise} - */ -async function killProcessByPort(port) { - try { - // Different commands based on OS - const command = process.platform === 'win32' - ? `FOR /F "tokens=5" %a in ('netstat -ano ^| find "LISTENING" ^| find ":${port}"') do taskkill /F /PID %a` - : `lsof -i :${port} -t | xargs -r kill -9`; - - console.log(`Killing process on port ${port}...`); - try { - // Use { reject: false } to prevent execa from throwing on non-zero exit codes - const result = await execa.command(command, { shell: true, reject: false }); - if (result.failed) { - // It's okay if this fails because there might not be a process on that port - console.log(`No process found on port ${port} or command returned non-zero exit code`); - } else { - console.log(`Successfully killed process on port ${port}`); - } - } catch (err) { - // This catch block will only be reached for operational errors, not for command failures - console.log(`Error executing kill command: ${err.message}`); - } - console.log(`Successfully ensured no process is running on port ${port}`); - } catch (error) { - // This should never be reached with the inner try/catch, but keeping as a safety net - console.error(`Error killing process on port ${port}:`, error); - } -} - -// Get the absolute path to the meteor executable -const REPO_ROOT = path.resolve(__dirname, '../..'); -console.log('REPO_ROOT:', REPO_ROOT); -const METEOR_EXECUTABLE = path.join(REPO_ROOT, 'meteor'); -console.log('METEOR_EXECUTABLE:', METEOR_EXECUTABLE); - -/** - * Helper function to run a Meteor app in a temporary directory - * Uses fs-extra for file operations - * @param {string} appName - Name of the app in the apps directory - * @param {number} port - Port to run the app on - * @returns {Object} - Object containing the meteor process and temp directory path - */ -async function runMeteorApp(appName, port) { - // Create a unique temporary directory - const randomSuffix = Math.random().toString(36).substring(2, 10); - const tempDir = path.join(os.tmpdir(), `${appName}-${randomSuffix}`); - - // Source app directory - const sourceAppDir = path.join(__dirname, 'apps', appName); - console.log(`Source app directory: ${sourceAppDir}`); - console.log(`Temporary directory: ${tempDir}`); - - try { - // Create the destination directory if it doesn't exist - if (!fs.existsSync(tempDir)) { - await fs.mkdir(tempDir, { recursive: true }); - } - - // Use fs-extra's copy method with recursive option - await fs.copy(sourceAppDir, tempDir, { - dereference: true, - preserveTimestamps: true, - overwrite: true - }); - console.log(`Copied app to temporary directory: ${tempDir}`); - } catch (err) { - console.error('Error during copy:', err); - } - - // Run npm install in the temporary directory - console.log('Running npm install...'); - await execa('npm', ['install'], { - cwd: tempDir, - stdio: 'inherit', - shell: true, - }); - - // Start Meteor CLI in dev mode - console.log(`Starting Meteor app on port ${port}...`); - const meteorProcess = execa(METEOR_EXECUTABLE, ['run', '--port', port.toString()], { - cwd: tempDir, - stdio: 'inherit', - }); - - // Wait for server to be up - console.log(`Waiting for app to be available on port ${port}...`); - await waitOn({ - resources: [`http-get://localhost:${port}`], - timeout: 60000 - }); - - return { meteorProcess, tempDir }; -} +import { killProcessByPort, setupMeteorApp, runMeteorApp, cleanupTempDir, killMeteorProcess } from './helpers'; describe('rspack build and serve', () => { let meteorProcess; @@ -109,35 +6,22 @@ describe('rspack build and serve', () => { const PORT = 3100; beforeAll(async () => { - // Run the react app using our helper - const result = await runMeteorApp('react', PORT); - meteorProcess = result.meteorProcess; - tempDir = result.tempDir; + // Setup the Meteor app + tempDir = (await setupMeteorApp('react'))?.tempDir; + + // Run the Meteor app + meteorProcess = (await runMeteorApp(tempDir, PORT))?.meteorProcess; }); afterAll(async () => { // Kill the meteor process - if (meteorProcess) { - try { - await meteorProcess.kill('SIGKILL'); - } catch (err) { - console.log(`Error killing meteor process: ${err.message}`); - } - } + await killMeteorProcess(meteorProcess); // Ensure any process on the port is killed await killProcessByPort(PORT); // Clean up the temporary directory - if (tempDir) { - try { - // First try synchronous rimraf - rimraf.sync(tempDir, { disableGlob: true, maxRetries: 5, retryDelay: 500 }); - console.log(`Removed temporary directory: ${tempDir}`); - } catch (err) { - console.log(`Sync removal failed, trying async removal: ${err}`); - } - } + await cleanupTempDir(tempDir); }); test('loads and has correct content', async () => {