Skip to content

Commit ff4d1de

Browse files
committed
doc, test: doc API design, add tests
1 parent 46674ae commit ff4d1de

File tree

2 files changed

+216
-0
lines changed

2 files changed

+216
-0
lines changed

JSAPI.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Draft of the JavaScript API
2+
3+
This is currently a work in progress, expect the API to change significantly.
4+
Specifically, the APIs returning strings as results of inspection should return
5+
structured data instead for ease of use.
6+
7+
```js
8+
class LLNode {
9+
/**
10+
* @param {string} dump path to the coredump
11+
* @param {string} executable path to the node executable
12+
* @returns {LLNode} an LLNode instance
13+
*/
14+
static fromCoredump(dump, executable) {}
15+
16+
/**
17+
* @returns {string} SBProcess information
18+
*/
19+
getProcessInfo() {}
20+
21+
/**
22+
* @typedef {object} Frame
23+
* @property {string} function
24+
*
25+
* @typedef {object} Thread
26+
* @property {number} threadId
27+
* @property {number} frameCount
28+
* @property {Frame[]} frames
29+
*
30+
* @typedef {object} Process
31+
* @property {number} pid
32+
* @property {string} state
33+
* @property {number} threadCount
34+
* @property {Thread[]} threads
35+
*
36+
* @returns {Process} Process data
37+
*/
38+
getProcessobject() {}
39+
40+
/**
41+
* @typedef {object} HeapInstance
42+
* @property {string} address
43+
* @property {string} value
44+
*
45+
* @typedef {object} HeapType
46+
* @property {string} typeName
47+
* @property {string} instanceCount
48+
* @property {string} totalSize
49+
* @property {LLNode} llnode
50+
* @property {Iterator<HeapInstance>} instances
51+
*
52+
* @returns {HeapType[]}
53+
*/
54+
getHeapTypes() {}
55+
56+
/**
57+
* TODO: rematerialize object
58+
* @returns {HeapInstance}
59+
*/
60+
getobjectAtAddress(address) {}
61+
}
62+
```

test/jsapi-test.js

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

0 commit comments

Comments
 (0)