From 7d650b34e08e06007559b848ae7a1734ade5483e Mon Sep 17 00:00:00 2001 From: Krzysztof Platis Date: Fri, 4 Oct 2024 18:54:20 +0200 Subject: [PATCH] test(SSR e2e): Validate http port 4000 is not used by other process, before running tests (#19337) Problem: Each SSR test starts and kills an SSR process in the background, listening on port 4000. If you interrupt the tests that you run on local (e.g. by pressing Cmd+C), then the Jest tests process is stopped but the SSR process might be still dangling in the background. So the next time you try to re-run SSR Tests, they'll fail with because the new SSR process cannot be started on the already occupied port 4000. Previosuly, the we had to wait for all tests fail (30secons), until we were presented with an vague error message about port being in use. Moreover it didn't get any hint how to solve the problem. Now, the problem is detected before running any test. The error message is descriptive and provides a hint how to kill a process that is listening on the conflicting port. Note: Instead of detecting the problem and showing an useful message, my first strategy was to avoid the problem in general. I mean that whenever the parent Jest process is interupted, I wanted the child SSR process to be killed as well. I've tried hooks like `process.on('SIGTERM')` etc. , I've tried tampering the config of spawning the `child` process (remove `detach: true`, add `killSignal`) or calling `child.unref()`, but I had various problems - the child process was still not killed when parent was interupted or the child process couldn't be killed with `process.kill()` in the tests body **QA steps** 1. Build SSR `npm build:libs && npm build && npm build:local-http-backend` 2. In one terminal run `npm run serve:ssr` 3. In second terminal run `npm run test:ssr` 4. Verify the following error message is presented in the second terminal: BEFORE: ![image](https://github.com/user-attachments/assets/d7df0b02-b5a7-4e42-93e4-bec7ad24fbad) AFTER: ![image](https://github.com/user-attachments/assets/da6c0659-ece9-4bec-9ba6-161d1c20d98e) 5. Kill the SSR process in the first terminal (e.g. using the commands from the advice from the console) 6. Re-run ssr tests in the second terminal and verity they pass Fixes https://jira.tools.sap/browse/CXSPA-8587 --- projects/ssr-tests/validate-ssr-build.ts | 56 ++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/projects/ssr-tests/validate-ssr-build.ts b/projects/ssr-tests/validate-ssr-build.ts index 7430b00c2eb..5ff1f6503d6 100644 --- a/projects/ssr-tests/validate-ssr-build.ts +++ b/projects/ssr-tests/validate-ssr-build.ts @@ -6,6 +6,7 @@ /** * Verify that the SSR app under tests meets the following criteria: + * * 1. The app us built in prod mode. * * Why it's needed: @@ -20,13 +21,24 @@ * Some tests require mocking the behavior of the backend using a local backend http proxy. * If the app is not calling a local backend proxy as a base OCC url, some tests will fail. * + * 3. No other process is already listening on the HTTP port used by the SSR server. + * + * Why it's needed: + * In tests we start a new SSR server process listening on a specific port. + * If anything is listening on that port, the tests will fail. * * In this file we'll validate the build of the app before running the tests. */ import * as fs from 'fs'; +import * as http from 'http'; import * as path from 'path'; +/** + * The port the SSR server will listen on. + */ +const SSR_PORT = 4000; + /** * String that always appears in the `main.js` file of the SSR app when built with a local backend proxy. */ @@ -52,6 +64,22 @@ const BUILD_COMMAND_ADVICE = colorToYellow('Please build the SSR app with the following command\n') + colorToYellow('> npm run build && npm run build:ssr:local-http-backend\n'); +/** + * Advice to the user on how to kill the process listening on the port. + */ +export const KILL_PORT_ADVICE = + colorToYellow( + `Please kill the process listening on port ${SSR_PORT} to free the port for the SSR server.\n` + ) + + // include 2 commands that will work both on Mac and Linux for port 4000 + colorToYellow('a) on Mac/Linux run:\n') + + colorToYellow(` > kill -9 $(lsof -t -i :${SSR_PORT})\n`) + + colorToYellow( + 'b) on Windows run 2 commands (to find PID and then to kill it): \n' + ) + + colorToYellow(` > netstat -ano | findstr :${SSR_PORT}\n`) + + colorToYellow(` > taskkill /f /pid \n`); + export default async function validateSsrBuild() { if (!fs.existsSync(SSR_APP_PATH)) { throw new Error( @@ -77,8 +105,36 @@ SSR app is not using prod mode. ${BUILD_COMMAND_ADVICE}` ); } + + if (!(await isHttpPortFree(SSR_PORT))) { + throw new Error( + ` +Port ${SSR_PORT} is already in use. +${KILL_PORT_ADVICE}` + ); + } } function colorToYellow(text: string) { return `\x1b[33m${text}\x1b[0m`; } + +async function isHttpPortFree(port: number): Promise { + return new Promise((resolve) => { + const server = http.createServer(); + + server.listen(port, () => { + server.close(() => { + resolve(true); + }); + }); + + server.once('error', (error: NodeJS.ErrnoException) => { + if (error.code === 'EADDRINUSE') { + resolve(false); + } else { + resolve(true); + } + }); + }); +}