Skip to content

Commit 8efd7d0

Browse files
committed
test: Add profile utils test
1 parent 0c2f8c9 commit 8efd7d0

1 file changed

Lines changed: 169 additions & 0 deletions

File tree

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
import test from "ava";
2+
import sinon from "sinon";
3+
import esmock from "esmock";
4+
5+
function createSessionStubs() {
6+
const connectStub = sinon.stub();
7+
const postStub = sinon.stub().callsFake(async (method) => {
8+
if (method === "Profiler.stop") {
9+
return {profile: {foo: "bar"}};
10+
}
11+
return {};
12+
});
13+
class Session {
14+
connect() {
15+
return connectStub();
16+
}
17+
post(method) {
18+
return postStub(method);
19+
}
20+
}
21+
return {Session, connectStub, postStub};
22+
}
23+
24+
test.afterEach.always(() => {
25+
sinon.restore();
26+
});
27+
28+
test.serial("start() enables and starts profiler and registers signals", async (t) => {
29+
const {Session, connectStub, postStub} = createSessionStubs();
30+
const writeFileSyncStub = sinon.stub();
31+
32+
const installed = new Map();
33+
const onStub = sinon.stub(process, "on").callsFake((signal, handler) => {
34+
installed.set(signal, handler);
35+
return process;
36+
});
37+
const removeListenerStub = sinon.stub(process, "removeListener");
38+
39+
const {start, stop} = await esmock("../../../lib/utils/profile.js", {
40+
"node:inspector/promises": {Session},
41+
"node:fs": {writeFileSync: writeFileSyncStub}
42+
});
43+
44+
await start();
45+
46+
t.true(connectStub.calledOnce, "session.connect called once");
47+
t.true(postStub.calledWith("Profiler.enable"), "Profiler.enable posted");
48+
t.true(postStub.calledWith("Profiler.start"), "Profiler.start posted");
49+
50+
// Four signals should be registered
51+
t.true(onStub.calledWith("SIGHUP"));
52+
t.true(onStub.calledWith("SIGINT"));
53+
t.true(onStub.calledWith("SIGTERM"));
54+
t.true(onStub.calledWith("SIGBREAK"));
55+
56+
// Cleanup to reset internal state
57+
await stop();
58+
59+
// stop should deregister the same handlers
60+
t.true(removeListenerStub.calledWith("SIGHUP", installed.get("SIGHUP")));
61+
t.true(removeListenerStub.calledWith("SIGINT", installed.get("SIGINT")));
62+
t.true(removeListenerStub.calledWith("SIGTERM", installed.get("SIGTERM")));
63+
t.true(removeListenerStub.calledWith("SIGBREAK", installed.get("SIGBREAK")));
64+
});
65+
66+
test.serial("start() is idempotent", async (t) => {
67+
const {Session, connectStub} = createSessionStubs();
68+
const writeFileSyncStub = sinon.stub();
69+
70+
sinon.stub(process, "on").returns(process);
71+
sinon.stub(process, "removeListener");
72+
73+
const {start, stop} = await esmock("../../../lib/utils/profile.js", {
74+
"node:inspector/promises": {Session},
75+
"node:fs": {writeFileSync: writeFileSyncStub}
76+
});
77+
78+
await start();
79+
await start();
80+
t.true(connectStub.calledOnce, "connect should be called only once");
81+
82+
await stop();
83+
});
84+
85+
test.serial("stop() writes profile and deregisters signals", async (t) => {
86+
const {Session, postStub} = createSessionStubs();
87+
const writeFileSyncStub = sinon.stub();
88+
89+
const installed = new Map();
90+
sinon.stub(process, "on").callsFake((signal, handler) => {
91+
installed.set(signal, handler);
92+
return process;
93+
});
94+
const removeListenerStub = sinon.stub(process, "removeListener");
95+
96+
const {start, stop} = await esmock("../../../lib/utils/profile.js", {
97+
"node:inspector/promises": {Session},
98+
"node:fs": {writeFileSync: writeFileSyncStub}
99+
});
100+
101+
await start();
102+
t.true(postStub.calledWith("Profiler.start"));
103+
104+
await stop();
105+
106+
t.true(writeFileSyncStub.calledOnce, "profile written once");
107+
const [fileName, content] = writeFileSyncStub.firstCall.args;
108+
t.regex(fileName, /^\.\/ui5_\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}\.cpuprofile$/, "filename matches pattern");
109+
t.deepEqual(JSON.parse(content), {foo: "bar"}, "written profile content matches");
110+
111+
t.true(removeListenerStub.calledWith("SIGHUP", installed.get("SIGHUP")));
112+
t.true(removeListenerStub.calledWith("SIGINT", installed.get("SIGINT")));
113+
t.true(removeListenerStub.calledWith("SIGTERM", installed.get("SIGTERM")));
114+
t.true(removeListenerStub.calledWith("SIGBREAK", installed.get("SIGBREAK")));
115+
});
116+
117+
test.serial("stop() without start is a no-op", async (t) => {
118+
const writeFileSyncStub = sinon.stub();
119+
120+
const removeListenerStub = sinon.stub(process, "removeListener");
121+
122+
const {stop} = await esmock("../../../lib/utils/profile.js", {
123+
"node:inspector/promises": {Session: class {}},
124+
"node:fs": {writeFileSync: writeFileSyncStub}
125+
});
126+
127+
await stop();
128+
129+
t.true(removeListenerStub.notCalled, "no signal deregistration happened");
130+
t.true(writeFileSyncStub.notCalled, "no write happened");
131+
});
132+
133+
test.serial("signal handler stops profiling and exits", async (t) => {
134+
const {Session, postStub} = createSessionStubs();
135+
const writeFileSyncStub = sinon.stub();
136+
137+
const installed = new Map();
138+
sinon.stub(process, "on").callsFake((signal, handler) => {
139+
installed.set(signal, handler);
140+
return process;
141+
});
142+
const removeListenerStub = sinon.stub(process, "removeListener");
143+
const exitStub = sinon.stub(process, "exit");
144+
145+
const {start} = await esmock("../../../lib/utils/profile.js", {
146+
"node:inspector/promises": {Session},
147+
"node:fs": {writeFileSync: writeFileSyncStub}
148+
});
149+
150+
await start();
151+
152+
t.true(typeof installed.get("SIGINT") === "function", "SIGINT handler registered");
153+
154+
// Trigger the signal handler
155+
installed.get("SIGINT")();
156+
157+
// Allow the Promise chain in the handler (stop().then(...)) to run
158+
await new Promise((resolve) => setImmediate(resolve));
159+
160+
t.true(postStub.calledWith("Profiler.stop"), "Profiler.stop posted via handler");
161+
t.true(writeFileSyncStub.calledOnce, "profile written by handler");
162+
t.true(exitStub.calledWith(128 + 2), "process.exit called with SIGINT code");
163+
164+
// Signals should be deregistered during stop
165+
t.true(removeListenerStub.calledWith("SIGHUP", installed.get("SIGHUP")));
166+
t.true(removeListenerStub.calledWith("SIGINT", installed.get("SIGINT")));
167+
t.true(removeListenerStub.calledWith("SIGTERM", installed.get("SIGTERM")));
168+
t.true(removeListenerStub.calledWith("SIGBREAK", installed.get("SIGBREAK")));
169+
});

0 commit comments

Comments
 (0)