Skip to content

Commit cefb762

Browse files
committed
doc, test: doc API design, add tests
1 parent 31cd764 commit cefb762

File tree

3 files changed

+224
-0
lines changed

3 files changed

+224
-0
lines changed

JSAPI.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
```js
2+
class LLNode {
3+
/**
4+
* @param {string} dump path to the coredump
5+
* @param {string} executable path to the node executable
6+
* @returns {LLNode} an LLNode instance
7+
*/
8+
static fromCoreDump(dump, executable) {}
9+
10+
/**
11+
* @returns {string} SBProcess information
12+
*/
13+
getProcessInfo() {}
14+
15+
/**
16+
* @typedef {object} Frame
17+
* @property {string} function
18+
*
19+
* @typedef {object} Thread
20+
* @property {number} threadId
21+
* @property {number} frameCount
22+
* @property {Frame[]} frames
23+
*
24+
* @typedef {object} Process
25+
* @property {number} pid
26+
* @property {string} state
27+
* @property {number} threadCount
28+
* @property {Thread[]} threads
29+
*
30+
* @returns {Process} Process data
31+
*/
32+
getProcessobject() {}
33+
34+
/**
35+
* @typedef {object} HeapInstance
36+
* @property {string} address
37+
* @property {string} value
38+
*
39+
* @typedef {object} HeapType
40+
* @property {string} typeName
41+
* @property {string} instanceCount
42+
* @property {string} totalSize
43+
* @property {Iterator<HeapInstance>} instances
44+
*
45+
* @returns {HeapType[]}
46+
*/
47+
getHeapTypes() {}
48+
49+
/**
50+
* TODO: rematerialize object
51+
* @returns {HeapInstance}
52+
*/
53+
getobjectAtAddress(address) {}
54+
}
55+
```

test/common.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ else
2222
pluginName = path.join('lib.target', 'llnode.so');
2323

2424
exports.llnodePath = path.join(exports.buildDir, pluginName);
25+
exports.saveCoreTimeout = 90000;
2526

