diff --git a/README.md b/README.md index 25bfee7..c6a07ec 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Memory Leak Detector is a tiny node server that exposes an HTTP API for making s It requires a path to the source code so it can scan codebase for `class`. -`pnpm memory-leak-detector ./app` +`pnpm memory-leak-detector --path ./app` ## Example app @@ -22,11 +22,47 @@ An example app can be found [here](https://github.com/mainmatter/memory-leak-det Install the `memory-leak-detector` dependency - `pnpm add -D memory-leak-detector` +- Edit your test runner's option to run Chrome with `--remote-debugging-port=9222` option. + Example Configuration for Testem. + +```js +// testem.js +"use strict"; + +module.exports = { + test_page: "tests/index.html?hidepassed", + disable_watching: true, + launch_in_ci: ["Chrome"], + launch_in_dev: ["Chrome"], + browser_start_timeout: 120, + browser_args: { + Chrome: { + ci: [ + // --no-sandbox is needed when running Chrome inside a container + process.env.CI ? "--no-sandbox" : null, + "--headless", + "--disable-dev-shm-usage", + "--disable-software-rasterizer", + "--mute-audio", + "--remote-debugging-port=9222", + "--window-size=1440,900", + ].filter(Boolean), + dev: [ + "--disable-dev-shm-usage", + "--disable-software-rasterizer", + "--mute-audio", + "--remote-debugging-port=9222", + "--window-size=1440,900", + ].filter(Boolean), + }, + }, +}; +``` Define a script that runs a `memory-leak-detector` node server and listen, run your tests as usual, finally wait for the process results. ```sh -pnpm memory-leak-detector ./app & pid=$!; pnpm test; wait $pid +pnpm memory-leak-detector --path ./app & pid=$!; pnpm test; wait $pid ``` The reason for waiting for that process' pid is that e.g. Qunit doesn't have a good way to dynamically create and run a test at the end of your test suite while we must have a way to fail the CI in case something's wrong. @@ -35,7 +71,7 @@ For that reason `detectLeakingClasses` is meant to be used at the end only, beca That also means that if you don't intend to use `detectLeakingClasses`, your script should not wait for the `memory-leak-detector` to exit i.e. ```sh -pnpm memory-leak-detector ./app & pnpm test +pnpm memory-leak-detector --path ./app & pnpm test ``` ### Checking for memory leaks after test suite finishes @@ -88,3 +124,35 @@ test("paginating back and forth", async function (assert) { ``` ![Checking for specific class in a test](https://github.com/mainmatter/memory-leak-detector/blob/main/assets/memory_leak_2.png) + +## Configuration + +### memory-leak-detector + +- path: , has no default value. +- port: , defaults to 3000. + +### Chrome / Test runner + +By default a Google Chrome instance is expected to be running on address `127.0.0.1:9222`. +In case it runs on a different address, you can provide the connection info when calling the HTTP API e.g. + +```js +await detectLeakingClasses("title", document.title, { + host: "localhost", + port: 9333, +}); +``` + +```js +const assertions = { MyClass: 10 }; +const results = await detectMemoryLeak( + "url", + document.location.href, + assertions, + { + host: "localhost", + port: 9333, + }, +); +``` diff --git a/packages/memory-leak-detector/cli.js b/packages/memory-leak-detector/cli.js deleted file mode 100644 index 5d6996b..0000000 --- a/packages/memory-leak-detector/cli.js +++ /dev/null @@ -1,19 +0,0 @@ -const CDP = require('chrome-remote-interface'); - -async function connect() { - let client; - try { - client = await CDP({ port: 9222, host: "127.0.0.1" }); - console.log('Connected to Chrome!'); - // Perform operations using the client here - } catch (err) { - console.error('Error connecting to Chrome:', err); - } finally { - if (client) { - await client.close(); - } - } -} - -connect(); - diff --git a/packages/memory-leak-detector/index.js b/packages/memory-leak-detector/index.js index 7c060bc..e496e2f 100644 --- a/packages/memory-leak-detector/index.js +++ b/packages/memory-leak-detector/index.js @@ -1,5 +1,9 @@ -export async function detectMemoryLeak(by, value, assertions) { - // @TODO: Make the server configurable +export async function detectMemoryLeak( + by, + value, + assertions, + browserAddress = { port: 9222, host: "127.0.0.1" }, +) { let response = await fetch(`http://localhost:3000/detect_memory_leak`, { method: "POST", headers: { @@ -10,6 +14,7 @@ export async function detectMemoryLeak(by, value, assertions) { by, value, }, + ...browserAddress, assertions, }), }); @@ -18,7 +23,12 @@ export async function detectMemoryLeak(by, value, assertions) { return json.results; } -export async function detectLeakingClasses(by, value) { +export async function detectLeakingClasses( + by, + value, + + browserAddress = { port: 9222, host: "127.0.0.1" }, +) { let response = await fetch(`http://localhost:3000/detect_leaking_classes`, { method: "POST", headers: { @@ -29,6 +39,7 @@ export async function detectLeakingClasses(by, value) { by, value, }, + ...browserAddress, }), }); let json = await response.json(); diff --git a/packages/memory-leak-detector/package.json b/packages/memory-leak-detector/package.json index c16e0c8..2f453ff 100644 --- a/packages/memory-leak-detector/package.json +++ b/packages/memory-leak-detector/package.json @@ -18,6 +18,7 @@ "@babel/parser": "^7.12.10", "ast-types": "^0.14.2", "chrome-remote-interface": "^0.28.2", + "commander": "^12.1.0", "ember-cli-babel": "^7.22.1", "ember-cli-htmlbars": "^5.3.1", "glob": "^11.0.0", diff --git a/packages/memory-leak-detector/server/index.js b/packages/memory-leak-detector/server/index.js index 9ff1937..216f485 100755 --- a/packages/memory-leak-detector/server/index.js +++ b/packages/memory-leak-detector/server/index.js @@ -6,8 +6,22 @@ const inventoryClasses = require("./inventory-classes.js"); const express = require("express"); const cors = require("cors"); +const { program } = require("commander"); + function main() { - const [, , sourceDirectory] = process.argv; + program + .requiredOption( + "--path ", + "A path to the codebase in order to scan any class declaration for automated leak detection.", + ) + .option( + "--port ", + "A port number that `memory-leak-detector` should listen to.", + 3000, + ); + program.parse(); + + const cliOptions = program.opts(); const app = express(); app.use(cors()); @@ -16,8 +30,6 @@ function main() { app.post( "/detect_memory_leak", handleDetectMemoryLinkRequest({ - port: 9222, - host: "127.0.0.1", classes: [], writeSnapshot: false, }), @@ -25,18 +37,16 @@ function main() { app.post( "/detect_leaking_classes", checkLeakingClasses({ - port: 9222, - host: "127.0.0.1", allowedToLeak: ["App"], - classes: inventoryClasses(sourceDirectory), + classes: inventoryClasses(cliOptions.path), writeSnapshot: false, }), ); - app.post("/close", (_req, _res) => process.exit(0)) + app.post("/close", (_req, _res) => process.exit(0)); - app.listen(3000, () => { - console.log("Memory Leak Detector listening on port :3000"); + app.listen(cliOptions.port, () => { + console.log(`Memory Leak Detector listening on port :${cliOptions.port}`); }); } diff --git a/packages/test-app/package.json b/packages/test-app/package.json index 6f8093c..07d0cfd 100644 --- a/packages/test-app/package.json +++ b/packages/test-app/package.json @@ -23,7 +23,7 @@ "start": "ember serve", "test": "concurrently \"pnpm:lint\" \"pnpm:test:*\" --names \"lint,test:\"", "test:ember": "ember test", - "start:memory-leak-detector": "memory-leak-detector ./app/", + "start:memory-leak-detector": "memory-leak-detector --path ./app/", "test:memory-leak": "ember test --filter MEMORY_LEAK", "test:users": "ember test --filter Users", "test": "ember test --filter !MEMORY_LEAK" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 469b65f..8adbdf7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,6 +26,9 @@ importers: chrome-remote-interface: specifier: ^0.28.2 version: 0.28.2 + commander: + specifier: ^12.1.0 + version: 12.1.0 ember-cli-babel: specifier: ^7.22.1 version: 7.26.11 @@ -2478,6 +2481,10 @@ packages: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} + commander@12.1.0: + resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} + engines: {node: '>=18'} + commander@2.11.0: resolution: {integrity: sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==} @@ -10200,6 +10207,8 @@ snapshots: dependencies: delayed-stream: 1.0.0 + commander@12.1.0: {} + commander@2.11.0: {} commander@2.20.3: {}