Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@azl397985856
Last active January 1, 2024 07:05
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save azl397985856/5d5617ef617df23c8aea1fa1481e4d87 to your computer and use it in GitHub Desktop.
Save azl397985856/5d5617ef617df23c8aea1fa1481e4d87 to your computer and use it in GitHub Desktop.
Automatically detect memory leaks with Puppeteer
// from : https://github.com/chrisguttandin/standardized-audio-context/blob/master/test/integration/memory.js
const MemoryFileSystem = require('memory-fs'); // eslint-disable-line no-undef
const puppeteer = require('puppeteer'); // eslint-disable-line no-undef
const webpack = require('webpack'); // eslint-disable-line no-undef
// eslint-disable-next-line padding-line-between-statements
const compileBundle = () => {
return new Promise((resolve, reject) => {
const memoryFileSystem = new MemoryFileSystem();
const compiler = webpack({
entry: {
bundle: './build/es2018/module.js'
},
mode: 'development',
output: {
filename: '[name].js',
libraryTarget: 'umd',
path: '/'
}
});
compiler.outputFileSystem = memoryFileSystem;
compiler.run((err, stats) => {
if (stats.hasErrors() || stats.hasWarnings()) {
reject(new Error(stats.toString({ errorDetails: true, warnings: true })));
} else {
resolve(memoryFileSystem.readFileSync('/bundle.js', 'utf-8')); // eslint-disable-line no-sync
}
});
});
};
const countObjects = async (page) => {
const prototypeHandle = await page.evaluateHandle(() => Object.prototype);
const objectsHandle = await page.queryObjects(prototypeHandle);
const numberOfObjects = await page.evaluate((instances) => instances.length, objectsHandle);
await Promise.all([
prototypeHandle.dispose(),
objectsHandle.dispose()
]);
return numberOfObjects;
};
describe('module', () => {
let browser;
let context;
let page;
after(() => browser.close());
afterEach(() => context.close());
before(async function () {
this.timeout(10000);
browser = await puppeteer.launch();
});
beforeEach(async function () {
this.timeout(10000);
context = await browser.createIncognitoBrowserContext();
page = await context.newPage();
await page.evaluate(await compileBundle());
await page.evaluate(async () => {
audioContext = new AudioContext(); // eslint-disable-line no-undef
await new Promise((resolve) => setTimeout(resolve, 1000));
});
});
describe('with a GainNode', () => {
it('should collect unconnected GainNodes', async function () {
this.timeout(10000);
const run = (numberOfIterations) => {
for (let i = 0; i < numberOfIterations; i += 1) {
new GainNode(audioContext); // eslint-disable-line no-undef
}
};
const numberOfObjects = await countObjects(page);
await page.evaluate(run, 1000);
expect(await countObjects(page)).to.equal(numberOfObjects);
});
it('should collect connected GainNodes', async function () {
this.timeout(10000);
const run = (numberOfIterations) => {
for (let i = 0; i < numberOfIterations; i += 1) {
const gainNode = new GainNode(audioContext); // eslint-disable-line no-undef
gainNode.connect(audioContext.destination); // eslint-disable-line no-undef
}
};
const numberOfObjects = await countObjects(page);
await page.evaluate(run, 1000);
expect(await countObjects(page)).to.equal(numberOfObjects);
});
it('should collect disconnected GainNodes', async function () {
this.timeout(10000);
const run = (numberOfIterations) => {
for (let i = 0; i < numberOfIterations; i += 1) {
const gainNode = new GainNode(audioContext); // eslint-disable-line no-undef
gainNode.connect(audioContext.destination); // eslint-disable-line no-undef
gainNode.disconnect(audioContext.destination); // eslint-disable-line no-undef
}
};
const numberOfObjects = await countObjects(page);
await page.evaluate(run, 1000);
expect(await countObjects(page)).to.equal(numberOfObjects);
});
});
describe('with an AudioBufferSourceNode', () => {
it('should collect unconnected AudioBufferSourceNodes', async function () {
this.timeout(10000);
const run = (numberOfIterations) => {
for (let i = 0; i < numberOfIterations; i += 1) {
new AudioBufferSourceNode(
audioContext, // eslint-disable-line no-undef
{ buffer: new AudioBuffer({ length: 1, sampleRate: audioContext.sampleRate }) } // eslint-disable-line no-undef
);
}
};
// Run the test once because the first run will trigger some memoizations.
await page.evaluate(run, 1);
const numberOfObjects = await countObjects(page);
await page.evaluate(run, 1000);
expect(await countObjects(page)).to.equal(numberOfObjects);
});
it('should collect connected AudioBufferSourceNodes', async function () {
this.timeout(10000);
const run = (numberOfIterations) => {
for (let i = 0; i < numberOfIterations; i += 1) {
const audioBufferSourceNode = new AudioBufferSourceNode(
audioContext, // eslint-disable-line no-undef
{ buffer: new AudioBuffer({ length: 1, sampleRate: audioContext.sampleRate }) } // eslint-disable-line no-undef
);
audioBufferSourceNode.connect(audioContext.destination); // eslint-disable-line no-undef
}
};
// Run the test once because the first run will trigger some memoizations.
await page.evaluate(run, 1);
const numberOfObjects = await countObjects(page);
await page.evaluate(run, 1000);
expect(await countObjects(page)).to.equal(numberOfObjects);
});
// @todo Run a test with started AudioBufferSourceNodes.
it('should collect disconnected AudioBufferSourceNodes', async function () {
this.timeout(10000);
const run = (numberOfIterations) => {
for (let i = 0; i < numberOfIterations; i += 1) {
const audioBufferSourceNode = new AudioBufferSourceNode(
audioContext, // eslint-disable-line no-undef
{ buffer: new AudioBuffer({ length: 1, sampleRate: audioContext.sampleRate }) } // eslint-disable-line no-undef
);
audioBufferSourceNode.connect(audioContext.destination); // eslint-disable-line no-undef
audioBufferSourceNode.disconnect(audioContext.destination); // eslint-disable-line no-undef
}
};
// Run the test once because the first run will trigger some memoizations.
await page.evaluate(run, 1);
const numberOfObjects = await countObjects(page);
await page.evaluate(run, 1000);
expect(await countObjects(page)).to.equal(numberOfObjects);
});
});
});
@mfdeveloper
Copy link

mfdeveloper commented Jun 24, 2021

Hello! Thank you so much for share this!

Unfortunately, I'm always got the error Can't resolve './bundle/es2018/module.js.
Please, could you explain how do you generate this file? I'm trying an approach to test AudioContext with jest + puppeteer without success 😢

I tried generate a bundle.js from my app and use it as entry, but instead of the error above I got: Evaluation failed: ReferenceError: tslib_1 is not defined error

@azl397985856
Copy link
Author

azl397985856 commented Jun 25, 2021

@mfdeveloper the code comes from here : https://github.com/chrisguttandin/standardized-audio-context/blob/v20.1.3/test/integration/memory.js

So I think you can test the code above by running it

@mfdeveloper
Copy link

mfdeveloper commented Jun 25, 2021

@mfdeveloper the code comes from here : https://github.com/chrisguttandin/standardized-audio-context/blob/v20.1.3/test/integration/memory.js

So I think you can test the code above by running it

Ok! Thanks a lot for your reply

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment