Skip to content

Commit efb4929

Browse files
committed
Add leak tests and its runner.
1 parent b64f260 commit efb4929

File tree

2 files changed

+101
-0
lines changed

2 files changed

+101
-0
lines changed

__leaks__/index.test.js

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/* index.test.js - Leak tests for React store context hooks functionality.
2+
* Copyright (c) 2019 - 2021 Richard Huang <rickypc@users.noreply.github.com>
3+
*
4+
* This Source Code Form is subject to the terms of the Mozilla Public
5+
* License, v. 2.0. If a copy of the MPL was not distributed with this
6+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
7+
8+
import proxyquire from 'proxyquire';
9+
import run from './runner.js';
10+
11+
const Component = () => null;
12+
const context = {
13+
get() {},
14+
remove() {},
15+
set() {},
16+
sets() {},
17+
};
18+
19+
const {
20+
isEmpty,
21+
useStore,
22+
useStores,
23+
withStore,
24+
} = proxyquire('../src/index.js', {
25+
react: {
26+
createContext: () => context,
27+
useCallback: (cb) => cb,
28+
useContext: () => context,
29+
useState() {},
30+
},
31+
});
32+
33+
(async () => {
34+
await run('isEmpty', () => isEmpty(false));
35+
await run('useStore', () => useStore('key', 'default'));
36+
await run('useStores', () => useStores({ key1: 'value1' }));
37+
await run('withStore', () => withStore(Component));
38+
})();

__leaks__/runner.js

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/* runner.js - Leak test runner for React store context hooks.
2+
* Copyright (c) 2019 - 2021 Richard Huang <rickypc@users.noreply.github.com>
3+
*
4+
* This Source Code Form is subject to the terms of the Mozilla Public
5+
* License, v. 2.0. If a copy of the MPL was not distributed with this
6+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
7+
8+
import memwatch from '@airbnb/node-memwatch';
9+
import tape from 'tape';
10+
11+
const concurrency = 50;
12+
const concurrents = [...Array(concurrency).keys()];
13+
const exiter = () => process.exit(0);
14+
const iterations = 5;
15+
const log = (t, message) => message.split('\n')
16+
.forEach((line) => t.emit('result', line));
17+
const max = concurrency * iterations;
18+
19+
process.once('SIGINT', exiter);
20+
process.once('SIGTERM', exiter);
21+
22+
export default (name, fn) => new Promise((resolve) => tape(name, async (t) => {
23+
t.plan(1);
24+
let error = null;
25+
const catcher = (err = {}) => {
26+
if (err.name === 'MaxListenersExceededWarning') {
27+
error = error || err;
28+
}
29+
};
30+
const runner = async (count, done) => {
31+
if (error || count === iterations) {
32+
done();
33+
return;
34+
}
35+
await fn();
36+
// Enough time to take a deep breath.
37+
setTimeout(() => runner(count + 1, done), 25);
38+
};
39+
40+
const heapDiff = new memwatch.HeapDiff();
41+
process.on('warning', catcher);
42+
await Promise.all(concurrents.map(() => new Promise((done) => runner(0, done))));
43+
process.removeListener('warning', catcher);
44+
45+
if (error) {
46+
log(t, error.stack);
47+
t.fail('event listener leak detected');
48+
resolve();
49+
return;
50+
}
51+
52+
const diff = heapDiff.end();
53+
const leaks = diff.change.details.filter((change) => (change['+'] >= max));
54+
55+
if (leaks.length) {
56+
log(t, JSON.stringify(diff, null, 2));
57+
t.fail('memory leak detected');
58+
} else {
59+
t.pass('no memory leak detected');
60+
}
61+
62+
resolve();
63+
}));

0 commit comments

Comments
 (0)