2627
function SessionOutput(session, stream) {
2728
EventEmitter.call(this);

test/jsapi-test.js

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
const { fromCoredump } = require('../');
2+
3+
const debug = process.env.LLNODE_DEBUG ?
4+
console.log.bind(console) : () => { };
5+
6+
const common = require('./common');
7+
const tape = require('tape');
8+
9+
tape('llnode API', (t) => {
10+
t.timeoutAfter(common.saveCoreTimeout);
11+
12+
// Use prepared core and executable to test
13+
if (process.env.LLNODE_CORE && process.env.LLNODE_NODE_EXE) {
14+
test(process.env.LLNODE_NODE_EXE, process.env.LLNODE_CORE, t);
15+
t.end();
16+
} else {
17+
if (process.platform === 'linux') {
18+
t.skip('No `process save-core` on linux');
19+
t.end();
20+
} else {
21+
saveCoreAndTest(t);
22+
}
23+
}
24+
});
25+
26+
function saveCoreAndTest(t) {
27+
// Create a core and test
28+
const sess = common.Session.create('inspect-scenario.js');
29+
30+
sess.waitBreak(() => {
31+
debug('saving core...');
32+
sess.send(`process save-core ${common.core}`);
33+
// Just a separator
34+
sess.send('version');
35+
});
36+
37+
sess.wait(/lldb\-/, () => {
38+
t.ok(true, 'Saved core');
39+
debug('saved core');
40+
sess.quit();
41+
42+
test(process.execPath, common.core, t);
43+
t.end();
44+
});
45+
}
46+
47+
function verifySBProcess(llnode, t) {
48+
const processInfo = llnode.getProcessInfo();
49+
debug('Process info', processInfo);
50+
const procRe = new RegExp(
51+
'SBProcess: pid = (\\d+), state = (\\w+), ' +
52+
'threads = (\\d+), executable = .+');
53+
const procMatch = processInfo.match(procRe);
54+
t.ok(procMatch, 'SBProcess info should be formatted correctly');
55+
56+
const procObj = llnode.getProcessObject();
57+
debug('Process object', procObj);
58+
t.equal(procObj.pid, parseInt(procMatch[1]), 'SBProcess pid');
59+
t.equal(procObj.state, procMatch[2], 'SBProcess state');
60+
t.equal(procObj.threadCount, parseInt(procMatch[3]),
61+
'SBProcess thread count');
62+
t.ok(procObj.threadCount > 0,
63+
'SBProcess should have more than one thread');
64+
t.ok(Array.isArray(procObj.threads),
65+
'processObject.threads should be an array');
66+
t.equal(procObj.threads.length,
67+
procObj.threadCount,
68+
'processObject.threads should contain all the threads');
69+
70+
let i = 0;
71+
for (const thread of procObj.threads) {
72+
debug(`Thread ${i}:`, thread);
73+
t.equal(thread.threadId, i++, 'thread.threadId');
74+
t.ok(Array.isArray(thread.frames),
75+
'thread.frames should be an array');
76+
t.equal(thread.frameCount, thread.frames.length,
77+
'thread.frames should contain all the frames');
78+
79+
for (const frame of thread.frames) {
80+
debug(` #`, frame);
81+
t.ok(typeof frame.function === 'string',
82+
'frame.function should be a string');
83+
}
84+
}
85+
}
86+
87+
function verifyBasicTypes(llnode, t) {
88+
debug('============= Heap Types ==============');
89+
const heapTypes = llnode.getHeapTypes();
90+
// debug('Heap types', heapTypes);
91+
const basicTypes = [
92+
// basic JS types
93+
'(Array)', '(String)', 'Object', '(Object)', '(ArrayBufferView)',
94+
// Node types
95+
'process', 'NativeModule', 'console', 'TickObject'
96+
].sort();
97+
98+
const typeMap = new Map();
99+
for (const item of heapTypes) {
100+
if (basicTypes.includes(item.typeName)) {
101+
typeMap.set(item.typeName, item);
102+
}
103+
}
104+
105+
const foundTypes = Array.from(typeMap.keys()).sort();
106+
t.deepEqual(foundTypes, basicTypes,
107+
'The heap should contain all the basic types');
108+
109+
return typeMap;
110+
}
111+
112+
function verifyProcessType(typeMap, llnode, t) {
113+
const processType = typeMap.get('process');
114+
115+
t.equal(processType.typeName, 'process',
116+
'The typeName of process type should be "process"')
117+
t.ok(processType.instanceCount > 0,
118+
'There should be more than one process instances');
119+
t.ok(processType.totalSize > 0,
120+
'The process objects should have a non-zero size');
121+
return processType;
122+
}
123+
124+
function verifyProcessInstances(processType, llnode, t) {
125+
// TODO: should be implemented as an iterator
126+
let foundProcess = false;
127+
128+
const propRe = [
129+
/.pid=<Smi: \d+>/,
130+
/.platform=0x[0-9a-z]+:<String: ".+">/,
131+
/.arch=0x[0-9a-z]+:<String: ".+">/,
132+
/.version=0x[0-9a-z]+:<String: ".+">/,
133+
/.versions=0x[0-9a-z]+:<Object: Object>/,
134+
/.release=0x[0-9a-z]+:<Object: Object>/,
135+
/.execPath=0x[0-9a-z]+:<String: ".+">/,
136+
/.execArgv=0x[0-9a-z]+:<Array: length=\d+>/,
137+
/.argv=0x[0-9a-z]+:<Array: length=\d+>/
138+
];
139+
140+
const visited = new Map();
141+
142+
for (const instance of processType.instances) {
143+
t.ok(!visited.get(instance.address),
144+
'should not duplicate instances');
145+
visited.set(instance.address, instance.value);
146+
t.deepEqual(
147+
instance,
148+
llnode.getObjectAtAddress(instance.address),
149+
'instance should be the same as obtained from address')
150+
151+
if (propRe.every((re) => re.test(instance.value))) {
152+
foundProcess = true;
153+
}
154+
}
155+
t.ok(foundProcess, 'should find the process object');
156+
}
157+
158+
function test(executable, dump, t) {
159+
debug('============= Loading ==============');
160+
// Equivalent to lldb executable -c dump
161+
debug(`Loading dump: ${dump}, executable: ${executable}`);
162+
const llnode = fromCoredump(dump, executable);
163+
164+
verifySBProcess(llnode, t);
165+
const typeMap = verifyBasicTypes(llnode, t);
166+
const processType = verifyProcessType(typeMap, llnode, t);
167+
verifyProcessInstances(processType, llnode, t);
168+
}

0 commit comments

Comments
 (0)