@memlab/core 1.0.9 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/parser/HeapParser.test.js +2 -2
- package/dist/__tests__/parser/NodeHeap.test.js +5 -5
- package/dist/__tests__/parser/StringNode.test.js +1 -1
- package/dist/__tests__/parser/traverse/HeapNodeTraverse.test.js +2 -2
- package/dist/lib/Config.d.ts +1 -0
- package/dist/lib/Config.js +2 -0
- package/dist/lib/Console.d.ts +1 -0
- package/dist/lib/Console.js +3 -0
- package/dist/lib/FileManager.d.ts +2 -0
- package/dist/lib/FileManager.js +6 -0
- package/dist/lib/NodeHeap.d.ts +64 -5
- package/dist/lib/NodeHeap.js +64 -3
- package/dist/lib/Types.d.ts +498 -2
- package/dist/lib/Types.js +1 -0
- package/dist/lib/Utils.js +1 -0
- package/package.json +2 -2
|
@@ -36,7 +36,7 @@ test('Capture inserted object', () => __awaiter(void 0, void 0, void 0, function
|
|
|
36
36
|
}
|
|
37
37
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
38
38
|
const injected = new TestObject();
|
|
39
|
-
const heap = yield (0, NodeHeap_1.
|
|
39
|
+
const heap = yield (0, NodeHeap_1.getNodeInnocentHeap)();
|
|
40
40
|
expect(heap.hasObjectWithClassName('TestObject')).toBe(true);
|
|
41
41
|
}), timeout);
|
|
42
42
|
test('Does not capture transcient object', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -49,6 +49,6 @@ test('Does not capture transcient object', () => __awaiter(void 0, void 0, void
|
|
|
49
49
|
let injected = new TestObject();
|
|
50
50
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
51
51
|
injected = null;
|
|
52
|
-
const heap = yield (0, NodeHeap_1.
|
|
52
|
+
const heap = yield (0, NodeHeap_1.getNodeInnocentHeap)();
|
|
53
53
|
expect(heap.hasObjectWithClassName('TestObject')).toBe(false);
|
|
54
54
|
}), timeout);
|
|
@@ -30,7 +30,7 @@ const timeout = 5 * 60 * 1000;
|
|
|
30
30
|
test('Capture current node heap snapshot', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
31
31
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
32
32
|
const object = { 'memlab-test-heap-property': 'memlab-test-heap-value' };
|
|
33
|
-
const heap = yield (0, NodeHeap_1.
|
|
33
|
+
const heap = yield (0, NodeHeap_1.getNodeInnocentHeap)();
|
|
34
34
|
expect(heap.hasObjectWithPropertyName('memlab-test-heap-property')).toBe(true);
|
|
35
35
|
}), timeout);
|
|
36
36
|
test('Nullified Object should not exist in heap', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -39,7 +39,7 @@ test('Nullified Object should not exist in heap', () => __awaiter(void 0, void 0
|
|
|
39
39
|
};
|
|
40
40
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
41
41
|
object = null;
|
|
42
|
-
const heap = yield (0, NodeHeap_1.
|
|
42
|
+
const heap = yield (0, NodeHeap_1.getNodeInnocentHeap)();
|
|
43
43
|
expect(heap.hasObjectWithPropertyName('memlab-test-heap-property')).toBe(false);
|
|
44
44
|
}), timeout);
|
|
45
45
|
test('Strongly referenced object should exist in heap', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -59,7 +59,7 @@ test('Strongly referenced object should exist in heap', () => __awaiter(void 0,
|
|
|
59
59
|
}
|
|
60
60
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
61
61
|
const object = buildTest();
|
|
62
|
-
const heap = yield (0, NodeHeap_1.
|
|
62
|
+
const heap = yield (0, NodeHeap_1.getNodeInnocentHeap)();
|
|
63
63
|
expect(heap.hasObjectWithClassName('TestClass1')).toBe(true);
|
|
64
64
|
expect(heap.hasObjectWithClassName('TestClass2')).toBe(true);
|
|
65
65
|
}), timeout);
|
|
@@ -80,7 +80,7 @@ test('Weakly referenced object should not exist in heap', () => __awaiter(void 0
|
|
|
80
80
|
}
|
|
81
81
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
82
82
|
const object = buildTest();
|
|
83
|
-
const heap = yield (0, NodeHeap_1.
|
|
83
|
+
const heap = yield (0, NodeHeap_1.getNodeInnocentHeap)();
|
|
84
84
|
expect(heap.hasObjectWithClassName('TestClass3')).toBe(true);
|
|
85
85
|
expect(heap.hasObjectWithClassName('TestClass4')).toBe(false);
|
|
86
86
|
}), timeout);
|
|
@@ -90,7 +90,7 @@ test('Check annotated objects', () => __awaiter(void 0, void 0, void 0, function
|
|
|
90
90
|
(0, NodeHeap_1.tagObject)(o1, 'memlab-mark-1');
|
|
91
91
|
(0, NodeHeap_1.tagObject)(o2, 'memlab-mark-2');
|
|
92
92
|
o2 = null;
|
|
93
|
-
const heap = yield (0, NodeHeap_1.
|
|
93
|
+
const heap = yield (0, NodeHeap_1.getNodeInnocentHeap)();
|
|
94
94
|
expect(heap.hasObjectWithTag('memlab-mark-1')).toBe(true);
|
|
95
95
|
expect(heap.hasObjectWithTag('memlab-mark-2')).toBe(false);
|
|
96
96
|
}), timeout);
|
|
@@ -41,7 +41,7 @@ test('String heap object APIs work', () => __awaiter(void 0, void 0, void 0, fun
|
|
|
41
41
|
injected.complexConcatString += 'value_';
|
|
42
42
|
injected.complexConcatString += 123;
|
|
43
43
|
injected.complexConcatString += '_suffix';
|
|
44
|
-
const heap = yield (0, NodeHeap_1.
|
|
44
|
+
const heap = yield (0, NodeHeap_1.getNodeInnocentHeap)();
|
|
45
45
|
const testObject = heap.getAnyObjectWithClassName('TestObject');
|
|
46
46
|
expect(testObject).not.toBe(null);
|
|
47
47
|
// testObject.originalString === 'test'
|
|
@@ -74,7 +74,7 @@ test('Check getReference and getReferenceNode', () => __awaiter(void 0, void 0,
|
|
|
74
74
|
});
|
|
75
75
|
return detected;
|
|
76
76
|
};
|
|
77
|
-
const heap = yield (0, NodeHeap_1.
|
|
77
|
+
const heap = yield (0, NodeHeap_1.getNodeInnocentHeap)();
|
|
78
78
|
expect(checker(heap)).toBe(true);
|
|
79
79
|
}), timeout);
|
|
80
80
|
test('Check getReferrers and getReferrerNodes', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -135,6 +135,6 @@ test('Check getReferrers and getReferrerNodes', () => __awaiter(void 0, void 0,
|
|
|
135
135
|
}
|
|
136
136
|
return true;
|
|
137
137
|
};
|
|
138
|
-
const heap = yield (0, NodeHeap_1.
|
|
138
|
+
const heap = yield (0, NodeHeap_1.getNodeInnocentHeap)();
|
|
139
139
|
expect(checker(heap)).toBe(true);
|
|
140
140
|
}), timeout);
|
package/dist/lib/Config.d.ts
CHANGED
|
@@ -139,6 +139,7 @@ export declare class MemLabConfig {
|
|
|
139
139
|
waitAfterOperation: number;
|
|
140
140
|
waitAfterScrolling: number;
|
|
141
141
|
waitAfterTyping: number;
|
|
142
|
+
waitForNetworkInDefaultScenario: number;
|
|
142
143
|
stressTestRepeat: number;
|
|
143
144
|
avoidLeakWithoutDetachedElements: boolean;
|
|
144
145
|
hideBrowserLeak: boolean;
|
package/dist/lib/Config.js
CHANGED
|
@@ -192,6 +192,8 @@ class MemLabConfig {
|
|
|
192
192
|
this.waitAfterScrolling = 5000;
|
|
193
193
|
// extra delay after typing
|
|
194
194
|
this.waitAfterTyping = 1000;
|
|
195
|
+
// page load checker: default wait for network idle in scenario test
|
|
196
|
+
this.waitForNetworkInDefaultScenario = 10000;
|
|
195
197
|
// default repeat for stress testing
|
|
196
198
|
this.stressTestRepeat = 5;
|
|
197
199
|
// only show leaks with detached HTML elements
|
package/dist/lib/Console.d.ts
CHANGED
package/dist/lib/Console.js
CHANGED
|
@@ -43,6 +43,8 @@ export declare class FileManager {
|
|
|
43
43
|
getAllFilesInDir(dir: string): string[];
|
|
44
44
|
getDataOutDir(options?: FileOption): string;
|
|
45
45
|
getCoreProjectBaseDir(): string;
|
|
46
|
+
getMonoRepoDir(): string;
|
|
47
|
+
getDocDir(): string;
|
|
46
48
|
getReportOutDir(options?: FileOption): string;
|
|
47
49
|
getPreviewReportDir(options?: FileOption): string;
|
|
48
50
|
getLeakSummaryFile(options?: FileOption): string;
|
package/dist/lib/FileManager.js
CHANGED
|
@@ -133,6 +133,12 @@ class FileManager {
|
|
|
133
133
|
getCoreProjectBaseDir() {
|
|
134
134
|
return path_1.default.join(__dirname, '..', '..');
|
|
135
135
|
}
|
|
136
|
+
getMonoRepoDir() {
|
|
137
|
+
return path_1.default.join(this.getCoreProjectBaseDir(), '..', '..');
|
|
138
|
+
}
|
|
139
|
+
getDocDir() {
|
|
140
|
+
return path_1.default.join(this.getMonoRepoDir(), 'website', 'docs');
|
|
141
|
+
}
|
|
136
142
|
getReportOutDir(options = {}) {
|
|
137
143
|
return path_1.default.join(this.getPersistDataDir(options), 'reports');
|
|
138
144
|
}
|
package/dist/lib/NodeHeap.d.ts
CHANGED
|
@@ -7,10 +7,69 @@
|
|
|
7
7
|
* @emails oncall+ws_labs
|
|
8
8
|
* @format
|
|
9
9
|
*/
|
|
10
|
-
import type {
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
import type { IHeapSnapshot } from './Types';
|
|
11
|
+
/**
|
|
12
|
+
* Tags a string marker to an object instance, which can later be checked by
|
|
13
|
+
* {@link hasObjectWithTag}. This API does not modify the object instance in
|
|
14
|
+
* any way (e.g., no additional or hidden properties added to the tagged
|
|
15
|
+
* object).
|
|
16
|
+
*
|
|
17
|
+
* @param o specify the object instance you want to tag, you cannot tag a
|
|
18
|
+
* [primitive](https://developer.mozilla.org/en-US/docs/Glossary/Primitive).
|
|
19
|
+
* @param tag marker name to tag on the object instance
|
|
20
|
+
* @returns returns the tagged object instance (same reference as
|
|
21
|
+
* the input argument `o`)
|
|
22
|
+
* * **Examples**:
|
|
23
|
+
* ```typescript
|
|
24
|
+
* import type {IHeapSnapshot, AnyValue} from '@memlab/core';
|
|
25
|
+
* import {config, getNodeInnocentHeap, tagObject} from '@memlab/core';
|
|
26
|
+
*
|
|
27
|
+
* test('memory test', async () => {
|
|
28
|
+
* config.muteConsole = true;
|
|
29
|
+
* const o1: AnyValue = {};
|
|
30
|
+
* let o2: AnyValue = {};
|
|
31
|
+
*
|
|
32
|
+
* // tag o1 with marker: "memlab-mark-1", does not modify o1 in any way
|
|
33
|
+
* tagObject(o1, 'memlab-mark-1');
|
|
34
|
+
* // tag o2 with marker: "memlab-mark-2", does not modify o2 in any way
|
|
35
|
+
* tagObject(o2, 'memlab-mark-2');
|
|
36
|
+
*
|
|
37
|
+
* o2 = null;
|
|
38
|
+
*
|
|
39
|
+
* const heap: IHeapSnapshot = await getNodeInnocentHeap();
|
|
40
|
+
*
|
|
41
|
+
* // expect object with marker "memlab-mark-1" exists
|
|
42
|
+
* expect(heap.hasObjectWithTag('memlab-mark-1')).toBe(true);
|
|
43
|
+
*
|
|
44
|
+
* // expect object with marker "memlab-mark-2" can be GCed
|
|
45
|
+
* expect(heap.hasObjectWithTag('memlab-mark-2')).toBe(false);
|
|
46
|
+
*
|
|
47
|
+
* }, 30000);
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export declare function tagObject<T extends object>(o: T, tag: string): T;
|
|
13
51
|
export declare function dumpNodeHeapSnapshot(): string;
|
|
14
|
-
|
|
15
|
-
|
|
52
|
+
/**
|
|
53
|
+
* Take a heap snapshot of the current program state
|
|
54
|
+
* and parse it as {@link IHeapSnapshot}. Notice that
|
|
55
|
+
* this API does not calculate some heap analysis meta data
|
|
56
|
+
* for heap analysis. But this also means faster heap parsing.
|
|
57
|
+
*
|
|
58
|
+
* @returns heap representation without heap analysis meta data.
|
|
59
|
+
*
|
|
60
|
+
* If you need to get the heap snapshot with heap analysis meta data
|
|
61
|
+
* use {@link dumpNodeHeapSnapshot} and {@link getHeapFromFile},
|
|
62
|
+
* for example:
|
|
63
|
+
* ```typescript
|
|
64
|
+
* import type {IHeapSnapshot} from '@memlab/core';
|
|
65
|
+
* import {dumpNodeHeapSnapshot} from '@memlab/core';
|
|
66
|
+
* import {getHeapFromFile} from '@memlab/heap-analysis';
|
|
67
|
+
*
|
|
68
|
+
* (async function () {
|
|
69
|
+
* const heapFile = dumpNodeHeapSnapshot();
|
|
70
|
+
* const heap: IHeapSnapshot = await getHeapFromFile(heapFile);
|
|
71
|
+
* })();
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
export declare function getNodeInnocentHeap(): Promise<IHeapSnapshot>;
|
|
16
75
|
//# sourceMappingURL=NodeHeap.d.ts.map
|
package/dist/lib/NodeHeap.js
CHANGED
|
@@ -21,7 +21,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
21
21
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
22
22
|
};
|
|
23
23
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
-
exports.
|
|
24
|
+
exports.getNodeInnocentHeap = exports.dumpNodeHeapSnapshot = exports.tagObject = void 0;
|
|
25
25
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
26
26
|
const path_1 = __importDefault(require("path"));
|
|
27
27
|
const v8_1 = __importDefault(require("v8"));
|
|
@@ -33,6 +33,45 @@ class MemLabTaggedStore {
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
const store = new MemLabTaggedStore();
|
|
36
|
+
/**
|
|
37
|
+
* Tags a string marker to an object instance, which can later be checked by
|
|
38
|
+
* {@link hasObjectWithTag}. This API does not modify the object instance in
|
|
39
|
+
* any way (e.g., no additional or hidden properties added to the tagged
|
|
40
|
+
* object).
|
|
41
|
+
*
|
|
42
|
+
* @param o specify the object instance you want to tag, you cannot tag a
|
|
43
|
+
* [primitive](https://developer.mozilla.org/en-US/docs/Glossary/Primitive).
|
|
44
|
+
* @param tag marker name to tag on the object instance
|
|
45
|
+
* @returns returns the tagged object instance (same reference as
|
|
46
|
+
* the input argument `o`)
|
|
47
|
+
* * **Examples**:
|
|
48
|
+
* ```typescript
|
|
49
|
+
* import type {IHeapSnapshot, AnyValue} from '@memlab/core';
|
|
50
|
+
* import {config, getNodeInnocentHeap, tagObject} from '@memlab/core';
|
|
51
|
+
*
|
|
52
|
+
* test('memory test', async () => {
|
|
53
|
+
* config.muteConsole = true;
|
|
54
|
+
* const o1: AnyValue = {};
|
|
55
|
+
* let o2: AnyValue = {};
|
|
56
|
+
*
|
|
57
|
+
* // tag o1 with marker: "memlab-mark-1", does not modify o1 in any way
|
|
58
|
+
* tagObject(o1, 'memlab-mark-1');
|
|
59
|
+
* // tag o2 with marker: "memlab-mark-2", does not modify o2 in any way
|
|
60
|
+
* tagObject(o2, 'memlab-mark-2');
|
|
61
|
+
*
|
|
62
|
+
* o2 = null;
|
|
63
|
+
*
|
|
64
|
+
* const heap: IHeapSnapshot = await getNodeInnocentHeap();
|
|
65
|
+
*
|
|
66
|
+
* // expect object with marker "memlab-mark-1" exists
|
|
67
|
+
* expect(heap.hasObjectWithTag('memlab-mark-1')).toBe(true);
|
|
68
|
+
*
|
|
69
|
+
* // expect object with marker "memlab-mark-2" can be GCed
|
|
70
|
+
* expect(heap.hasObjectWithTag('memlab-mark-2')).toBe(false);
|
|
71
|
+
*
|
|
72
|
+
* }, 30000);
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
36
75
|
function tagObject(o, tag) {
|
|
37
76
|
if (!store.taggedObjects[tag]) {
|
|
38
77
|
store.taggedObjects[tag] = new WeakSet();
|
|
@@ -47,7 +86,29 @@ function dumpNodeHeapSnapshot() {
|
|
|
47
86
|
return file;
|
|
48
87
|
}
|
|
49
88
|
exports.dumpNodeHeapSnapshot = dumpNodeHeapSnapshot;
|
|
50
|
-
|
|
89
|
+
/**
|
|
90
|
+
* Take a heap snapshot of the current program state
|
|
91
|
+
* and parse it as {@link IHeapSnapshot}. Notice that
|
|
92
|
+
* this API does not calculate some heap analysis meta data
|
|
93
|
+
* for heap analysis. But this also means faster heap parsing.
|
|
94
|
+
*
|
|
95
|
+
* @returns heap representation without heap analysis meta data.
|
|
96
|
+
*
|
|
97
|
+
* If you need to get the heap snapshot with heap analysis meta data
|
|
98
|
+
* use {@link dumpNodeHeapSnapshot} and {@link getHeapFromFile},
|
|
99
|
+
* for example:
|
|
100
|
+
* ```typescript
|
|
101
|
+
* import type {IHeapSnapshot} from '@memlab/core';
|
|
102
|
+
* import {dumpNodeHeapSnapshot} from '@memlab/core';
|
|
103
|
+
* import {getHeapFromFile} from '@memlab/heap-analysis';
|
|
104
|
+
*
|
|
105
|
+
* (async function () {
|
|
106
|
+
* const heapFile = dumpNodeHeapSnapshot();
|
|
107
|
+
* const heap: IHeapSnapshot = await getHeapFromFile(heapFile);
|
|
108
|
+
* })();
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
function getNodeInnocentHeap() {
|
|
51
112
|
return __awaiter(this, void 0, void 0, function* () {
|
|
52
113
|
const file = dumpNodeHeapSnapshot();
|
|
53
114
|
const snapshot = yield Utils_1.default.getSnapshotFromFile(file, {
|
|
@@ -59,4 +120,4 @@ function getCurrentNodeHeap() {
|
|
|
59
120
|
return snapshot;
|
|
60
121
|
});
|
|
61
122
|
}
|
|
62
|
-
exports.
|
|
123
|
+
exports.getNodeInnocentHeap = getNodeInnocentHeap;
|
package/dist/lib/Types.d.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { ParsedArgs } from 'minimist';
|
|
11
11
|
import type { LaunchOptions, Page } from 'puppeteer';
|
|
12
12
|
import type { ErrorHandling, MemLabConfig } from './Config';
|
|
13
|
+
/** @internal */
|
|
13
14
|
export declare type AnyValue = any;
|
|
14
15
|
/** @internal */
|
|
15
16
|
export declare type RecordValue = string | number | boolean | null | RecordValue[] | {
|
|
@@ -141,23 +142,319 @@ export declare type QuickExperiment = {
|
|
|
141
142
|
experiment: string;
|
|
142
143
|
group: string;
|
|
143
144
|
};
|
|
145
|
+
/**
|
|
146
|
+
* The type for defining custom leak-filtering logic.
|
|
147
|
+
* * **Examples**:
|
|
148
|
+
* ```typescript
|
|
149
|
+
* const scenario = {
|
|
150
|
+
*
|
|
151
|
+
* };
|
|
152
|
+
*
|
|
153
|
+
* let map = Object.create(null);
|
|
154
|
+
* const beforeLeakFilter = (snapshot: IHeapSnapshot, _leakedNodeIds: HeapNodeIdSet): void => {
|
|
155
|
+
* map = initializeMapUsingSnapshot(snapshot);
|
|
156
|
+
* };
|
|
157
|
+
*
|
|
158
|
+
* // duplicated string with size > 1KB as memory leak
|
|
159
|
+
* const leakFilter = (node: IHeapNode): boolean => {
|
|
160
|
+
* if (node.type !== 'string' || node.retainedSize < 1000) {
|
|
161
|
+
* return false;
|
|
162
|
+
* }
|
|
163
|
+
* const str = utils.getStringNodeValue(node);
|
|
164
|
+
* return map[str] > 1;
|
|
165
|
+
* };
|
|
166
|
+
*
|
|
167
|
+
* export default {beforeLeakFilter, leakFilter};
|
|
168
|
+
* ```
|
|
169
|
+
*/
|
|
144
170
|
export interface ILeakFilter {
|
|
145
171
|
beforeLeakFilter?: InitLeakFilterCallback;
|
|
146
172
|
leakFilter: LeakFilterCallback;
|
|
147
173
|
}
|
|
174
|
+
/**
|
|
175
|
+
* Lifecycle function callback that is invoked initially once before calling any
|
|
176
|
+
* leak filter function.
|
|
177
|
+
*
|
|
178
|
+
* @param snaphost - heap snapshot see {@link IHeapSnapshot}
|
|
179
|
+
* @param leakedNodeIds - the set of leaked object (node) ids.
|
|
180
|
+
*/
|
|
148
181
|
export declare type InitLeakFilterCallback = (snapshot: IHeapSnapshot, leakedNodeIds: HeapNodeIdSet) => void;
|
|
182
|
+
/**
|
|
183
|
+
* Callback that can be used to define a logic to filter the
|
|
184
|
+
* leaked objects. The callback is only called for every node
|
|
185
|
+
* allocated but not released from the target interaction
|
|
186
|
+
* in the heap snapshot.
|
|
187
|
+
*
|
|
188
|
+
* @param node - the node that is kept alive in the memory in the heap snapshot
|
|
189
|
+
* @param snapshot - the snapshot of target interaction
|
|
190
|
+
* @param leakedNodeIds - the set of leaked node ids
|
|
191
|
+
*
|
|
192
|
+
* @returns the value indicating whether the given node in the snapshot
|
|
193
|
+
* should be considered as leaked.
|
|
194
|
+
* * **Examples**:
|
|
195
|
+
* ```javascript
|
|
196
|
+
* // any node in the heap snapshot that is greater than 1MB
|
|
197
|
+
* function leakFilter(node, _snapshot, _leakedNodeIds) {
|
|
198
|
+
* return node.retainedSize > 1000000;
|
|
199
|
+
* };
|
|
200
|
+
* ```
|
|
201
|
+
*/
|
|
149
202
|
export declare type LeakFilterCallback = (node: IHeapNode, snapshot: IHeapSnapshot, leakedNodeIds: HeapNodeIdSet) => boolean;
|
|
203
|
+
/**
|
|
204
|
+
* The callback defines browser interactions which are
|
|
205
|
+
* used by memlab to interact with the web app under test.
|
|
206
|
+
*/
|
|
150
207
|
export declare type InteractionsCallback = (page: Page, args?: OperationArgs) => Promise<void>;
|
|
208
|
+
/**
|
|
209
|
+
* Test scenario specifies how you want a E2E test to interact with a web browser.
|
|
210
|
+
* The test scenario can be saved as a `.js` file and passed to the `memlab
|
|
211
|
+
* run --scenario` command:
|
|
212
|
+
* ```javascript
|
|
213
|
+
* // save as test.js and use in terminal:
|
|
214
|
+
* // $ memlab run --scenario test.js
|
|
215
|
+
*
|
|
216
|
+
* module.exports = {
|
|
217
|
+
* url: () => 'https://www.npmjs.com/',
|
|
218
|
+
* action: async () => ... ,
|
|
219
|
+
* back: async () => ... ,
|
|
220
|
+
* };
|
|
221
|
+
* ```
|
|
222
|
+
*
|
|
223
|
+
* The test scenario instance can also be passed to the
|
|
224
|
+
* [`run` API](../modules/api_src#run) exported by `@memlab/api`.
|
|
225
|
+
* ```typescript
|
|
226
|
+
* const {run} = require('@memlab/api');
|
|
227
|
+
*
|
|
228
|
+
* (async function () {
|
|
229
|
+
* const scenario = {
|
|
230
|
+
* url: () => 'https://www.facebook.com',
|
|
231
|
+
* action: async () => ... ,
|
|
232
|
+
* back: async () => ... ,
|
|
233
|
+
* };
|
|
234
|
+
* const leaks = await run({scenario});
|
|
235
|
+
* })();
|
|
236
|
+
* ```
|
|
237
|
+
*/
|
|
151
238
|
export interface IScenario {
|
|
239
|
+
/** @internal */
|
|
152
240
|
name?: () => string;
|
|
241
|
+
/** @internal */
|
|
153
242
|
app?: () => string;
|
|
243
|
+
/**
|
|
244
|
+
* If the page you are running memlab against requires authentication or
|
|
245
|
+
* specific cookie(s) to be set, you can pass them as
|
|
246
|
+
* a list of <name, value> pairs.
|
|
247
|
+
* @returns cookie list
|
|
248
|
+
* * **Examples**:
|
|
249
|
+
* ```typescript
|
|
250
|
+
* const scenario = {
|
|
251
|
+
* url: () => 'https://www.facebook.com/',
|
|
252
|
+
* cookies: () => [
|
|
253
|
+
* {"name":"cm_j","value":"none"},
|
|
254
|
+
* {"name":"datr","value":"yJvIY..."},
|
|
255
|
+
* {"name":"c_user","value":"8917..."},
|
|
256
|
+
* {"name":"xs","value":"95:9WQ..."},
|
|
257
|
+
* // ...
|
|
258
|
+
* ],
|
|
259
|
+
* };
|
|
260
|
+
* ```
|
|
261
|
+
*/
|
|
154
262
|
cookies?: () => Cookies;
|
|
263
|
+
/**
|
|
264
|
+
* String value of the initial url of the page.
|
|
265
|
+
*
|
|
266
|
+
* @returns the string value of the initial url
|
|
267
|
+
* * **Examples**:
|
|
268
|
+
* ```typescript
|
|
269
|
+
* const scenario = {
|
|
270
|
+
* url: () => 'https://www.npmjs.com/',
|
|
271
|
+
* };
|
|
272
|
+
* ```
|
|
273
|
+
* If a test scenario only specifies the `url` callback (without the `action`
|
|
274
|
+
* callback), memlab will try to detect memory leaks from the initial page
|
|
275
|
+
* load. All objects allocated by the initial page load will be candidates
|
|
276
|
+
* for memory leak filtering.
|
|
277
|
+
*/
|
|
155
278
|
url: () => string;
|
|
279
|
+
/**
|
|
280
|
+
* `action` is the callback function that defines the interaction
|
|
281
|
+
* where you want to trigger memory leaks after the initial page load.
|
|
282
|
+
* All JS objects in browser allocated by the browser interactions triggered
|
|
283
|
+
* from the `action` callback will be candidates for memory leak filtering.
|
|
284
|
+
*
|
|
285
|
+
* * **Parameters**:
|
|
286
|
+
* * page: `Page` | the puppeteer [`Page`](https://pptr.dev/api/puppeteer.page)
|
|
287
|
+
* object, which provides APIs to interact with the web browser
|
|
288
|
+
*
|
|
289
|
+
* * **Examples**:
|
|
290
|
+
* ```typescript
|
|
291
|
+
* const scenario = {
|
|
292
|
+
* url: () => 'https://www.npmjs.com/',
|
|
293
|
+
* action: async (page) => {
|
|
294
|
+
* await page.click('a[href="/link"]');
|
|
295
|
+
* },
|
|
296
|
+
* back: async (page) => {
|
|
297
|
+
* await page.click('a[href="/back"]');
|
|
298
|
+
* },
|
|
299
|
+
* }
|
|
300
|
+
* ```
|
|
301
|
+
* Note: always clean up external puppeteer references to JS objects
|
|
302
|
+
* in the browser context.
|
|
303
|
+
* ```typescript
|
|
304
|
+
* const scenario = {
|
|
305
|
+
* url: () => 'https://www.npmjs.com/',
|
|
306
|
+
* action: async (page) => {
|
|
307
|
+
* const elements = await page.$x("//button[contains(., 'Text in Button')]");
|
|
308
|
+
* const [button] = elements;
|
|
309
|
+
* if (button) {
|
|
310
|
+
* await button.click();
|
|
311
|
+
* }
|
|
312
|
+
* // dispose external references to JS objects in browser context
|
|
313
|
+
* await promise.all(elements.map(e => e.dispose()));
|
|
314
|
+
* },
|
|
315
|
+
* back: async (page) => ... ,
|
|
316
|
+
* }
|
|
317
|
+
```
|
|
318
|
+
*/
|
|
156
319
|
action?: InteractionsCallback;
|
|
320
|
+
/**
|
|
321
|
+
* `back` is the callback function that specifies how memlab should
|
|
322
|
+
* back/revert the `action` callback. Think of it as an undo action.
|
|
323
|
+
*
|
|
324
|
+
* * **Examples**:
|
|
325
|
+
* ```typescript
|
|
326
|
+
* const scenario = {
|
|
327
|
+
* url: () => 'https://www.npmjs.com/',
|
|
328
|
+
* action: async (page) => {
|
|
329
|
+
* await page.click('a[href="/link"]');
|
|
330
|
+
* },
|
|
331
|
+
* back: async (page) => {
|
|
332
|
+
* await page.click('a[href="/back"]');
|
|
333
|
+
* },
|
|
334
|
+
* }
|
|
335
|
+
* ```
|
|
336
|
+
* Check out [this page](/docs/how-memlab-works) on why
|
|
337
|
+
* memlab needs to undo/revert the `action` callback.
|
|
338
|
+
*/
|
|
157
339
|
back?: InteractionsCallback;
|
|
340
|
+
/**
|
|
341
|
+
* Specifies how many **extra** `action` and `back` actions performed by memlab.
|
|
342
|
+
* * **Examples**:
|
|
343
|
+
* ```typescript
|
|
344
|
+
* module.exports = {
|
|
345
|
+
* url: () => ... ,
|
|
346
|
+
* action: async (page) => ... ,
|
|
347
|
+
* back: async (page) => ... ,
|
|
348
|
+
* // browser interaction: two additional [ action -> back ]
|
|
349
|
+
* // init-load -> action -> back -> action -> back -> action -> back
|
|
350
|
+
* repeat: () => 2,
|
|
351
|
+
* };
|
|
352
|
+
* ```
|
|
353
|
+
*/
|
|
158
354
|
repeat?: () => number;
|
|
355
|
+
/**
|
|
356
|
+
* Optional callback function that checks if the web page is loaded
|
|
357
|
+
* after for initial page loading and subsequent browser interactions.
|
|
358
|
+
*
|
|
359
|
+
* If this callback is not provided, memlab by default
|
|
360
|
+
* considers a navigation to be finished when there are no network
|
|
361
|
+
* connections for at least 500ms.
|
|
362
|
+
*
|
|
363
|
+
* * **Parameters**:
|
|
364
|
+
* * page: `Page` | the puppeteer [`Page`](https://pptr.dev/api/puppeteer.page)
|
|
365
|
+
* object, which provides APIs to interact with the web browser
|
|
366
|
+
* * **Returns**: a boolean value, if it returns `true`, memlab will consider
|
|
367
|
+
* the navigation completes, if it returns `false`, memlab will keep calling
|
|
368
|
+
* this callback until it returns `true`. This is an async callback, you can
|
|
369
|
+
* also `await` and returns `true` until some async logic is resolved.
|
|
370
|
+
* * **Examples**:
|
|
371
|
+
* ```typescript
|
|
372
|
+
* module.exports = {
|
|
373
|
+
* url: () => ... ,
|
|
374
|
+
* action: async (page) => ... ,
|
|
375
|
+
* back: async (page) => ... ,
|
|
376
|
+
* isPageLoaded: async (page) => {
|
|
377
|
+
* await page.waitForNavigation({
|
|
378
|
+
* // consider navigation to be finished when there are
|
|
379
|
+
* // no more than 2 network connections for at least 500 ms.
|
|
380
|
+
* waitUntil: 'networkidle2',
|
|
381
|
+
* // Maximum navigation time in milliseconds
|
|
382
|
+
* timeout: 5000,
|
|
383
|
+
* });
|
|
384
|
+
* return true;
|
|
385
|
+
* },
|
|
386
|
+
* };
|
|
387
|
+
* ```
|
|
388
|
+
*/
|
|
159
389
|
isPageLoaded?: CheckPageLoadCallback;
|
|
390
|
+
/**
|
|
391
|
+
* Lifecycle function callback that is invoked initially once before
|
|
392
|
+
* the subsequent `leakFilter` function calls. This callback could
|
|
393
|
+
* be used to initialize some data stores or to do some one-off
|
|
394
|
+
* preprocessings.
|
|
395
|
+
*
|
|
396
|
+
* * **Parameters**:
|
|
397
|
+
* * snapshot: `IHeapSnapshot` | the final heap snapshot taken after
|
|
398
|
+
* all browser interactions are done.
|
|
399
|
+
* Check out {@link IHeapSnapshot} for more APIs that queries the heap snapshot.
|
|
400
|
+
* * leakedNodeIds: `Set<number>` | the set of ids of all JS heap objects
|
|
401
|
+
* allocated by the `action` call but not released after the `back` call
|
|
402
|
+
* in browser.
|
|
403
|
+
*
|
|
404
|
+
* * **Examples**:
|
|
405
|
+
* ```typescript
|
|
406
|
+
* module.exports = {
|
|
407
|
+
* url: () => ... ,
|
|
408
|
+
* action: async (page) => ... ,
|
|
409
|
+
* back: async (page) => ... ,
|
|
410
|
+
* beforeLeakFilter: (snapshot, leakedNodeIds) {
|
|
411
|
+
* // initialize some data stores
|
|
412
|
+
* },
|
|
413
|
+
* };
|
|
414
|
+
* ```
|
|
415
|
+
*/
|
|
160
416
|
beforeLeakFilter?: InitLeakFilterCallback;
|
|
417
|
+
/**
|
|
418
|
+
* This callback that defines how you want to filter out the
|
|
419
|
+
* leaked objects. The callback is called for every node (JS heap
|
|
420
|
+
* object in browser) allocated by the `action` callback, but not
|
|
421
|
+
* released after the `back` callback. Those objects could be caches
|
|
422
|
+
* that are retained in memory on purpose, or they are memory leaks.
|
|
423
|
+
*
|
|
424
|
+
* This optional callback allows you to define your own algorithm
|
|
425
|
+
* to cherry pick memory leaks for specific JS program under test.
|
|
426
|
+
*
|
|
427
|
+
* If this optional callback is not defined, memlab will use its
|
|
428
|
+
* built-in leak filter, which considers detached DOM elements
|
|
429
|
+
* and unmounted Fiber nodes (detached from React Fiber tree) as
|
|
430
|
+
* memory leaks.
|
|
431
|
+
*
|
|
432
|
+
* * **Parameters**:
|
|
433
|
+
* * node: `IHeapNode` | one of the heap object allocated but not released.
|
|
434
|
+
* * snapshot: `IHeapSnapshot` | the final heap snapshot taken after
|
|
435
|
+
* all browser interactions are done.
|
|
436
|
+
* Check out {@link IHeapSnapshot} for more APIs that queries the heap snapshot.
|
|
437
|
+
* * leakedNodeIds: `Set<number>` | the set of ids of all JS heap objects
|
|
438
|
+
* allocated by the `action` call but not released after the `back` call
|
|
439
|
+
* in browser.
|
|
440
|
+
*
|
|
441
|
+
* * **Returns**: the boolean value indicating whether the given node in
|
|
442
|
+
* the snapshot should be considered as leaked.
|
|
443
|
+
*
|
|
444
|
+
* * **Examples**:
|
|
445
|
+
* ```typescript
|
|
446
|
+
* module.exports = {
|
|
447
|
+
* url: () => ... ,
|
|
448
|
+
* action: async (page) => ... ,
|
|
449
|
+
* back: async (page) => ... ,
|
|
450
|
+
* leakFilter(node, snapshot, leakedNodeIds) {
|
|
451
|
+
* // any unreleased node (JS heap object) with 1MB+
|
|
452
|
+
* // retained size is considered a memory leak
|
|
453
|
+
* return node.retainedSize > 1000000;
|
|
454
|
+
* },
|
|
455
|
+
* };
|
|
456
|
+
* ```
|
|
457
|
+
*/
|
|
161
458
|
leakFilter?: LeakFilterCallback;
|
|
162
459
|
}
|
|
163
460
|
/** @internal */
|
|
@@ -245,7 +542,10 @@ export interface IDataBuilder {
|
|
|
245
542
|
className: string;
|
|
246
543
|
state: Record<string, AnyValue>;
|
|
247
544
|
}
|
|
248
|
-
/**
|
|
545
|
+
/**
|
|
546
|
+
* Callback function to provide if the page is loaded.
|
|
547
|
+
* @param page - puppeteer's [Page](https://pptr.dev/api/puppeteer.page/) object.
|
|
548
|
+
*/
|
|
249
549
|
export declare type CheckPageLoadCallback = (page: Page) => Promise<boolean>;
|
|
250
550
|
/** @internal */
|
|
251
551
|
export interface IE2EScenarioVisitPlan {
|
|
@@ -307,15 +607,211 @@ export declare type RunMetaInfo = {
|
|
|
307
607
|
browserInfo: IBrowserInfo;
|
|
308
608
|
};
|
|
309
609
|
export interface IHeapSnapshot {
|
|
610
|
+
/** @internal */
|
|
310
611
|
snapshot: RawHeapSnapshot;
|
|
612
|
+
/**
|
|
613
|
+
* A pseudo array containing all heap graph nodes (JS objects in heap).
|
|
614
|
+
* A JS heap could contain millions of heap objects, so memlab uses
|
|
615
|
+
* a pseudo array as the collection of all the heap objects. The pseudo
|
|
616
|
+
* array provides API to query and traverse all heap objects.
|
|
617
|
+
*
|
|
618
|
+
* * **Examples**:
|
|
619
|
+
* ```typescript
|
|
620
|
+
* import type {IHeapSnapshot, IHeapNode} from '@memlab/core';
|
|
621
|
+
* import {dumpNodeHeapSnapshot} from '@memlab/core';
|
|
622
|
+
* import {getHeapFromFile} from '@memlab/heap-analysis';
|
|
623
|
+
*
|
|
624
|
+
* (async function () {
|
|
625
|
+
* const heapFile = dumpNodeHeapSnapshot();
|
|
626
|
+
* const heap: IHeapSnapshot = await getHeapFromFile(heapFile);
|
|
627
|
+
*
|
|
628
|
+
* // get the total number of heap objects
|
|
629
|
+
* heap.nodes.length;
|
|
630
|
+
*
|
|
631
|
+
* heap.nodes.forEach((node: IHeapNode) => {
|
|
632
|
+
* // traverse each heap object
|
|
633
|
+
* });
|
|
634
|
+
* })();
|
|
635
|
+
* ```
|
|
636
|
+
*/
|
|
311
637
|
nodes: IHeapNodes;
|
|
638
|
+
/**
|
|
639
|
+
* A pseudo array containing all heap graph edges (references to heap objects
|
|
640
|
+
* in heap). A JS heap could contain millions of references, so memlab uses
|
|
641
|
+
* a pseudo array as the collection of all the heap edges. The pseudo
|
|
642
|
+
* array provides API to query and traverse all heap references.
|
|
643
|
+
*
|
|
644
|
+
* * **Examples**:
|
|
645
|
+
* ```typescript
|
|
646
|
+
* import type {IHeapSnapshot, IHeapEdge} from '@memlab/core';
|
|
647
|
+
* import {dumpNodeHeapSnapshot} from '@memlab/core';
|
|
648
|
+
* import {getHeapFromFile} from '@memlab/heap-analysis';
|
|
649
|
+
*
|
|
650
|
+
* (async function () {
|
|
651
|
+
* const heapFile = dumpNodeHeapSnapshot();
|
|
652
|
+
* const heap: IHeapSnapshot = await getHeapFromFile(heapFile);
|
|
653
|
+
*
|
|
654
|
+
* // get the total number of heap references
|
|
655
|
+
* heap.edges.length;
|
|
656
|
+
*
|
|
657
|
+
* heap.edges.forEach((edge: IHeapEdge) => {
|
|
658
|
+
* // traverse each reference in the heap
|
|
659
|
+
* });
|
|
660
|
+
* })();
|
|
661
|
+
* ```
|
|
662
|
+
*/
|
|
312
663
|
edges: IHeapEdges;
|
|
664
|
+
/**
|
|
665
|
+
* If you have the id of a heap node (JS object in heap), use this API
|
|
666
|
+
* to get an {@link IHeapNode} associated with the id.
|
|
667
|
+
* @param id id of the heap node (JS object in heap) you would like to query
|
|
668
|
+
* @returns the API returns `null` if no heap object has the specified id.
|
|
669
|
+
*
|
|
670
|
+
* * **Examples**:
|
|
671
|
+
* ```typescript
|
|
672
|
+
* import type {IHeapSnapshot} from '@memlab/core';
|
|
673
|
+
* import {dumpNodeHeapSnapshot} from '@memlab/core';
|
|
674
|
+
* import {getHeapFromFile} from '@memlab/heap-analysis';
|
|
675
|
+
*
|
|
676
|
+
* (async function () {
|
|
677
|
+
* const heapFile = dumpNodeHeapSnapshot();
|
|
678
|
+
* const heap: IHeapSnapshot = await getHeapFromFile(heapFile);
|
|
679
|
+
*
|
|
680
|
+
* const node = heap.getNodeById(351);
|
|
681
|
+
* node?.id; // should be 351
|
|
682
|
+
* })();
|
|
683
|
+
* ```
|
|
684
|
+
*/
|
|
313
685
|
getNodeById(id: number): Nullable<IHeapNode>;
|
|
314
|
-
|
|
686
|
+
/**
|
|
687
|
+
* Search for the heap and check if there is any JS object instance with
|
|
688
|
+
* a specified constructor name.
|
|
689
|
+
* @param className The contructor name of the object instance
|
|
690
|
+
* @returns `true` if there is at least one such object in the heap
|
|
691
|
+
*
|
|
692
|
+
* * **Examples**: you can write a jest unit test with memory assertions:
|
|
693
|
+
* ```typescript
|
|
694
|
+
* // save as example.test.ts
|
|
695
|
+
* import type {IHeapSnapshot, Nullable} from '@memlab/core';
|
|
696
|
+
* import {config, getNodeInnocentHeap} from '@memlab/core';
|
|
697
|
+
*
|
|
698
|
+
* class TestObject {
|
|
699
|
+
* public arr1 = [1, 2, 3];
|
|
700
|
+
* public arr2 = ['1', '2', '3'];
|
|
701
|
+
* }
|
|
702
|
+
*
|
|
703
|
+
* test('memory test with heap assertion', async () => {
|
|
704
|
+
* config.muteConsole = true; // no console output
|
|
705
|
+
*
|
|
706
|
+
* let obj: Nullable<TestObject> = new TestObject();
|
|
707
|
+
* // get a heap snapshot of the current program state
|
|
708
|
+
* let heap: IHeapSnapshot = await getNodeInnocentHeap();
|
|
709
|
+
*
|
|
710
|
+
* // call some function that may add references to obj
|
|
711
|
+
* rabbitHole(obj)
|
|
712
|
+
*
|
|
713
|
+
* expect(heap.hasObjectWithClassName('TestObject')).toBe(true);
|
|
714
|
+
* obj = null;
|
|
715
|
+
*
|
|
716
|
+
* heap = await getNodeInnocentHeap();
|
|
717
|
+
* // if rabbitHole does not have any side effect that
|
|
718
|
+
* // adds new references to obj, then obj can be GCed
|
|
719
|
+
* expect(heap.hasObjectWithClassName('TestObject')).toBe(false);
|
|
720
|
+
*
|
|
721
|
+
* }, 30000);
|
|
722
|
+
* ```
|
|
723
|
+
*/
|
|
315
724
|
hasObjectWithClassName(className: string): boolean;
|
|
725
|
+
/**
|
|
726
|
+
* Search for the heap and get one of the JS object instances with
|
|
727
|
+
* a specified constructor name (if there is any).
|
|
728
|
+
* @param className The contructor name of the object instance
|
|
729
|
+
* @returns a handle pointing to any one of the object instances, returns
|
|
730
|
+
* `null` if no such object exists in the heap.
|
|
731
|
+
*
|
|
732
|
+
* * **Examples**:
|
|
733
|
+
* ```typescript
|
|
734
|
+
* import type {IHeapSnapshot} from '@memlab/core';
|
|
735
|
+
* import {getNodeInnocentHeap} from '@memlab/core';
|
|
736
|
+
*
|
|
737
|
+
* class TestObject {
|
|
738
|
+
* public arr1 = [1, 2, 3];
|
|
739
|
+
* public arr2 = ['1', '2', '3'];
|
|
740
|
+
* }
|
|
741
|
+
*
|
|
742
|
+
* (async function () {
|
|
743
|
+
* const obj = new TestObject();
|
|
744
|
+
* // get a heap snapshot of the current program state
|
|
745
|
+
* const heap: IHeapSnapshot = await getNodeInnocentHeap();
|
|
746
|
+
*
|
|
747
|
+
* const node = heap.getAnyObjectWithClassName('TestObject');
|
|
748
|
+
* console.log(node?.name); // should be 'TestObject'
|
|
749
|
+
* })();
|
|
750
|
+
* ```
|
|
751
|
+
*/
|
|
316
752
|
getAnyObjectWithClassName(className: string): Nullable<IHeapNode>;
|
|
753
|
+
/**
|
|
754
|
+
* Search for the heap and check if there is any JS object instance with
|
|
755
|
+
* a specified property name.
|
|
756
|
+
* @param nameOrIndex The property name (string) or element index (number)
|
|
757
|
+
* on the object instance
|
|
758
|
+
* @returns returns `true` if there is at least one such object in the heap
|
|
759
|
+
*
|
|
760
|
+
* * **Examples**:
|
|
761
|
+
* ```typescript
|
|
762
|
+
* import type {IHeapSnapshot} from '@memlab/core';
|
|
763
|
+
* import {dumpNodeHeapSnapshot} from '@memlab/core';
|
|
764
|
+
* import {getHeapFromFile} from '@memlab/heap-analysis';
|
|
765
|
+
*
|
|
766
|
+
* (async function () {
|
|
767
|
+
* // eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
768
|
+
* const object = {'memlab-test-heap-property': 'memlab-test-heap-value'};
|
|
769
|
+
*
|
|
770
|
+
* const heapFile = dumpNodeHeapSnapshot();
|
|
771
|
+
* const heap: IHeapSnapshot = await getHeapFromFile(heapFile);
|
|
772
|
+
*
|
|
773
|
+
* // should be true
|
|
774
|
+
* console.log(heap.hasObjectWithPropertyName('memlab-test-heap-property'));
|
|
775
|
+
* })();
|
|
776
|
+
* ```
|
|
777
|
+
*/
|
|
317
778
|
hasObjectWithPropertyName(nameOrIndex: string | number): boolean;
|
|
779
|
+
/**
|
|
780
|
+
* Search for the heap and check if there is any JS object instance with
|
|
781
|
+
* a marker tagged by {@link tagObject}.
|
|
782
|
+
* @param tag marker name on the object instances tagged by {@link tagObject}
|
|
783
|
+
* @returns returns `true` if there is at least one such object in the heap
|
|
784
|
+
*
|
|
785
|
+
* ```typescript
|
|
786
|
+
* import type {IHeapSnapshot, AnyValue} from '@memlab/core';
|
|
787
|
+
* import {config, getNodeInnocentHeap, tagObject} from '@memlab/core';
|
|
788
|
+
*
|
|
789
|
+
* test('memory test', async () => {
|
|
790
|
+
* config.muteConsole = true;
|
|
791
|
+
* const o1: AnyValue = {};
|
|
792
|
+
* let o2: AnyValue = {};
|
|
793
|
+
*
|
|
794
|
+
* // tag o1 with marker: "memlab-mark-1", does not modify o1 in any way
|
|
795
|
+
* tagObject(o1, 'memlab-mark-1');
|
|
796
|
+
* // tag o2 with marker: "memlab-mark-2", does not modify o2 in any way
|
|
797
|
+
* tagObject(o2, 'memlab-mark-2');
|
|
798
|
+
*
|
|
799
|
+
* o2 = null;
|
|
800
|
+
*
|
|
801
|
+
* const heap: IHeapSnapshot = await getNodeInnocentHeap();
|
|
802
|
+
*
|
|
803
|
+
* // expect object with marker "memlab-mark-1" exists
|
|
804
|
+
* expect(heap.hasObjectWithTag('memlab-mark-1')).toBe(true);
|
|
805
|
+
*
|
|
806
|
+
* // expect object with marker "memlab-mark-2" can be GCed
|
|
807
|
+
* expect(heap.hasObjectWithTag('memlab-mark-2')).toBe(false);
|
|
808
|
+
*
|
|
809
|
+
* }, 30000);
|
|
810
|
+
* ```
|
|
811
|
+
*/
|
|
318
812
|
hasObjectWithTag(tag: string): boolean;
|
|
813
|
+
/** @internal */
|
|
814
|
+
clearShortestPathInfo(): void;
|
|
319
815
|
}
|
|
320
816
|
export interface IHeapLocation {
|
|
321
817
|
snapshot: IHeapSnapshot;
|
package/dist/lib/Types.js
CHANGED
package/dist/lib/Utils.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memlab/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "memlab core libraries",
|
|
6
6
|
"author": "Liang Gong <lgong@fb.com>",
|
|
@@ -56,9 +56,9 @@
|
|
|
56
56
|
"directory": "packages/core"
|
|
57
57
|
},
|
|
58
58
|
"scripts": {
|
|
59
|
-
"preinstall": "export PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true",
|
|
60
59
|
"build-pkg": "tsc",
|
|
61
60
|
"test-pkg": "jest .",
|
|
61
|
+
"publish-patch": "npm version patch --force && npm publish",
|
|
62
62
|
"clean-pkg": "rm -rf ./dist && rm -rf ./node_modules && rm -f ./tsconfig.tsbuildinfo"
|
|
63
63
|
},
|
|
64
64
|
"bugs": {
|